From 3dfcde7c8864d33d288109c3ae5c8fc23b46cd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:02:14 +0800 Subject: [PATCH] Delete docs directory --- docs/MULTI_AGENT_EINO.md | 65 ---- docs/VISION.md | 45 --- docs/frontend-i18n.md | 335 -------------------- docs/hitl-best-practices.md | 122 -------- docs/hitl-best-practices_en.md | 122 -------- docs/robot.md | 491 ----------------------------- docs/robot_en.md | 463 --------------------------- docs/workflow-graph.md | 553 --------------------------------- docs/workflow-graph_en.md | 553 --------------------------------- 9 files changed, 2749 deletions(-) delete mode 100644 docs/MULTI_AGENT_EINO.md delete mode 100644 docs/VISION.md delete mode 100644 docs/frontend-i18n.md delete mode 100644 docs/hitl-best-practices.md delete mode 100644 docs/hitl-best-practices_en.md delete mode 100644 docs/robot.md delete mode 100644 docs/robot_en.md delete mode 100644 docs/workflow-graph.md delete mode 100644 docs/workflow-graph_en.md diff --git a/docs/MULTI_AGENT_EINO.md b/docs/MULTI_AGENT_EINO.md deleted file mode 100644 index 76bf3919..00000000 --- a/docs/MULTI_AGENT_EINO.md +++ /dev/null @@ -1,65 +0,0 @@ -# Eino 多代理改造说明(DeepAgent) - -本文档记录 **Eino 单代理(ADK)** 与 **多 Agent(CloudWeGo Eino `adk/prebuilt`)** 的改造范围、进度与后续事项。原生 ReAct 执行路径已移除。 - -## 总体结论 - -- **改造已可用于生产试验**:流式对话、MCP 工具桥接、配置开关、前端模式切换均已落地。 -- **入口策略**:**单代理** 走 `/api/eino-agent/stream`;多代理走 `/api/multi-agent/stream`,请求体 **`orchestration`** 指定编排。模式定位按 Eino ADK 最佳实践区分:**Deep** 适合复杂安全测试与 task 子代理协作;**Plan-Execute** 适合目标明确的规划 → 执行 → 重规划闭环;**Supervisor** 适合多个专业子代理动态分派的专家路由场景。机器人默认 `robot_default_agent_mode: eino_single`;批量队列默认 `eino_single`,多代理模式需 `multi_agent.enabled`。 - -## 已完成项 - -| 项 | 说明 | -|----|------| -| 依赖与代理 | `go.mod` 直接依赖 `github.com/cloudwego/eino`、`eino-ext/.../openai`;`go.mod` 注释与 `scripts/bootstrap-go.sh` 指导 **GOPROXY**(如 `https://goproxy.cn,direct`)。 | -| 配置 | `config.yaml` → `agent.max_iterations` 为全局 ReAct 上限(主/子代理统一);`multi_agent`:`enabled`、`robot_use_multi_agent`、`sub_agents`(含可选 `bind_role`)、`eino_skills`、`eino_middleware` 等;结构体见 `internal/config/config.go`。 | -| Markdown 子代理 / 主代理 | 在 `agents_dir` 下放 `*.md`。**子代理**:供 Deep `task` 与 `supervisor` `transfer`。**主代理(按模式分离)**:`orchestrator.md`(或 `kind: orchestrator` 的**单个**其他 .md)→ **Deep**;固定名 `orchestrator-plan-execute.md` → **plan_execute**;固定名 `orchestrator-supervisor.md` → **supervisor**。正文优先于 YAML:`multi_agent.orchestrator_instruction`、`orchestrator_instruction_plan_execute`、`orchestrator_instruction_supervisor`;plan_execute / supervisor **不会**回退到 Deep 的 `orchestrator_instruction`。皆空时 plan_execute / supervisor 使用代码内置默认提示。管理:**Agents → Agent管理**;API:`/api/multi-agent/markdown-agents*`。 | -| MCP 桥 | `internal/einomcp`:`ToolsFromDefinitions` + 会话 ID 持有者,执行走 `Agent.ExecuteMCPToolForConversation`。 | -| 编排 | `internal/multiagent/runner.go`:`deep.New` + 子 `ChatModelAgent` + `adk.NewRunner`(`EnableStreaming: true`,可选 `CheckPointStore`),事件映射为现有 SSE `tool_call` / `response_delta` 等。 | -| HTTP | `POST /api/multi-agent`(非流式)、`POST /api/multi-agent/stream`(SSE);路由**常注册**,是否可用由运行时 `multi_agent.enabled` 决定(流式未启用时 SSE 内 `error` + `done`)。 | -| 会话准备 | `internal/handler/multi_agent_prepare.go`:`prepareMultiAgentSession`(含 **WebShell** `CreateConversationWithWebshell`、工具白名单与单代理一致)。 | -| 单 Agent | `internal/agent` 为 MCP/工具层(`ToolsForRole`、`ExecuteMCPToolForConversation`);单代理编排走 `RunEinoSingleChatModelAgent`(`/api/eino-agent*`)。 | -| 前端 | 主聊天 / WebShell:**Eino 单代理**(`/api/eino-agent/stream`)与 **Deep / Plan-Execute / Supervisor**(`/api/multi-agent/stream` + `orchestration`);`multi_agent.enabled` 控制多代理选项是否展示。 | -| 流式兼容 | Eino 单/多代理与 Web UI 共用 `handleStreamEvent`:`conversation`、`progress`、`response_start` / `response_delta`、`thinking` / `thinking_stream_*`、`tool_*`、`response`、`done` 等。 | -| 批量任务 | 队列 `agentMode` 为 `deep` / `plan_execute` / `supervisor` 时子任务带对应 `orchestration` 调用 `RunDeepAgent`;旧值 `multi` 与「`agentMode` 为空且 `batch_use_multi_agent: true`」均按 `deep`。 | -| 配置 API | `GET /api/config` 返回 `multi_agent: { enabled, robot_use_multi_agent, sub_agent_count }`;`PUT /api/config` 可更新 `enabled`、`robot_use_multi_agent`(不覆盖 `sub_agents`)。 | -| OpenAPI | 多代理路径说明已更新(流式未启用为 SSE 错误事件)。 | -| 机器人 | `ProcessMessageForRobot` 按 `robot_default_agent_mode`(默认 `eino_single`)调用 `RunEinoSingleChatModelAgent` 或 `RunDeepAgent`。 | -| 预置编排 | 聊天 / WebShell:`POST /api/multi-agent*` 请求体 `orchestration`:`deep` \| `plan_execute` \| `supervisor`(缺省 `deep`)。`deep` 使用 task 子代理协作;`plan_execute` 不构建 YAML/Markdown 子代理;`plan_execute_loop_max_iterations` 仍来自配置;`supervisor` 至少需一个子代理,只有一个子代理时会提示其专家路由空间有限。 | -| Eino 中间件 | `multi_agent.eino_middleware`(可选):`patchtoolcalls`(默认开)、`toolsearch`(按阈值拆分 MCP 工具列表)、`plantask`(需 `eino_skills`)、`reduction`(大工具输出截断/落盘)、`checkpoint_dir`(Runner 断点)、`deep_output_key` / `deep_model_retry_max_retries` / `task_tool_description_prefix`(Deep 与 supervisor 主代理共享其中模型重试与 OutputKey)。**`plan_execute`**:Executor 使用 Eino 官方允许的自定义 `adk.ChatModelAgent`,保持官方 Plan/UserInput/ExecutedSteps session contract,同时挂载与 Deep/Supervisor 主代理同源的 middleware(patch → reduction → toolsearch → plantask → filesystem → skill → summarization tail)。Planner/Replanner 仅 summarization tail + prompt 预算截断,不跑 MCP 工具链。当前 Eino 官方 `planexecute.NewExecutor` 尚未暴露 Handlers 字段,因此该自定义 Executor 是保留 middleware 的对齐实现。 | - -## 进行中 / 待办( backlog ) - -| 优先级 | 项 | 说明 | -|--------|----|------| -| P3 | **观测与计费** | Eino 事件可进一步打结构化日志 / trace id,便于排障。 | -| P3 | **测试** | 增加 `internal/multiagent` 与 einomcp 的集成测试(mock model 或录屏回放)。 | - -## 关键文件索引 - -- `internal/multiagent/runner.go` — DeepAgent / plan_execute / supervisor 组装与事件循环 -- `internal/multiagent/eino_orchestration.go` — PlanExecute 根节点与 Executor 中间件栈(`buildPlanExecuteExecutorHandlers`) -- `internal/handler/multi_agent.go` — SSE 与(同步)HTTP -- `internal/handler/multi_agent_prepare.go` — 会话准备(含 WebShell) -- `internal/einomcp/` — MCP → Eino Tool -- `config.yaml` — `multi_agent` 示例块 -- `web/static/js/chat.js` — 模式选择与 stream URL -- `web/static/js/webshell.js` — WebShell AI 流式 URL 与主聊天模式对齐 -- `web/static/js/settings.js` — 多代理标量保存 - -## 版本记录 - -| 日期 | 说明 | -|------|------| -| 2026-03-22 | 首版:Eino DeepAgent + stream + 前端开关 + GOPROXY 脚本。 | -| 2026-03-22 | 补充:进度文档、`prepareMultiAgentSession` 抽取、WebShell 后端对齐、`POST /api/multi-agent`、OpenAPI `/api/multi-agent*` 条目。 | -| 2026-03-22 | 路由常注册、流式未启用 SSE 错误、`robot_use_multi_agent`、设置页持久化、WebShell/机器人多代理、`bind_role` 子代理 Skills/tools。 | -| 2026-03-22 | `tool_result.toolCallId`、`ReasoningContent`→思考流、`batch_use_multi_agent` 与批量队列 Eino 执行。 | -| 2026-03-22 | 流式工具事件:按稳定签名去重,避免每 chunk 刷屏与「未知工具」;最终回复去重相同段落;内置调度显示为 `task`。 | -| 2026-03-22 | `agents/*.md` 子代理定义、`agents_dir`、合并进 `RunDeepAgent`、前端 Agents 菜单与 CRUD API。 | -| 2026-03-22 | `orchestrator.md` / `kind: orchestrator` 主代理、列表主/子标记、与 `orchestrator_instruction` 优先级。 | -| 2026-04-19 | 主聊天「对话模式」:原生 ReAct 与 Deep / Plan-Execute / Supervisor;`POST /api/multi-agent*` 请求体 `orchestration` 与界面一致;`config.yaml` / 设置页不再维护预置编排字段(机器人/批量默认 `deep`)。 | -| 2026-04-21 | 移除角色 `skills` 与 `/api/roles/skills/list`;`bind_role` 仅继承 tools;Skills 仅通过 Eino `skill` 工具按需加载。 | -| 2026-07-06 | **最佳实践对齐**:Deep / Plan-Execute / Supervisor 改为中性适用场景描述;Supervisor 标为专家路由特定场景并收紧 transfer/exit 约束;plan_execute Executor 明确为遵循官方 session contract 的自定义 ChatModelAgent,保留 middleware 并补类型保护。 | -| 2026-07-02 | **plan_execute Executor 中间件对齐**:`ExecPreMiddlewares` 与 Deep 主代理同源;`buildPlanExecuteExecutorHandlers` + 回归测试;文档更正。 | -| 2026-06-02 | **移除原生 ReAct**:删除 `/api/agent-loop*` 执行入口与 `AgentLoopWithProgress`;统一 Eino ADK(单代理 `/api/eino-agent*`,多代理 `/api/multi-agent*`);任务 cancel/tasks API 保留。 | diff --git a/docs/VISION.md b/docs/VISION.md deleted file mode 100644 index e7107b40..00000000 --- a/docs/VISION.md +++ /dev/null @@ -1,45 +0,0 @@ -# 视觉分析(analyze_image) - -## 概述 - -- **工具名**:`analyze_image`(MCP 内置) -- **行为**:读取本地图片 → `imaging` 缩放/JPEG 压缩 → 调用独立 **Vision** 模型 → 返回**纯文本**给 Agent -- **上下文**:图片字节**不会**写入对话历史;仅路径与文字摘要进入 Agent 上下文 - -## 配置(`config.yaml` → `vision`) - -```yaml -vision: - enabled: true - model: qwen-vl-max # 必填 - api_key: # 留空 → openai.api_key - base_url: # 留空 → openai.base_url - provider: # 留空 → openai.provider - max_image_bytes: 5242880 - max_dimension: 2048 - jpeg_quality: 82 - max_payload_bytes: 524288 - skip_preprocess_below_bytes: 2097152 # 低于 2MB 且长边<=max_dimension 时原图直传;0=始终 JPEG 压缩 - detail: low # low | high | auto - timeout_seconds: 60 -``` - -`enabled: false` 时不注册工具。 - -## Web 设置 - -**系统设置 → 基本设置 → 视觉分析(analyze_image)** 可配置启用开关、视觉模型、API Key/Base URL(留空复用 OpenAI)、预处理参数;**保存并应用** 后写入 `config.yaml` 并重新注册 MCP 工具。 - -## 路径 - -`analyze_image` 可读取服务器上任意可读的图片文件路径(绝对路径或相对于进程工作目录的相对路径)。仍校验图片扩展名与常规文件类型。 - -## Agent 使用 - -系统提示已说明:遇图片调用 `analyze_image`,勿用 `read_file` 读二进制图。 - -`multi_agent.eino_middleware.tool_search_always_visible_tools` 建议包含 `analyze_image`。 - -## 合规 - -启用后图片会发往 Vision API 配置的上游;敏感环境请使用可信网关或保持 `enabled: false`。 diff --git a/docs/frontend-i18n.md b/docs/frontend-i18n.md deleted file mode 100644 index 7a9f832f..00000000 --- a/docs/frontend-i18n.md +++ /dev/null @@ -1,335 +0,0 @@ -## CyberStrikeAI 前端国际化方案 - -本文档说明 CyberStrikeAI Web 前端(`web/templates/index.html` + `web/static/js/*.js`)的国际化设计与开发规范,确保在不引入打包工具和不改动后端路由的前提下,实现可扩展、低返工的多语言支持。 - -当前目标: - -- **支持中英文切换(zh-CN / en-US)** -- 后续可方便扩展更多语言(如 ja-JP、ko-KR 等) - ---- - -## 一、总体设计原则 - -- **前端主导的客户端国际化**:所有 UI 文案在浏览器端根据当前语言动态渲染,后端 Go 仅负责结构和数据,不参与语言分发。 -- **单一 HTML 模板**:继续使用一份 `index.html` 模板,不为不同语言复制模板文件。 -- **文案与逻辑分离**:所有可见文本通过「键值表」管理(多语言 JSON),HTML / JS 只写 key,不直接写中文/英文常量。 -- **渐进式改造**:先覆盖 header / 登录 / 侧边栏 / 系统设置等关键区域,其他页面按模块逐步迁移,避免一次性大改动。 -- **可回退默认语言**:即使目标语言未完全翻译,也能回退到默认中文,不出现原始 key。 - ---- - -## 二、技术选型与目录结构 - -### 2.1 技术选型 - -- **i18n 引擎**:使用 [i18next](https://www.i18next.com/) 的浏览器 UMD 版本(通过 CDN 引入),无需打包器。 -- **资源格式**:每种语言一份 JSON 文件,采用「域 + 语义」的层级 key 方案,例如: - - `common.ok` - - `nav.dashboard` - - `header.apiDocs` - - `settings.robot.wecom.token` - -### 2.2 目录结构 - -- `web/templates/index.html` - - 页面骨架 + 所有静态文案位置,将逐步改为 `data-i18n` 标记。 -- `web/static/js/i18n.js` - - 前端 i18n 初始化与 DOM 应用逻辑(本方案新增)。 -- `web/static/i18n/`(新增目录) - - `zh-CN.json`:中文文案(默认语言) - - `en-US.json`:英文文案 - - 未来可新增:`ja-JP.json`、`ko-KR.json` 等。 - ---- - -## 三、文案组织规范 - -### 3.1 Key 命名约定 - -- 采用「**模块.语义**」形式,最多 2–3 级,确保可读性: - - 导航:`nav.dashboard`、`nav.chat`、`nav.settings` - - 头部:`header.title`、`header.apiDocs`、`header.logout` - - 登录:`login.title`、`login.subtitle`、`login.passwordLabel`、`login.submit` - - 仪表盘:`dashboard.title`、`dashboard.refresh`、`dashboard.runningTasks` - - 系统设置:`settings.title`、`settings.nav.basic`、`settings.nav.robot`、`settings.apply` - - 机器人配置:`settings.robot.wecom.enabled`、`settings.robot.wecom.token` 等。 -- 尽量按「界面区域」而不是「文件名」划分域,便于非开发人员理解。 - -### 3.2 JSON 示例 - -`web/static/i18n/zh-CN.json` 示例: - -```json -{ - "common": { - "ok": "确定", - "cancel": "取消" - }, - "nav": { - "dashboard": "仪表盘", - "chat": "对话", - "infoCollect": "信息收集", - "tasks": "任务管理", - "vulnerabilities": "漏洞管理", - "settings": "系统设置" - }, - "header": { - "title": "CyberStrikeAI", - "apiDocs": "API 文档", - "logout": "退出登录", - "language": "界面语言" - }, - "login": { - "title": "登录 CyberStrikeAI", - "subtitle": "请输入配置中的访问密码", - "passwordLabel": "密码", - "passwordPlaceholder": "输入登录密码", - "submit": "登录" - } -} -``` - -英文文件 `en-US.json` 保持相同 key,不同 value: - -```json -{ - "common": { - "ok": "OK", - "cancel": "Cancel" - }, - "nav": { - "dashboard": "Dashboard", - "chat": "Chat", - "infoCollect": "Recon", - "tasks": "Tasks", - "vulnerabilities": "Vulnerabilities", - "settings": "Settings" - }, - "header": { - "title": "CyberStrikeAI", - "apiDocs": "API Docs", - "logout": "Sign out", - "language": "Interface language" - }, - "login": { - "title": "Sign in to CyberStrikeAI", - "subtitle": "Enter the access password from config", - "passwordLabel": "Password", - "passwordPlaceholder": "Enter password", - "submit": "Sign in" - } -} -``` - -> 约定:**新增界面时,必须先定义 i18n key,再在 HTML/JS 中使用 key**,禁止直接写死中文/英文。 - ---- - -## 四、HTML 标记规范(data-i18n) - -### 4.1 基本规则 - -- 使用 `data-i18n` 将元素文本与某个 key 绑定: - -```html -仪表盘 -``` - -- 默认行为:脚本会替换元素的 `textContent`。 -- 同时翻译属性时,额外使用 `data-i18n-attr`,逗号分隔多个属性名: - -```html - -``` - -### 4.2 默认文本的作用 - -- HTML 内的中文默认值作为「**无 JS / 初始化前**」的占位内容: - - 页面在 JS 尚未加载完成时不会出现空白或 key。 - - JS 初始化后会用当前语言覆盖这些文本。 - ---- - -## 五、JavaScript 中的文案规范 - -### 5.1 全局翻译函数 `t()` - -由 `i18n.js` 暴露以下全局函数: - -- `window.t(key: string): string` - - 返回当前语言下的翻译文本,若缺失则回退到默认语言,再不行则返回 key 本身。 -- `window.changeLanguage(lang: string): Promise` - - 切换语言并刷新页面文案(不会刷新整页)。 - -示例(以 `web/static/js/settings.js` 为例): - -```js -// 之前 -alert('加载配置失败: ' + error.message); - -// 之后 -alert(t('settings.loadConfigFailed') + ': ' + error.message); -``` - -> 规范:**JS 内所有面向用户的提示、按钮文字、对话框标题都应通过 `t()` 获取**,不直接写死中文/英文。 - -### 5.2 渐进迁移建议 - -- 优先改造: - - 频繁弹出的错误提示 / 成功提示; - - 登录相关、系统设置相关文案。 -- 低优先级: - - 仅面向运维人员的调试提示,可以暂时保留英文/中文常量。 - ---- - -## 六、i18n 初始化与语言切换实现 - -### 6.1 语言选择策略 - -- 默认语言:`zh-CN`。 -- 优先级(从高到低): - 1. `localStorage` 中的用户选择(key:`csai_lang`)。 - 2. 浏览器 `navigator.language`(`zh` 开头 → `zh-CN`,否则 `en-US`)。 - 3. 默认 `zh-CN`。 - -### 6.2 初始化流程(`i18n.js`) - -1. 读取初始语言。 -2. 初始化 i18next: - - `lng` 为当前语言; - - `fallbackLng` 为 `zh-CN`; - - 资源先留空,采用按需加载。 -3. 通过 `fetch` 拉取 `/static/i18n/{lng}.json` 并 `i18next.addResources`。 -4. 更新: - - `` 属性; - - 所有带 `data-i18n` / `data-i18n-attr` 的元素。 -5. 暴露 `window.t` 与 `window.changeLanguage`。 - -### 6.3 DOM 应用逻辑 - -伪代码: - -```js -function applyTranslations(root = document) { - const elements = root.querySelectorAll('[data-i18n]'); - elements.forEach(el => { - const key = el.getAttribute('data-i18n'); - if (!key) return; - const text = i18next.t(key); - if (text) { - el.textContent = text; - } - - const attrList = el.getAttribute('data-i18n-attr'); - if (attrList) { - attrList.split(',').map(s => s.trim()).forEach(attr => { - if (!attr) return; - const val = i18next.t(key); - if (val) el.setAttribute(attr, val); - }); - } - }); -} -``` - -> 对于由 JS 动态插入的元素,需要在插入后再次调用 `applyTranslations(新容器)`。 - ---- - -## 七、语言切换 UI 规范 - -### 7.1 位置与形态 - -- 位置:`index.html` header 右侧 `API 文档` 按钮附近(靠近用户头像)。 -- 交互形式: - - 一个紧凑的语言切换组件,例如: - - `🌐` 图标 + 当前语言文本(`中文` / `English`)的下拉按钮; - - 下拉内容列出所有可用语言。 - -### 7.2 示例结构 - -```html -
- - -
-``` - -对应 JS(在 `i18n.js` 中): - -```js -function onLanguageSelect(lang) { - changeLanguage(lang).then(updateLangLabel).catch(console.error); - closeLangDropdown(); -} - -function updateLangLabel() { - const labelEl = document.getElementById('current-lang-label'); - if (!labelEl) return; - const lang = i18next.language || 'zh-CN'; - labelEl.textContent = lang.startsWith('zh') ? '中文' : 'English'; -} -``` - -> 规范:**语言切换只更新文案,不刷新整页,也不修改 URL hash**。 - ---- - -## 八、开发流程建议 - -### 8.1 新增 / 修改界面的流程 - -1. 设计界面时,先列出所有文案。 -2. 在对应语言 JSON 中补充/修改 key 与翻译。 -3. 在 HTML 中使用 `data-i18n`,在 JS 中使用 `t('...')`。 -4. 在浏览器中切换中英文,确认两种语言显示都正确。 - -### 8.2 渐进式改造顺序(推荐) - -1. **阶段 1(已规划)** - - 引入 i18next 与 `i18n.js`。 - - 新建 `zh-CN.json` / `en-US.json`(先覆盖 header / 登录 / 左侧导航)。 - - 实现 header 区域语言切换组件。 -2. **阶段 2**(已完成) - - 系统设置页面(包括机器人配置页面)全部文案 i18n 化。 - - `settings.js` 中的提示与错误信息改用 `t()`。 -3. **阶段 3**(进行中) - - 仪表盘、任务管理、漏洞管理、MCP、Skills、Roles 等页面按模块逐步迁移。 -4. **阶段 4** - - 清理 JS / HTML 中残留的硬编码中文,统一通过 i18n。 - ---- - -## 九、后续扩展新语言 - -当需要新增语言时: - -1. 在 `web/static/i18n/` 中新增 `{lang}.json`,复制现有英文/中文文件结构,补充对应翻译。 -2. 在语言切换下拉中添加对应选项,例如: - - `data-lang="ja-JP"` / 文本 `日本語` -3. 无需修改 `i18n.js` 或现有 HTML/JS 逻辑,即可支持新语言。 - ---- - -## 十、注意事项与坑点 - -- **不要复制多份 HTML 模板** 来做多语言,那样维护成本极高,本方案统一由前端 i18n 控制。 -- **避免 key 直接用中文/英文句子**,统一采用「模块.语义」短 key,便于 diff 与搜索。 -- 避免在 CSS 中写死文本(如 `content: "xxx"`),如确有需要,应通过 JS 设置并走 i18n。 -- 对于后端返回的可本地化错误文本(未来可能支持),优先由后端根据 `Accept-Language` 返回对应语言,前端只负责展示。 - diff --git a/docs/hitl-best-practices.md b/docs/hitl-best-practices.md deleted file mode 100644 index 930cd5f1..00000000 --- a/docs/hitl-best-practices.md +++ /dev/null @@ -1,122 +0,0 @@ -# 人机协同(HITL)最佳实践 - -[English](hitl-best-practices_en.md) - -人机协同用于在 Agent 执行工具前做审批拦截。它适合控制高风险操作、保留审计痕迹,并在人工审计压力过大时让审计 Agent 接管常规审批。 - -## 配置入口 - -Web 端进入 **系统设置 → 人机协同**,可配置: - -- 全局默认审批方:`human` 或 `audit_agent` -- 审计 Agent 专用模型:`hitl.audit_model` -- 已决策审计日志保留天数 -- 免审批工具白名单:`hitl.tool_whitelist` -- 审批模式与审查编辑模式的审计提示词 - -对应的 `config.yaml` 示例: - -```yaml -hitl: - default_reviewer: human - audit_model: - provider: "" - base_url: "" - api_key: "" - model: "" # 可填小模型;留空复用 openai.model - retention_days: 90 - tool_whitelist: [read_file, list_dir, glob, grep, tool_search] -``` - -`audit_model` 的字段可以只填一部分。空字段会自动继承主 `openai` 配置,因此常见做法是只填 `model`,让审计 Agent 使用更便宜的小模型。 - -## 推荐审批策略 - -### 1. 默认人工,逐步放权 - -刚开始建议: - -- `default_reviewer: human` -- 仅把明显只读工具加入 `tool_whitelist` -- 对写文件、执行命令、C2 任务、WebShell 操作保持人工审批 - -运行一段时间后,观察审计日志,把重复、低风险、误报少的工具加入白名单。 - -### 2. 人工审不过来时,用小模型接管常规审批 - -当待审批积压明显时,可以切换为: - -```yaml -hitl: - default_reviewer: audit_agent - audit_model: - model: "your-small-reviewer-model" -``` - -建议让小模型处理: - -- 只读查询 -- 信息收集 -- 端口与服务扫描 -- 目录枚举 -- 无破坏性的验证命令 - -仍建议人工处理: - -- 删除、覆盖、清空数据 -- 修改权限、密码、账号 -- 持久化、横向移动、C2 高风险任务 -- 对生产目标的写入操作 - -### 3. 用提示词定义组织策略 - -审计 Agent 的提示词应该写成策略,而不是泛泛地说“谨慎审批”。建议明确: - -- 默认放行哪些低风险操作 -- 必须拒绝哪些破坏性操作 -- 哪些情况需要人工升级 -- 审查编辑模式下允许怎样收窄参数 - -示例策略片段: - -```text -常规信息收集、只读查询、端口扫描默认 approve。 -涉及删除文件、清空数据库、修改账号权限、写入持久化后门、停止关键服务时必须 reject。 -若目标范围超出用户授权范围,应 reject。 -审查编辑模式下,可将路径、目标、命令参数收窄后 approve,但不得扩大攻击面。 -``` - -### 4. 白名单只放稳定低风险工具 - -白名单工具会跳过审批,因此要保守维护。推荐放: - -- `read_file` -- `list_dir` -- `glob` -- `grep` -- `tool_search` - -不建议直接全局白名单: - -- 任意 shell 执行工具 -- 文件写入/删除工具 -- C2 任务工具 -- WebShell 命令执行工具 - -## 模式选择 - -| 模式 | 适用场景 | -|------|----------| -| 关闭 | 本地实验、完全信任工具链 | -| 审批模式 | 只需要通过/拒绝 | -| 审查编辑 | 希望审计 Agent 收窄参数后放行 | - -如果你已经配置了小模型审计,推荐从 **审批模式** 开始。只有当你希望 AI 自动收窄路径、目标范围或命令参数时,再开启 **审查编辑**。 - -## 运维建议 - -- 定期查看 **人机协同 → 审计日志**,调整白名单和提示词。 -- 高风险环境下保持 `default_reviewer: human`,只让审计 Agent 辅助给出建议。 -- 小模型审批失败时默认保守拒绝,这是预期行为。 -- 修改 `hitl.audit_model` 后先在页面点击 **测试审计模型**。 -- 对生产、客户、真实业务系统操作前,应保留人工最终确认。 diff --git a/docs/hitl-best-practices_en.md b/docs/hitl-best-practices_en.md deleted file mode 100644 index 5f64574c..00000000 --- a/docs/hitl-best-practices_en.md +++ /dev/null @@ -1,122 +0,0 @@ -# Human-in-the-loop (HITL) Best Practices - -[中文](hitl-best-practices.md) - -HITL reviews tool calls before an Agent executes them. Use it to control high-risk operations, keep an audit trail, and let an Audit Agent take over routine approvals when human reviewers cannot keep up. - -## Where To Configure - -Open **System Settings → Human-in-the-loop** in the web UI. You can configure: - -- Global default reviewer: `human` or `audit_agent` -- Dedicated Audit Agent model: `hitl.audit_model` -- Resolved audit log retention days -- No-approval tool allowlist: `hitl.tool_whitelist` -- Audit prompts for approval mode and review-edit mode - -Example `config.yaml`: - -```yaml -hitl: - default_reviewer: human - audit_model: - provider: "" - base_url: "" - api_key: "" - model: "" # set a small model here; blank reuses openai.model - retention_days: 90 - tool_whitelist: [read_file, list_dir, glob, grep, tool_search] -``` - -`audit_model` supports partial configuration. Empty fields inherit from the main `openai` config, so the common setup is to fill only `model` and run approvals on a cheaper small model. - -## Recommended Approval Strategy - -### 1. Start With Humans, Then Delegate Gradually - -At the beginning, prefer: - -- `default_reviewer: human` -- Only clearly read-only tools in `tool_whitelist` -- Human approval for file writes, command execution, C2 tasks, and WebShell operations - -After observing audit logs, move repeated low-risk operations into the allowlist. - -### 2. Use A Small Model When Humans Cannot Keep Up - -When pending approvals start piling up, switch routine review to the Audit Agent: - -```yaml -hitl: - default_reviewer: audit_agent - audit_model: - model: "your-small-reviewer-model" -``` - -Good candidates for small-model review: - -- Read-only queries -- Reconnaissance -- Port and service scans -- Directory enumeration -- Non-destructive validation commands - -Keep human review for: - -- Deleting, overwriting, or clearing data -- Modifying permissions, passwords, or accounts -- Persistence, lateral movement, and high-risk C2 tasks -- Writes against production targets - -### 3. Encode Your Policy In The Prompt - -The Audit Agent prompt should describe an operational policy, not just say “be careful.” Make it explicit: - -- Which low-risk actions are normally approved -- Which destructive actions must be rejected -- Which cases require escalation to a human -- How review-edit mode may narrow arguments - -Example policy snippet: - -```text -Approve routine reconnaissance, read-only queries, and port scans by default. -Reject file deletion, database clearing, account or permission changes, persistence, and stopping critical services. -Reject actions outside the user-authorized target scope. -In review-edit mode, you may narrow paths, targets, or command arguments before approving, but must not expand the attack surface. -``` - -### 4. Keep The Allowlist Conservative - -Allowlisted tools skip approval, so keep the list stable and low-risk. Recommended examples: - -- `read_file` -- `list_dir` -- `glob` -- `grep` -- `tool_search` - -Avoid globally allowlisting: - -- Arbitrary shell execution tools -- File write/delete tools -- C2 task tools -- WebShell command execution tools - -## Mode Selection - -| Mode | Best for | -|------|----------| -| Off | Local labs or fully trusted toolchains | -| Approval | Approve/reject only | -| Review-edit | Let the Audit Agent narrow arguments before approval | - -If you configured a small audit model, start with **Approval** mode. Use **Review-edit** only when you want the AI to safely narrow paths, target ranges, or command arguments. - -## Operations Tips - -- Review **Human-in-the-loop → Audit logs** regularly and tune allowlists/prompts. -- In high-risk environments, keep `default_reviewer: human` and use the Audit Agent only for recommendations. -- If the small-model reviewer fails, CyberStrikeAI rejects conservatively by default. -- After changing `hitl.audit_model`, click **Test audit model** in the settings page. -- For production, customer, or real business systems, keep a human as the final approver. diff --git a/docs/robot.md b/docs/robot.md deleted file mode 100644 index 0ce35959..00000000 --- a/docs/robot.md +++ /dev/null @@ -1,491 +0,0 @@ -# CyberStrikeAI 机器人使用说明 - -[English](robot_en.md) - -本文档说明如何通过**个人微信**、**钉钉**、**飞书**与 **企业微信** 与 CyberStrikeAI 对话(长连接 / 回调模式),在手机端即可使用,无需在服务器上打开网页。按下面步骤操作可避免常见弯路。 - ---- - -## 一、在 CyberStrikeAI 里从哪里配置 - -1. 登录 CyberStrikeAI Web 端 -2. 左侧导航进入 **系统设置** -3. 在左侧设置分类中点击 **机器人设置**(位于「基本设置」与「安全设置」之间) -4. 按平台配置: - - **个人微信**:点击「微信 / iLink」→「生成二维码并绑定」,用微信扫码确认(见 [3.4 个人微信](#34-个人微信-wechat--ilink)) - - **钉钉**:勾选并填写 Client ID / Client Secret - - **飞书**:勾选并填写 App ID / App Secret -5. 点击 **应用配置** 保存(微信扫码绑定成功后会**自动保存并启用**,一般无需再点) -6. **重启 CyberStrikeAI 应用**(钉钉/飞书:只保存不重启,长连接不会建立;微信绑定成功后会自动重启连接,通常无需手动重启) - -配置会写入 `config.yaml` 的 `robots` 段,也可在配置文件中直接编辑。**修改钉钉/飞书配置后必须重启,长连接才会生效。** 个人微信绑定成功后程序会自动写入 `robots.wechat` 并重启 iLink 长轮询。 - ---- - -## 二、支持的平台(长连接 / 回调) - -| 平台 | 说明 | -|----------|------| -| 个人微信 | 使用微信 iLink 协议,Web 端扫码绑定后长轮询收消息,**无需公网回调** | -| 钉钉 | 使用 Stream 长连接,程序主动连接钉钉接收消息 | -| 飞书 | 使用长连接,程序主动连接飞书接收消息 | -| 企业微信 | 使用 HTTP 回调接收消息,被动回包 + 主动调用企业微信发送消息 API | -| Telegram | Bot API 长轮询(getUpdates),**无需公网回调** | -| Slack | Socket Mode(出站 WebSocket),**无需公网回调** | -| Discord | Gateway WebSocket,**无需公网回调** | -| QQ 机器人 | QQ 开放平台 WebSocket(C2C / 群 @),**无需公网回调** | - -下面第三节会按平台写清:在开放平台要做什么、要复制哪些字段、填到 CyberStrikeAI 的哪一栏。 - ---- - -## 三、各平台配置项与详细步骤 - -### 3.1 钉钉 - -**先搞清楚:两种钉钉机器人不一样** - -| 类型 | 从哪里创建 | 能否做「用户发消息→机器人回复」 | 本程序是否支持 | -|------|------------|----------------------------------|----------------| -| **自定义机器人** | 钉钉群里:群设置 → 添加机器人 → 自定义(Webhook) | ❌ 不能,只能你往群里发消息 | ❌ 不支持 | -| **企业内部应用机器人** | [钉钉开放平台](https://open.dingtalk.com) 创建应用并开通机器人 | ✅ 能 | ✅ 支持 | - -如果你手里是「自定义机器人」的 Webhook 地址(`oapi.dingtalk.com/robot/send?access_token=xxx`)和加签密钥(`SEC...`),**不能直接填到本程序**,必须按下面步骤在开放平台创建「企业内部应用」并拿到 **Client ID**、**Client Secret**。 - ---- - -**钉钉配置完整步骤(按顺序做)** - -1. **打开钉钉开放平台** - 浏览器访问 [https://open.dingtalk.com](https://open.dingtalk.com),用**企业管理员**账号登录。 - -2. **进入应用开发** - 左侧选 **应用开发** → **企业内部开发** → 点击 **创建应用**(或选择已有应用)。填写应用名称等基本信息后创建。 - -3. **拿到 Client ID 和 Client Secret** - - 左侧点 **凭证与基础信息**(在「基础信息」下)。 - - 页面上有 **Client ID(原 AppKey)** 和 **Client Secret(原 AppSecret)**。 - - 点击复制,**不要手打**,注意:数字 **0** 和字母 **o**、数字 **1** 和字母 **l** 容易抄错(例如 `ding9gf9tiozuc504aer` 中间是数字 **504** 不是 5o4)。 - -4. **开通机器人并选 Stream 模式** - - 左侧 **应用能力** → **机器人**。 - - 打开「机器人配置」开关。 - - 填写机器人名称、简介等(必填项按提示填)。 - - **关键**:消息接收方式要选 **「Stream 模式」**(流式接入)。若只有「HTTP 回调」或未选 Stream,本程序收不到消息。 - - 保存。 - -5. **权限与发布** - - 左侧 **权限管理**:搜索「机器人」「消息」等,勾选**接收消息**、**发送消息**等机器人相关权限,并确认授权。 - - 左侧 **版本管理与发布**:若有未发布配置,点击 **发布新版本** / **上线**,否则修改不生效。 - -6. **填回 CyberStrikeAI** - - 回到 CyberStrikeAI → 系统设置 → 机器人设置 → 钉钉。 - - 勾选「启用钉钉机器人」。 - - **Client ID (AppKey)** 粘贴第 3 步复制的 Client ID。 - - **Client Secret** 粘贴第 3 步复制的 Client Secret。 - - 点击 **应用配置**,然后**重启 CyberStrikeAI**。 - ---- - -**CyberStrikeAI 钉钉栏位对照** - -| CyberStrikeAI 中填写项 | 在钉钉开放平台的来源 | -|------------------------|------------------------| -| 启用钉钉机器人 | 勾选即启用 | -| Client ID (AppKey) | 凭证与基础信息 → **Client ID(原 AppKey)** | -| Client Secret | 凭证与基础信息 → **Client Secret(原 AppSecret)** | - ---- - -### 3.2 飞书 (Lark) - -| 配置项 | 说明 | -|--------|------| -| 启用飞书机器人 | 勾选后启动飞书长连接 | -| App ID | 飞书开放平台应用凭证中的 App ID | -| App Secret | 飞书开放平台应用凭证中的 App Secret | -| Verify Token | 事件订阅用(可选) | - -**飞书配置简要步骤**:登录 [飞书开放平台](https://open.feishu.cn) → 创建企业自建应用 → 在「凭证与基础信息」中获取 **App ID**、**App Secret** → 在「应用能力」中开通**机器人**并启用相应权限 → **在「事件订阅」中添加事件**(见下)→ 发布应用 → 将 App ID、App Secret 填到 CyberStrikeAI 机器人设置 → 保存。 - -**重要:事件订阅** -飞书长连接只有在开放平台订阅了「接收消息」事件后才会收到用户消息。请在该应用的 **事件订阅** 页面点击「添加事件」,在「消息与群组」下勾选 **接收消息(im.message.receive_v1)** 或同类事件;若未添加,连接会建立成功但收不到任何消息,表现为发消息后本地无日志、机器人无回复。 - -**飞书权限配置(必读)** -在 **权限管理** 中需开通以下权限(与开放平台列表中的名称、标识一致);修改后需在 **版本管理与发布** 中发布新版本才生效。 - -| 权限名称(开放平台中显示) | 权限标识 | 说明 | -|----------------------------|----------|------| -| 获取与发送单聊、群组消息 | `im:message` | 收发消息的基础权限,**必须开通**。 | -| 接收群聊中@机器人消息事件 | `im:message.group_at_msg:readonly` | 群聊中 @ 机器人时收消息,需开通。 | -| 读取用户发给机器人的单聊消息 | `im:message.p2p_msg:readonly` | 单聊收消息,**必须开通**,否则私聊发消息没反应。 | -| 获取单聊、群组消息 | `im:message:readonly` | 读取消息内容,**必须开通**。 | - -**事件订阅**(与权限分开配置):在 **事件订阅** 中添加 **接收消息(im.message.receive_v1)**,否则长连接收不到消息推送。 - -- **单聊**:在飞书里打开与机器人的私聊窗口,直接发「帮助」或任意文字即可,无需 @。 -- **群聊**:在群里只有 **@ 机器人** 后发送的内容才会被机器人收到并回复。 - ---- - -### 3.3 企业微信 (WeCom) - -> 企业微信目前采用「HTTP 回调 + 主动发送消息 API」的方式工作: -> - 用户发消息 → 企业微信以加密 XML **回调到你的服务器**(本程序的 `/api/robot/wecom`); -> - CyberStrikeAI 解密并调用 AI → 使用企业微信的 `message/send` 接口**主动发消息给用户**。 - -**配置概览:** - -- 在企业微信管理后台创建或选择一个**自建应用**。 -- 在该应用的「接收消息」处配置回调 URL、Token、EncodingAESKey。 -- 在 CyberStrikeAI 的 `config.yaml` 中填入: - - `robots.wecom.corp_id`:企业 ID(CorpID) - - `robots.wecom.agent_id`:应用的 AgentId - - `robots.wecom.token`:消息回调使用的 Token - - `robots.wecom.encoding_aes_key`:消息回调使用的 EncodingAESKey - - `robots.wecom.secret`:该应用的 Secret(用于调用企业微信主动发送消息接口) - -> **重要:IP 白名单(errcode 60020)** -> CyberStrikeAI 使用 `https://qyapi.weixin.qq.com/cgi-bin/message/send` 主动发送 AI 回复。 -> 若企业微信日志或本程序日志中出现 `errcode 60020 not allow to access from your ip`: -> -> - 说明你的服务器出口 IP **没有加入企业微信的 IP 白名单**; -> - 请在企业微信管理后台中找到该自建应用的**「安全设置 / IP 白名单」**(具体入口可能因版本略有不同),将运行 CyberStrikeAI 的服务器公网 IP(如 `110.xxx.xxx.xxx`)加入白名单; -> - 保存后等待生效,再次发送消息测试。 -> -> 如果 IP 未加入白名单,企业微信会拒绝主动发送消息,表现为: -> - 回调接口 `/api/robot/wecom` 能正常收到并处理消息; -> - 但手机端**始终收不到 AI 回复**,日志中有 `not allow to access from your ip` 提示。 - ---- - -### 3.4 个人微信 (WeChat / iLink) - -> 个人微信采用「Web 扫码绑定 + iLink 长轮询」方式工作: -> - 在 CyberStrikeAI Web 端生成二维码 → 用**手机微信**扫码并确认绑定; -> - 绑定成功后自动写入 `config.yaml` 的 `robots.wechat`,并启动 iLink 长轮询(程序主动连接 `ilinkai.weixin.qq.com` 收消息); -> - **无需**在服务器上配置公网回调 URL,也**无需**去微信开放平台注册应用。 - -**与企业微信的区别** - -| 项目 | 个人微信 (iLink) | 企业微信 (WeCom) | -|------|------------------|------------------| -| 使用场景 | 个人微信私聊 | 企业微信自建应用 | -| 配置方式 | Web 端扫码绑定 | 管理后台配置回调 URL + Token | -| 是否需要公网 | 否(长轮询出站即可) | 是(需可被企业微信访问的 HTTPS 回调) | -| 配置段 | `robots.wechat` | `robots.wecom` | - -**绑定步骤(按顺序做)** - -1. **登录 CyberStrikeAI Web 端** - 左侧 **系统设置** → **机器人设置** → 点击 **微信 / iLink** 卡片。 - -2. **(可选)勾选「启用微信机器人」** - 首次绑定可跳过;绑定成功后会自动勾选并启用。 - -3. **生成二维码** - 点击 **「生成二维码并绑定」**。页面会显示二维码(约 **5 分钟**有效;过期请重新生成)。 - -4. **微信扫码确认** - - 用手机微信扫描页面二维码; - - 按手机提示完成确认; - - 若手机微信弹出**配对数字**,在 Web 页面对应输入框填写并点击 **提交**(仅部分账号需要)。 - -5. **等待绑定完成** - 页面显示「绑定成功,微信机器人已启用」即完成。`bot_token`、`ilink_bot_id` 等会自动写入 `config.yaml`,程序会自动重启 iLink 长轮询,**一般无需手动重启服务**。 - -6. **在手机微信里测试** - 打开与 CyberStrikeAI 机器人的**私聊**(绑定后微信内会出现对应会话),发送「帮助」或任意文字测试。 - -**CyberStrikeAI 微信栏位说明** - -| 栏位 | 说明 | -|------|------| -| 启用微信机器人 | 勾选后启动 iLink 长轮询;绑定成功后会自动勾选 | -| 生成二维码并绑定 | 发起扫码绑定流程 | -| **高级设置**(一般保持默认即可) | | -| API Base URL | 默认 `https://ilinkai.weixin.qq.com` | -| Bot Type | 默认 `3` | -| Bot Agent | 默认 `CyberStrikeAI/1.0` | -| iLink Bot ID | 绑定成功后自动填充,只读 | - -**使用方式** - -- 仅支持在与机器人的**私聊**中对话,直接发送文字即可,**不需要 @**。 -- 不支持群聊 @ 机器人(与钉钉/飞书群聊不同)。 -- 仅处理**文本消息**;图片、语音等会忽略或提示暂不支持。 - -**重新绑定** - -- 若需更换绑定的微信账号,在机器人设置页点击 **「重新绑定」**,再次扫码即可。 -- 若提示「该微信已绑定过,无需重复绑定」,说明该账号此前已完成绑定。 - -**常见问题** - -| 现象 | 处理 | -|------|------| -| 二维码过期 | 重新点击「生成二维码并绑定」(有效期约 5 分钟) | -| 扫码后要求输入数字 | 查看手机微信显示的配对数字,在 Web 页面输入并提交 | -| 绑定成功但发消息无回复 | 看程序日志是否有 `微信 iLink 长轮询已启动`、`微信收到消息`;确认已勾选「启用微信机器人」 | -| 断网或睡眠后无回复 | 程序会自动重连(约 5~60 秒);仍无回复可重启 CyberStrikeAI | -| 无法生成二维码 | 确认服务器能访问 `https://ilinkai.weixin.qq.com`(出站 HTTPS) | - ---- - -### 3.5 Telegram - -> Telegram 使用 **Bot API 长轮询**(`getUpdates`):程序主动连接 `api.telegram.org` 收消息,**无需公网回调**。 - -**配置步骤:** - -1. 在 Telegram 中找 **@BotFather**,发送 `/newbot` 创建机器人,获得 **Bot Token**。 -2. CyberStrikeAI → **系统设置** → **机器人设置** → **Telegram**。 -3. 勾选「启用 Telegram 机器人」,粘贴 **Bot Token**。 -4. (可选)填写 Bot Username(不含 `@`),或留空由程序自动 `getMe`。 -5. (可选)勾选「允许群聊」— 群聊中仅响应 **@机器人** 的消息。 -6. 点击 **应用配置**(会自动重启长轮询连接)。 - -**使用:** 与机器人私聊直接发消息;群聊需 @ 机器人(且已勾选允许群聊)。 - ---- - -### 3.6 Slack - -> Slack 使用 **Socket Mode**(出站 WebSocket):需 **Bot Token** 与 **App-Level Token**,**无需公网回调**。 - -**配置步骤:** - -1. 在 [Slack API](https://api.slack.com/apps) 创建 App → 启用 **Socket Mode**。 -2. **Basic Information** → **App-Level Tokens** → 创建 token(scope: `connections:write`),即 **xapp-** 开头。 -3. **OAuth & Permissions** → 添加 Bot Token Scopes:`app_mentions:read`、`chat:write`、`im:history`、`im:read` 等 → 安装到工作区,获得 **xoxb-** Bot Token。 -4. **Event Subscriptions** → 订阅 `message.im`、`app_mention` 等(Socket Mode 下在应用内配置)。 -5. 在 CyberStrikeAI 填入 Bot Token 与 App-Level Token → **应用配置**。 - -**使用:** 与 Bot 私聊直接发;频道中需 @ 机器人。 - ---- - -### 3.7 Discord - -> Discord 使用 **Gateway WebSocket**:程序主动连接 Discord Gateway,**无需公网回调**。 - -**配置步骤:** - -1. 在 [Discord Developer Portal](https://discord.com/developers/applications) 创建应用 → **Bot** → 复制 **Token**。 -2. 开启 **Privileged Gateway Intents** 中的 **Message Content Intent**(否则读不到消息正文)。 -3. OAuth2 → URL Generator → scopes: `bot` → 权限勾选 **Send Messages**、**Read Message History** 等 → 邀请 Bot 到服务器。 -4. CyberStrikeAI → **机器人设置** → **Discord** → 填入 Token → **应用配置**。 -5. (可选)勾选「允许服务器频道」— 频道中仅响应 **@机器人**。 - -**使用:** 与 Bot 私聊直接发;服务器频道需 @ 机器人(且已勾选允许服务器频道)。 - ---- - -### 3.8 QQ 机器人 - -> QQ 机器人使用 **QQ 开放平台 WebSocket**(官方 `botgo` SDK):支持 C2C 私聊与群 @,**无需公网回调**(WebSocket 出站连接)。 - -**配置步骤:** - -1. 在 [QQ 机器人开放平台](https://q.qq.com) 创建机器人,获取 **App ID** 与 **Client Secret**。 -2. 在沙箱中添加测试成员(上线前仅沙箱可对话)。 -3. 订阅 **C2C 消息**、**群 @ 消息** 等事件(WebSocket 模式)。 -4. CyberStrikeAI → **机器人设置** → **QQ 机器人** → 填入 App ID、Client Secret。 -5. 测试阶段勾选 **沙箱环境**;正式上线后取消沙箱并发布。 -6. 点击 **应用配置**。 - -**使用:** 与机器人 C2C 私聊直接发;QQ 群中需 @ 机器人。 - -> 注意:QQ 官方正逐步推广 Webhook 回调;当前实现使用 WebSocket(与钉钉/飞书类似的长连接模式)。若配置变更后连接未刷新,可重启 CyberStrikeAI 进程。 - ---- - -## 四、机器人命令 - -在任一已接入平台(钉钉/飞书/微信/Telegram/Slack/Discord/QQ 等)向机器人发送以下**文本命令**(仅支持文本): - -| 命令 | 说明 | -|------|------| -| **帮助** | 显示命令帮助与说明 | -| **列表** 或 **对话列表** | 列出所有对话的标题与对话 ID | -| **切换 \<对话ID\>** 或 **继续 \<对话ID\>** | 指定对话 ID,后续消息在该对话中继续 | -| **新对话** | 开启一个新对话,后续消息在新对话中 | -| **清空** | 清空当前对话上下文(效果等同「新对话」) | -| **当前** | 显示当前对话 ID 与标题 | -| **停止** | 中断当前正在执行的任务 | -| **角色** 或 **角色列表** | 列出所有可用角色(渗透测试、CTF、Web 应用扫描等) | -| **角色 \<角色名\>** 或 **切换角色 \<角色名\>** | 切换当前使用的角色 | -| **删除 \<对话ID\>** | 删除指定对话 | -| **版本** | 显示当前 CyberStrikeAI 版本号 | - -除以上命令外,**直接输入任意文字**会作为用户消息发给 AI,与 Web 端对话逻辑一致(渗透测试/安全分析等)。 - ---- - -## 五、如何使用(要 @ 机器人吗?) - -- **个人微信**:在与 CyberStrikeAI 机器人的**私聊**中直接发送即可,**不需要 @**(不支持群聊)。 -- **钉钉 / 飞书单聊(推荐)**:**搜索并打开该机器人**,进入**私聊**,直接输入「帮助」或任意文字即可,**不需要 @**。 -- **钉钉 / 飞书群聊**:若机器人被添加到群里,在群内只有 **@机器人** 后发送的消息才会被机器人收到并回复;不 @ 的群消息不会触发机器人。 - -总结:**个人微信、单聊时直接发**;**钉钉/飞书在群里用时需要 @机器人** 再发内容。 - ---- - -## 六、推荐使用流程(避免漏步骤) - -**个人微信(最简单,无需开放平台)** - -1. CyberStrikeAI Web 端 → 系统设置 → 机器人设置 → **微信 / iLink** → **生成二维码并绑定**。 -2. 手机微信扫码确认(如需配对数字则在 Web 页填写)。 -3. 绑定成功后,在手机微信私聊中发「帮助」测试。 - -**钉钉 / 飞书** - -1. **在开放平台**:按第三节完成应用创建、凭证复制、机器人开通(钉钉务必选 **Stream 模式**)、权限与发布。 -2. **在 CyberStrikeAI**:系统设置 → 机器人设置 → 勾选对应平台,粘贴 Client ID/App ID、Client Secret/App Secret → 点击 **应用配置**。 -3. **重启 CyberStrikeAI 进程**(否则长连接不会建立)。 -4. **在手机钉钉/飞书**:找到该机器人(单聊直接发,群聊需 @机器人),发「帮助」或任意内容测试。 - -若发消息没反应,先看 **第九节排查** 和 **第十节常见弯路**。 - ---- - -## 七、配置文件示例 - -`config.yaml` 中机器人相关片段示例: - -```yaml -robots: - wechat: # 个人微信 iLink(扫码绑定后自动写入,一般无需手填) - enabled: true - bot_token: "your_bot_token@im.bot:..." - ilink_bot_id: "your_bot_id@im.bot" - ilink_user_id: "your_user_id@im.wechat" - base_url: "https://ilinkai.weixin.qq.com" - bot_type: "3" - bot_agent: "CyberStrikeAI/1.0" - dingtalk: - enabled: true - client_id: "your_dingtalk_app_key" - client_secret: "your_dingtalk_app_secret" - lark: - enabled: true - app_id: "your_lark_app_id" - app_secret: "your_lark_app_secret" - verify_token: "" - wecom: - enabled: false - corp_id: "" - agent_id: 0 - token: "" - encoding_aes_key: "" - secret: "" - telegram: - enabled: false - bot_token: "" - allow_group_messages: false - slack: - enabled: false - bot_token: "" - app_token: "" - discord: - enabled: false - bot_token: "" - allow_guild_messages: false - qq: - enabled: false - app_id: "" - client_secret: "" - sandbox: true -``` - -修改钉钉/飞书/企业微信/Telegram/Slack/Discord/QQ 配置后,点击 **应用配置** 会自动重启对应长连接。个人微信扫码绑定成功后会自动写入并重启 iLink 连接。 - ---- - -## 八、如何验证是否可用(无需钉钉/飞书客户端) - -在未安装钉钉或飞书时,可用**测试接口**验证机器人逻辑是否正常: - -1. 先登录 CyberStrikeAI Web 端(保证有登录态)。 -2. 使用 curl 调用测试接口(需携带登录后的 Cookie): - -```bash -# 将 YOUR_COOKIE 替换为登录后获得的 Cookie(浏览器 F12 → 网络 → 任意请求 → 请求头中的 Cookie) -curl -X POST "http://localhost:8080/api/robot/test" \ - -H "Content-Type: application/json" \ - -H "Cookie: YOUR_COOKIE" \ - -d '{"platform":"dingtalk","user_id":"test_user","text":"帮助"}' -``` - -若返回 JSON 中含有 `"reply":"【CyberStrikeAI 机器人命令】..."`,说明命令处理正常。可再试 `"text":"列表"`、`"text":"当前"` 等。 - -接口说明:`POST /api/robot/test`(需登录),请求体 `{"platform":"可选","user_id":"可选","text":"必填"}`,响应 `{"reply":"回复内容"}`。 - ---- - -## 九、发消息没反应时排查 - -### 9.1 个人微信 - -按顺序检查: - -1. **是否已完成扫码绑定** - 机器人设置页应显示「已连接」或已绑定 Bot ID;`config.yaml` 中 `robots.wechat.bot_token` 不应为空。 - -2. **是否已启用** - 确认「启用微信机器人」已勾选;若刚修改过,可重启 CyberStrikeAI 进程。 - -3. **看程序日志** - - 启动后应看到:`微信 iLink 长轮询已启动`; - - 发消息后应有:`微信收到消息`;若没有,多为未绑定成功或 `bot_token` 失效,可尝试 **重新绑定**。 - - 若出现 `微信 iLink 长轮询异常,将自动重连`,等待自动重连或重启进程。 - -4. **网络** - 服务器需能访问 `https://ilinkai.weixin.qq.com`(出站 HTTPS)。绑定阶段若无法生成二维码,优先检查此项。 - -5. **断网或睡眠后** - 与钉钉/飞书类似,程序会**自动重连**(约 5~60 秒);仍无回复可重启 CyberStrikeAI。 - -### 9.2 钉钉 - -按顺序检查: - -0. **笔记本合盖睡眠 / 断网后** - 钉钉、飞书均使用长连接收消息,睡眠或断网后连接会断开。程序会**自动重连**(约 5 秒~60 秒内重试)。唤醒或恢复网络后稍等一会儿再发消息;若仍无反应,可重启 CyberStrikeAI 进程。 - -1. **Client ID / Client Secret 是否与开放平台完全一致** - 从「凭证与基础信息」里**复制粘贴**,不要手打。注意数字 **0** 与字母 **o**、数字 **1** 与字母 **l**(例如 `ding9gf9tiozuc504aer` 中间是 **504** 不是 5o4)。 - -2. **是否在保存配置后重启了应用** - 机器人长连接在**应用启动时**建立。在 Web 端点击「应用配置」只写入配置文件,**必须重启 CyberStrikeAI 进程**后钉钉连接才会生效。 - -3. **看程序日志** - - 启动后应看到:`钉钉 Stream 正在连接…`、`钉钉 Stream 已启动(无需公网),等待收消息`。 - - 若出现 `钉钉 Stream 长连接退出` 且带错误信息,多为 **Client ID / Client Secret 错误**或**开放平台未开通流式接入**。 - - 在钉钉里发一条消息后,若有收到,应有日志:`钉钉收到消息`;若没有,说明钉钉未把消息推到本程序(回头检查开放平台「机器人」是否开通、是否选用 **Stream 模式**)。 - -4. **开放平台侧** - 应用需已**发布**;在「机器人」能力中需开启**流式接入(Stream)** 用于接收消息(仅 HTTP 回调不够);权限管理里需有机器人接收、发送消息等权限。 - ---- - -## 十、常见弯路(避免踩坑) - -- **个人微信与企业微信混淆**:个人微信走 `robots.wechat` + Web 扫码绑定;企业微信走 `robots.wecom` + 管理后台回调 URL,二者完全不同。 -- **个人微信二维码过期**:二维码约 5 分钟有效,过期需重新生成,不要一直扫旧码。 -- **用错了机器人类型**:在钉钉**群里**添加的「自定义」机器人(Webhook + 加签)**不能**用来做对话,本程序只支持**开放平台「企业内部应用」**里的机器人。 -- **只保存没重启**:钉钉/飞书改完配置后必须**重启应用**,否则长连接不会建立(个人微信扫码绑定会自动重启连接)。 -- **Client ID 抄错**:开放平台是 `504` 就填 `504`,不要填成 `5o4`;尽量用复制粘贴。 -- **钉钉只开了 HTTP 回调没开 Stream**:本程序通过 **Stream 长连接**收消息,开放平台里机器人的消息接收方式必须选 **Stream 模式**。 -- **应用没发布**:开放平台里修改了机器人或权限后,要在「版本管理与发布」里**发布新版本**,否则不生效。 - ---- - -## 十一、注意事项 - -- 各平台均**仅处理文本消息**;其他类型(如图片、语音)会提示暂不支持或忽略。 -- 个人微信仅支持**私聊**,不支持群聊 @ 机器人。 -- 会话与 Web 端共用同一套对话数据:在机器人里创建的对话会在 Web 端「对话」列表中看到,反之亦然。 -- 机器人执行与 **Eino 单/多代理** 相同逻辑(`ProcessMessageForRobot`,含进度回调与过程详情入库),仅不向客户端推送 SSE,最后一次性回复个人微信/钉钉/飞书/企业微信。默认 `robot_default_agent_mode: eino_single`。 diff --git a/docs/robot_en.md b/docs/robot_en.md deleted file mode 100644 index 8284f896..00000000 --- a/docs/robot_en.md +++ /dev/null @@ -1,463 +0,0 @@ -# CyberStrikeAI Robot / Chatbot Guide - -[中文](robot.md) - -This document explains how to chat with CyberStrikeAI from **personal WeChat**, **DingTalk**, **Lark (Feishu)**, and **WeCom (Enterprise WeChat)** using long-lived connections or HTTP callbacks—no need to open a browser on the server. Following the steps below helps avoid common mistakes. - ---- - -## 1. Where to configure in CyberStrikeAI - -1. Log in to the CyberStrikeAI web UI. -2. Open **System Settings** in the left sidebar. -3. Click **Robot settings** (between “Basic” and “Security”). -4. Configure per platform: - - **Personal WeChat**: Open **WeChat / iLink** → **Generate QR code and bind**, then scan with WeChat (see [Section 3.4](#34-personal-wechat-wechat--ilink)) - - **DingTalk**: Enable and fill in Client ID / Client Secret - - **Lark**: Enable and fill in App ID / App Secret -5. Click **Apply configuration** to save (WeChat binding saves and enables automatically on success—usually no extra click needed) -6. **Restart the CyberStrikeAI process** (DingTalk/Lark: saving alone does not establish the connection; WeChat auto-restarts the iLink poll after binding—usually no manual restart needed) - -Settings are written to the `robots` section of `config.yaml`; you can also edit the file directly. **After changing DingTalk or Lark config, you must restart for the long-lived connection to take effect.** Personal WeChat binding automatically writes `robots.wechat` and restarts the iLink long poll. - ---- - -## 2. Supported platforms (long-lived / callback) - -| Platform | Description | -|----------------|-------------| -| Personal WeChat| WeChat iLink protocol; scan QR in the web UI to bind, then long-poll for messages—**no public callback URL needed** | -| DingTalk | Stream long-lived connection; the app connects to DingTalk to receive messages | -| Lark (Feishu) | Long-lived connection; the app connects to Lark to receive messages | -| WeCom (Qiye WX)| HTTP callback to receive messages; CyberStrikeAI replies via WeCom’s message sending API | -| Telegram | Bot API long polling (`getUpdates`); **no public callback URL needed** | -| Slack | Socket Mode (outbound WebSocket); **no public callback URL needed** | -| Discord | Gateway WebSocket; **no public callback URL needed** | -| QQ Bot | QQ Open Platform WebSocket (C2C / group @); **no public callback URL needed** | - -Section 3 below describes, per platform, what to do in the developer console and which fields to copy into CyberStrikeAI. - ---- - -## 3. Configuration and step-by-step setup - -### 3.1 DingTalk - -**Important: two types of DingTalk bots** - -| Type | Where it’s created | Can do “user sends message → bot replies”? | Supported here? | -|------|-------------------|-------------------------------------------|------------------| -| **Custom bot (Webhook)** | In a DingTalk group: Group settings → Add robot → Custom (Webhook) | No; you can only post to the group | No | -| **Enterprise internal app bot** | [DingTalk Open Platform](https://open.dingtalk.com): create an app and enable the bot | Yes | Yes | - -If you only have a **custom bot** Webhook URL (`oapi.dingtalk.com/robot/send?access_token=...`) and sign secret (`SEC...`), **do not** put them into CyberStrikeAI. You must create an **enterprise internal app** in the open platform and obtain **Client ID** and **Client Secret** as below. - ---- - -**DingTalk setup (in order)** - -1. **Open DingTalk Open Platform** - Go to [https://open.dingtalk.com](https://open.dingtalk.com) and log in with an **enterprise admin** account. - -2. **Create or select an app** - In the left menu: **Application development** → **Enterprise internal development** → **Create application** (or choose an existing app). Fill in the app name and create. - -3. **Get Client ID and Client Secret** - - In the left menu open **Credentials and basic info** (under “Basic information”). - - Copy **Client ID (formerly AppKey)** and **Client Secret (formerly AppSecret)**. - - Use copy/paste; avoid typing by hand. Watch for **0** vs **o** and **1** vs **l** (e.g. `ding9gf9tiozuc504aer` has the digits **504**, not 5o4). - -4. **Enable the bot and choose Stream mode** - - Left menu: **Application capabilities** → **Robot**. - - Turn on “Robot configuration”. - - Fill in robot name, description, etc. as required. - - **Critical**: set message reception to **“Stream mode”** (流式接入). If you only enable “HTTP callback” or do not select Stream, CyberStrikeAI will not receive messages. - - Save. - -5. **Permissions and release** - - Left menu: **Permission management** — search for “robot”, “message”, etc., and enable **receive message**, **send message**, and other bot-related permissions; confirm. - - Left menu: **Version management and release** — if there are unpublished changes, click **Release new version** / **Publish**; otherwise changes do not take effect. - -6. **Fill in CyberStrikeAI** - - In CyberStrikeAI: System settings → Robot settings → DingTalk. - - Enable “Enable DingTalk robot”. - - Paste the Client ID and Client Secret from step 3. - - Click **Apply configuration**, then **restart CyberStrikeAI**. - ---- - -**Field mapping (DingTalk)** - -| Field in CyberStrikeAI | Source in DingTalk Open Platform | -|------------------------|----------------------------------| -| Enable DingTalk robot | Check to enable | -| Client ID (AppKey) | Credentials and basic info → **Client ID (formerly AppKey)** | -| Client Secret | Credentials and basic info → **Client Secret (formerly AppSecret)** | - ---- - -### 3.2 Lark (Feishu) - -| Field | Description | -|-------|-------------| -| Enable Lark robot | Check to start the Lark long-lived connection | -| App ID | From Lark open platform app credentials | -| App Secret | From Lark open platform app credentials | -| Verify Token | Optional; for event subscription | - -**Lark setup in short**: Log in to [Lark Open Platform](https://open.feishu.cn) → Create an enterprise app → In “Credentials and basic info” get **App ID** and **App Secret** → In “Application capabilities” enable **Robot** and the right permissions → Add **event subscription** and **permissions** below → Publish the app → Enter App ID and App Secret in CyberStrikeAI robot settings → Save and **restart** the app. - -**Event subscription** -The long-lived connection only receives message events if you subscribe to them. In the app’s **Events and callbacks** (事件与回调) → **Event subscription** (事件订阅), add the event **Receive message** (**im.message.receive_v1**). Without it, the connection succeeds but no message events are delivered (no logs when users send messages). - -**Lark permissions (required)** -In **Permission management** (权限管理), enable the following (names and identifiers match the Lark console). After changes, **publish a new version** in Version management and release so they take effect. - -| Permission name (as shown in console) | Identifier | Notes | -|--------------------------------------|------------|-------| -| 获取与发送单聊、群组消息 (Get and send direct & group messages) | `im:message` | Base permission for sending and receiving; **required**. | -| 接收群聊中@机器人消息事件 (Receive @bot messages in group chat) | `im:message.group_at_msg:readonly` | Required for group chat when users @ the bot. | -| 读取用户发给机器人的单聊消息 (Read direct messages from users to bot) | `im:message.p2p_msg:readonly` | **Required** for 1:1 chat; otherwise no response in private chat. | -| 获取单聊、群组消息 (Get direct & group messages) | `im:message:readonly` | **Required** to read message content. | - -**Event subscription** (configured separately): In **Event subscription** (事件订阅), add **Receive message** (**im.message.receive_v1**). Without it, the long-lived connection will not receive message events. - -- **1:1 chat**: Open the bot’s private chat in Lark and send e.g. “帮助” or “help”; no @ needed. -- **Group chat**: Only messages that **@ the bot** are received and replied to. - ---- - -### 3.3 WeCom (Enterprise WeChat) - -> WeCom uses a **“HTTP callback + active message send API”** model: -> - User sends a message → WeCom sends an **encrypted XML callback** to your server (CyberStrikeAI’s `/api/robot/wecom`). -> - CyberStrikeAI decrypts it, calls the AI, then uses WeCom’s `message/send` API to **actively push the reply** to the user. - -**Configuration overview:** - -- In the WeCom admin console, create or select a **custom app** (自建应用). -- In that app’s settings, configure the message **callback URL**, **Token**, and **EncodingAESKey**. -- In CyberStrikeAI’s `config.yaml`, fill in: - - `robots.wecom.corp_id`: your CorpID (企业 ID) - - `robots.wecom.agent_id`: the app’s AgentId - - `robots.wecom.token`: the Token used for message callbacks - - `robots.wecom.encoding_aes_key`: the EncodingAESKey used for callbacks - - `robots.wecom.secret`: the app’s Secret (used when calling WeCom APIs to send messages) - -> **Important: IP allowlist (errcode 60020)** -> CyberStrikeAI calls `https://qyapi.weixin.qq.com/cgi-bin/message/send` to actively send AI replies. -> If logs show `errcode 60020 not allow to access from your ip`: -> -> - Your server’s outbound IP is **not in WeCom’s IP allowlist**. -> - In the WeCom admin console, open the custom app’s **Security / IP allowlist** settings (name may vary slightly), and add the public IP of the machine running CyberStrikeAI (e.g. `110.xxx.xxx.xxx`). -> - Save and wait for it to take effect, then test again. -> -> If the IP is not whitelisted, WeCom will reject active message sending. You will see that `/api/robot/wecom` receives and processes callbacks, but users **never see AI replies**, and logs contain `not allow to access from your ip`. - ---- - -### 3.4 Personal WeChat (WeChat / iLink) - -> Personal WeChat uses **“web QR binding + iLink long polling”**: -> - Generate a QR code in the CyberStrikeAI web UI → scan and confirm with **WeChat on your phone**; -> - On success, `robots.wechat` in `config.yaml` is updated automatically and iLink long polling starts (the app connects outbound to `ilinkai.weixin.qq.com`); -> - **No** public callback URL on your server and **no** WeChat Open Platform app registration required. - -**Personal WeChat vs WeCom** - -| Item | Personal WeChat (iLink) | WeCom (Enterprise WeChat) | -|------|-------------------------|---------------------------| -| Use case | Private chat in personal WeChat | Custom app in WeCom | -| Setup | QR scan in web UI | Admin console callback URL + Token | -| Public IP needed? | No (outbound long poll only) | Yes (HTTPS callback reachable by WeCom) | -| Config key | `robots.wechat` | `robots.wecom` | - -**Binding steps (in order)** - -1. **Log in to CyberStrikeAI web UI** - **System settings** → **Robot settings** → click the **WeChat / iLink** card. - -2. **(Optional) Enable “Enable WeChat robot”** - You can skip this on first bind; it is checked automatically after a successful bind. - -3. **Generate QR code** - Click **“Generate QR code and bind”**. The QR code is valid for about **5 minutes**; regenerate if it expires. - -4. **Scan and confirm in WeChat** - - Scan the QR code with WeChat on your phone; - - Complete confirmation on the phone; - - If WeChat shows a **pairing code**, enter it on the web page and click **Submit** (only some accounts need this). - -5. **Wait for binding to complete** - When the page shows “Binding successful, WeChat robot enabled”, you’re done. `bot_token`, `ilink_bot_id`, etc. are saved to `config.yaml` and the iLink poll restarts automatically—**usually no manual service restart**. - -6. **Test in WeChat** - Open the **private chat** with the CyberStrikeAI bot in WeChat and send “帮助” (help) or any text. - -**Field reference (WeChat)** - -| Field | Description | -|-------|-------------| -| Enable WeChat robot | Starts iLink long polling when checked; auto-enabled after bind | -| Generate QR code and bind | Starts the scan-to-bind flow | -| **Advanced** (defaults are fine) | | -| API Base URL | Default `https://ilinkai.weixin.qq.com` | -| Bot Type | Default `3` | -| Bot Agent | Default `CyberStrikeAI/1.0` | -| iLink Bot ID | Filled automatically after bind (read-only) | - -**How to use** - -- **Private chat only**—send text directly; **no @ needed**. -- Group @-bot is **not** supported (unlike DingTalk/Lark groups). -- **Text messages only**; images, voice, etc. are ignored or not supported. - -**Re-bind** - -- To bind a different WeChat account, click **“Re-bind”** on the robot settings page and scan again. -- If you see “This WeChat account is already bound”, that account was bound before. - -**Common issues** - -| Symptom | What to do | -|---------|------------| -| QR code expired | Click “Generate QR code and bind” again (~5 min TTL) | -| Phone asks for a pairing code | Enter the digits shown in WeChat on the web page | -| Bound but no replies | Check logs for `微信 iLink 长轮询已启动` and `微信收到消息`; ensure “Enable WeChat robot” is on | -| No reply after sleep / network drop | Auto-reconnect in ~5–60 s; restart CyberStrikeAI if still stuck | -| Cannot generate QR code | Ensure outbound HTTPS to `https://ilinkai.weixin.qq.com` | - ---- - -### 3.5 Telegram - -> Telegram uses **Bot API long polling** (`getUpdates`): the app connects outbound to `api.telegram.org`—**no public callback URL needed**. - -1. Create a bot via **@BotFather** (`/newbot`) and copy the **Bot Token**. -2. CyberStrikeAI → **System settings** → **Robot settings** → **Telegram**. -3. Enable, paste the token, optionally allow group @ mentions → **Apply configuration**. - ---- - -### 3.6 Slack - -> Slack uses **Socket Mode** (outbound WebSocket): requires **Bot Token (xoxb-)** and **App-Level Token (xapp-)** with `connections:write`. - -1. Create an app at [api.slack.com](https://api.slack.com/apps) → enable **Socket Mode**. -2. Create an App-Level Token; install the app to get a Bot Token. -3. Subscribe to `message.im` and `app_mention` events. -4. Paste both tokens in CyberStrikeAI → **Apply configuration**. - ---- - -### 3.7 Discord - -> Discord uses **Gateway WebSocket**—**no public callback URL needed**. - -1. [Discord Developer Portal](https://discord.com/developers/applications) → create app → **Bot** → copy **Token**. -2. Enable **Message Content Intent** under Privileged Gateway Intents. -3. Invite the bot with `Send Messages` permission. -4. Paste token in CyberStrikeAI; optionally allow guild @ mentions → **Apply configuration**. - ---- - -### 3.8 QQ Bot - -> QQ Bot uses **QQ Open Platform WebSocket** (official `botgo` SDK) for C2C and group @—**no public callback URL needed**. - -1. Create a bot at [q.qq.com](https://q.qq.com) → get **App ID** and **Client Secret**. -2. Add sandbox testers before going live. -3. Subscribe to C2C and group @ events (WebSocket). -4. Fill in CyberStrikeAI; use **Sandbox** for testing → **Apply configuration**. - ---- - -## 4. Bot commands - -Send these **text commands** to the bot on any connected platform (text only): - -| Command | Description | -|---------|-------------| -| **帮助** (help) | Show command help | -| **列表** or **对话列表** (list) | List all conversation titles and IDs | -| **切换 \** or **继续 \** | Continue in the given conversation | -| **新对话** (new) | Start a new conversation | -| **清空** (clear) | Clear current context (same effect as new conversation) | -| **当前** (current) | Show current conversation ID and title | -| **停止** (stop) | Abort the currently running task | -| **角色** or **角色列表** (roles) | List all available roles (penetration testing, CTF, Web scan, etc.) | -| **角色 \** or **切换角色 \** | Switch to the specified role | -| **删除 \** | Delete the specified conversation | -| **版本** (version) | Show current CyberStrikeAI version | - -Any other text is sent to the AI as a user message, same as in the web UI (e.g. penetration testing, security analysis). - ---- - -## 5. How to use (do I need to @ the bot?) - -- **Personal WeChat**: Send directly in the **private chat** with the bot; **no @ needed** (group chat not supported). -- **DingTalk / Lark direct chat (recommended)**: **Search for the bot and open a direct chat**. Type “帮助” or any message; **no @ needed**. -- **DingTalk / Lark group chat**: If the bot is in a group, only messages that **@ the bot** are received and answered; other group messages are ignored. - -Summary: **Personal WeChat and direct chat**—just send; **DingTalk/Lark in a group**—@ the bot first, then send. - ---- - -## 6. Recommended flow (so you don’t skip steps) - -**Personal WeChat (simplest—no open platform)** - -1. CyberStrikeAI web UI → System settings → Robot settings → **WeChat / iLink** → **Generate QR code and bind**. -2. Scan with WeChat and confirm (enter pairing code on the web page if prompted). -3. After binding, send “帮助” in the WeChat private chat to test. - -**DingTalk / Lark** - -1. **In the open platform**: Complete app creation, copy credentials, enable the bot (DingTalk: **Stream mode**), set permissions, and publish (Section 3). -2. **In CyberStrikeAI**: System settings → Robot settings → Enable the platform, paste Client ID/App ID and Client Secret/App Secret → **Apply configuration**. -3. **Restart the CyberStrikeAI process** (otherwise the long-lived connection is not established). -4. **On your phone**: Open DingTalk or Lark, find the bot (direct chat or @ in a group), send “帮助” or any message to test. - -If the bot does not respond, see **Section 9 (troubleshooting)** and **Section 10 (common pitfalls)**. - ---- - -## 7. Config file example - -Example `robots` section in `config.yaml`: - -```yaml -robots: - wechat: # Personal WeChat iLink (auto-filled after QR bind; usually no manual edit) - enabled: true - bot_token: "your_bot_token@im.bot:..." - ilink_bot_id: "your_bot_id@im.bot" - ilink_user_id: "your_user_id@im.wechat" - base_url: "https://ilinkai.weixin.qq.com" - bot_type: "3" - bot_agent: "CyberStrikeAI/1.0" - dingtalk: - enabled: true - client_id: "your_dingtalk_app_key" - client_secret: "your_dingtalk_app_secret" - lark: - enabled: true - app_id: "your_lark_app_id" - app_secret: "your_lark_app_secret" - verify_token: "" - wecom: - enabled: false - corp_id: "" - agent_id: 0 - token: "" - encoding_aes_key: "" - secret: "" - telegram: - enabled: false - bot_token: "" - allow_group_messages: false - slack: - enabled: false - bot_token: "" - app_token: "" - discord: - enabled: false - bot_token: "" - allow_guild_messages: false - qq: - enabled: false - app_id: "" - client_secret: "" - sandbox: true -``` - -After changing DingTalk/Lark/WeCom/Telegram/Slack/Discord/QQ settings, **Apply configuration** restarts the corresponding connections. Personal WeChat QR binding saves and restarts automatically. - ---- - -## 8. Testing without DingTalk/Lark installed - -You can verify bot logic with the **test API** (no DingTalk/Lark client needed): - -1. Log in to the CyberStrikeAI web UI (so you have a session). -2. Call the test endpoint with curl (include your session Cookie): - -```bash -# Replace YOUR_COOKIE with the Cookie from your browser (F12 → Network → any request → Request headers → Cookie) -curl -X POST "http://localhost:8080/api/robot/test" \ - -H "Content-Type: application/json" \ - -H "Cookie: YOUR_COOKIE" \ - -d '{"platform":"dingtalk","user_id":"test_user","text":"帮助"}' -``` - -If the JSON response contains `"reply":"【CyberStrikeAI 机器人命令】..."`, command handling works. You can also try `"text":"列表"` or `"text":"当前"`. - -API: `POST /api/robot/test` (requires login). Body: `{"platform":"optional","user_id":"optional","text":"required"}`. Response: `{"reply":"..."}`. - ---- - -## 9. Troubleshooting: no response when sending messages - -### 9.1 Personal WeChat - -Check in this order: - -1. **Binding completed?** - Robot settings should show “Connected” or a bound Bot ID; `robots.wechat.bot_token` in `config.yaml` must not be empty. - -2. **Enabled?** - Confirm “Enable WeChat robot” is checked; restart CyberStrikeAI if you just changed settings. - -3. **Application logs** - - On startup: `微信 iLink 长轮询已启动`; - - After sending a message: `微信收到消息`; if missing, binding may have failed or `bot_token` is invalid—try **Re-bind**. - - `微信 iLink 长轮询异常,将自动重连`: wait for auto-reconnect or restart. - -4. **Network** - The server must reach `https://ilinkai.weixin.qq.com` (outbound HTTPS). If QR generation fails, check this first. - -5. **After sleep or network drop** - Same as DingTalk/Lark: **auto-reconnect** in ~5–60 s; restart if still no response. - -### 9.2 DingTalk - -Check in this order: - -0. **After laptop sleep or network drop** - DingTalk and Lark both use long-lived connections; they break when the machine sleeps or the network drops. The app **auto-reconnects** (retries within about 5–60 seconds). After wake or network recovery, wait a moment before sending; if there is still no response, restart the CyberStrikeAI process. - -1. **Client ID / Client Secret match the open platform exactly** - Copy from “Credentials and basic info”; avoid typing. Watch **0** vs **o** and **1** vs **l** (e.g. `ding9gf9tiozuc504aer` has **504**, not 5o4). - -2. **Did you restart after saving?** - The long-lived connection is created at **startup**. “Apply configuration” only updates the config file; you **must restart the CyberStrikeAI process** for the DingTalk connection to start. - -3. **Application logs** - - On startup you should see: `钉钉 Stream 正在连接…`, `钉钉 Stream 已启动(无需公网),等待收消息`. - - If you see `钉钉 Stream 长连接退出` with an error, it’s usually wrong **Client ID / Client Secret** or **Stream not enabled** in the open platform. - - After sending a message in DingTalk, you should see `钉钉收到消息` in the logs; if not, the platform is not pushing to this app (check that the bot is enabled and **Stream mode** is selected). - -4. **Open platform** - The app must be **published**. Under “Robot” you must enable **Stream** for receiving messages (HTTP callback only is not enough). Permission management must include robot receive/send message permissions. - ---- - -## 10. Common pitfalls - -- **Personal WeChat vs WeCom**: Personal WeChat uses `robots.wechat` + web QR bind; WeCom uses `robots.wecom` + admin callback URL—they are completely different. -- **WeChat QR expired**: QR codes last ~5 minutes; regenerate instead of reusing an old one. -- **Wrong bot type**: The “Custom” bot added in a DingTalk **group** (Webhook + sign secret) **cannot** be used for two-way chat. Only the **enterprise internal app** bot from the open platform is supported. -- **Saved but not restarted**: After changing DingTalk/Lark robot settings you **must restart** the app (WeChat QR bind restarts the connection automatically). -- **Client ID typo**: If the platform shows `504`, use `504` (not `5o4`); prefer copy/paste. -- **DingTalk: only HTTP callback, no Stream**: This app receives messages via **Stream**. In the open platform, message reception must be **Stream mode**. -- **App not published**: After changing the bot or permissions in the open platform, **publish a new version** under “Version management and release”, or changes won’t apply. - ---- - -## 11. Notes - -- All platforms: **text messages only**; other types (e.g. image, voice) are not supported and may be ignored. -- Personal WeChat: **private chat only**—group @-bot is not supported. -- Conversations are shared with the web UI: conversations created from the bot appear in the web “Conversations” list and vice versa. -- Bot execution uses the same **Eino single/multi-agent** path as the web UI (`ProcessMessageForRobot`, with progress callbacks and process details stored in the DB); only the final reply is sent back to personal WeChat/DingTalk/Lark/WeCom in one message (no SSE). Default: `robot_default_agent_mode: eino_single`. diff --git a/docs/workflow-graph.md b/docs/workflow-graph.md deleted file mode 100644 index f2e6abdd..00000000 --- a/docs/workflow-graph.md +++ /dev/null @@ -1,553 +0,0 @@ -# CyberStrikeAI 图编排使用说明 - -[English](workflow-graph_en.md) - -本文档说明 **图编排(Graph Orchestration)** 的完整使用方式:如何在画布上搭建流程、配置各类型节点、在节点之间传递数据,以及如何将流程绑定到角色并自动运行。 - ---- - -## 一、在哪里使用图编排 - -1. 登录 CyberStrikeAI Web 端 -2. 左侧导航进入 **图编排** -3. 在左侧列表选择已有流程,或新建流程 -4. 在中央画布拖拽、连线、配置节点 -5. 填写流程 **ID**、**名称**、**描述** 后点击 **保存** - -保存后的流程可在 **角色管理** 中绑定到某个角色。绑定后,用户与该角色对话时会按流程图自动执行(`workflow_policy: auto`)。 - ---- - -## 二、画布基本操作 - -| 操作 | 说明 | -|------|------| -| 添加节点 | 点击画布上方节点类型按钮(开始、工具、Agent、条件、审批、输出、结束) | -| 连线 | 点击 **连线**,依次点击源节点和目标节点;再次点击 **连线** 退出连线模式 | -| 选中元素 | 单击节点或连线,右侧显示 **节点属性** | -| 删除选中 | 点击 **删除选中** 删除当前节点或连线 | -| 自动布局 | 点击 **自动布局** 整理节点位置 | -| 试运行 | 点击 **试运行** 使用安全 dry-run 验证数据流;工具、Agent、审批不会真实执行 | -| 删除流程 | 点击 **删除** 删除整个流程定义 | - -**硬性规则:** 每个流程至少包含 **1 个开始节点** 和 **1 个输出节点**;开始节点不能有入边,输出 / 结束节点不能有出边。保存时前端和后端都会执行严格校验。 - ---- - -## 三、执行模型(先理解再配置) - -图编排按 **有向图** 执行,引擎从 **开始** 节点出发,沿连线依次运行下游节点。 - -每次运行会维护一份内部状态,模板变量 `{{...}}` 从这里取值: - -| 内部状态 | 模板前缀 | 含义 | -|----------|----------|------| -| `inputs` | `{{inputs.xxx}}` | 流程启动时的输入(用户消息、会话 ID 等) | -| `lastOutput` | `{{previous.xxx}}` | **上一个刚执行完** 的节点的输出 | -| `outputs` | `{{outputs.xxx}}` | 全局 **命名变量池**(由节点的「输出变量名」写入) | -| `nodeOutputs` | `{{节点ID.xxx}}` | 指定节点 ID 的完整输出对象 | -| `metrics` | 运行详情中查看 | 节点耗时、工具调用数、可收集到的 token / cost 等指标 | - -### 3.1 `previous` 是什么? - -`{{previous.output}}` 表示 **紧邻的上一个执行节点** 的 `output` 字段。 - -- 每执行完一个节点,引擎都会更新 `lastOutput` -- **不是**「画布上画线的上游」,而是 **实际执行顺序上的上一步** - -示例: - -```text -开始 → Agent A → Agent B -``` - -Agent B 的 `{{previous.output}}` = Agent A 的输出。 - -但若中间有条件节点: - -```text -开始 → Agent A → 条件 → Agent B -``` - -Agent B 的 `{{previous.output}}` = **条件节点** 的输出(`true` / `false`),**不是** Agent A 的结果。 - -如果一个节点有 **多个上游节点** 同时连入,`previous` 会先按该节点的 **汇聚策略** 生成: - -| 汇聚策略 | 含义 | 适合场景 | -|----------|------|----------| -| `all_merge` | 合并所有上游输出,`previous.output` 为数组 | 默认推荐,综合多路结果 | -| `last_by_canvas` | 按画布顺序取最后一个上游输出 | 明确只采用一路结果 | -| `first_non_empty` | 取第一个非空输出 | 多路兜底 | -| `fail_fast` | 任一上游失败则中止当前节点 | 关键链路、审批前置、安全检查 | - -### 3.2 `outputs` 是什么? - -`outputs` 是引擎在运行过程中维护的 **命名变量注册表**。 - -当 Agent、工具、输出 等节点配置了 **输出变量名**(字段 `output_key`)后,节点执行成功会把结果写入: - -```text -outputs["你填的变量名"] = 节点输出内容 -``` - -之后 **任意下游节点** 都可以通过 `{{outputs.变量名}}` 引用,不要求两个节点直接相连。 - -示例: - -- Agent A 的 **输出变量名** 填 `agent_result1` -- Agent B 的 **输入来源** 填 `{{outputs.agent_result1}}` - -即使 A 和 B 之间隔着条件节点,B 仍能拿到 A 的输出。 - -### 3.3 什么时候用 `previous`,什么时候用 `outputs`? - -| 场景 | 推荐写法 | -|------|----------| -| 两个节点 **直连**,只取上一步结果 | `{{previous.output}}` | -| 中间有其他节点(条件、工具、审批等) | `{{outputs.变量名}}` | -| 需要引用 **更早** 的某个节点结果 | `{{outputs.变量名}}` 或 `{{节点ID.output}}` | -| 条件判断要基于某 Agent 的输出 | `{{outputs.变量名}} != ""` | -| 读取用户最初输入 | `{{inputs.message}}` | - -**记忆口诀:** - -- `previous` = 上一步(链式、紧邻) -- `outputs` = 按名字取(跨节点、可回溯) - ---- - -## 四、模板语法 - -### 4.1 基本格式 - -```text -{{变量路径}} -``` - -支持字母、数字、下划线、点、连字符,例如: - -```text -{{previous.output}} -{{outputs.agent_result1}} -{{inputs.message}} -{{inputs.conversationId}} -{{previous.matched}} -{{node-abc123.output}} -``` - -### 4.2 可用路径一览 - -| 路径 | 说明 | -|------|------| -| `{{inputs.message}}` | 用户消息(开始节点输入) | -| `{{inputs.conversationId}}` | 会话 ID | -| `{{inputs.projectId}}` | 项目 ID | -| `{{previous.output}}` | 上一节点主输出 | -| `{{previous.matched}}` | 上一条件节点的匹配结果(`true` / `false`) | -| `{{outputs.变量名}}` | 某节点注册过的命名输出 | -| `{{节点ID.output}}` | 指定节点 ID 的 `output` 字段 | -| `{{previous.kind}}` | 上一节点输出类型,如 `agent` / `tool` / `condition` | -| `{{previous.status}}` | 上一节点状态,如 `completed` / `failed` / `simulated` | - -节点输出会保留兼容字段(如 `output`、`matched`),同时带有结构化字段: - -```json -{ - "kind": "agent", - "node_id": "node-2", - "node_type": "agent", - "status": "completed", - "output": "..." -} -``` - -### 4.3 条件表达式 - -条件节点和连线条件支持比较、文本匹配、正则、逻辑组合与安全 JSONPath/JQ 路径读取: - -```text -{{outputs.agent_result1}} != "" -{{previous.output}} == "ok" -{{outputs.count}} >= 100 -{{previous.output}} contains "success" -{{previous.output}} matches "^ok" -{{outputs.risk_score}} >= 8 && {{previous.output}} != "" -jsonpath({{previous.output}}, "$.status") == "ok" -jq({{outputs.scan}}, ".severity") == "high" -``` - -规则: - -- 支持 `==`、`!=`、`>`、`>=`、`<`、`<=` -- 支持 `contains` 子串匹配与 `matches` 正则匹配 -- 支持简单 `&&` / `||` -- 支持 `jsonpath(value, "$.path")` 与 `jq(value, ".path")` 的**安全路径子集**,仅做字段读取,不执行任意脚本 -- 比较两侧会自动去掉首尾空格和引号 -- 无比较符时,非空且不为 `false` / `0` / `null` 视为真 -- 保存时会静态校验表达式格式、JSONPath/JQ 路径和正则语法 - -### 4.4 嵌套字段绑定 - -节点的字段绑定除 `output`、`message` 等普通字段外,也支持 JSONPath/JQ 风格路径: - -| 绑定配置 | 含义 | -|----------|------| -| `from=previous, field=$.status` | 从上一节点输出对象读取 `status` | -| `from=outputs, field=$.scan.severity` | 从命名输出中读取嵌套字段 | -| `from=node-1, field=.output.items[0]` | 从指定节点输出读取数组元素 | - ---- - -## 五、节点类型与配置 - -### 5.1 开始(start) - -流程入口,将用户输入注入 `inputs`。 - -| 字段 | 说明 | 默认值 | -|------|------|--------| -| 输入变量 | 逗号分隔的输入键名 | `message, conversationId, projectId` | - -开始节点输出包含:`output`、`message`、`conversationId`、`projectId`。 - -### 5.2 Agent(agent) - -调用大模型 Agent 处理任务,支持多种运行模式。 - -| 字段 | 说明 | 默认值 | -|------|------|--------| -| Agent 模式 | `eino_single` / `deep` / `plan_execute` / `supervisor` | `eino_single` | -| 输入来源 | 上游数据的模板表达式 | `{{previous.output}}` | -| 节点指令 | 本节点要完成的任务描述 | 空 | -| 输出变量名 | 写入 `outputs` 的键名 | `agent_result` | -| 汇聚策略 | 多上游进入本节点时如何生成 `previous` | `all_merge` | - -**消息拼装规则:** - -- 仅填 **节点指令**:直接把指令发给 Agent -- 仅填 **输入来源**:生成「请基于上游节点输出继续处理:…」 -- 两者都填:合并为「上游输入 + 节点指令」 - -Agent 节点执行后: - -- `previous.output` 更新为本节点响应文本 -- 若配置了 **输出变量名**,同时写入 `outputs[输出变量名]` -- Agent 子图在 Eino 中拆为 `prepare → execute → finalize`,便于 trace 与后续局部 checkpoint - -### 5.3 工具(tool) - -调用已启用的 MCP 工具。 - -| 字段 | 说明 | 默认值 | -|------|------|--------| -| MCP 工具 | 工具名称(必填) | — | -| 参数模板 | JSON,支持 `{{...}}` 模板 | `{}` | -| 超时秒数 | 可选 | 空 | -| 汇聚策略 | 多上游进入本节点时如何生成 `previous` | `all_merge` | - -示例参数模板: - -```json -{"target": "{{inputs.message}}", "port": "443"} -``` - -若配置了 **输出变量名**,工具返回结果会写入 `outputs`。 - -### 5.4 条件(condition) - -根据表达式计算分支,输出 `matched`(`true` / `false`)。 - -| 字段 | 说明 | 默认值 | -|------|------|--------| -| 条件表达式 | 支持 `{{...}}` 与 `==` / `!=` | `{{previous.output}} != ""` | -| 汇聚策略 | 多上游进入本节点时如何生成 `previous` | `all_merge` | - -**分支规则:** - -- 从条件节点连出的 **第一条线** 默认为 **「是」** 分支(`matched == true`) -- **第二条线** 默认为 **「否」** 分支(`matched == false`) -- 连线标签可写 `是` / `否`(或 `yes` / `no`、`true` / `false`)辅助识别 -- 第三条及以后的出边需在 **连线条件** 中自定义表达式 - -连线条件示例(选中连线后在右侧配置): - -```text -{{previous.matched}} == "true" -{{previous.matched}} == "false" -``` - -### 5.5 审批(hitl) - -人工确认检查点。流程运行到该节点前会通过 Eino interrupt/checkpoint 暂停,等待 API 或监控面板审批后恢复。 - -| 字段 | 说明 | 默认值 | -|------|------|--------| -| 审批提示 | 支持模板 | `请审批该步骤是否继续执行` | -| 提示字段绑定 | 留空审批提示时,从绑定字段读取说明 | `previous.output` | -| 审批方 | `human` / `audit_agent` | `human` | -| 汇聚策略 | 多上游进入本节点时如何生成 `previous` | `all_merge` | - -HITL 等待信息会记录: - -- `checkpointId` -- interrupt `beforeNodes` -- resume target / address / path -- resume payload schema(`approved`、`comment`) - -### 5.6 输出(output) - -将流程最终结果写入 `outputs`,供结束摘要和对话展示使用。 - -| 字段 | 说明 | 默认值 | -|------|------|--------| -| 输出变量名 | 必填,最终结果的键名 | `result` | -| 变量来源 | 模板表达式,决定写入的值 | `{{previous.output}}` | -| 固定输出值 | 可选,填写后覆盖变量来源 | 空 | -| 汇聚策略 | 多上游进入本节点时如何生成 `previous` | `all_merge` | - -**注意:** 输出节点是流程的「出口」,不应再有出边。 - -### 5.7 结束(end) - -可选节点,用于生成结束摘要模板(角色绑定流程中较少单独使用)。 - -| 字段 | 说明 | 默认值 | -|------|------|--------| -| 结束摘要模板 | 支持 `{{outputs.xxx}}` | `{{outputs.result}}` | -| 汇聚策略 | 多上游进入本节点时如何生成 `previous` | `all_merge` | - ---- - -## 六、连线配置 - -选中 **连线** 后,右侧可配置 **连线条件**。 - -| 场景 | 示例 | -|------|------| -| 普通节点后的过滤 | `{{previous.output}} == "ok"` | -| 条件节点「是」分支 | `{{previous.matched}} == "true"` | -| 条件节点「否」分支 | `{{previous.matched}} == "false"` | - -若不填连线条件: - -- 非条件节点:连线始终放行 -- 条件节点:按出边顺序自动分配是/否分支 - ---- - -## 七、完整示例:跨条件节点传递 Agent 输出 - -### 7.1 流程结构 - -```text -开始 → Agent(生成初始值)→ 条件 → Agent(加工)→ 输出 - ↘ 否 → 输出 -``` - -### 7.2 节点配置 - -**Agent 1(第一个 Agent)** - -| 字段 | 值 | -|------|-----| -| 节点指令 | 只输出 `123333333` | -| 输出变量名 | `agent_result1` | - -**条件** - -| 字段 | 值 | -|------|-----| -| 条件表达式 | `{{outputs.agent_result1}} != ""` | - -**Agent 2(第二个 Agent)** - -| 字段 | 值 | -|------|-----| -| 输入来源 | `{{outputs.agent_result1}}` | -| 节点指令 | 在输入基础上加 100,然后输出 | -| 输出变量名 | `agent_result` | - -**输出** - -| 字段 | 值 | -|------|-----| -| 输出变量名 | `result` | -| 变量来源 | `{{outputs.agent_result}}` | - -### 7.3 常见错误 - -| 错误配置 | 原因 | -|----------|------| -| Agent 2 输入来源写 `{{previous.output}}` | `previous` 指向条件节点,得到的是 `true`/`false`,不是 Agent 1 的文本 | -| 未给 Agent 1 填输出变量名 | `outputs.agent_result1` 不存在,下游取到空值 | -| 条件表达式写 `{{previous.output}}` | 判断的是开始节点或上一节点的输出,而非 Agent 1 的命名变量 | - ---- - -## 八、绑定角色并运行 - -### 8.1 在角色管理中绑定 - -1. 进入 **角色管理**,编辑或新建角色 -2. 选择 **工作流 / 图编排** 绑定的流程 ID -3. 策略设为 `auto`(默认:有 `workflow_id` 时自动执行) -4. 保存角色 - -也可在角色 YAML 中直接配置: - -```yaml -name: 工作流测试 -workflow_id: "1233" -workflow_version: latest -workflow_policy: auto -``` - -### 8.2 运行效果 - -用户选择该角色并发送消息后: - -1. 引擎加载对应 `graph_json` 并按图执行 -2. 对话页可看到 `workflow_start`、`workflow_node_start`、Agent 推理等进度事件 -3. 流程结束后返回摘要,列出 `outputs` 中所有命名输出 - -若未配置输出节点或条件未命中,`outputs` 可能为空,摘要会提示检查输出节点与分支。 - ---- - -## 九、调试、试运行与复盘 - -### 9.1 安全试运行(dry-run) - -画布工具栏点击 **试运行**,输入一条测试消息即可模拟执行流程。 - -dry-run 的安全边界: - -- `start` / `condition` / `output` / `end` 会按真实逻辑计算 -- `tool` 不会真实调用 MCP,只返回 `[dry-run] tool call skipped` -- `agent` 不会真实调用模型,只返回 `[dry-run] agent execution skipped` -- `hitl` 不会暂停,只模拟通过 - -相关 API: - -```http -POST /api/workflows/dry-run -``` - -请求体: - -```json -{ - "graph": { "nodes": [], "edges": [], "config": {} }, - "inputs": { "message": "ping" } -} -``` - -响应包含: - -- `outputs` -- `nodeOutputs` -- `trace` -- `metrics` -- `replayScript` - -### 9.2 运行详情与 replay - -运行后可查询完整节点执行轨迹: - -```http -GET /api/workflows/runs/{runId} -``` - -返回 `run` 与 `nodeRuns`,每个节点记录包含: - -- input 快照 -- output 快照 -- status / error -- started_at / finished_at -- `duration_ms` - -复盘接口: - -```http -GET /api/workflows/runs/{runId}/replay -``` - -该接口只根据已保存的 `nodeRuns` 生成步骤,不会重新执行工具或 Agent。 - -### 9.3 指标(metrics) - -工作流会尽量累计: - -- `node_count` -- `duration_ms` -- `tool_call_count` -- Agent progress 中可收集到的 `prompt_tokens` / `completion_tokens` / `total_tokens` / `cost` - -token 与成本是否存在取决于底层模型/Agent 事件是否上报 usage。 - ---- - -## 十、保存前校验规则 - -保存时系统会自动检查: - -| 规则 | 说明 | -|------|------| -| 必须有开始节点 | 至少 1 个 `start` | -| 必须有输出节点 | 至少 1 个 `output`,且填写输出变量名 | -| 连线合法 | 源/目标节点存在,不能自环 | -| 开始节点无入边 | 开始节点不能被指向 | -| 输出 / 结束节点无出边 | 输出 / 结束节点后不应再连线 | -| 非开始节点必须有入边 | 避免孤岛节点 | -| 非输出 / 结束节点必须有出边 | 避免执行到死路 | -| 无环路 | Workflow 编排必须是 DAG | -| 可达性 | 所有节点必须能从开始节点到达,并能最终到达 output/end | -| 工具节点 | 必须选择 MCP 工具;参数 JSON 必须合法;超时必须为正整数 | -| Agent 节点 | 必须填写节点指令或输入绑定;必须填写输出变量名 | -| 条件节点 | 必须填写表达式;需要 1~2 条出边;分支必须标记是/否且不能重复 | -| 连线条件 | 表达式、正则、JSONPath/JQ 路径必须通过静态校验 | -| 汇聚策略 | 必须是 `all_merge` / `last_by_canvas` / `first_non_empty` / `fail_fast` | - ---- - -## 十一、排错指南 - -| 现象 | 可能原因 | 处理建议 | -|------|----------|----------| -| 下游拿到空值 | 上游未配置输出变量名 | 给上游 Agent/工具填 **输出变量名**,下游用 `{{outputs.xxx}}` | -| 下游拿到 `true`/`false` | 误用 `{{previous.output}}`,上一步是条件节点 | 改用 `{{outputs.xxx}}` | -| 条件总走「否」 | 表达式与真实输出格式不一致 | 检查 Agent 输出是否带引号、换行;用 `!= ""` 先验证 | -| 流程无最终输出 | 未命中输出节点所在分支 | 检查条件分支连线;确保至少一条路径到达 **输出** 节点 | -| 角色对话未跑流程 | 角色未绑定或未启用 | 确认 `workflow_id`、`workflow_policy: auto`、流程 `enabled: true` | -| 工具节点失败 | 参数 JSON 不合法或工具未启用 | 检查参数模板;在 MCP 中启用对应工具 | -| 保存失败提示分支非法 | 条件节点出边未标记是/否或重复 | 选中连线,设置条件分支为 `true` 或 `false` | -| 多上游结果不符合预期 | 汇聚策略不合适 | 根据场景改为 `all_merge` / `first_non_empty` / `last_by_canvas` / `fail_fast` | -| 嵌套字段取不到 | JSONPath/JQ 路径不符合安全子集 | 使用 `$.a.b[0]` 或 `.a.b[0]`,不要用通配符/递归/表达式 | - ---- - -## 十二、最佳实践 - -1. **命名规范**:为每个需要被引用的节点设置有意义的输出变量名,如 `scan_result`、`parsed_targets`,避免都叫 `agent_result`。 -2. **跨节点传参优先用 `outputs`**:只要中间可能插入条件、工具、审批节点,就应用命名变量。 -3. **`previous` 仅用于直连**:A → B 且无中间节点时,`{{previous.output}}` 最简洁。 -4. **条件判断引用源数据**:判断 Agent 输出时用 `{{outputs.xxx}}`,不要用 `{{previous.output}}`(除非条件紧跟在目标 Agent 之后)。 -5. **每条路径都要有出口**:确保「是」「否」分支最终都能到达 **输出** 节点(或你期望的终点)。 -6. **多上游节点显式选择汇聚策略**:综合结果用 `all_merge`,兜底用 `first_non_empty`,关键链路用 `fail_fast`。 -7. **嵌套 JSON 用 JSONPath/JQ 安全路径**:例如 `jsonpath({{previous.output}}, "$.status") == "ok"`。 -8. **保存前先 dry-run**:用简单消息验证数据传递和分支,再绑定角色真实执行。 - ---- - -## 十三、相关代码位置(开发者参考) - -| 模块 | 路径 | -|------|------| -| 执行引擎 | `internal/workflow/runner.go` | -| Eino 编译 / checkpoint / HITL | `internal/workflow/eino_compile.go` | -| 图校验 | `internal/workflow/validation.go` | -| 表达式 / JSONPath / 汇聚 | `internal/workflow/expression.go`、`jsonpath.go`、`join.go` | -| dry-run / replay 数据 | `internal/workflow/dry_run.go`、`internal/handler/workflow_run.go` | -| 画布前端 | `web/static/js/workflows.js` | -| 流程 API | `internal/handler/workflow.go` | -| 角色绑定 | `internal/config/config.go`(`workflow_id` 字段) | diff --git a/docs/workflow-graph_en.md b/docs/workflow-graph_en.md deleted file mode 100644 index a4b4223c..00000000 --- a/docs/workflow-graph_en.md +++ /dev/null @@ -1,553 +0,0 @@ -# CyberStrikeAI Graph Orchestration Guide - -[中文](workflow-graph.md) - -This document explains how to use **Graph Orchestration**: building workflows on the canvas, configuring node types, passing data between nodes, and binding a graph to a role for automatic execution. - ---- - -## 1. Where to find Graph Orchestration - -1. Log in to the CyberStrikeAI web UI. -2. Open **Graph Orchestration** in the left sidebar. -3. Select an existing workflow from the list, or create a new one. -4. Drag nodes, draw edges, and configure properties on the canvas. -5. Fill in **ID**, **Name**, and **Description**, then click **Save**. - -Saved workflows can be bound to a role under **Role Management**. When `workflow_policy` is `auto`, chatting with that role runs the bound graph automatically. - ---- - -## 2. Canvas basics - -| Action | Description | -|--------|-------------| -| Add node | Click a node type button above the canvas (Start, Tool, Agent, Condition, HITL, Output, End) | -| Connect | Click **Connect**, then click source and target nodes; click **Connect** again to exit connect mode | -| Select | Click a node or edge; properties appear in the right panel | -| Delete selected | Remove the current node or edge | -| Auto layout | Rearrange node positions | -| Dry run | Safely simulate data flow; Tool, Agent, and HITL nodes are not executed for real | -| Delete workflow | Remove the entire workflow definition | - -**Hard requirements:** Every workflow needs at least **one Start node** and **one Output node**. Start nodes must not have incoming edges; Output / End nodes must not have outgoing edges. Both frontend and backend run strict validation before save. - ---- - -## 3. Execution model (read this before configuring) - -The engine executes the workflow as a **directed graph**, starting from the **Start** node and following edges to downstream nodes. - -During a run, the engine keeps internal state. Template expressions `{{...}}` read from that state: - -| Internal state | Template prefix | Meaning | -|----------------|-----------------|---------| -| `inputs` | `{{inputs.xxx}}` | Workflow inputs at start (user message, conversation ID, etc.) | -| `lastOutput` | `{{previous.xxx}}` | Output of the **most recently executed** node | -| `outputs` | `{{outputs.xxx}}` | Global **named variable pool** (written by nodes with an output key) | -| `nodeOutputs` | `{{nodeId.xxx}}` | Full output object of a specific node ID | -| `metrics` | available in run details | Node duration, tool call count, and usage/cost metrics when reported | - -### 3.1 What is `previous`? - -`{{previous.output}}` is the `output` field of the **immediately preceding executed node**. - -- After every node finishes, the engine updates `lastOutput`. -- It is **not** “the node drawn upstream on the canvas”; it is **the previous step in actual execution order**. - -Example: - -```text -Start → Agent A → Agent B -``` - -For Agent B, `{{previous.output}}` = Agent A’s output. - -With a condition in between: - -```text -Start → Agent A → Condition → Agent B -``` - -For Agent B, `{{previous.output}}` = the **condition node** output (`true` / `false`), **not** Agent A’s result. - -If a node has **multiple upstream nodes**, `previous` is built by that node’s **join strategy** first: - -| Join strategy | Meaning | Use case | -|---------------|---------|----------| -| `all_merge` | Merge all upstream outputs; `previous.output` is an array | Default; aggregate multiple results | -| `last_by_canvas` | Use the last upstream output by canvas order | Explicitly use one branch | -| `first_non_empty` | Use the first non-empty output | Fallback chains | -| `fail_fast` | Stop the node if any upstream failed | Critical gates, approval prechecks, safety checks | - -### 3.2 What is `outputs`? - -`outputs` is a **named variable registry** maintained by the engine during execution. - -When an Agent, Tool, or Output node sets an **Output variable name** (`output_key`), the result is stored as: - -```text -outputs["your_variable_name"] = node_output -``` - -Any downstream node can then reference it via `{{outputs.variable_name}}`, even if other nodes sit in between. - -Example: - -- Agent A **Output variable name**: `agent_result1` -- Agent B **Input source**: `{{outputs.agent_result1}}` - -Agent B still receives Agent A’s output even when a condition node lies between them. - -### 3.3 When to use `previous` vs `outputs` - -| Scenario | Recommended | -|----------|-------------| -| Two nodes are **directly connected**; you only need the last step | `{{previous.output}}` | -| Other nodes sit in between (condition, tool, HITL, etc.) | `{{outputs.variable_name}}` | -| Reference output from an **earlier** node | `{{outputs.variable_name}}` or `{{nodeId.output}}` | -| Condition should test an Agent’s output | `{{outputs.variable_name}} != ""` | -| Read the original user input | `{{inputs.message}}` | - -**Rule of thumb:** - -- `previous` = last step (chained, adjacent) -- `outputs` = by name (cross-node, look back) - ---- - -## 4. Template syntax - -### 4.1 Basic format - -```text -{{path.to.value}} -``` - -Allowed characters in paths: letters, digits, underscore, dot, hyphen. Examples: - -```text -{{previous.output}} -{{outputs.agent_result1}} -{{inputs.message}} -{{inputs.conversationId}} -{{previous.matched}} -{{node-abc123.output}} -``` - -### 4.2 Available paths - -| Path | Description | -|------|-------------| -| `{{inputs.message}}` | User message (Start node input) | -| `{{inputs.conversationId}}` | Conversation ID | -| `{{inputs.projectId}}` | Project ID | -| `{{previous.output}}` | Primary output of the previous node | -| `{{previous.matched}}` | Match result of the previous condition node (`true` / `false`) | -| `{{outputs.variable_name}}` | Named output registered by a node | -| `{{nodeId.output}}` | `output` field of the node with that ID | -| `{{previous.kind}}` | Previous node output kind, e.g. `agent` / `tool` / `condition` | -| `{{previous.status}}` | Previous node status, e.g. `completed` / `failed` / `simulated` | - -Node outputs keep compatibility fields such as `output` and `matched`, and also include a structured envelope: - -```json -{ - "kind": "agent", - "node_id": "node-2", - "node_type": "agent", - "status": "completed", - "output": "..." -} -``` - -### 4.3 Condition expressions - -Condition nodes and edge conditions support comparisons, text matching, regex, logical operators, and safe JSONPath/JQ path reads: - -```text -{{outputs.agent_result1}} != "" -{{previous.output}} == "ok" -{{outputs.count}} >= 100 -{{previous.output}} contains "success" -{{previous.output}} matches "^ok" -{{outputs.risk_score}} >= 8 && {{previous.output}} != "" -jsonpath({{previous.output}}, "$.status") == "ok" -jq({{outputs.scan}}, ".severity") == "high" -``` - -Rules: - -- Operators: `==`, `!=`, `>`, `>=`, `<`, `<=` -- `contains` checks substrings; `matches` checks regular expressions -- Simple `&&` / `||` is supported -- `jsonpath(value, "$.path")` and `jq(value, ".path")` support a **safe path-only subset**; no arbitrary script execution -- Leading/trailing spaces and quotes are trimmed before comparison -- Without a comparator, non-empty values that are not `false`, `0`, or `null` are treated as true -- Expressions, regexes, and JSONPath/JQ paths are statically validated before save - -### 4.4 Nested field binding - -Field bindings can read ordinary fields such as `output` or `message`, and also JSONPath/JQ-style paths: - -| Binding | Meaning | -|---------|---------| -| `from=previous, field=$.status` | Read `status` from previous output | -| `from=outputs, field=$.scan.severity` | Read a nested field from named outputs | -| `from=node-1, field=.output.items[0]` | Read an array element from a specific node output | - ---- - -## 5. Node types and configuration - -### 5.1 Start - -Workflow entry point; injects user input into `inputs`. - -| Field | Description | Default | -|-------|-------------|---------| -| Input keys | Comma-separated input key names | `message, conversationId, projectId` | - -Start node output includes: `output`, `message`, `conversationId`, `projectId`. - -### 5.2 Agent - -Runs an LLM Agent task. Supports multiple modes. - -| Field | Description | Default | -|-------|-------------|---------| -| Agent mode | `eino_single` / `deep` / `plan_execute` / `supervisor` | `eino_single` | -| Input source | Template for upstream data | `{{previous.output}}` | -| Node instruction | Task description for this node | empty | -| Output variable name | Key written into `outputs` | `agent_result` | -| Join strategy | How to build `previous` when multiple upstreams enter this node | `all_merge` | - -**Message assembly:** - -- Instruction only → send instruction to the Agent -- Input source only → “Continue based on upstream output: …” -- Both → combined “upstream input + node instruction” - -After execution: - -- `previous.output` becomes this node’s response text -- If **Output variable name** is set, the value is also stored in `outputs[variable_name]` -- In the Eino graph, the Agent node is split into `prepare → execute → finalize` for clearer trace and future checkpointing - -### 5.3 Tool - -Calls an enabled MCP tool. - -| Field | Description | Default | -|-------|-------------|---------| -| MCP tool | Tool name (required) | — | -| Argument template | JSON with `{{...}}` templates | `{}` | -| Timeout (seconds) | Optional | empty | -| Join strategy | How to build `previous` when multiple upstreams enter this node | `all_merge` | - -Example argument template: - -```json -{"target": "{{inputs.message}}", "port": "443"} -``` - -If an output variable name is configured, the tool result is written to `outputs`. - -### 5.4 Condition - -Evaluates an expression and outputs `matched` (`true` / `false`). - -| Field | Description | Default | -|-------|-------------|---------| -| Expression | Supports `{{...}}` and `==` / `!=` | `{{previous.output}} != ""` | -| Join strategy | How to build `previous` when multiple upstreams enter this node | `all_merge` | - -**Branching rules:** - -- The **first outgoing edge** defaults to the **“yes”** branch (`matched == true`) -- The **second outgoing edge** defaults to the **“no”** branch (`matched == false`) -- Edge labels such as `是` / `否` (or `yes` / `no`, `true` / `false`) help identify branches -- A third or later edge needs a custom **edge condition** - -Edge condition examples (select an edge, configure in the right panel): - -```text -{{previous.matched}} == "true" -{{previous.matched}} == "false" -``` - -### 5.5 HITL (human-in-the-loop) - -Human approval checkpoint. The run pauses before this node through Eino interrupt/checkpoint and resumes after approval via API or the monitor panel. - -| Field | Description | Default | -|-------|-------------|---------| -| Prompt | Supports templates | `Please approve before continuing` | -| Prompt binding | If prompt text is empty, read approval text from a bound field | `previous.output` | -| Reviewer | `human` / `audit_agent` | `human` | -| Join strategy | How to build `previous` when multiple upstreams enter this node | `all_merge` | - -Pending HITL metadata records: - -- `checkpointId` -- interrupt `beforeNodes` -- resume target / address / path -- resume payload schema (`approved`, `comment`) - -### 5.6 Output - -Writes the final workflow result into `outputs` for summary and chat display. - -| Field | Description | Default | -|-------|-------------|---------| -| Output variable name | Required key for the final result | `result` | -| Variable source | Template deciding what to write | `{{previous.output}}` | -| Static output value | Optional; overrides variable source when set | empty | -| Join strategy | How to build `previous` when multiple upstreams enter this node | `all_merge` | - -**Note:** Output nodes are workflow exits and must not have outgoing edges. - -### 5.7 End - -Optional node for an end summary template (less common in role-bound flows). - -| Field | Description | Default | -|-------|-------------|---------| -| Result template | Supports `{{outputs.xxx}}` | `{{outputs.result}}` | -| Join strategy | How to build `previous` when multiple upstreams enter this node | `all_merge` | - ---- - -## 6. Edge configuration - -Select an **edge** to configure its **condition** in the right panel. - -| Scenario | Example | -|----------|---------| -| Filter after a normal node | `{{previous.output}} == "ok"` | -| “Yes” branch from a condition | `{{previous.matched}} == "true"` | -| “No” branch from a condition | `{{previous.matched}} == "false"` | - -If no edge condition is set: - -- Non-condition nodes: edge is always allowed -- Condition nodes: yes/no branches are assigned by edge order automatically - ---- - -## 7. Full example: passing Agent output across a condition - -### 7.1 Graph structure - -```text -Start → Agent (initial value) → Condition → Agent (transform) → Output - ↘ no → Output -``` - -### 7.2 Node configuration - -**Agent 1** - -| Field | Value | -|-------|-------| -| Node instruction | Output only `123333333` | -| Output variable name | `agent_result1` | - -**Condition** - -| Field | Value | -|-------|-------| -| Expression | `{{outputs.agent_result1}} != ""` | - -**Agent 2** - -| Field | Value | -|-------|-------| -| Input source | `{{outputs.agent_result1}}` | -| Node instruction | Add 100 to the input, then output | -| Output variable name | `agent_result` | - -**Output** - -| Field | Value | -|-------|-------| -| Output variable name | `result` | -| Variable source | `{{outputs.agent_result}}` | - -### 7.3 Common mistakes - -| Wrong config | Why it fails | -|--------------|--------------| -| Agent 2 input source = `{{previous.output}}` | `previous` points to the condition node → `true`/`false`, not Agent 1’s text | -| Agent 1 has no output variable name | `outputs.agent_result1` does not exist → empty downstream | -| Condition uses `{{previous.output}}` | Tests the wrong upstream value instead of Agent 1’s named output | - ---- - -## 8. Bind to a role and run - -### 8.1 Bind in Role Management - -1. Open **Role Management**, edit or create a role. -2. Select the workflow / graph ID to bind. -3. Set policy to `auto` (default when `workflow_id` is set). -4. Save the role. - -You can also configure this in role YAML: - -```yaml -name: workflow-test -workflow_id: "1233" -workflow_version: latest -workflow_policy: auto -``` - -### 8.2 Runtime behavior - -When a user chats with that role: - -1. The engine loads `graph_json` and executes the graph. -2. The chat UI shows progress events (`workflow_start`, `workflow_node_start`, Agent reasoning, etc.). -3. When finished, a summary lists all named entries in `outputs`. - -If no Output node is reached or no branch matches, `outputs` may be empty and the summary will suggest checking the Output node and branches. - ---- - -## 9. Debugging, dry-run, and replay - -### 9.1 Safe dry-run - -Click **Dry run** on the canvas toolbar and enter a test message to simulate the workflow. - -Dry-run safety rules: - -- `start` / `condition` / `output` / `end` use real logic -- `tool` does not call MCP; it returns `[dry-run] tool call skipped` -- `agent` does not call the model; it returns `[dry-run] agent execution skipped` -- `hitl` does not pause; it simulates approval - -API: - -```http -POST /api/workflows/dry-run -``` - -Request: - -```json -{ - "graph": { "nodes": [], "edges": [], "config": {} }, - "inputs": { "message": "ping" } -} -``` - -Response includes: - -- `outputs` -- `nodeOutputs` -- `trace` -- `metrics` -- `replayScript` - -### 9.2 Run details and replay - -Query full node execution traces after a run: - -```http -GET /api/workflows/runs/{runId} -``` - -The response contains `run` and `nodeRuns`. Each node run records: - -- input snapshot -- output snapshot -- status / error -- started_at / finished_at -- `duration_ms` - -Replay API: - -```http -GET /api/workflows/runs/{runId}/replay -``` - -This generates replay steps from saved `nodeRuns`; it does not re-execute tools or Agents. - -### 9.3 Metrics - -The workflow accumulates, when available: - -- `node_count` -- `duration_ms` -- `tool_call_count` -- Agent progress usage such as `prompt_tokens` / `completion_tokens` / `total_tokens` / `cost` - -Token and cost metrics depend on whether the underlying model/Agent events report usage. - ---- - -## 10. Validation before save - -On save, the system checks: - -| Rule | Description | -|------|-------------| -| Start node required | At least one `start` node | -| Output node required | At least one `output` node with an output variable name | -| Valid edges | Source and target exist; no self-loops | -| Start has no incoming edges | Start must not be targeted | -| Output / End has no outgoing edges | Nothing after Output / End | -| Non-start nodes must have incoming edges | Prevent orphan nodes | -| Non-output/end nodes must have outgoing edges | Prevent dead ends | -| No cycles | Workflow orchestration must be a DAG | -| Reachability | Every node must be reachable from Start and eventually reach output/end | -| Tool nodes | MCP tool required; argument JSON must be valid; timeout must be a positive integer | -| Agent nodes | Must have node instruction or input binding; output variable name required | -| Condition nodes | Expression required; 1–2 outgoing edges; branches must be yes/no and unique | -| Edge conditions | Expressions, regexes, and JSONPath/JQ paths must pass static validation | -| Join strategy | Must be `all_merge` / `last_by_canvas` / `first_non_empty` / `fail_fast` | - ---- - -## 11. Troubleshooting - -| Symptom | Likely cause | Fix | -|---------|--------------|-----| -| Downstream gets empty value | Upstream has no output variable name | Set **Output variable name** on upstream; use `{{outputs.xxx}}` downstream | -| Downstream gets `true`/`false` | Used `{{previous.output}}` while previous node is a condition | Use `{{outputs.xxx}}` instead | -| Condition always takes “no” | Expression does not match actual output format | Check Agent output for quotes/newlines; try `!= ""` first | -| No final output | Output node branch not reached | Verify condition wiring; ensure every path reaches an **Output** node | -| Role chat does not run workflow | Role not bound or disabled | Check `workflow_id`, `workflow_policy: auto`, workflow `enabled: true` | -| Tool node fails | Invalid JSON in arguments or tool disabled | Fix argument template; enable the tool in MCP settings | -| Save fails with invalid branch | Condition outgoing edges are not marked yes/no, or are duplicated | Select the edge and set branch to `true` or `false` | -| Multi-upstream result is unexpected | Join strategy does not match the workflow | Switch between `all_merge`, `first_non_empty`, `last_by_canvas`, and `fail_fast` | -| Nested field is empty | JSONPath/JQ path is outside the safe subset | Use `$.a.b[0]` or `.a.b[0]`; avoid wildcards, recursion, or expressions | - ---- - -## 12. Best practices - -1. **Meaningful names**: Use descriptive output variable names (`scan_result`, `parsed_targets`) instead of reusing `agent_result` everywhere. -2. **Prefer `outputs` for cross-node data**: If a condition, tool, or HITL node might sit in between, use named variables. -3. **Use `previous` only for direct links**: `A → B` with nothing in between is the ideal case for `{{previous.output}}`. -4. **Conditions should reference source data**: When testing Agent output, use `{{outputs.xxx}}` unless the condition immediately follows that Agent. -5. **Every path needs an exit**: Ensure both yes and no branches eventually reach an **Output** node (or your intended end). -6. **Choose join strategy explicitly for multi-upstream nodes**: Use `all_merge` for aggregation, `first_non_empty` for fallback, and `fail_fast` for critical gates. -7. **Use JSONPath/JQ safe paths for nested JSON**: e.g. `jsonpath({{previous.output}}, "$.status") == "ok"`. -8. **Dry-run before real execution**: Validate data flow and branches with a simple message before binding the workflow to a role. - ---- - -## 13. Code references (for developers) - -| Module | Path | -|--------|------| -| Execution engine | `internal/workflow/runner.go` | -| Eino compile / checkpoint / HITL | `internal/workflow/eino_compile.go` | -| Graph validation | `internal/workflow/validation.go` | -| Expressions / JSONPath / joins | `internal/workflow/expression.go`, `jsonpath.go`, `join.go` | -| Dry-run / replay data | `internal/workflow/dry_run.go`, `internal/handler/workflow_run.go` | -| Canvas UI | `web/static/js/workflows.js` | -| Workflow API | `internal/handler/workflow.go` | -| Role binding | `internal/config/config.go` (`workflow_id` field) |