# dsh visual workflow 架构设计文档 **文档编号**:AD-001 **当前版本**:v0.1.0 **修订日期**:2026-08-23 **对应需求**:docs/需求文档.md v0.1.0(定稿) **官方源码根**:D:\AiCoding-Gzx\HarnessPlugin\deepseek-harness-dsh-v0.1.1-rc.2(下文 @repo) --- ## 1. 架构总览 dsh visual workflow 是 **host + client 双面插件**(需 Web 可视化),运行面最小化: - **Host 半区**(Node.js):数据存储、工作流模型、编排器、子代理管理、wf_* 工具、GUI HTTP API、模式二服务管理器、向量检索/查询工具。 - **Client 半区**(浏览器):React 设计器 UI(画布/左栏/右栏/控制栏/运行历史/组合管理),挂载于官方 conversation.view slot。 **核心设计原则**: 1. **零官方包运行时依赖**:所有 DSH 生态服务(代理、子代理、工具、用户提问、预设、MCP、持久化、web server)均经 ctx.get() 运行时解析或经官方事件/组合观察;Host 工具以纯对象/defineTool 定义注册。唯一允许的第三方运行时依赖:@huggingface/transformers(本地嵌入模型推理,非 @deepseek-ai/* 官方包)。 2. **节点 JSON 即事实源**:模板深拷贝解耦;工作流 JSON 为唯一事实源。 3. **编排执行权交给父代理**:插件不决定执行顺序——父代理按注入的编排指令与流程定义文件自主调度(沿用旧项目验证过的消息驱动模型)。 4. **双向同步(项目特点)**:画布→编排(每节点执行前重读最新快照)+ 编排→画布(轮询 runStatus 回显节点状态/高亮),防回环。 5. **模式二**:服务进程 = 官方 headless 组合 + 插件 serve 层的 `dsh --profile headless --patch ` fork(不手写 profile manifest);一服务一进程一端口。 --- ## 2. 插件契约 ### 2.1 package.json ```jsonc { "name": "dsh-visual-workflow", "type": "module", "main": "lib/index.js", "types": "lib/types/index.d.ts", "exports": { ".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, "./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" }, "./service-runner": { "default": "./lib/service-runner.js" }, "./serve.patch.yml": "./serve.patch.yml", "./cordis.patch.yml": "./cordis.patch.yml", "./package.json": "./package.json" }, "files": ["lib", "cordis.patch.yml", "serve.patch.yml", "assets/models/bge-small-zh-v1.5", "README.md"], "dsh": { "bundle": { "patch": "./cordis.patch.yml" }, "client": { "platform": "web", "inject": ["@deepseek-ai/dsh-client-runtime"] } }, "peerDependencies": { "@deepseek-ai/cordis": "*" }, "dependencies": { "@huggingface/transformers": "…" } } ``` 要点: - 不声明 @deepseek-ai/* 运行时依赖(§1 原则);client 类型贡献经 type-only import。 - `serve.patch.yml`:模式二服务进程使用的组合层文件(见 §7)。 - `./service-runner`:模式二服务进程入口插件(host 半区复用代码的独立入口)。 - `files` 与 `exports` 一致;模型资产随包分发(量化 ONNX 约 25MB)。 ### 2.2 cordis.patch.yml(Web profile 挂载层) ```yaml - insert: - id: visual-workflow name: dsh-visual-workflow config: dataDir: !!js dshHomePath('visual-workflow') servicePortBase: 7860 apiKey: null maxConcurrentPerService: 50 wfAskAgentTimeoutMs: 120000 runIdleTimeoutMs: 1800000 # 运行状态轮询间隔(预留:当前前端固定 600ms,尚未消费该配置键) runPollMs: 2000 reactIterationLimitDefault: 50 retryLimitDefault: 3 outputFullLimit: 102400 documentTextLimit: 20000 embeddingModelDir: null embeddingEndpoint: null ``` ### 2.3 TS 工程 - 双 program:`tsconfig.host.json`(host + shared)与 `tsconfig.client.json`(client + shared,含 CSS module 声明),共享 `src/shared/` 仅**纯类型**(零运行时 import)。 - Host 编译:tsc emit `lib/`(保留 `src/` 相对路径回退);Client 构建:tsdown + 共享助手(复用官方 `packages/client/tsdown.client.ts` 模式:`window.__ModuleLoader__.load({id, factory})`、CSS 模块 `style[data-plugin]` 注入、sourcemap、host/client 产物并存、client bundle 纯度门——见 §8 官方索引 #18)。 --- ## 3. 目录结构 ``` dsh-visual-workflow/ ├─ src/ │ ├─ host/ # Host 半区 │ │ ├─ shared/ # 前后端共享纯类型契约(graph-model / types / protocol) │ │ ├─ storage/ # 原子 JSON 存储(flow-store / atomic) │ │ ├─ orchestrator/ # 编排运行时(锁/快照/断点/暂停门/挂起/续跑) │ │ ├─ agent/ # 节点子代理执行引擎、护栏、模型选择、提示词注入 │ │ ├─ tools/ # wf_* 工具(wf_run_node / wf_finish / wf_ask / wf_db_query…) │ │ ├─ remote/ # GUI API 端点(api / api-workflows / api-runs / api-scheduler…) │ │ ├─ service/ # 模式二服务管理器(fork / 端口池 / serve-patch) │ │ ├─ embedding/ # 本地嵌入(bge-small-zh-v1.5)与向量/BM25 索引 │ │ ├─ scheduler/ # 定时任务(planner / task-store / engine / session-provider…) │ │ └─ prompts/ # 编排/节点任务/协作提示词模板 │ └─ client/ # Client 半区 │ ├─ studio/ # 工作台状态机(useReducer)与布局 │ ├─ components/ # 画布 / 组合管理 / 运行历史 / 服务控制台 / 定时任务 / 日期/时间选择器 │ ├─ hooks/ # controller hooks(文档/画布/运行/模板/面板…) │ ├─ lib/ # remote / files / graph-model / bundle 导入导出 │ └─ i18n.ts / styles.ts ├─ tests/ # 单测(host / client(jsdom) / integration) ├─ scripts/ # 构建与 watch 脚本(build.mjs / watch-client.mjs …) ├─ docs/ # 需求文档 / 架构文档 / 运行复盘 ├─ assets/models/ # 本地嵌入模型资产 ├─ cordis.patch.yml # Web profile 组合挂载层 ├─ serve.patch.yml # 模式二服务进程组合层模板 ├─ tsconfig.host.json # Host program(emit lib/) ├─ tsconfig.client.json # Client program(noEmit,仅类型检查) └─ package.json ``` ## 4. Host 半区模块设计 ### 4.1 storage/(FlowStore) - 数据根:`dataDir`(默认 `~/.dsh/visual-workflow`),目录规划与需求文档 §6 一致(workflows/ services/ roles/ data/ flow-templates/ combos.json runs/ orchestrations/)。 - 原子写:同目录临时文件(open 'wx' 独占创建)→ fsync 句柄 → 原子发布(rename 覆盖 + POSIX 补 fsync 父目录)→ 进程内按文件 withLock(互斥 Map)+ 磁盘锁文件(open 'wx' no-clobber,陈旧锁按 mtime + pid 回收)。**为何发布用 rename 覆盖而非 link() no-clobber**:本插件数据形态为「整文件 JSON 重写 + 磁盘锁串行化」的单写者场景,last-write-wins 是正确语义(官方 storage-json 同款;link()+unlink() no-clobber 仅用于官方 jsonl append-only 日志的「首次物化拒覆盖」场景);跨进程互斥由磁盘锁保证,进程内再叠 FIFO 互斥——覆盖发生时目标只可能是锁持有者刚发布的完整性版本。Windows 无 link()+unlink() no-clobber 原语,官方「拒绝覆盖」等价协议即 open('wx')(O_CREAT|O_EXCL → CREATE_NEW,跨进程原子)。 - API 面:`listWorkflows(sessionId)/getWorkflow/saveWorkflow/deleteWorkflow`、`listServices/saveService/deleteService`、`listTemplates(kind)/saveTemplate/deleteTemplate`、`listFlowTemplates/getFlowTemplate/saveFlowTemplate/deleteFlowTemplate`(图2 交互改造:工作流模板全局共享)、`listRuns(flowId)/saveRun/runExists`、`saveToolCombo/listToolCombos/deleteToolCombo`、`userIdMap(serviceId)/saveUserIdMap`。 - 删除语义:`deleteWorkflow` 仅删除对应工作流 JSON,`deleteService` 删除服务 JSON 并级联删除其 sessions 映射;历史 run(runs/)与编排事实源(orchestrations/)**保留**——供运行历史面板与断点追溯(需求 §4.7),按 flowId 过滤隔离,删除工作流不影响其他流程、也不向新流程暴露残留。 - 按会话隔离:workflow/service 文件内记录 `sessionId`;模板(roles/data/combos)全局共享。 ### 4.2 graph/(工作流数据模型与校验) 判别节点模型(TS): ```ts export type NodeKind = 'parent' | 'agent' | 'file' | 'database' | 'start' | 'end' | 'pause' | 'group' | 'proxy' export interface BaseNode { id: string; kind: NodeKind; position: { x: number; y: number } } export interface RoleNode extends BaseNode { kind: 'parent' | 'agent' data: { label: string; systemPrompt: string; provider: string; model: string reasoning?: string presetId?: string | null retryLimit: number; reactLimit?: number | null inputSchema?: string; outputSchema?: string systemPromptSource?: string injectSystemPrompt?: boolean // 官方系统提示词(人设/身份/系统/上下文)注入开关,默认 true injectToolSections?: boolean // 工具提示词 tool:* 散文段注入开关,默认 true promptFilePath?: string groupId?: string | null } } export interface FileNode extends BaseNode { kind: 'file' data: { fileKind: 'text' | 'file'; content?: string; managedPath?: string; fileName?: string } } export interface DatabaseNode extends BaseNode { kind: 'database' data: { dbType: 'local' | 'server'; dbKind: 'sqlite' | 'mysql' | 'postgresql' localPath?: string conn?: { host: string; port: number; user: string; password: string; db: string } vectorSource?: 'embedding' | 'bm25' vectorOptions?: { topK?: number; chunkSize?: number; overlap?: number; scoreThreshold?: number; maxRows?: number } } } export interface StageNode extends BaseNode { kind: 'start' | 'end' | 'pause'; data: { label: string } } export interface GroupNode extends BaseNode { kind: 'group' data: { label: string; collabPrompt: string; memberIds: string[]; size?: { w: number; h: number } } } export interface ProxyNode extends BaseNode { kind: 'proxy' proxySourceId: string } ``` 工作流文档(完整编排流程定义,`workflows/.json` 与服务 nodes/lines 的载体;需求文档 §4.2.2): ```ts export interface WorkflowDocument { id: string; sessionId: string; mode: 'mode1' | 'mode2' name: string; description: string nodes: GraphNode[]; lines: Line[] revision?: number // 修订版本号(乐观锁/缓存优化,修订语义由 FlowStore 管理,缺省 0) createdAt?: string // 创建时间(ISO 字符串) updatedAt?: string // 最近更新时间(ISO 字符串) } ``` 连线模型: ```ts export type Handle = 'flow-in' | 'ctx-in' | 'db-in' | 'flow-out' | 'ctx-out' | 'db-out' export type ConditionType = 'pass' | 'fail' | 'content' export interface Line { id: string; source: string; target: string sourceHandle: Handle; targetHandle: Handle condition?: { type: ConditionType; label?: string } } ``` 校验规则(validate.ts,复制旧 flow-model 并扩展):连接点兼容矩阵、无自环、无重复连线、主/虚节点互斥(同一目标同一连接点不可同时为主/虚)、协作组边界(成员节点 ctx/db 点可跨组连线;组卡片仅 flow-in/out)、启动/结束唯一、父代理唯一(模式二必须存在)、虚拟节点引用存在、条件仅流程线、阶段节点属性锁定。归一化补默认值(reactLimit 默认 null、retryLimit 默认 3 等)。入口解析改为**显式启动节点**(模式一 start、模式二 input 的右出即流程入口)。 ### 4.3 orchestrator/(运行管理器) **运行状态机**: ``` running <-> paused -> completed / failed / stopped (后端重启后磁盘上 running/paused -> interrupted,可恢复 = 触发新 run 续跑) ``` > run 级持久化状态为六态(`running/paused/completed/failed/stopped/interrupted`,与 §6.1 `RunSnapshot.status`、`types.ts` 的 `RunStatus`、`protocol.ts` 的 `RUN_STATUSES` 逐字一致)。`pending` 仅为**节点级**状态(快照内 `nodes[].status`,§6.1);run 快照创建即进入 `running`,不存在「排队/待启动」的持久化中间态(需求文档 §4.7 规则 3 断点数据字段同样不含 `pending`)。 - **运行锁**:flowId -> { runId, sessionId }(复制旧项目 flowLocks);跨会话/同会话重复运行拒绝。 - **开始**:校验 -> 锁 -> 建 run 快照(全量节点 pending + 断点字段)-> 写流程定义文件 `orchestrations/.json`(只读事实源)-> followup 注入编排指令(消息必须带 `source: { kind: 'user' }` 与 `id`——旧项目踩坑结论保留)。 - **协同执行**:父代理调用 wf_run_node 异步启动子代理;引擎仅通过 `subagent/end` 事件观察更新节点状态(ok/fail + output),不轮询子代理、不注入。 - **双向同步(画布->编排)**:`currentResolvedFlow()` 每节点执行前重读最新工作流快照(复制旧项目);**运行期画布保存(putWorkflow/putService)后经 `refreshActiveDefinitions()` 同步重写对应活跃 run 的编排事实源**(orchestrations/.json)——否则父代理(编排指令 definitionPath 指向该文件)永远读到 startRun 时的一次性快照,运行中新增节点/连线不可见(已实证缺陷);保存失败不阻断主流程(下一节调度前 currentResolvedFlow 兜底重读)。 - **暂停门**:父代理对 pause 节点调用 `wf_run_node({ nodeId: })`(无独立 wf_pause 工具,工具精简)-> run=paused、断点(resumeFromNodeId/已 ok 节点产出)持久化、保留锁;再点运行 -> 注入"断点继续指令"(已 ok 节点不重跑 + 从 resumeFromNodeId 继续)-> 新 runId(resumedFromRunId 继承链)。 - **护栏**:全局调用上限 500、单节点重试上限(attempts Map)、空闲超时(无 inflight 时默认 30 分钟)、父代理回合 error/aborted 自动终止(watchdog + `agent/error` 快速路径)、dispose 清理(abort 全部 controller + 中断 inflight)。 - **断点恢复**:重启后 reconcileStaleRuns:running/paused -> interrupted(历史面板"可恢复");恢复时已 ok 节点不重跑(其完整输出从断点快照回填,供 ctx 连线注入)。 ### 4.4 agent/(子代理管理与护栏) - **节点子代理**:`ctx.subagents.startContinuable({ provider, label, request: { prompt, parent, toolFilter, agentOptions, maxDepth }, signal })`;复用键 `sessionId:flowId:nodeId`,配置签名(rolePrompt/provider/model/工具清单/reasoning/injectSystemPrompt/injectToolSections)变化即重建(旧子代理保留历史)。角色 Prompt 不再经官方 `request.persona` 注入,而是由 prompt-setup 注册为独立系统提示词段 `visual-workflow:prompt`。 - **工具白名单**:resolveAgentTools() 解析 preset 或 combo(combo 工具 ∩ 父代理工具集),得 `toolFilter: { allow: [] }`;**无强制追加**——`wf_ask`/`wf_ask_agent`/`wf_db_query` 仅在组合白名单勾选或存在数据库连线(db-in)时进入 allow 名单(可选注入,需求 §4.4.2 规则 7);`wf_run_node`/`wf_run_node_wait`/`wf_finish` 不出现在任何子代理 allow 名单,且子代理 scope 再经 `tools.restrict` 显式隐藏(双保险)。MCP 工具以 `mcp____*` 前缀加入。 - **角色 Prompt 系统段 + 官方系统提示词/工具散文段开关**:prompt-setup.ts 经 registerContinuableSetup 把节点自定义 System Prompt 注册为独立系统提示词段 `visual-workflow:prompt`(order 1,创建时注入一次、回合间稳定,KV 缓存友好);不再整段替换/插入官方段,也不传 `request.persona`。双开关:`injectSystemPrompt`(默认 true)= 官方 `harness:identity`/人设/系统/上下文段正常注入,false=清空这些官方段;`injectToolSections`(默认 true)= 各工具 `tool:*` 散文段正常注入,false=移除 `tool:*` 散文段。**无论开关如何组合,Code Mode 协议段 `tools:sdk`/`tools:code-only` 与 `tools[]` 工具 Schema 都始终保留**(前者是 Code Mode 调用协议声明——旧实现用单数 `tool:` 前缀匹配误清复数的 `tools:*`,属操作失误已修复;后者决定工具是否可调用,与散文段注入无关——移除 `tool:*` 散文段仅去掉使用指引,不改变调用能力)。协作 Prompt 改为追加到成员首条**用户消息**(不再注入系统提示词),并始终追加成员 ID+角色名清单。 - **父代理(会话根 Agent)配置注入**:`bindParent`(prompt-setup.ts)+ `modelSelection.bindParent`(model-selection.ts)在 startRun/resumeRun 时把父代理节点的角色 Prompt(含 .md 路径读取)、`injectSystemPrompt` 与 `injectToolSections` 开关、服务商/模型/思考强度写入根 Agent 的 `ctx`(官方 `agents.get(sessionId)?.ctx` 可达)。非侵入:仅挂载到根 Agent ctx、只对本会话生效,不修改官方源码;同一 sessionId 只注册一次,后续仅更新可变状态。**父代理"模式"(preset)因会话初始化后固定,UI 锁定不可改**;模型/思考强度会话内可调。 - **思考强度/模型选择**:apply() 内注册一次 `ctx.subagents.registerContinuableSetup((childCtx) => installModelSelection(childCtx, { current: { provider, model, reasoningEffort } }))`,贡献内从节点配置取 selection(child scope 独立;V-02 定稿)。 - **ReAct 软截停护栏**(V-01):guards.ts 在 registerContinuableSetup 内安装:① `agent/pre-step` 计步,达到 reactLimit 后返回 `{ kind: 'enter', messages: [强制收尾指令] }`;② `tools.guard()`(child scope)步数超限后拒绝工具调用(原因"已达迭代上限")→ 模型被迫输出结论。节点结果标记 `react-capped`(非失败,正常产出)。 - **模式二临时子代理**:每次请求 startContinuable(节点粒度),请求结束 `drainContinuableChildren(parent, childIds)` 释放(用完即清,不跨请求复用)。 ### 4.5 tools/(wf_* 工具与数据工具) 注册方式:`ctx.tools.register(defineTool({ name, description, parameters, output: { schema, render }, execute }))`(官方 DSL,输出 schema 用支持子集)。render 统一 textRender(复制旧项目)。 | 工具 | 归属 | 语义 | |---|---|---| | wf_run_node({ nodeId, ...nodeParams }) | 父代理(模式一) | 启动节点子代理:**异步**启动,立即返回 { status: "started", childId };`nodeId` 为暂停节点时执行**暂停门**(run=paused + 断点持久化,返回 { status: "paused" });透传 thinking/reasoning、reactLimit、retryLimit 等节点参数 | | wf_run_node_wait({ nodeId, ...nodeParams }) | 父代理(模式二) | 启动节点子代理并**阻塞**等待完成,返回 { status: "ok"\|"fail", output };`nodeId` 为暂停节点时同样立即返回 { status: "paused" };透传同样的节点级参数 | | wf_finish({ status?, summary? }) | 父代理 | 幂等收尾、释放锁 | | wf_ask({ questions }) | 子代理(可选注入) | 官方提问卡(userQuestions.ask({ questions, agent: parentRoot, signal });子代理专用,父代理用官方 ask_user_question) | | wf_ask_agent({ cmd: "ask" / "reply" / "resolve", targetChildId, message?, askId? }) | 子代理/父代理(可选注入) | Agent 间通信(§5.3):ask=发起(挂起等待)、reply=回复、resolve=超时裁决(continue/resend/abort,父代理专用能力) | | wf_db_query({ dataId, mode: "search" / "query" / "schema", query?/sql?, topK? }) | 子代理/父代理(有 db-in 连线时注入) | 单工具三模式:向量检索(§6.5,本地与服务器库均在本地构建索引;不可用降级 BM25 并标注)/ 结构化只读查询(SELECT + LIMIT 白名单;超长单元格截断防护)/ 表结构只读 | **工具可见性(上下文精简)**: | 角色 | 可见工具 | 实现 | |---|---|---| | 父代理(主会话 Agent) | wf_run_node、wf_run_node_wait、wf_finish、wf_ask_agent(resolve 能力内聚)+(有数据库连线时)wf_db_query | 全局注册;子代理侧隐藏 | | 子代理 | 白名单(preset/combo 勾选)∪ 可选注入:wf_ask、wf_ask_agent(勾选)∪ wf_db_query(有 db-in 连线);wf_run_node/wf_finish 经 `tools.restrict` 永久隐藏;保留名 `run_code`(官方 Code Mode presentation transport)不入 allow 名单 | resolveAgentTools() + registerContinuableSetup 内 scope restriction | | (保留名)`run_code` | 官方保留传输名:非 native 模式官方自动注入每个 scope(子代理自带,勾选无意义) | 官方 core/tools 在 view() 能力过滤之外注入(@repo packages/core/tools/src/index.ts L1189-1191);restrict 名单禁止出现(L1085,插件组合管理可选列表剔除、resolveAgentTools 剔除双保险) | | (模式专用工具)`str_replace_editor` | 官方简单模式专用工具:仅简单模式 preset standing scope 提供(组合可选列表保留展示,描述标注「简单模式专用,非该模式禁止勾选」) | 组合管理可选列表不剔除(api.ts TOOL_ZH 描述批注);运行时 resolveAgentTools 以父代理 scope 视图兜底——简单模式未启用时该名不在父代理视图 → 不进 allow(避免官方 restrict unknown 抛错,core/tools L1088-1091);简单模式启用时父代理视图含该名 → 正常进入 | > 子代理汇报链路:依赖官方组合中的 `dsh-tool-subagent-report`(child-scoped `report` 工具 + `tool:report` 引导段,web/headless 组合均含——见 §8 索引 #19/#20);插件不重复实现,仅在编排指令与节点任务块中引导调用 report。 ### 4.6 remote/(GUI API) 复制旧项目 VisualWorkflowApi 端点白名单模式(`POST /visual-workflow/`,body { args },响应 { ok, value / error },Cache-Control: no-store),经 `ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: '/visual-workflow', handler }))` 挂载(重复注册抛错;未知端点 404;不入 SPA fallback)。 端点清单: ``` listWorkflows / getWorkflow / putWorkflow / deleteWorkflow / createWorkflow listServices / getService / putService / deleteService / serviceStart / serviceStop / serviceStatus / serviceDebug(服务调试流式代理:Host 转发服务进程 /v1/chat/completions 的 SSE,§4.1.3 服务控制台) listTemplates / putTemplate / deleteTemplate / deleteTemplatePreview(角色/文件/数据库)/ fileUpload(非文本文件上传 → data/files/ 受管拷贝,需求 §4.2.4.1 规则 2) listFlowTemplates / putFlowTemplate / deleteFlowTemplate(工作流模板:图2 交互改造,flow-templates/ 全局共享;导入 v2 bundle 亦落为模板) presets / tools / models(思考强度列表来自适配器公布的 reasoning efforts) toolCombos / toolComboPut / toolComboDelete / pluginCatalog / mcpList / mcpPut / mcpDelete / mcpToggle run / runStatus / activeRuns(会话活跃 run 列表:进入工作台自动选中运行中实例用,running/paused) / runStop / runHistory / runResume dbTest / dbSchema / dbSearchPreview exportWorkflow / importWorkflow / exportAgentTemplate / importAgentTemplate(v2 bundle) ``` > 端点契约细节: > - `runHistory` 必填 `sessionId`(与 flowId 一起),按会话过滤运行历史——跨会话 > (多租户隔离 §9)不得读取他人 run 记录。 > - `exportWorkflow` 导出的 v2 bundle `embedded` 含 roles/files/databases/groups/combos > 五类资源(§6.4);`importWorkflow` 导入时重建模板库(重名复用、id 冲突换新 id) > 仅针对 roles/files/databases 与 combos——`embedded.groups` 为协作组信息 > (id/name/collabPrompt)的携带视图,已内联在工作流节点(kind=group)中随 bundle > 往返,**不作为独立模板库重建**(store 的 TemplateKind 仅 role/file/database 三类; > 左侧栏「其他」Tab 的协作组为静态入口而非模板列表)。 > - 模式二 SSE 流式响应默认 5 分钟超时(需求 §5;`OpenAiApiDeps.sseTimeoutMs` 可配置), > 超时终止请求并停止后台编排运行;客户端断开(req close)同样停止后台运行并释放并发槽。 ### 4.7 service/(模式二服务管理器) - **启动**:serviceStart(serviceId) -> 渲染 `/services/.serve.patch.yml`(模板见 §7)-> fork `dsh --profile headless --patch <产物路径> __visual_workflow_service__`(环境继承 + cwd=数据根;PATH 无 dsh 时报明确错误)。serviceId/port **经 serve.patch.yml 的 config 域传入**;service-runner 优先解析 `cmdlineArgs` 中的 `--visual-workflow-serve --port ` 作权威覆盖,缺省回退 config——当前管理器只传占位 task 位置参数(headless 应用 commander 不识别 app 级 flag,见 §7)。 - **端口池**:从 base=7860 起向上探测空闲(port-pool.ts),绑定成功记录 service.port。 - **进程生命周期**:child_process.spawn;SIGTERM -> 5s -> SIGKILL;非主动停止的 exit 事件 -> status=crashed(UI 可重启);主进程稳定。 - **鉴权**:apiKey(配置)-> 请求校验 Authorization: Bearer;默认关闭。 - **userId->sessionId**:sessions-map.ts 持久化映射(/services/.sessions.json,原子写);请求未带 userId -> 400。 - **自动恢复**:扫描 services/*.json 中 status=running -> 重启(端口冲突重分配)。 - **服务内**:service-runner 执行 openai-api.ts(POST /v1/chat/completions SSE + GET /v1/models)-> 问题注入 input 节点 -> 按 userId 建立/恢复主会话(根 Agent)-> 服务内编排(父代理=最终回答者;子代理临时)-> 流式回传。 --- ## 5. 关键协议与时序 ### 5.1 运行启动(模式一) ``` UI 运行(bid) -> autoSave -> POST /visual-workflow/run -> 校验(启动+结束存在、锁空)-> run 快照 -> orchestrations/.json -> root.followup({ id, role: 'user', content: [编排指令文本], source: { kind: 'user' } }) -> 轮询 runStatus(600ms,前端固定;`runPollMs` 配置预留未接入):节点状态/高亮/摘要回显画布(防回环:只写视图,不写保存/撤销) ``` ### 5.2 暂停与断点续跑 ``` 父代理读到 pause 节点 -> wf_run_node({ nodeId: }) -> run.status = 'paused',断点快照持久化(resumeFromNodeId=pause 右出;已 ok 节点完整输出保留) -> 用户点运行 -> 新 run(resumedFromRunId)-> 断点继续指令注入(已 ok 节点不重跑;ctx 注入用断点产出) -> 后端重启:reconcile -> interrupted(历史面板可恢复) ``` ### 5.3 wf_ask_agent 通信(V-03 定稿) ``` A 调 wf_ask_agent(cmd:'ask', targetChildId: , message) -> 校验:运行锁 + A 为当前 run 的节点子代理;B 解析为当前 run 的节点子代理 (targetChildId 可填协作块列出的成员节点 id(推荐:成员彼此可知的稳定寻址), 也可填其运行期子代理会话 id;节点 id 经 childIndex 反查出该节点的子代理会话 id) -> pendingAsk 注册 { askId, from: A(childId), to: B(childId), message, timer=120s } -> B 在线(ctx.agents.get(B))-> agent.steer({ id, role:'user', content, source:{ kind:'coordinator', form:'relay', senderSessionId: A } }) -> B 在下一步边界收到(运行中插入,即"插队");B 回复 wf_ask_agent(cmd:'reply', targetChildId: A 的节点 id 或会话 id, askId, message) -> 解除 A 阻塞(工具结果 = 回复文本) 超时(默认 120s,可配置): -> 把超时详情(askId/请求参数/目标代理 id)steer 注入父代理 -> 父代理 ask_user_question 征询用户(继续等待/重发/终止) -> 父代理 wf_ask_agent({ cmd: 'resolve', askId, action, message? }): continue -> 重启 timer 并通知 A 继续等待(A 工具调用仍挂起) resend -> 重新 steer B abort -> A 的工具调用以超时错误返回(A 继续;节点结果含超时记录) -> 目标 B 不在线/冷态(节点已结束/闲置)-> 回退 ctx.subagents.followup(parent, B, content, { source, signal })(官方冷恢复,直接唤醒 B;无需 B 保持运行中) 权威说明:steer 为官方底层原语(与 report 投递 next-step 同款),非 subagent seam 公开操作,故插件侧强制校验(运行锁 + 表内所有权 + 会话归属)并写入审计日志。 childIndex 生命周期:仅随 run 启动/销毁登记与清理,节点结束不注销——冷态目标仍可被寻址与唤醒(协作组内「待命/已收尾成员也收得到消息」由此成立)。 寻址性能(P2-4):wf_run_node 登记时同步维护 childByNode(nodeId → childId)反向索引,节点 id 反查由 O(n) 收敛为 O(1);命中后仍按 sessionId/flowId 归属校验。 错误指引(P2-3):目标不可寻达时按情形给出可行动提示——目标为发起者自身→禁止自投;目标是流程节点但未/非本 run 启动→提示该成员可能尚未被父代理调度,请稍后重试或请父代理调度;目标不匹配任何成员→列出发起者协作块中的可用成员 id。 ``` ### 5.4 协作组并行 ``` 组卡片 flow-in 触发 -> 对 memberIds 逐个 wf_run_node(编排指令注明"协作组成员并行启动,组内用 wf_ask_agent 通信") -> 各成员并行 startContinuable(官方允许并发 distinct children) -> 组成员回合结束(stopReason=completed)落「armed/待命」非终态(P0-1:其在组内仍可被唤醒;运行收尾终态化为 ok) -> 组卡片 ok 判定 = 全部成员均已产出一轮(armed/ok/react-capped)且该组无挂起/超时 ask(P0-1 建议 2) -> 流程从组卡片 flow-out 继续(父代理判定) 待编排节点清单:仅列可执行 agent 节点(父代理即编排者本人、协作组/阶段/文件/数据库/虚拟 proxy 均不列入; proxy 镜像主节点 agent id 相同故不重复列出;协作组并行说明单独成段)。用户批注(图3)。 ``` ### 5.5 模式二请求流 ``` POST /v1/chat/completions -> 鉴权(可选)-> userId 校验(必填,缺失 400) -> userId -> sessionId(映射持久化)-> 问题 = messages 末条 user content -> 服务内:输入节点注入 question -> 父代理主会话(按 userId 恢复/创建,上下文保留) -> 父代理按工作流调度(节点子代理临时、用完即清;父代理以 wf_run_node_wait({ nodeId }) 阻塞等待,取回结果后汇总)-> 输出节点收集最终汇总 -> SSE 流式返回(打字机);stream=false 返回完整 JSON ``` --- ## 6. 数据与模型资产 ### 6.1 run 快照(runs/.json) ```ts interface RunSnapshot { id: string; flowId: string; flowName: string; sessionId: string; mode: 'mode1' | 'mode2' status: 'running' | 'paused' | 'completed' | 'failed' | 'stopped' | 'interrupted' startedAt: string; endedAt: string | null; summary: string resumedFromRunId?: string; resumeFromNodeId?: string nodes: Array<{ nodeId: string status: 'pending' | 'running' | 'armed' | 'ok' | 'fail' | 'skipped' | 'react-capped' attempts: number; startedAt: string | null; endedAt: string | null output: string; outputSummary: string; resumed?: boolean stopReason?: string turns?: Array<{ startedAt: string | null; endedAt: string | null; stopReason?: string; outputSummary: string }> }> } ``` > **节点状态(NodeRunStatus)**:`pending` 待执行 / `running` 执行中 / `armed` 待命(协作组成员回合结束但仍在组内可被 `wf_ask_agent` 唤醒,非终态;运行收尾终态化为 `ok`,P0-1)/ `ok` 成功 / `fail` 失败(异常=终止)/ `skipped` 已跳过 / `react-capped` ReAct 软截停(非失败)。展示层与内部统一(客户端仅对 agent 节点渲染状态徽标,阶段/文件/数据库/协作组不显示)。 > **endedAt** 语义为「最近一次完成时间」并随回合刷新(P0-2,不再冻结在首次完成);`turns[]` 记录可续跑节点每次被唤醒执行的回合明细;`stopReason` 记录最近终止原因(stop/interrupt/fail/react-capped/completed),供父代理区分「用户停止」与「异常失败」(P2-5)。 ### 6.2 服务(services/.json) ```ts interface ServiceState { id: string; sessionId: string; name: string; description: string revision: number; nodes: GraphNode[]; lines: Line[]; createdAt: string; updatedAt: string status: 'stopped' | 'running' | 'crashed'; port?: number; apiKeyHash?: string lastStartedAt?: string; lastStoppedAt?: string } ``` ### 6.3 模板 ```ts interface RoleTemplate { id: string; kind: 'parent' | 'agent'; name: string systemPrompt: string; provider: string; model: string; reasoning?: string presetId?: string | null; retryLimit: number; reactLimit?: number | null inputSchema?: string; outputSchema?: string systemPromptSource?: string injectSystemPrompt?: boolean; injectToolSections?: boolean promptFilePath?: string } interface FileTemplate { id: string; name: string; fileKind: 'text' | 'file'; content?: string; managedPath?: string } interface DatabaseTemplate { id: string; name: string; description: string dbType: 'local' | 'server'; dbKind: 'sqlite' | 'mysql' | 'postgresql' localPath?: string; conn?: { host: string; port: number; user: string; password: string; db: string } vectorSource?: 'embedding' | 'bm25' } // 图2 交互改造新增:工作流模板(flow-templates/.json,全局共享、不按会话隔离; // 与 WorkflowDocument 同构但无 sessionId;拖入画布「创建实例」后转为当前会话实例) interface WorkflowTemplate { id: string; mode: 'mode1' | 'mode2'; name: string; description: string nodes: GraphNode[]; lines: Line[]; revision?: number; createdAt?: string; updatedAt?: string } interface ToolCombo { id: `combo-${string}`; name: string; tools: string[]; mcpServers: string[] } ``` ### 6.4 导入导出 v2 bundle ```ts interface BundleV2 { format: 'dsh-vw-bundle'; version: 2; mode: 'mode1' | 'mode2' workflow?: { name: string; description: string; nodes: GraphNode[]; lines: Line[] } service?: { name: string; description: string; nodes: GraphNode[]; lines: Line[] } embedded: { roles?: RoleTemplate[]; files?: FileTemplate[]; databases?: DatabaseTemplate[]; groups?: GroupTemplate[]; combos?: ToolCombo[] } } ``` > 导入重建范围:roles/files/databases(store 模板库)与 combos(工具组合)导入时 > 解耦重建(重名复用、id 冲突换新 id);`groups` 仅随工作流节点(kind=group) > 内联携带、不重建为独立模板库(TemplateKind 无 group 类;client 无协作组模板列表)。 > 导入返回值 `importedTemplates` 记录前三类实际导入数量。 > **图2 交互改造(导入语义变更)**:`importWorkflow` 导入的 bundle **一律落为工作流模板** > (`store.saveFlowTemplate`,不再直接创建 workflows/services 实例);用户需在画布中 > 「创建实例」后才能运行。重名冲突按模板库名称判定(rename/overwrite 语义不变)。 ### 6.5 本地嵌入模型与向量索引(Q-E 定稿) - **资产**:`assets/models/bge-small-zh-v1.5/`——tokenizer.json / config.json / special_tokens_map.json / 1_Pooling/config.json 直接复制自本地 `D:\AiCoding-Gzx\models\backend\models\bge-small-zh-v1.5`;**推理权重需 ONNX 格式**(本地为 PyTorch safetensors,91MB):`scripts/embedding-model.mjs` 负责产出 `model_quantized.onnx`(约 25MB)——优先本机 optimum 导出(需 python optimum 环境),否则从官方 BAAI/bge-small-zh-v1.5 仓库 onnx/ 目录获取后落入 assets(R-03 实现时验证)。 - **运行时**:@huggingface/transformers(node)加载 tokenizer + onnx 模型,feature-extraction(pooling: 'mean', normalize: true)产出 512 维句向量(CPU)——**唯一第三方运行时依赖**。 - **索引**:`/data/vector/.json`(分块默认 384 字符/步长 128;每块 { text, vector[], source };增量重建;原子写+锁)。查询 = 归一化内积 Top-K。 - **降级**:模型资产缺失/加载失败,或配置 embeddingEndpoint(外部 OpenAI 兼容 /embeddings)时:外部端点优先;否则 BM25(token 倒排,UI 标注"相似度检索(非语义)")。vectorSource 字段记录实际模式。 - **服务器数据库**:同样在本地构建向量/BM25 索引(复用同一索引基础设施,索引文件按 dataId 落盘;读取时按 dbKind 正确引用标识符——MySQL 反引号、SQLite/PostgreSQL 双引号;索引文本构建时跳过向量/嵌入列),叠加结构化只读查询工具(SELECT 白名单)。 - **检索高级选项(node.data.vectorOptions,UI「高级选项」区可调,均有默认值)**:召回条数 `topK`(默认 5,search 内夹到 [1,50])、分块窗口 `chunkSize`(默认 384)/重叠 `overlap`(默认 128,须小于 chunkSize)、相似度阈值 `scoreThreshold`(默认 0,仅保留余弦/BM25 得分高于此值的命中)、索引容量 `maxRows`(默认 10000,防超大库打爆内存)。 - **命中回传 `rowKey`**:索引构建时按列名识别主键(`detectKeyIndex`:优先 id/pk/uuid/key/编号/code,其次含 id/key/code/no 的列名),随记录写入分块;检索命中携带 `rowKey`,供把命中映射回整行做精确回表。 --- ## 7. 模式二 serve 层(serve.patch.yml 模板) ```yaml # 渲染产物:/services/.serve.patch.yml - id: headless-runner disabled: true - insert: - id: visual-workflow-service name: dsh-visual-workflow/service-runner inject: [cmdlineArgs] config: serviceId: dataDir: port: apiKey: <可选> maxConcurrent: 50 ``` fork 命令:`dsh --profile headless --patch <产物路径> __visual_workflow_service__`。占位 task 位置参数仅满足 headless-startup 的非空校验(headless-runner 已被 serve patch disabled,不会执行该任务);serviceId/port 经上方 patch 的 `config` 域(serviceId/dataDir/port/apiKey/maxConcurrent)传入服务进程。service-runner 的 `parseServiceArgs` 支持从 `ctx.cmdlineArgs` 读取 `--visual-workflow-serve --port ` 作为权威覆盖源(官方 app 自持参数族模式),但当前服务管理器**不传**这两个显式 flag。服务进程天然不含 Web/HMR 组件;退出码与错误经 stdout/stderr 及 ctx.appExit 协调。 --- ## 8. 官方源码引用索引(核心功能 -> 官方源码位置) > @repo = D:\AiCoding-Gzx\HarnessPlugin\deepseek-harness-dsh-v0.1.1-rc.2 | # | 核心功能 | 官方源码位置(@repo 相对路径) | |---|---|---| | 1 | 子代理服务 API(start/startContinuable/followup/interrupt/reportFrom/listChildren/registerContinuableSetup) | packages/subagent/subagent/README.md L9-29;src/index.ts L203-290;src/types.ts L100-149;src/continuation.ts L112-155 | | 2 | 插队原语(Agent.send/steer/inject/followup + next-step 消费语义) | packages/core/agent/src/runtime-types.ts L106-143;packages/core/agent-loop/README.md L58;packages/core/agent/README.md L67-71 | | 3 | 子代理跟随消息 FIFO 约束(不可中断当前回合) | packages/subagent/subagent/README.md L76、L150 | | 4 | 思考强度(ModelSelection/installModelSelection/reasoningEffort、llm call-config) | packages/core/agent/src/model-selection.ts L10-75;packages/llm/llm/README.md L27-51、L66;packages/llm/llm-deepseek/README.md L19-20、L69-71;packages/core/agent-default-model/src/index.ts L24-57 | | 5 | ReAct 护栏扩展点(agent/pre-step 可替换 messages、agent/turn-stopping、无内置 turn 预算) | packages/core/agent/src/runtime-types.ts L52-56、L221-231、L278;packages/core/agent-loop/README.md L129-134;packages/guard/README.md L9-12 | | 6 | 子代理请求字段(persona/toolFilter/agentOptions/maxDepth/outputSchema 语义) | packages/subagent/subagent/src/types.ts L86-149;README.md L29 | | 7 | 子代理组合与 setup 注册(registerContinuableSetup 时序/贡献签名) | packages/subagent/subagent/src/activation-setup-registry.ts L26;src/index.ts L286-290;README.md L23、L48 | | 8 | 子代理 report 工具与汇报链路(组合内提供) | packages/subagent/tool-subagent-report/README.md L5-13 | | 9 | 用户提问(userQuestions.ask 契约/错误码/agent 身份限制) | packages/interaction/user-questions/README.md L7-25 | | 10 | 工具注册(register/defineTool/DSL/schemas(scope)/restrict/guard/output render) | packages/core/tools/README.md L18-27、L63-101 | | 11 | HTTP 路由(webServer.register exact/prefix/fallback、匹配序、host 边界) | packages/host/webserver/README.md L5-9 | | 12 | preset 服务(list/resolve/standingKeyFor/composeFrom/authorable) | packages/preset/agent-presets/README.md L13-25 | | 13 | MCP 官方客户端(工具命名 mcp__server__tool、一服务器一插件行、重连语义) | packages/mcp/mcp-client/README.md L4-51 | | 14 | 会话持久化(jsonl 布局/崩溃恢复/单写者) | packages/session/session-persistence-jsonl/README.md L5、L40-48、L76 | | 15 | 无头运行(one-shot runner 限制、组合内容) | packages/bundle/headless/README.md L1-20;cordis.patch.yml 全文 | | 16 | 应用命令行(cmdlineArgs/appExit/parseCmdline) | packages/boot/cmdline/README.md L7-29 | | 17 | 客户端 slot 挂载(conversation.view list seat 与 owner props) | packages/client/ui-conversation/src/client/contract/slots.ts L106-113 | | 18 | client bundle 构建(tsdown 共享助手、__ModuleLoader__、CSS 注入/纯度门) | packages/client/tsdown.client.ts L1-9;packages/client/AGENTS.md L136;各 tsdown.config.ts | | 19 | web/base 组合组成(storage-json、user-questions、tool-subagent-report/control、subagent providers、llm-deepseek、persistence) | packages/bundle/web-app/cordis.patch.yml L51-60;packages/bundle/base/cordis.patch.yml L63-64、L98-99、L292-333、L436-451 | | 20 | Agent 注册表(agents.get/isOwnedBy/list/roots、AgentStatus、inbox 投影) | packages/core/agent/README.md L9-25;src/runtime-types.ts L43-77 | | 21 | 子代理生命周期事件(subagent/start、subagent/end 观察语义,供节点状态回写) | packages/subagent/subagent/src/index.ts L134-167;README.md L90-98 | | 22 | 会话回合事件(turn/end、agent/turn-stopping 用于护栏与空闲判定) | packages/core/agent/src/runtime-types.ts L217-290 | --- ## 9. 安全、权限与边界 1. **API 暴露面**:GUI API 仅回环(webServer 默认 127.0.0.1);模式二服务端口默认绑定 127.0.0.1,可配置 0.0.0.0(文档警示);apiKey 可选开启。 2. **多租户隔离**:userId->sessionId 映射为唯一会话分配通道;映射持久化于服务实例内;不同 userId 的会话与对话日志完全隔离。 3. **数据库工具**:仅只读 SELECT(强制 LIMIT、拒绝写/DDL/多语句)、连接信息不出示;本地 SQLite 只读访问;向量索引文件仅本机。 4. **文件节点**:非文本仅注入受管路径(data/files/),代理经官方读取工具访问。 5. **命令注入**:fork 命令参数化(serviceId/port 消毒正则),patch 文件由模板生成。 6. **插件卸载**:ctx.on('dispose') 中止全部运行、中断 in-flight 子代理、停止看护、尽力停止服务进程。 --- ## 10. Client 半区设计(由旧项目迁移;P11 起入口改版) - **入口(P11 改版,Q-UI-01)**:主界面右下角**圆形浮窗按钮**(FAB,body 常驻容器 `#visual-workflow-float-host`,与视图环激活态解耦)→ 点击展开**独立窗口型页面**(floating-window.tsx:标题栏拖动 + 八方向缩放(最小 480×320)+ 几何 localStorage 记忆);窗口内渲染 Studio 完整工作台。样式经 ctx.effect 注入 style[data-plugin];i18n 经官方 locale 服务注册命名空间 `visualWorkflow`(无 locale 服务时按浏览器语言回退)。 - **studio.js 拆分**:Studio.tsx(布局)+ studio-state.ts(useReducer 状态机,纯函数可测:列表/画布投影/选中/撤销重做/运行/面板几何)+ hooks/ 目录 13 个 hooks(职责单一):useStudioState / useRemote / useToast / useWorkflows / useTemplates / useSelection / useGraphHistory / useUnsavedGuard / useRunControl / useRunPolling / useServiceControl / useModeSwitch / usePanelLayout。 - **画布**:graph-canvas/graph-model 复制改造(SVG、无限画布、网格、连线、条件标签、虚拟节点虚线边框 + 引用角标、运行高亮)。 - **面板**:left-panel(Tabs:工作流/角色/数据/其他)、inspector(所见即所操作)、toolbar(控制栏)、confirm-dialog、run-history(含断点恢复入口)、service-console(模式二状态/调试框)、combo-manager(复制)。 - **样式**:styles.ts 复制 + 新增 CSS 变量(--wf-flow/--wf-context/--wf-database/--wf-pass/--wf-fail/--wf-content,深色/浅色自适应)。 - **i18n**:i18n.js 复制扩展(新增键:模式/服务/断点/协作组/浮窗等)。 - **lib**:remote.ts(同源 fetch /visual-workflow/*,端点名直接引用共享协议常量表 EP_* 零漂移)、files.ts、graph-model.ts、bundle.ts(v2 导入导出)。 --- ## 11. 测试与验证矩阵 | 层 | 内容 | |---|---| | host 单元 | storage 原子性/锁/并发;graph 校验矩阵;run 状态机(paused/interrupted/resume);护栏计数;wf 工具 schema/render;端口池;userId 映射;embedding 分块/相似度(纯函数) | | client 单元(jsdom) | Studio 挂载/slot 注册/会话隔离/dispose 清理;run 轮询回显防回环;撤销/重做;虚拟节点渲染;协作组拉伸;未保存守卫 | | integration | 真实组合启动(Loader/patch):startRun -> wf_run_node -> subagent/end 回写;暂停/续跑;wf_ask_agent(steer 注入断言);服务 fork 启动/请求/停止/崩溃 | | 从零安装 | 临时 DSH_HOME + scratch profile add + --dump-config 校验插件层;Git 分发(git+file://)验证 | | GUI | 独立 web profile 浏览器验证:模式切换、拖拽连线、运行高亮、恢复入口、服务控制台、宽窄屏 | --- ## 12. 风险与实现时验证项 | 编号 | 风险/待验证 | 应对 | |---|---|---| | R-01 | profile 级 cordis.patch.yml 行写入(MCP 服务器管理)后 Loader 热更新/HMR 入口 | 组合管理保存后提示"需重启生效"或调用官方 loader reload(实现时验证 extensions 包 API);MCP 行语义完全对齐 mcp-client(#13) | | R-02 | dsh --profile headless --patch 的组合覆盖语义(headless-runner disabled、cmdline 参数透传) | 采用"覆盖行 + insert 行"模板;集成测试覆盖;必要时构建独立 bundle 层(不手写 profile) | | R-03 | 嵌入模型 ONNX 产物来源(本机转换/官方 onnx 产物)与 @huggingface/transformers 版本兼容 | 脚本化 + 失败降级 BM25;发布 CI 断言资产存在 | | R-04 | Agent.steer 非 subagent seam 公开入口(权威校验) | 插件侧严格校验 + 审计日志;集成测试覆盖越权拒绝 | | R-05 | 服务进程内 maxConcurrent 并发下模型/工具资源 | 每请求独立子代理;并发上限返回 429;部署建议容器化 | --- ## 13. 编码与提示词工程规范(横切,所有任务必须遵守) ### 13.1 提示词与上下文注入(缓存命中 + 注意力机制) 1. **前缀稳定(KV 缓存友好)**:任何请求内、请求之间保持**前缀字节稳定**——系统提示固定前置、工具 schema 顺序稳定(注册顺序即渲染顺序)、对话历史追加式增长;**禁止**在历史前中段插入/重排内容(会整体失效缓存);不稳定内容(时间戳、运行状态、临时标记、轮询序号)一律放**末尾**或单独尾部段落。 2. **注意力位置**:遵守 lost-in-the-middle 处置——**最重要的约束**(权限边界、调用协议、失败语义、硬性规则)同时出现在①任务文本**开头**(首段)与②系统提示**最末**(重申),中间段只放过程性信息;长文本(文档/上游产出)置于任务主体中部之后,关键结论由代理用 report 摘要回传。 3. **稳定段落化**:自研提示词模板(编排指令、节点任务块、协作 Prompt、wf_* 工具 description)集中定义在 src/host/prompts/ 与配置模板中,**同一 run 内不再变化**;运行态动态信息(节点状态、本次 step 的产出)以变量注入尾部,避免模板字符串每步重排。 4. **协作 Prompt 追加位置**:协作信息**不注入系统提示词**,改为追加到组内成员**首条用户消息(任务块)末尾**;且无论用户文本是否为空,都默认列出组内全部成员的 ID + 角色名(告知协作对象与可发消息对象),再追加用户自定义协作说明。 5. **工具 schema 与可见性**:工具可见性按 §4.5 规则控制(子代理隐藏 wf_run_node/wf_run_node_wait/wf_finish、可选注入 wf_ask/wf_ask_agent、按连线注入 wf_db_query),未使用工具不进入请求。 ### 13.2 工具提示词标准(官方一致写法) - **description 使用官方标准英文**(参照官方工具风格,见 §8 索引 #10 工具目录与 packages/fs/tool-fs 等):第一句明确"何时调用"(触发条件),随后给出必要前置条件、失败语义(超时/拒绝/护栏错误)与副作用(阻塞/插队/持久化影响);用词精炼,无客户化口吻;单条 description 目标 ≤ 120 tokens,参数 description 同样英文且短句。 - 参数命名与官方约定一致(小写 snake / 官方既有命名);值域与枚举在参数 description 内联,避免额外说明段。 - **输出 render 稳定**:textRender 输出固定 schema(键序稳定、截断一致),不随运行状态改变,保证工具结果展示与 KV cache 前缀复用。 ### 13.3 注释、文档与语言 - **代码注释、JSDoc、README、AGENTS.md 与全部文档使用中文**(代码标识符、工具 description、面向模型的提示词按 13.2 使用英文)。 - 关键时序/协议处注释必须说明"为什么"(缓存/注意力/权威校验理由),不只叙述"做了什么"。