# 后台子代理(主代理异步并行委派)技术方案 > 文档状态:已达成共识,待实施 > 日期:2026-08-16 > 适用范围:`backend-ts/`、`desktop/`(共用 UI) > 关联能力:现有 `delegate` / `delegate_followup`(同步子代理)、`SIDE_TASK`(用户手动边路任务)、`subagent_execution` 崩溃恢复/结果交付体系 --- ## 1. 需求背景 ### 1.1 问题 当前主代理触发的子代理是**同步阻塞**的:主代理一旦调用 `delegate` 或 `delegate_followup`,就必须原地等待子代理执行完成,拿到结果后才能继续下一步。这带来两个体验问题: 1. **无法并行**:主代理不能把若干相对独立的分支工作派发出去后继续推进主线,只能串行等待,整体任务耗时长。 2. **主线被阻塞**:子代理运行期间主代理完全空转,无法利用这段时间做主线工作。 项目中已有的 `SIDE_TASK`(边路任务)虽然具备「后台并行、可查看进度、可取消」的能力,但只能由**用户在前端手动创建**,主代理无法主动调用,因此不能直接满足「主代理主动派发后台工作」的需求。 ### 1.2 目标 让主代理能够**主动发起后台子代理**,实现: - 主代理派发子代理后**立即返回**,继续执行主线工作; - 主代理可**主动查看**后台子代理的执行进度; - 后台子代理**完成时主动汇报结果**(无需主代理轮询); - 当还存在后台子代理未结束时,主代理**不能完结**,需挂起等待全部后台子代理结束; - 主代理可**主动取消**后台子代理; - 复用现有 SUBAGENT 会话、`subagent_execution`、崩溃恢复、结果交付与前端子代理 Tab 基础设施。 ### 1.3 边界说明(要做 / 不做) | 要做的 | 不做的 | |---|---| | 新增「后台子代理」工具族:`spawn_subagent` / `check_subagent` / `cancel_subagent` / `wait_subagents` | 不改变现有 `delegate` / `delegate_followup` 的同步语义 | | 后台子代理复用 `SUBAGENT` 会话类型与 `subagent_execution` 记录,`invocation_type` 新增 `BACKGROUND` | 不复用 `SIDE_TASK` 作为主代理后台子代理的承载 | | 主代理派发后立即拿到 `task_id`(执行记录 id)与 `child_session_id`,继续主线 | 不在 `spawn_subagent` 内同步等待子代理完成 | | 主循环结束时若仍有运行中后台子代理,自动挂起(进入 `WAITING_SUBAGENTS` 相位),全部结束后自动续跑 | 不在主循环内真正阻塞持有 WebSocket 直到子代理结束 | | 子代理完成时,把结果摘要注入主代理上下文,并在父会话追加可见的「后台子代理完成」卡片 | 不把完成结果构造成第二条同 `tool_call_id` 的 TOOL 消息(与 OpenAI 协议冲突) | | 主代理可 `check_subagent(task_id)` 查单个、`check_subagent()` 列全部 | 不在 `check` 返回子代理完整消息历史 | | 主代理可 `cancel_subagent(task_id)` 取消单个;父会话被取消时级联取消全部后台子代理 | 不新增用户在前端手动取消后台子代理的按钮 | | 后台子代理继承父会话执行模式,CLOUD 与 LOCAL 都支持 | 不做「LOCAL 不支持、自动退化为同步」的降级 | | 后台子代理的并发数与运行时长不设硬上限、不设超时 | 不因并发或超时自动中断后台子代理 | | 后台子代理禁止再派生自己的后台子代理(父子关系只有一层) | 不实现多层级嵌套后台子代理与树状等待 | | 后台子代理接入现有 `subagent_execution` 崩溃恢复体系 | 不做独立于现有恢复体系的新恢复机制 | | 后台子代理产生的文件变更归集到父任务变更清单 | 不做「只在子代理 Tab 展示、不归集到父任务」 | | 前端复用现有子代理 Tab 只读展示后台子代理状态,主会话显示完成卡片 | 不新增用户取消按钮、不新增后台子代理专属管理面板 | --- ## 2. 需求描述 ### 2.1 用户故事 > 作为主代理,我在处理一个开发任务时,希望把「调研方案 A」「代码审查模块 B」这类相对独立的分支工作派发给后台子代理并行执行,自己继续推进主线。中途我能查看它们跑到哪了,也能在必要时取消某个。当我主线工作做完时,如果还有子代理在跑,我会先挂起等待;等它们全部汇报完成后,我再综合所有结果给出最终结论。 ### 2.2 核心交互流程 ```text 主代理 loop 运行中 │ ├─ spawn_subagent(agent_type, task) │ └─ 后台创建 SUBAGENT 子会话 + subagent_execution(BACKGROUND) │ └─ 立即返回 { task_id, child_session_id, status: RUNNING } │ └─ 主代理继续主线 │ ├─ check_subagent(task_id?) ← 主动查看进度(单查 / 列全部) │ ├─ cancel_subagent(task_id) ← 主动取消单个后台子代理 │ ├─ wait_subagents() ← 主动等待全部后台子代理结束,返回汇总结果 │ ├─ (子代理完成)→ 完成通知写入父会话 + 注入主循环上下文(主动汇报) │ └─ 主循环准备结束 ├─ 无运行中后台子代理 → 正常输出最终答案,完结 └─ 仍有运行中后台子代理 → 挂起(WAITING_SUBAGENTS),全部结束后自动续跑 ``` ### 2.3 四个工具 | 工具 | 入参 | 返回 | 语义 | |---|---|---|---| | `spawn_subagent` | `agent_type`、`task` | `{ task_id, child_session_id, status: "RUNNING" }` | 创建后台子代理并立即返回 | | `check_subagent` | `task_id?` | 单个:进度快照;缺省:当前会话全部后台子代理列表 | 主动查看进度 | | `cancel_subagent` | `task_id` | `{ cancelled: true, task_id }` | 取消指定后台子代理 | | `wait_subagents` | 无 | 全部后台子代理的汇总结果 | 阻塞等待全部结束 | `check_subagent` 单个进度快照字段:`status`(RUNNING/COMPLETED/FAILED/CANCELLED)、`total_rounds`、`total_tool_calls`、`total_prompt_tokens` / `total_completion_tokens`、最近一条 assistant 输出摘要(截断到可控长度,例如 2000 字符)。 ### 2.4 状态与相位 - 后台子代理复用 `SUBAGENT` 会话,其 `phase` 状态机沿用现有 `RUNNING` → `COMPLETED` / `FAILED` / `CANCELLED`,崩溃恢复时使用 `RESUMING`。 - 父会话新增非终态相位 `WAITING_SUBAGENTS`,表示「主线已暂停、正在等待后台子代理结束」。对外事件仍保持前端可识别的既有映射(见第 8 节)。 --- ## 3. 现状分析 ### 3.1 同步 delegate 的执行路径 - `DelegateTool`(`backend-ts/src/harness/tool/impl/delegate-tool.ts`)在 `executeWithSession` 中创建子会话与执行记录后,直接 `await visibilityService.executeVisible(...)` 同步等待子代理跑完,再返回结果。 - `DelegateFollowupTool` 同理,且校验 `childSession.phase === 'RUNNING'` 时拒绝追问。 ### 3.2 边路任务(SIDE_TASK)的异步执行路径 - `StreamingWsHandler.handleCreateSideSession`(`backend-ts/src/session/ws/streaming-ws-handler.ts`)由用户 WS 消息触发,创建 `SIDE_TASK` 会话后,通过 `agentExecutor.submit(...)` 在后台执行 `executeSideFirstMessage`。 - 已有 `cancel_side_task`、`runningTasks` / `runningExecutionIds` / `executionClaims` / `cancelFlags` 等后台任务管理能力,可被新能力参考复用,但 SIDE_TASK 与 SUBAGENT 是两个独立 session 类型,不能直接互用。 ### 3.3 崩溃恢复与结果交付 - `subagent_execution` 表(V040/V075)已具备 `invocation_type`(`DELEGATE`/`FOLLOWUP`)、`parent_tool_call_id`、`delivery_status`、`parent_assistant_message_id` / `parent_tool_message_id`、`execution_start_message_id`、`final_message_id`、`total_tool_calls` 等字段。 - `SubagentRecoveryCoordinator` + `SubagentExecutionRecoveryService` + `SubagentResultDeliveryService` 已实现「父任务等待全部关联子代理交付后再恢复」的屏障。 - 现有 `SubagentResultDeliveryService.deliver()` 通过「重建 assistant `tool_calls` + TOOL」把同步 delegate 的结果补入父历史,**不适用于后台子代理**(后台 spawn 的 TOOL 结果已同步返回,完成结果需要以通知形式注入)。 ### 3.4 可复用点 - `SubAgentVisibilityService`:`notifySubagentCreated`(发 `subagent_session_created`,前端打开子代理 Tab)、`executeVisible` / `executeVisibleWithTimeout`、`finishSubagent`。 - `AgentLoop`:每轮开始已调用 `backgroundTaskManager.consumeCompletedResults()` 注入后台任务结果;循环结束点可插入「后台子代理挂起」判断。 - `AbortSubagentChildren`(`StreamingWsHandler.abortSubagentChildren`):父会话取消时级联取消 SUBAGENT 子会话。 - `agentExecutor`:`createAgentExecutor` 提供的受控异步执行器,用于真正并行跑后台子代理。 --- ## 4. 技术选型 ### 4.1 总体方案 新增一套「后台子代理」工具族,复用 `SUBAGENT` 会话 + `subagent_execution` + 现有恢复/交付体系;在 `AgentLoop` 结束点增加自动挂起,在子代理完成时以「完成通知」注入父会话并触发续跑。现有 `delegate` / `delegate_followup` 保持同步语义不变。 ### 4.2 关键设计决策 | 决策项 | 结论 | |---|---| | 落地形态 | 新增 `spawn_subagent` / `check_subagent` / `cancel_subagent` / `wait_subagents` 工具族 | | 会话承载 | 复用 `session_type = 'SUBAGENT'`,执行记录 `invocation_type = 'BACKGROUND'` | | 挂起语义 | 主循环结束点自动拦截 + 显式 `wait_subagents` 工具 | | 挂起模型 | 暂停本轮(父会话 `WAITING_SUBAGENTS`)+ 全部完成后自动续跑(复用恢复的 resume 机制) | | 结果汇报 | 完成结果注入主代理上下文 + 父会话追加可见「后台子代理完成」卡片 | | 取消 | `cancel_subagent(task_id)` 单个取消 + 结果回传 + 父会话取消级联 | | 前端 | 复用子代理 Tab 只读展示 + 主会话完成卡片;不新增用户取消按钮 | | 执行模式 | CLOUD 与 LOCAL 都支持,继承父会话执行模式 | | 并发 / 超时 | 不设硬上限、不设超时(由主代理自行控制) | | 嵌套 | 禁止后台子代理再派生后台子代理 | | 崩溃恢复 | 集成现有 `subagent_execution` 恢复体系 | | 文件归集 | 后台子代理文件变更归集到父任务变更清单 | | 进度粒度 | 状态 + 轮次 + 用量 + 最近输出摘要 | ### 4.3 数据模型 复用 `subagent_execution`,`invocation_type` 扩展为 `DELEGATE` / `FOLLOWUP` / `BACKGROUND`(`VARCHAR(20)`,无需结构变更,仅更新注释语义)。`parent_tool_call_id` 记录 `spawn_subagent` 的工具调用 id(用于关联与幂等,但不用于重建第二条 TOOL 结果)。`delivery_status` 对 `BACKGROUND` 表示「完成通知是否已交付父会话」。 ### 4.4 完成通知的交付形式 后台子代理完成时,不在父会话重建 assistant + TOOL 对,而是: 1. 向父会话持久化一条 `ASSISTANT` 消息,`toolCalls` 为空,`metadata` 携带 `backgroundSubagentCompletion`(含 `child_session_id`、`execution_id`、`status`、结果摘要),`content` 为可读摘要文本。 2. 将该完成结果投递到父会话的「待消费后台结果」队列,供主循环下一轮开始注入上下文(主动汇报)。 3. 若父会话正处于 `WAITING_SUBAGENTS`,则同时触发父会话续跑(见第 5.4 节)。 --- ## 5. 核心设计 ### 5.1 后台子代理管理器 `BackgroundSubagentManager` 新增服务,持有: - `pendingByParent: Map>`:每个父会话尚未终态的后台执行; - `resultsByParent: Map>`:已完成、待主循环消费的结果; - `resumeCallbacks: Map Promise>`:父会话续跑入口(由调用方注入)。 职责: - `spawn(parentSession, definition, task, toolCallId)`:事务创建 SUBAGENT 子会话 + `subagent_execution(BACKGROUND)` + 初始 USER,注册 cancel flag,提交 `agentExecutor` 异步执行; - `onCompleted(executionId, status, resultJson)`:终态落库后,写完成通知、投递结果、必要时触发续跑; - `consumeResults(parentSessionId)`:供 AgentLoop 每轮开始消费; - `hasRunning(parentSessionId)`:供 AgentLoop 结束点判断是否挂起; - `cancel(executionId)`、`list(parentSessionId)`、`snapshot(executionId)`。 ### 5.2 `spawn_subagent` 异步执行 1. 校验父会话存在、`agent_type` 合法; 2. 取 `ToolCallContext.getToolCallId()` 作为 `parent_tool_call_id`; 3. 事务创建 child session(`SUBAGENT`)与 execution(`invocation_type=BACKGROUND`、`delivery_status=PENDING`、`status=RUNNING`); 4. 继承父会话 `executionMode` / `workspace` / `permissionLevel` / `modelId` / `isGit` / `platform` / `shellPath` / `osVersion` / `projectKey`; 5. LOCAL 模式下向 `LocalToolSessionRegistry` 注册 child → user 映射; 6. 通过 `SubAgentVisibilityService.notifySubagentCreated` 发 `subagent_session_created`(前端打开只读子代理 Tab); 7. 复用 `DelegateTool.buildSubContext` 构建子代理上下文(该函数需扩展:同时排除新后台工具族,防止嵌套); 8. 注册 child cancel flag,父 cancel 与 child cancel 联动(父取消 → 子取消); 9. 提交 `agentExecutor.submit(...)` 后台执行 `visibilityService.executeVisible`; 10. 执行完成后按终态调用 `onCompleted`; 11. 工具立即返回 `{ task_id: executionId, child_session_id, status: "RUNNING" }`。 ### 5.3 `check_subagent` / `cancel_subagent` / `wait_subagents` - `check_subagent(task_id?)`: - 有 `task_id`:从 `subagent_execution` 读状态、轮次、用量,并取最近一条 assistant 输出做截断摘要; - 无 `task_id`:列出当前父会话全部 `BACKGROUND` execution 的状态。 - `cancel_subagent(task_id)`:校验 execution 属于当前父会话且非终态,置子会话 cancel flag,等待其收尾为 `CANCELLED`,回传 `{ cancelled: true }`。 - `wait_subagents()`:阻塞等待当前父会话全部 `BACKGROUND` execution 进入终态,返回汇总结果;等待期间监听父 cancel flag,父取消时提前返回。因用户确认不设超时,该工具不设超时。 ### 5.4 主循环挂起与续跑 在 `AgentLoop.execute` 的结束点(`pendingToolCalls` 为空、准备 `break`)之前插入判断: 1. 若 `backgroundSubagentManager.hasRunning(sessionId)` 为 false,正常 `break` 并 `onMessageEnd`; 2. 若存在运行中后台子代理: - 不进入终态,调用一个 suspend 回调:父会话 `phase = 'WAITING_SUBAGENTS'`,发送 `session_status`(对外映射为 `RUNNING`,见第 8 节); - 直接 `return`(结束本轮,不 emit 终态 `onMessageEnd`); - 等待所有后台子代理完成时,由 `onCompleted` 触发父会话续跑(复用 `HarnessService.execute` 重建上下文并再次进入 AgentLoop)。 续跑时,父会话历史中已包含所有「后台子代理完成」卡片,模型据此产出最终综合结论。 ### 5.5 完成结果注入主循环上下文 `AgentLoop` 每轮开始时,除现有 `backgroundTaskManager.consumeCompletedResults` 外,追加: ```text backgroundSubagentManager.consumeResults(sessionId) → 把已完成后台子代理的结果 JSON 拼成系统消息注入 context ``` 这保证主代理在**未结束**时也能在下一轮「主动感知」到已完成的子代理,无需轮询。 ### 5.6 崩溃恢复集成 - `SubagentExecutionRecoveryService.recover()` 已能恢复 `RUNNING` / `RECOVERING` 且 `delivery_status=PENDING` 的执行记录;`BACKGROUND` execution 自然进入该扫描,恢复时继续复用 `buildSubContext` 并 `executeVisibleWithTimeout`(无超时决策下,恢复执行不再受 3600s 限制,仍以无超时处理)。 - `SubagentResultDeliveryService.deliver()` 增加 `invocation_type === 'BACKGROUND'` 分支:不重建 assistant + TOOL 对,改为写入「完成通知」卡片并投递结果;`SUPPRESSED` 逻辑复用(父会话已终态则不注入)。 - `SubagentRecoveryCoordinator` 的「父任务等待全部关联子代理交付后再恢复」屏障对 `BACKGROUND` 同样成立:父会话 `WAITING_SUBAGENTS` 或 `RUNNING` 时,全部后台子代理收敛并交付后恢复父会话。 ### 5.7 文件变更归集 后台子代理产生的 `message_file_change` 归属其 child session(与现有子代理一致)。归集到父任务时,按「父会话 id + 关联 `BACKGROUND` execution 的 `execution_start_message_id` / `final_message_id`」聚合,追加到主任务变更清单;不绑定到某一条父工具调用(因为完成时机异步)。复用现有 `2026-08-13-subagent-file-change-aggregation-fix-plan.md` 的归集服务,扩展支持 `BACKGROUND` invocation。 ### 5.8 父会话取消级联 父会话取消时,`abortSubagentChildren` 已级联取消 SUBAGENT 子会话;`BackgroundSubagentManager` 需将未终态的 `BACKGROUND` execution 收尾为 `CANCELLED`,`delivery_status` 置 `SUPPRESSED`(不注入父历史)。 --- ## 6. 数据库设计 无需结构变更(复用 V040/V075 已建字段)。仅需: 1. 在 `subagent_execution.invocation_type` 的注释/文档中补充 `BACKGROUND` 取值说明(`VARCHAR(20)` 已容纳); 2. `session.phase` 新增 `WAITING_SUBAGENTS` 取值(`VARCHAR(20)` 已容纳); 3. 若后续需要,新增一条迁移更新注释,不改列类型。 若需对「后台完成通知」做前端查询优化,可考虑对 `subagent_execution(invocation_type, delivery_status)` 增加索引,但当前量级下非必须(不做)。 --- ## 7. 实现步骤 ### 阶段一:领域模型与后台管理器 1. 扩展 `SubagentExecution.invocationType` 类型为 `'DELEGATE' | 'FOLLOWUP' | 'BACKGROUND'`; 2. 新增 `BackgroundSubagentManager` 服务(spawn / onCompleted / consumeResults / hasRunning / cancel / list / snapshot); 3. 在 `create-app.ts` 装配该服务,注入 `db`、`subagentExecutionMapper`、`sessionMapper`、`visibilityService`、`agentLoop`、`agentExecutor`、`localToolSessionRegistry` 等; 4. 扩展 `DelegateTool.buildSubContext`:子代理工具集排除 `spawn_subagent` / `check_subagent` / `cancel_subagent` / `wait_subagents`(防止嵌套)。 ### 阶段二:四个工具实现 1. 新建 `spawn-subagent-tool.ts`:创建后台执行并立即返回 handle; 2. 新建 `check-subagent-tool.ts`:单查/列表 + 进度快照; 3. 新建 `cancel-subagent-tool.ts`:取消指定后台子代理; 4. 新建 `wait-subagents-tool.ts`:阻塞等待全部结束并汇总; 5. 在 `ToolRegistry` / 工具装配处注册四个工具; 6. 工具 description / prompt 明确「何时用、何时不用」,并说明子代理无法与用户交互、禁止嵌套。 ### 阶段三:主循环挂起与续跑 1. 在 `AgentLoop.execute` 每轮开始处追加 `BackgroundSubagentManager.consumeResults` 注入; 2. 在循环结束点(无 tool calls 准备 break)前插入 `hasRunning` 判断,存在则 suspend(`WAITING_SUBAGENTS`)并 return; 3. 新增 suspend/resume 回调注入点,续跑复用 `HarnessService.execute`; 4. `BackgroundSubagentManager.onCompleted` 在最后一个子代理完成且父会话 `WAITING_SUBAGENTS` 时触发续跑; 5. 处理父取消时提前退出 `wait_subagents` 与挂起状态。 ### 阶段四:崩溃恢复集成 1. `SubagentResultDeliveryService.deliver()` 增加 `BACKGROUND` 分支(写完成通知卡片 + 投递结果); 2. 恢复扫描把 `BACKGROUND` execution 纳入现有 `listRecoveryCandidates`; 3. 恢复执行沿用 `buildSubContext`,无超时决策下取消 3600s 限制(如需保留安全阀,单独评估,不在本次引入); 4. `SubagentRecoveryCoordinator` 对 `BACKGROUND` 采用相同的「全部交付后再恢复父会话」屏障; 5. 父取消场景将 `BACKGROUND` execution 收敛为 `CANCELLED` / `SUPPRESSED`。 ### 阶段五:文件变更归集 1. 扩展归集服务支持 `invocation_type='BACKGROUND'`,按父会话 + execution 消息边界聚合; 2. 主任务变更清单接口返回后台子代理产生的文件变更; 3. 保持「子代理 Tab 内只展示自身变更,不递归父会话」的既有规则。 ### 阶段六:前端(复用 + 最小新增) 1. 复用 `subagent_session_created` → 打开只读子代理 Tab; 2. 复用 `session_status` → 子代理 Tab 状态同步(RUNNING/COMPLETED/FAILED/CANCELLED); 3. 主会话消息列表新增「后台子代理完成」卡片渲染(识别 `metadata.backgroundSubagentCompletion`),点击跳转子代理 Tab; 4. 父会话 `WAITING_SUBAGENTS` 对外映射为 `RUNNING`(或新增前端「等待后台子代理」的轻量态),不新增用户取消按钮。 ### 阶段七:测试、记录与验收 1. 后端 Vitest:工具行为、管理器状态机、主循环挂起/续跑、取消级联、恢复交付分支; 2. `npm run build` + `npm test`; 3. 前端 `vue-tsc` 类型检查 + 手动走查完成卡片与子代理 Tab; 4. 更新根 `CHANGELOG.md` 的 `### 后端` 与 `### 前端(桌面 / Web / 安卓)`; 5. 不自动部署、不重启后端(由用户执行)。 --- ## 8. 前端与事件协议 - 后台子代理创建:复用 `subagent_session_created`(含 `childSessionId`、`agentType`、`task`),前端据此打开只读子代理 Tab。 - 后台子代理状态:复用 `session_status`(`phase`),前端子代理 Tab 显示运行/完成/失败/取消。 - 完成卡片:父会话消息带 `metadata.backgroundSubagentCompletion`,前端渲染为「后台子代理完成」卡片,点击打开对应子代理 Tab。完成通知落库后由 `assistant_message_saved` 帧(`messageId` / `content` / `metadata` / `status`)实时推给该用户全部连接,前端据此即时插入卡片并可按 `messageId` 与 REST 历史去重;不推帧时用户盯屏期间看不到卡片,只有刷新后才补出来。 - `WAITING_SUBAGENTS` 相位:后端在 `session_status` / 会话列表 / 详情响应中映射为 `RUNNING`(保持「运行中」可观测),并可选地在主会话输入区/状态区显示「等待后台子代理」的轻提示(不做复杂状态页)。 --- ## 9. 落地清单 ### 数据库 - [ ] 确认 `subagent_execution.invocation_type` 支持 `BACKGROUND`(无结构变更,更新注释) - [ ] 确认 `session.phase` 支持 `WAITING_SUBAGENTS`(无结构变更) - [ ] (不做)新增索引 ### 后端(backend-ts) - [ ] 扩展 `SubagentExecution.invocationType` 类型 - [ ] 新增 `BackgroundSubagentManager` - [ ] 扩展 `DelegateTool.buildSubContext` 排除后台工具族 - [ ] 新增 `spawn_subagent` 工具 - [ ] 新增 `check_subagent` 工具 - [ ] 新增 `cancel_subagent` 工具 - [ ] 新增 `wait_subagents` 工具 - [ ] `AgentLoop` 每轮开始消费后台完成结果 - [ ] `AgentLoop` 结束点自动挂起(`WAITING_SUBAGENTS`) - [ ] `BackgroundSubagentManager.onCompleted` 触发续跑 - [ ] 父会话取消级联取消后台子代理 - [ ] `SubagentResultDeliveryService` 支持 `BACKGROUND` 完成通知交付 - [ ] 崩溃恢复扫描纳入 `BACKGROUND` - [ ] 文件变更归集支持 `BACKGROUND` - [ ] 装配 `create-app.ts` 依赖 - [ ] 新增 Vitest 用例 ### 前端(desktop,共用 Web/安卓) - [ ] 复用子代理 Tab 只读展示后台子代理 - [ ] 新增「后台子代理完成」卡片渲染与跳转 - [ ] `WAITING_SUBAGENTS` 对外映射为 `RUNNING` - [ ] (不做)用户手动取消后台子代理按钮 ### 验证与发布 - [ ] `cd backend-ts && npm run build` - [ ] `cd backend-ts && npm test` - [ ] `cd desktop && npx vue-tsc --noEmit`(或项目既有类型检查) - [ ] 更新 `CHANGELOG.md` - [ ] 用户确认后部署,后端重启由用户执行 --- ## 10. 测试方案(核心场景) | 编号 | 场景 | 预期 | |---|---|---| | T1 | 主代理 `spawn_subagent` 后继续执行其他工具 | spawn 立即返回,子代理在后台并行执行 | | T2 | 主代理 `check_subagent(task_id)` 查询单个 | 返回状态/轮次/用量/最近输出摘要 | | T3 | 主代理 `check_subagent()` 列全部 | 返回当前父会话全部后台子代理状态 | | T4 | 主代理 `wait_subagents()` 等待 | 全部结束后返回汇总结果,主线继续 | | T5 | 主线结束但仍有后台子代理运行 | 主循环挂起(`WAITING_SUBAGENTS`),不进入终态 | | T6 | 最后一个后台子代理完成 | 父会话自动续跑,产出综合结论 | | T7 | 后台子代理在主线运行中完成 | 结果以完成卡片 + 下轮上下文注入,主代理主动感知 | | T8 | 主代理 `cancel_subagent(task_id)` | 子代理收尾 `CANCELLED`,回传取消结果 | | T9 | 父会话被取消 | 级联取消全部后台子代理,结果 `SUPPRESSED` | | T10 | LOCAL 模式后台子代理 | 继承 LOCAL,经 Electron 执行工具 | | T11 | 后台子代理尝试 `spawn_subagent` | 工具不可用(已从子代理工具集排除) | | T12 | 后端重启时后台子代理运行中 | 恢复原执行与子会话,交付结果后恢复父会话 | | T13 | 后台子代理完成结果交付前重启 | 重启后继续交付,不重复注入 | | T14 | 后台子代理文件变更归集 | 主任务变更清单包含子代理修改的文件 | --- ## 11. 风险与控制 | 风险 | 控制措施 | |---|---| | 无超时导致后台子代理或挂起无限等待 | 用户明确选择「无上限、无超时」;保留父会话取消级联作为人工止损手段 | | 无并发上限导致大量 LLM 并发 | 由 `agentExecutor` 线程池的既有 core/max/queue 上限兜底;超限走现有 reject 语义,不在本次新增硬并发限制 | | 完成通知与同步 delegate 的 assistant+TOOL 交付混淆 | 通过 `invocation_type === 'BACKGROUND'` 显式分支,交付形式不同 | | 挂起后父会话被误判为终态 | 新增非终态相位 `WAITING_SUBAGENTS`,恢复/终态判断函数需排除该相位 | | 子代理嵌套导致树状等待 | 从子代理工具集排除全部后台工具族 | | 完成结果重复注入 | 以 `execution_id` + `delivery_status` 事务幂等 | | LOCAL 无客户端时后台子代理挂起 | 继承现有 LOCAL 等待逻辑;无超时决策下由父取消级联兜底 | --- ## 12. 验收标准 1. 主代理调用 `spawn_subagent` 后不阻塞,可继续主线工作; 2. `check_subagent` 能查单个与全部后台子代理进度; 3. `cancel_subagent` 能取消指定后台子代理并回传取消结果; 4. 后台子代理完成时,主代理无需轮询即可在下轮感知到完成结果,父会话出现完成卡片; 5. 主循环结束时若仍有后台子代理运行,父会话进入 `WAITING_SUBAGENTS`,全部结束后自动续跑; 6. `wait_subagents` 能阻塞等待全部后台子代理结束并返回汇总; 7. 父会话取消时级联取消全部后台子代理; 8. 后台子代理无法再派生后台子代理; 9. 后端重启后运行中的后台子代理可恢复,完成结果可交付,父会话可续跑; 10. 后台子代理文件变更归集到父任务变更清单; 11. CLOUD 与 LOCAL 两种模式均可运行; 12. 现有同步 `delegate` / `delegate_followup` 行为不回归。