Compare commits

...

12 Commits

Author SHA1 Message Date
公明 55b6bceb21 Update config.yaml 2026-04-26 15:11:48 +08:00
公明 65d73b3d66 Add files via upload 2026-04-26 15:08:48 +08:00
公明 913115d1fb Add files via upload 2026-04-26 04:26:29 +08:00
公明 e1b967d781 Add files via upload 2026-04-26 04:18:38 +08:00
公明 9d9efa886f Add files via upload 2026-04-26 04:17:27 +08:00
公明 cae45e9dc5 Add files via upload 2026-04-26 04:16:25 +08:00
公明 c788b59f25 Update config.yaml 2026-04-24 20:01:42 +08:00
公明 5edf3a70f9 Add files via upload 2026-04-24 20:00:50 +08:00
公明 3dfb3b4e82 Add files via upload 2026-04-24 19:59:15 +08:00
公明 a517fe0931 Add files via upload 2026-04-24 19:56:09 +08:00
公明 0ab5e31a64 Add files via upload 2026-04-24 18:24:52 +08:00
公明 ea6e027b25 Add files via upload 2026-04-24 17:30:22 +08:00
30 changed files with 460 additions and 441 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<div align="center">
<img src="web/static/logo.png" alt="CyberStrikeAI Logo" width="200">
<img src="images/logo.png" alt="CyberStrikeAI Logo" width="200">
</div>
# CyberStrikeAI
+1 -1
View File
@@ -1,5 +1,5 @@
<div align="center">
<img src="web/static/logo.png" alt="CyberStrikeAI Logo" width="200">
<img src="images/logo.png" alt="CyberStrikeAI Logo" width="200">
</div>
# CyberStrikeAI
+1 -1
View File
@@ -10,7 +10,7 @@
# ============================================
# 前端显示的版本号(可选,不填则显示默认版本)
version: "v1.5.6"
version: "v1.5.8"
# 服务器配置
server:
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

+8 -3
View File
@@ -603,11 +603,13 @@ func (h *AgentHandler) AgentLoop(c *gin.Context) {
baseCtx, cancelWithCause := context.WithCancelCause(c.Request.Context())
defer cancelWithCause(nil)
progressCallback := h.createProgressCallback(baseCtx, cancelWithCause, conversationID, "", nil)
baseCtx = h.injectReactHITLInterceptor(baseCtx, cancelWithCause, conversationID, "", nil)
taskCtx, timeoutCancel := context.WithTimeout(baseCtx, 600*time.Minute)
defer timeoutCancel()
progressCallback := h.createProgressCallback(taskCtx, cancelWithCause, conversationID, "", nil)
taskCtx = h.injectReactHITLInterceptor(taskCtx, cancelWithCause, conversationID, "", nil)
// 执行Agent Loop,传入历史消息和对话ID(使用包含角色提示词的finalMessage和角色工具列表)
result, err := h.agent.AgentLoopWithProgress(baseCtx, finalMessage, agentHistoryMessages, conversationID, progressCallback, roleTools)
result, err := h.agent.AgentLoopWithProgress(taskCtx, finalMessage, agentHistoryMessages, conversationID, progressCallback, roleTools)
if err != nil {
h.logger.Error("Agent Loop执行失败", zap.Error(err))
@@ -1209,6 +1211,9 @@ func (h *AgentHandler) AgentLoopStream(c *gin.Context) {
}
eventJSON, _ := json.Marshal(event)
fmt.Fprintf(c.Writer, "data: %s\n\n", eventJSON)
done := StreamEvent{Type: "done", Message: ""}
doneJSON, _ := json.Marshal(done)
fmt.Fprintf(c.Writer, "data: %s\n\n", doneJSON)
c.Writer.Flush()
return
}
+5 -3
View File
@@ -281,8 +281,10 @@ func (h *AgentHandler) EinoSingleAgentLoop(c *gin.Context) {
}
baseCtx, cancelWithCause := context.WithCancelCause(c.Request.Context())
defer cancelWithCause(nil)
progressCallback := h.createProgressCallback(baseCtx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, progressCallbackRaw)
baseCtx = multiagent.WithHITLToolInterceptor(baseCtx, func(ctx context.Context, toolName, arguments string) (string, error) {
taskCtx, timeoutCancel := context.WithTimeout(baseCtx, 600*time.Minute)
defer timeoutCancel()
progressCallback := h.createProgressCallback(taskCtx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, progressCallbackRaw)
taskCtx = multiagent.WithHITLToolInterceptor(taskCtx, func(ctx context.Context, toolName, arguments string) (string, error) {
return h.interceptHITLForEinoTool(ctx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, nil, toolName, arguments)
})
@@ -292,7 +294,7 @@ func (h *AgentHandler) EinoSingleAgentLoop(c *gin.Context) {
}
result, runErr := multiagent.RunEinoSingleChatModelAgent(
baseCtx,
taskCtx,
h.config,
&h.config.MultiAgent,
h.agent,
+8 -3
View File
@@ -40,6 +40,9 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
event := StreamEvent{Type: "error", Message: "请求参数错误: " + err.Error()}
b, _ := json.Marshal(event)
fmt.Fprintf(c.Writer, "data: %s\n\n", b)
done := StreamEvent{Type: "done", Message: ""}
db, _ := json.Marshal(done)
fmt.Fprintf(c.Writer, "data: %s\n\n", db)
c.Writer.Flush()
return
}
@@ -293,13 +296,15 @@ func (h *AgentHandler) MultiAgentLoop(c *gin.Context) {
baseCtx, cancelWithCause := context.WithCancelCause(c.Request.Context())
defer cancelWithCause(nil)
progressCallback := h.createProgressCallback(baseCtx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, nil)
baseCtx = multiagent.WithHITLToolInterceptor(baseCtx, func(ctx context.Context, toolName, arguments string) (string, error) {
taskCtx, timeoutCancel := context.WithTimeout(baseCtx, 600*time.Minute)
defer timeoutCancel()
progressCallback := h.createProgressCallback(taskCtx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, nil)
taskCtx = multiagent.WithHITLToolInterceptor(taskCtx, func(ctx context.Context, toolName, arguments string) (string, error) {
return h.interceptHITLForEinoTool(ctx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, nil, toolName, arguments)
})
result, runErr := multiagent.RunDeepAgent(
baseCtx,
taskCtx,
h.config,
&h.config.MultiAgent,
h.agent,
+140 -65
View File
@@ -18,6 +18,21 @@ import (
"go.uber.org/zap"
)
func isEinoIterationLimitError(err error) bool {
if err == nil {
return false
}
msg := strings.ToLower(strings.TrimSpace(err.Error()))
if msg == "" {
return false
}
return strings.Contains(msg, "max iteration") ||
strings.Contains(msg, "maximum iteration") ||
strings.Contains(msg, "maximum iterations") ||
strings.Contains(msg, "iteration limit") ||
strings.Contains(msg, "达到最大迭代")
}
// einoADKRunLoopArgs 将 Eino adk.Runner 事件循环从 RunDeepAgent / RunEinoSingleChatModelAgent 中抽出复用。
type einoADKRunLoopArgs struct {
OrchMode string
@@ -205,6 +220,98 @@ attemptLoop:
}
runner := adk.NewRunner(ctx, runnerCfg)
iter := runner.Run(ctx, msgs)
handleRunErr := func(runErr error, attempt int, reasonOverride string) (retry bool, retErr error) {
if runErr == nil {
return false, nil
}
if errors.Is(runErr, context.DeadlineExceeded) {
flushAllPendingAsFailed(runErr)
if progress != nil {
progress("error", runErr.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"errorKind": "timeout",
})
}
return false, runErr
}
// context.Canceled 是唯一应当直接终止编排的错误(用户关闭页面、主动停止等)。
if errors.Is(runErr, context.Canceled) {
flushAllPendingAsFailed(runErr)
if progress != nil {
progress("error", runErr.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
})
}
return false, runErr
}
if isEinoIterationLimitError(runErr) {
flushAllPendingAsFailed(runErr)
if progress != nil {
progress("iteration_limit_reached", runErr.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"orchestration": orchMode,
})
progress("error", runErr.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"errorKind": "iteration_limit",
})
}
return false, runErr
}
canRetry := attempt+1 < maxToolCallRecoveryAttempts
if !canRetry {
// 重试次数已耗尽,终止。
flushAllPendingAsFailed(runErr)
if progress != nil {
progress("error", runErr.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
})
}
return false, runErr
}
// 区分错误类型以选择最合适的纠错提示,但无论哪种都执行重试(default-soft)。
var hint *schema.Message
var reason, timelineMsg string
switch {
case strings.TrimSpace(reasonOverride) != "":
hint = toolExecutionRetryHint()
reason = strings.TrimSpace(reasonOverride)
timelineMsg = toolExecutionRecoveryTimelineMessage(attempt)
case isRecoverableToolCallArgumentsJSONError(runErr):
hint = toolCallArgumentsJSONRetryHint()
reason = "invalid_tool_arguments_json"
timelineMsg = toolCallArgumentsJSONRecoveryTimelineMessage(attempt)
default:
hint = toolExecutionRetryHint()
reason = "tool_execution_error"
timelineMsg = toolExecutionRecoveryTimelineMessage(attempt)
}
if logger != nil {
logger.Warn("eino: recoverable error, will retry with corrective hint",
zap.Error(runErr), zap.Int("attempt", attempt), zap.String("reason", reason))
}
flushAllPendingAsFailed(runErr)
retryHints = append(retryHints, hint)
if progress != nil {
progress("eino_recovery", timelineMsg, map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"einoRetry": attempt,
"runIndex": attempt + 1,
"maxRuns": maxToolCallRecoveryAttempts,
"reason": reason,
})
}
return true, nil
}
for {
// 检测 context 取消(用户关闭浏览器、请求超时等),flush pending 工具状态避免 UI 卡在 "执行中"。
@@ -223,6 +330,18 @@ attemptLoop:
ev, ok := iter.Next()
if !ok {
if len(pendingByID) > 0 {
orphanCount := len(pendingByID)
flushAllPendingAsFailed(errors.New("pending tool call missing result before run completion"))
if progress != nil {
progress("eino_pending_orphaned", "pending tool calls were force-closed at run end", map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"orchestration": orchMode,
"pendingCount": orphanCount,
})
}
}
lastRunMsgs = msgs
break attemptLoop
}
@@ -230,72 +349,11 @@ attemptLoop:
continue
}
if ev.Err != nil {
if errors.Is(ev.Err, context.DeadlineExceeded) {
flushAllPendingAsFailed(ev.Err)
if progress != nil {
progress("error", ev.Err.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"errorKind": "timeout",
})
}
return nil, ev.Err
if retry, retErr := handleRunErr(ev.Err, attempt, ""); retErr != nil {
return nil, retErr
} else if retry {
continue attemptLoop
}
// context.Canceled 是唯一应当直接终止编排的错误(用户关闭页面、主动停止等)。
if errors.Is(ev.Err, context.Canceled) {
flushAllPendingAsFailed(ev.Err)
if progress != nil {
progress("error", ev.Err.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
})
}
return nil, ev.Err
}
canRetry := attempt+1 < maxToolCallRecoveryAttempts
if !canRetry {
// 重试次数已耗尽,终止。
flushAllPendingAsFailed(ev.Err)
if progress != nil {
progress("error", ev.Err.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
})
}
return nil, ev.Err
}
// 区分错误类型以选择最合适的纠错提示,但无论哪种都执行重试(default-soft)。
var hint *schema.Message
var reason, timelineMsg string
if isRecoverableToolCallArgumentsJSONError(ev.Err) {
hint = toolCallArgumentsJSONRetryHint()
reason = "invalid_tool_arguments_json"
timelineMsg = toolCallArgumentsJSONRecoveryTimelineMessage(attempt)
} else {
hint = toolExecutionRetryHint()
reason = "tool_execution_error"
timelineMsg = toolExecutionRecoveryTimelineMessage(attempt)
}
if logger != nil {
logger.Warn("eino: recoverable error, will retry with corrective hint",
zap.Error(ev.Err), zap.Int("attempt", attempt), zap.String("reason", reason))
}
flushAllPendingAsFailed(ev.Err)
retryHints = append(retryHints, hint)
if progress != nil {
progress("eino_recovery", timelineMsg, map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"einoRetry": attempt,
"runIndex": attempt + 1,
"maxRuns": maxToolCallRecoveryAttempts,
"reason": reason,
})
}
continue attemptLoop
}
if ev.AgentName != "" && progress != nil {
iterEinoAgent := orchestratorName
@@ -349,6 +407,7 @@ attemptLoop:
var subAssistantBuf strings.Builder
var subReplyStreamID string
var mainAssistantBuf strings.Builder
var streamRecvErr error
for {
chunk, rerr := mv.MessageStream.Recv()
if rerr != nil {
@@ -361,6 +420,7 @@ attemptLoop:
zap.String("agent", ev.AgentName),
zap.Int("toolFragments", len(toolStreamFragments)))
}
streamRecvErr = rerr
break
}
if chunk == nil {
@@ -459,6 +519,21 @@ attemptLoop:
lastToolChunk = &schema.Message{ToolCalls: merged}
}
tryEmitToolCallsOnce(lastToolChunk, ev.AgentName, orchestratorName, conversationID, progress, toolEmitSeen, subAgentToolStep, markPending)
if streamRecvErr != nil {
if progress != nil {
progress("eino_stream_error", streamRecvErr.Error(), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"einoAgent": ev.AgentName,
"einoRole": einoRoleTag(ev.AgentName),
})
}
if retry, retErr := handleRunErr(streamRecvErr, attempt, "stream_recv_error"); retErr != nil {
return nil, retErr
} else if retry {
continue attemptLoop
}
}
continue
}
@@ -64,7 +64,7 @@ public class BurpExtender implements IBurpExtender, IContextMenuFactory {
String prompt = HttpMessageFormatter.toPrompt(helpers, msg, instruction);
String title = HttpMessageFormatter.getRequestTitle(helpers, msg);
String agentModeStr = (cfg.agentMode == CyberStrikeAIClient.AgentMode.MULTI) ? "Multi Agent" : "Single Agent";
String agentModeStr = cfg.agentMode.displayName;
String runId = tab.startNewRun(title, agentModeStr, msg);
tab.appendProgressToRun(runId, "\n[server] " + cfg.baseUrl + "\n\n");
@@ -26,8 +26,21 @@ final class CyberStrikeAIClient {
}
enum AgentMode {
SINGLE,
MULTI
NATIVE_REACT("Native ReAct", "/api/agent-loop/stream", null),
EINO_SINGLE("Eino Single (ADK)", "/api/eino-agent/stream", null),
DEEP("Deep (DeepAgent)", "/api/multi-agent/stream", "deep"),
PLAN_EXECUTE("Plan-Execute", "/api/multi-agent/stream", "plan_execute"),
SUPERVISOR("Supervisor", "/api/multi-agent/stream", "supervisor");
final String displayName;
final String streamPath;
final String orchestration;
AgentMode(String displayName, String streamPath, String orchestration) {
this.displayName = displayName;
this.streamPath = streamPath;
this.orchestration = orchestration;
}
}
interface StreamListener {
@@ -94,13 +107,15 @@ final class CyberStrikeAIClient {
}
void streamTest(Config cfg, String token, String message, StreamListener listener) {
String path = (cfg.agentMode == AgentMode.MULTI) ? "/api/multi-agent/stream" : "/api/agent-loop/stream";
String urlStr = cfg.baseUrl + path;
String urlStr = cfg.baseUrl + cfg.agentMode.streamPath;
Map<String, Object> payload = new HashMap<>();
payload.put("message", message);
payload.put("conversationId", "");
payload.put("role", "");
if (cfg.agentMode.orchestration != null) {
payload.put("orchestration", cfg.agentMode.orchestration);
}
new Thread(() -> {
HttpURLConnection conn = null;
@@ -184,11 +199,16 @@ final class CyberStrikeAIClient {
String message = payload.get("message") != null ? String.valueOf(payload.get("message")) : "";
String conversationId = payload.get("conversationId") != null ? String.valueOf(payload.get("conversationId")) : "";
String role = payload.get("role") != null ? String.valueOf(payload.get("role")) : "";
return "{"
+ "\"message\":\"" + escapeJson(message) + "\","
+ "\"conversationId\":\"" + escapeJson(conversationId) + "\","
+ "\"role\":\"" + escapeJson(role) + "\""
+ "}";
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"message\":\"").append(escapeJson(message)).append("\",");
sb.append("\"conversationId\":\"").append(escapeJson(conversationId)).append("\",");
sb.append("\"role\":\"").append(escapeJson(role)).append("\"");
if (payload.containsKey("orchestration") && payload.get("orchestration") != null) {
sb.append(",\"orchestration\":\"").append(escapeJson(String.valueOf(payload.get("orchestration")))).append("\"");
}
sb.append("}");
return sb.toString();
}
private static String escapeJson(String s) {
@@ -15,7 +15,9 @@ final class CyberStrikeAITab implements ITab {
private final JTextField hostField = new JTextField("127.0.0.1");
private final JTextField portField = new JTextField("8080");
private final JPasswordField passwordField = new JPasswordField();
private final JComboBox<String> agentModeBox = new JComboBox<>(new String[]{"Single Agent", "Multi Agent"});
private final JComboBox<String> agentModeBox = new JComboBox<>(new String[]{
"Native ReAct", "Eino Single (ADK)", "Deep (DeepAgent)", "Plan-Execute", "Supervisor"
});
private final JButton validateButton = new JButton("Validate");
private final JButton clearButton = new JButton("Clear Output");
private final JButton stopButton = new JButton("Stop");
@@ -98,7 +100,7 @@ final class CyberStrikeAITab implements ITab {
hostField.setColumns(14);
portField.setColumns(6);
passwordField.setColumns(12);
agentModeBox.setPreferredSize(new Dimension(160, agentModeBox.getPreferredSize().height));
agentModeBox.setPreferredSize(new Dimension(200, agentModeBox.getPreferredSize().height));
JPanel row1 = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 2));
row1.add(new JLabel("Host"));
@@ -475,14 +477,17 @@ final class CyberStrikeAITab implements ITab {
renderMarkdownBox.addActionListener(e -> refreshOutputView());
}
private static final CyberStrikeAIClient.AgentMode[] AGENT_MODES = CyberStrikeAIClient.AgentMode.values();
CyberStrikeAIClient.Config currentConfig() {
String host = hostField.getText().trim();
String port = portField.getText().trim();
String password = new String(passwordField.getPassword());
String baseUrl = "http://" + host + ":" + port;
CyberStrikeAIClient.AgentMode mode = agentModeBox.getSelectedIndex() == 1
? CyberStrikeAIClient.AgentMode.MULTI
: CyberStrikeAIClient.AgentMode.SINGLE;
int idx = agentModeBox.getSelectedIndex();
CyberStrikeAIClient.AgentMode mode = (idx >= 0 && idx < AGENT_MODES.length)
? AGENT_MODES[idx]
: CyberStrikeAIClient.AgentMode.NATIVE_REACT;
return new CyberStrikeAIClient.Config(baseUrl, password, mode);
}
-15
View File
@@ -2,19 +2,4 @@ name: API安全测试
description: API安全测试专家,专注于API接口安全检测
user_prompt: 你是一个专业的API安全测试专家。请使用专业的API测试工具对目标API接口进行全面的安全检测,包括GraphQL安全、API参数fuzzing、JWT分析、API架构分析等工作。
icon: "\U0001F4E1"
tools:
- api-fuzzer
- api-schema-analyzer
- graphql-scanner
- arjun
- jwt-analyzer
- http-intruder
- http-framework-test
- burpsuite
- httpx
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-28
View File
@@ -2,32 +2,4 @@ name: CTF
description: CTF竞赛专家,擅长解题和漏洞利用
user_prompt: 你是一个CTF竞赛专家。请使用CTF解题思维和方法,快速定位和利用漏洞,解决各类CTF题目。
icon: "\U0001F3C6"
tools:
- amass
- anew
- angr
- api-fuzzer
- api-schema-analyzer
- arjun
- arp-scan
- autorecon
- binwalk
- bloodhound
- burpsuite
- cat
- checkov
- checksec
- cloudmapper
- create-file
- cyberchef
- dalfox
- delete-file
- httpx
- http-framework-test
- exec
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-20
View File
@@ -2,24 +2,4 @@ name: Web应用扫描
description: Web应用漏洞扫描专家,全面的Web安全检测
user_prompt: 你是一个专业的Web应用漏洞扫描专家。请使用各种Web扫描工具对目标Web应用进行全面的安全检测,包括目录枚举、文件扫描、漏洞识别等工作。
icon: "\U0001F310"
tools:
- dirsearch
- dirb
- gobuster
- feroxbuster
- ffuf
- wfuzz
- sqlmap
- dalfox
- xsser
- nikto
- nuclei
- wpscan
- httpx
- http-framework-test
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-14
View File
@@ -2,18 +2,4 @@ name: Web框架测试
description: Web框架安全测试专家,专注于Web应用框架漏洞检测
user_prompt: 你是一个专业的Web框架安全测试专家。请使用专业的工具对Web应用框架进行安全测试,识别框架相关的安全漏洞和配置问题。
icon: "\U0001F310"
tools:
- http-framework-test
- nikto
- nuclei
- wafw00f
- wpscan
- httpx
- burpsuite
- zap
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-26
View File
@@ -2,30 +2,4 @@ name: 二进制分析
description: 二进制分析与利用专家,擅长逆向工程和密码破解
user_prompt: 你是一个专业的二进制分析与利用专家。请使用逆向工程工具分析二进制文件,识别漏洞,进行利用开发。同时擅长密码破解、哈希分析等技术。
icon: "\U0001F52C"
tools:
- dirsearch
- docker-bench-security
- exec
- execute-python-script
- install-python-package
- ghidra
- graphql-scanner
- hakrawler
- hash-identifier
- hashcat
- hashpump
- http-framework-test
- httpx
- gdb
- radare2
- objdump
- strings
- binwalk
- ropper
- ropgadget
- john
- cyberchef
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-12
View File
@@ -2,16 +2,4 @@ name: 云安全审计
description: 云安全审计专家,多云环境安全检测
user_prompt: 你是一个专业的云安全审计专家。请使用专业的云安全工具对AWS、Azure、GCP等云环境进行全面的安全审计,包括配置检查、合规性评估、权限审计、安全最佳实践验证等工作。
icon:
tools:
- prowler
- scout-suite
- cloudmapper
- pacu
- terrascan
- checkov
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-26
View File
@@ -2,30 +2,4 @@ name: 信息收集
description: 资产发现与信息搜集专家
user_prompt: 你是一个专业的信息收集专家。请使用各种信息收集技术和工具,对目标进行全面的资产发现、子域名枚举、端口扫描、服务识别等信息收集工作。
icon: "\U0001F50D"
tools:
- amass
- subfinder
- dnsenum
- fierce
- fofa_search
- zoomeye_search
- nmap
- masscan
- rustscan
- arp-scan
- nbtscan
- httpx
- http-framework-test
- katana
- hakrawler
- waybackurls
- paramspider
- gau
- uro
- qsreplace
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-18
View File
@@ -2,22 +2,4 @@ name: 后渗透测试
description: 后渗透测试专家,权限维持与横向移动
user_prompt: 你是一个专业的后渗透测试专家。请使用专业的后渗透工具在获得初始访问权限后进行权限提升、横向移动、权限维持、数据收集等后渗透测试工作。
icon: "\U0001F575"
tools:
- linpeas
- winpeas
- mimikatz
- bloodhound
- impacket
- responder
- netexec
- rpcclient
- smbmap
- enum4linux
- enum4linux-ng
- exec
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-13
View File
@@ -2,17 +2,4 @@ name: 容器安全
description: 容器与Kubernetes安全专家,容器环境安全检测
user_prompt: 你是一个专业的容器与Kubernetes安全专家。请使用专业的容器安全工具对Docker容器和Kubernetes集群进行全面的安全检测,包括镜像漏洞扫描、配置检查、运行时安全等工作。
icon: "\U0001F6E1"
tools:
- trivy
- clair
- docker-bench-security
- kube-bench
- kube-hunter
- falco
- exec
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-19
View File
@@ -2,23 +2,4 @@ name: 数字取证
description: 数字取证与隐写分析专家,文件与内存取证
user_prompt: 你是一个专业的数字取证与隐写分析专家。请使用专业的取证工具对文件、磁盘镜像、内存转储进行分析,提取证据信息。同时擅长隐写分析、数据恢复、元数据提取等技术。
icon: "\U0001F50E"
tools:
- volatility
- volatility3
- foremost
- steghide
- stegsolve
- zsteg
- exiftool
- binwalk
- strings
- xxd
- fcrackzip
- pdfcrack
- exec
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-28
View File
@@ -2,32 +2,4 @@ name: 渗透测试
description: 专业渗透测试专家,全面深入的漏洞检测
user_prompt: 你是一个专业的网络安全渗透测试专家。请使用专业的渗透测试方法和工具,对目标进行全面的安全测试,包括但不限于SQL注入、XSS、CSRF、文件包含、命令执行等常见漏洞。
icon: "\U0001F3AF"
tools:
- http-framework-test
- httpx
- amass
- anew
- angr
- api-fuzzer
- api-schema-analyzer
- arjun
- arp-scan
- autorecon
- binwalk
- bloodhound
- burpsuite
- cat
- checkov
- checksec
- cloudmapper
- create-file
- cyberchef
- dalfox
- delete-file
- exec
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
-18
View File
@@ -2,22 +2,4 @@ name: 综合漏洞扫描
description: 综合漏洞扫描专家,多类型漏洞检测
user_prompt: 你是一个专业的综合漏洞扫描专家。请使用各种漏洞扫描工具对目标进行全面的安全检测,包括Web漏洞、网络服务漏洞、配置缺陷等多种类型的漏洞识别和分析。
icon:
tools:
- nuclei
- nikto
- sqlmap
- nmap
- masscan
- rustscan
- wafw00f
- dalfox
- xsser
- jaeles
- httpx
- http-framework-test
- execute-python-script
- install-python-package
- record_vulnerability
- list_knowledge_risk_types
- search_knowledge_base
enabled: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 85 KiB

+7 -1
View File
@@ -179,6 +179,12 @@
"unknownTool": "Unknown tool",
"einoAgentReplyTitle": "Sub-agent reply",
"einoRecoveryTitle": "🔄 Invalid tool JSON · run {{n}}/{{max}} (hint appended)",
"einoStreamErrorTitle": "⚠️ Eino stream interrupted ({{agent}})",
"einoStreamErrorMessage": "Streaming read failed; the system will retry or terminate according to policy.",
"iterationLimitReachedTitle": "⛔ Iteration limit reached",
"iterationLimitReachedMessage": "Maximum iteration count reached; automatic iteration has stopped.",
"einoPendingOrphanedTitle": "🧹 Tool call reconciliation",
"einoPendingOrphanedMessage": "Detected {{count}} unclosed tool call(s); marked as failed and finalized automatically.",
"noDescription": "No description",
"noResponseData": "No response data",
"loading": "Loading...",
@@ -234,7 +240,7 @@
},
"hitl": {
"pageTitle": "HITL approvals",
"pendingTitle": "Pending interrupts"
"pendingTitle": "Pending approvals"
},
"progress": {
"callingAI": "Calling AI model...",
+7 -1
View File
@@ -179,6 +179,12 @@
"unknownTool": "未知工具",
"einoAgentReplyTitle": "子代理回复",
"einoRecoveryTitle": "🔄 工具参数无效 · 第 {{n}}/{{max}} 轮(已追加提示)",
"einoStreamErrorTitle": "⚠️ Eino 流式中断({{agent}}",
"einoStreamErrorMessage": "流式读取异常,系统将按策略重试或结束。",
"iterationLimitReachedTitle": "⛔ 达到迭代上限",
"iterationLimitReachedMessage": "已达到最大迭代次数,任务已停止继续自动迭代。",
"einoPendingOrphanedTitle": "🧹 工具调用收尾补偿",
"einoPendingOrphanedMessage": "检测到 {{count}} 个未闭合工具调用,已自动标记为失败并收尾。",
"noDescription": "暂无描述",
"noResponseData": "暂无响应数据",
"loading": "加载中...",
@@ -234,7 +240,7 @@
},
"hitl": {
"pageTitle": "人机协同审批",
"pendingTitle": "待处理中断"
"pendingTitle": "待处理审批"
},
"progress": {
"callingAI": "正在调用AI模型...",
+62 -37
View File
@@ -765,50 +765,59 @@ async function sendMessage() {
if (!response.ok) {
throw new Error('请求失败: ' + response.status);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // 保留最后一个不完整的行
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const eventData = JSON.parse(line.slice(6));
handleStreamEvent(eventData, progressElement, progressId,
() => assistantMessageId, (id) => { assistantMessageId = id; },
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
} catch (e) {
console.error('解析事件数据失败:', e, line);
window.__csAgentLiveStream = {
active: true,
conversationId: currentConversationId || null,
progressId: progressId
};
try {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // 保留最后一个不完整的行
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const eventData = JSON.parse(line.slice(6));
handleStreamEvent(eventData, progressElement, progressId,
() => assistantMessageId, (id) => { assistantMessageId = id; },
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
} catch (e) {
console.error('解析事件数据失败:', e, line);
}
}
}
}
}
// 处理剩余的buffer
if (buffer.trim()) {
const lines = buffer.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const eventData = JSON.parse(line.slice(6));
handleStreamEvent(eventData, progressElement, progressId,
() => assistantMessageId, (id) => { assistantMessageId = id; },
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
} catch (e) {
console.error('解析事件数据失败:', e, line);
// 处理剩余的buffer
if (buffer.trim()) {
const lines = buffer.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const eventData = JSON.parse(line.slice(6));
handleStreamEvent(eventData, progressElement, progressId,
() => assistantMessageId, (id) => { assistantMessageId = id; },
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
} catch (e) {
console.error('解析事件数据失败:', e, line);
}
}
}
}
} finally {
window.__csAgentLiveStream = { active: false, conversationId: null, progressId: null };
}
// 消息发送成功后,再次确保草稿被清除
clearChatDraft();
try {
@@ -2922,6 +2931,22 @@ async function loadConversation(conversationId) {
await window.restoreHitlInlineForConversation(conversationId);
}
}
// 页面刷新后主流式连接会中断;若该会话仍在后端运行,自动挂载 task-events 补流继续更新前端迭代进度。
const skipReplay = typeof window.shouldSkipTaskEventReplayAttach === 'function'
&& window.shouldSkipTaskEventReplayAttach(conversationId);
if (
seq === loadConversationRequestSeq &&
currentConversationId === conversationId &&
typeof window.attachRunningTaskEventStream === 'function' &&
!skipReplay
) {
Promise.resolve()
.then(() => window.attachRunningTaskEventStream(conversationId))
.catch((e) => {
console.warn('attachRunningTaskEventStream on loadConversation failed', e);
});
}
} catch (error) {
console.error('加载对话失败:', error);
alert('加载对话失败: ' + error.message);
+179 -72
View File
@@ -3,6 +3,36 @@ let activeTaskInterval = null;
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次
const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']);
/**
* 主对话 POST 流仍在读取时,禁止再挂 task-events 补流,否则同一事件会画两遍(与 HITL 是否开启无关)。
* window.__csAgentLiveStream 由 chat.js sendMessage 在读到 body 后设置,在 finally 中清除。
*/
function syncAgentLiveStreamConversationId(cid) {
if (!cid) return;
try {
const live = window.__csAgentLiveStream;
if (live && live.active) {
live.conversationId = cid;
}
} catch (e) { /* ignore */ }
}
function shouldSkipTaskEventReplayAttach(conversationId) {
try {
const live = window.__csAgentLiveStream;
if (!live || !live.active || !live.progressId) return false;
if (!document.getElementById(live.progressId)) return false;
// 新会话:conversation 事件尚未到达前 conversationId 可能仍为 null,一律不补挂
if (live.conversationId == null) return true;
return live.conversationId === conversationId;
} catch (e) {
return false;
}
}
if (typeof window !== 'undefined') {
window.shouldSkipTaskEventReplayAttach = shouldSkipTaskEventReplayAttach;
}
// 当前界面语言对应的 BCP 47 标签(与时间格式化一致)
function getCurrentTimeLocale() {
if (typeof window.__locale === 'string' && window.__locale.length) {
@@ -934,6 +964,7 @@ function handleStreamEvent(event, progressElement, progressId,
// 更新当前对话ID
currentConversationId = event.data.conversationId;
syncAgentLiveStreamConversationId(event.data.conversationId);
updateActiveConversation();
addAttackChainButton(currentConversationId);
loadActiveTasks();
@@ -1120,6 +1151,49 @@ function handleStreamEvent(event, progressElement, progressId,
break;
}
case 'eino_stream_error': {
const d = event.data || {};
const agent = d.einoAgent ? String(d.einoAgent) : '';
const title = typeof window.t === 'function'
? window.t('chat.einoStreamErrorTitle', { agent: agent || '-' })
: (agent ? ('⚠️ Eino 流式中断(' + agent + '') : '⚠️ Eino 流式中断');
addTimelineItem(timeline, 'warning', {
title: title,
message: event.message || (typeof window.t === 'function'
? window.t('chat.einoStreamErrorMessage')
: '流式读取异常,系统将按策略重试或结束。'),
data: d
});
break;
}
case 'iteration_limit_reached': {
addTimelineItem(timeline, 'warning', {
title: typeof window.t === 'function' ? window.t('chat.iterationLimitReachedTitle') : '⛔ 达到迭代上限',
message: event.message || (typeof window.t === 'function'
? window.t('chat.iterationLimitReachedMessage')
: '已达到最大迭代次数,任务已停止继续自动迭代。'),
data: event.data
});
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
break;
}
case 'eino_pending_orphaned': {
const d = event.data || {};
const count = Number(d.pendingCount || 0);
const countText = Number.isFinite(count) && count > 0 ? String(count) : '?';
addTimelineItem(timeline, 'warning', {
title: typeof window.t === 'function' ? window.t('chat.einoPendingOrphanedTitle') : '🧹 工具调用收尾补偿',
message: event.message || (typeof window.t === 'function'
? window.t('chat.einoPendingOrphanedMessage', { count: countText })
: ('检测到 ' + countText + ' 个未闭合工具调用,已自动标记为失败并收尾。')),
data: d
});
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
break;
}
case 'tool_call':
const toolInfo = event.data || {};
const toolName = toolInfo.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
@@ -1429,6 +1503,7 @@ function handleStreamEvent(event, progressElement, progressId,
break;
}
currentConversationId = responseData.conversationId;
syncAgentLiveStreamConversationId(responseData.conversationId);
updateActiveConversation();
addAttackChainButton(currentConversationId);
updateProgressConversation(progressId, responseData.conversationId);
@@ -1509,6 +1584,7 @@ function handleStreamEvent(event, progressElement, progressId,
}
currentConversationId = responseData.conversationId;
syncAgentLiveStreamConversationId(responseData.conversationId);
updateActiveConversation();
addAttackChainButton(currentConversationId);
updateProgressConversation(progressId, responseData.conversationId);
@@ -1639,6 +1715,7 @@ function handleStreamEvent(event, progressElement, progressId,
// 更新对话ID
if (event.data && event.data.conversationId) {
currentConversationId = event.data.conversationId;
syncAgentLiveStreamConversationId(event.data.conversationId);
updateActiveConversation();
addAttackChainButton(currentConversationId);
updateProgressConversation(progressId, event.data.conversationId);
@@ -1939,90 +2016,120 @@ async function refreshLastAssistantProcessDetails(conversationId) {
window.refreshLastAssistantProcessDetails = refreshLastAssistantProcessDetails;
const taskEventReplayAttachState = {
conversationId: null,
inFlightPromise: null
};
/**
* 订阅运行中任务的 SSE 镜像(GET /api/agent-loop/task-events),用于 HITL 通过后主连接已断开时接续 UI。
*/
async function attachRunningTaskEventStream(conversationId) {
if (!conversationId || typeof apiFetch !== 'function') return false;
try {
const check = await apiFetch('/api/agent-loop/tasks');
if (!check.ok) return false;
const j = await check.json().catch(function () { return {}; });
const active = (j.tasks || []).some(function (t) {
return t && t.conversationId === conversationId && (t.status === 'running' || t.status === 'cancelling');
});
if (!active) return false;
if (
taskEventReplayAttachState.inFlightPromise &&
taskEventReplayAttachState.conversationId === conversationId
) {
return taskEventReplayAttachState.inFlightPromise;
}
if (shouldSkipTaskEventReplayAttach(conversationId)) {
return false;
}
const asEl = findLastAssistantMessageElInChat();
if (!asEl || !asEl.id) return false;
const backendId = asEl.dataset && asEl.dataset.backendMessageId;
if (backendId && typeof renderProcessDetails === 'function') {
const res = await apiFetch('/api/messages/' + encodeURIComponent(String(backendId)) + '/process-details');
const jd = await res.json().catch(function () { return {}; });
if (res.ok && Array.isArray(jd.processDetails)) {
renderProcessDetails(asEl.id, jd.processDetails);
}
}
expandProcessDetailsTimeline(asEl.id);
const attachPromise = (async function () {
try {
const check = await apiFetch('/api/agent-loop/tasks');
if (!check.ok) return false;
const j = await check.json().catch(function () { return {}; });
const active = (j.tasks || []).some(function (t) {
return t && t.conversationId === conversationId && (t.status === 'running' || t.status === 'cancelling');
});
if (!active) return false;
const progressId = taskReplayProgressId(conversationId);
beginCsTaskReplay(progressId, asEl.id, conversationId);
const url = '/api/agent-loop/task-events?conversationId=' + encodeURIComponent(conversationId);
const response = await apiFetch(url, {
method: 'GET',
headers: { Accept: 'text/event-stream' }
});
if (!response.ok) {
clearCsTaskReplay();
if (progressTaskState.has(progressId)) {
progressTaskState.delete(progressId);
}
return false;
}
let mcpIds = [];
const assistantDomId = asEl.id;
const getAssistantIdFn = function () { return assistantDomId; };
const setAssistantIdFn = function () {};
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const chunk = await reader.read();
if (chunk.done) break;
buffer += decoder.decode(chunk.value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (let li = 0; li < lines.length; li++) {
const line = lines[li];
if (line.indexOf('data: ') === 0) {
try {
const eventData = JSON.parse(line.slice(6));
handleStreamEvent(eventData, null, progressId, getAssistantIdFn, setAssistantIdFn, function () { return mcpIds; }, function (ids) { mcpIds = ids; });
} catch (e) {
console.error('task-events parse', e);
const asEl = findLastAssistantMessageElInChat();
if (!asEl || !asEl.id) return false;
const backendId = asEl.dataset && asEl.dataset.backendMessageId;
if (backendId && typeof renderProcessDetails === 'function') {
const res = await apiFetch('/api/messages/' + encodeURIComponent(String(backendId)) + '/process-details');
const jd = await res.json().catch(function () { return {}; });
if (res.ok && Array.isArray(jd.processDetails)) {
renderProcessDetails(asEl.id, jd.processDetails);
// renderProcessDetails 会重建时间线节点,需重新挂载 HITL 审批入口
if (typeof window.restoreHitlInlineForConversation === 'function') {
await window.restoreHitlInlineForConversation(conversationId);
}
}
}
}
if (window.csTaskReplay && window.csTaskReplay.progressId === progressId) {
expandProcessDetailsTimeline(asEl.id);
const progressId = taskReplayProgressId(conversationId);
beginCsTaskReplay(progressId, asEl.id, conversationId);
const url = '/api/agent-loop/task-events?conversationId=' + encodeURIComponent(conversationId);
const response = await apiFetch(url, {
method: 'GET',
headers: { Accept: 'text/event-stream' }
});
if (!response.ok) {
clearCsTaskReplay();
if (progressTaskState.has(progressId)) {
progressTaskState.delete(progressId);
}
return false;
}
let mcpIds = [];
const assistantDomId = asEl.id;
const getAssistantIdFn = function () { return assistantDomId; };
const setAssistantIdFn = function () {};
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const chunk = await reader.read();
if (chunk.done) break;
buffer += decoder.decode(chunk.value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (let li = 0; li < lines.length; li++) {
const line = lines[li];
if (line.indexOf('data: ') === 0) {
try {
const eventData = JSON.parse(line.slice(6));
handleStreamEvent(eventData, null, progressId, getAssistantIdFn, setAssistantIdFn, function () { return mcpIds; }, function (ids) { mcpIds = ids; });
} catch (e) {
console.error('task-events parse', e);
}
}
}
}
if (window.csTaskReplay && window.csTaskReplay.progressId === progressId) {
clearCsTaskReplay();
}
if (progressTaskState.has(progressId)) {
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成');
}
if (typeof loadActiveTasks === 'function') loadActiveTasks();
if (typeof window.loadConversation === 'function' && window.currentConversationId === conversationId) {
await window.loadConversation(conversationId);
}
return true;
} catch (e) {
console.warn('attachRunningTaskEventStream', e);
clearCsTaskReplay();
return false;
} finally {
if (taskEventReplayAttachState.inFlightPromise === attachPromise) {
taskEventReplayAttachState.inFlightPromise = null;
taskEventReplayAttachState.conversationId = null;
}
}
if (progressTaskState.has(progressId)) {
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成');
}
if (typeof loadActiveTasks === 'function') loadActiveTasks();
if (typeof window.loadConversation === 'function' && window.currentConversationId === conversationId) {
await window.loadConversation(conversationId);
}
return true;
} catch (e) {
console.warn('attachRunningTaskEventStream', e);
clearCsTaskReplay();
return false;
}
})();
taskEventReplayAttachState.conversationId = conversationId;
taskEventReplayAttachState.inFlightPromise = attachPromise;
return attachPromise;
}
window.attachRunningTaskEventStream = attachRunningTaskEventStream;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

After

Width:  |  Height:  |  Size: 85 KiB

+1 -1
View File
@@ -734,7 +734,7 @@
</div>
<div class="page-content">
<div class="settings-section">
<h3 data-i18n="hitl.pendingTitle">待处理中断</h3>
<h3 data-i18n="hitl.pendingTitle">待处理审批</h3>
<div id="hitl-pending-list" class="hitl-pending-list"></div>
</div>
</div>