diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index cd9dcb1d..e295a852 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -18,6 +18,7 @@ | owner | task | branch | worktree | mutex | status | |---|---|---|---|---|---| +| Cursor | subagent-process-view-design-2026-09-28 | feature/subagent-process-view-design-2026-09-28 | .worktrees/subagent-process-view-design-2026-09-28 | subagent-process-view-design | in_progress | | Cursor | agent-reply-llm-narrative-2026-09-28 | feature/agent-reply-llm-narrative-2026-09-28 | .worktrees/agent-reply-llm-narrative-2026-09-28 | chat+coordinator | in_progress | | Cursor | test-user-doc-issues-2026-09-28 | fix/test-user-doc-issues-2026-09-28 | .worktrees/test-user-doc-issues-2026-09-28 | intent-write-verb | done | | Cursor | pi-runner-env-mismatch-2026-09-28 | fix/pi-runner-env-mismatch-2026-09-28 | .worktrees/pi-runner-env-mismatch-2026-09-28 | pi-runner-env | done | diff --git a/.doc/ops/short-term/2026-09-28-subagent-process-view-design.md b/.doc/ops/short-term/2026-09-28-subagent-process-view-design.md new file mode 100644 index 00000000..546574df --- /dev/null +++ b/.doc/ops/short-term/2026-09-28-subagent-process-view-design.md @@ -0,0 +1,54 @@ +# 任务:小队过程流式展示与 subagent 子页面设计 + +## 状态 + +- status: done(2026-09-28) +- owner: Cursor +- type: 方案研究(设计文档,无代码实施) +- created: 2026-09-28 +- base_dev_sha: 4c42d1bf74e262930bcf127e517ab6b76a0dda71 +- branch: feature/subagent-process-view-design-2026-09-28 +- worktree: .worktrees/subagent-process-view-design-2026-09-28 +- write_scope: `.doc/system-design/`、`.doc/ops/short-term/`(本文件) +- mutex: subagent-process-view-design + +## 用户目标与授权 + +> 是否能让助手的工作过程,像豆包、kimi、codex 等工具一样,输出思考、工具调用、过程内容输出等信息;使用流式输出,持续更新助手的工作过程。多智能体小队先设计如何使用 subagent 子页面来展示上面那样的信息,但可以先不实现。 + +(来源:2026-09-28 Cursor 会话,用户已确认计划) + +- 授权范围:撰写系统设计文档;**不实施任何代码变更**(用户明示"可以先不实现") +- 交付终点:设计文档评审通过(可审产物验收) +- 禁止项:修改 Go/TS 代码、数据库迁移、环境操作、推送远端 + +## 需求重叠探测 + +- 2026-09-28 `catalog search`(关键词:流式输出/思考/工具调用/subagent 页面、streaming agent thought tool call、小队过程可视化流式)均无命中 +- 既有关联文档为归档/演进方向(2026-09-08 三层信息架构、小队编排 §18.5),无进行中任务重叠 + +## 研究输入 + +- 三路并行只读探索(后端流式链路 / squad+taskrun 架构 / 前端聊天架构)+ 主线直接调研,交叉验证 +- 关键现状结论:协议/持久化/前端骨架已就绪,七处断点导致过程数据(思考链、工具调用、增量输出)无法到达前端;前端无 subagent 页面载体 + +## 交付物 + +- `.doc/system-design/2026-09-28-agent-process-streaming-and-subagent-view-design.md`(460 行,A–H 八章:目标参照/协议扩展/七断点打通/subagent 子页面/分期路线/风险边界/断点对照表/参考文件) +- 已登记 `.doc/system-design/README.md` 报告清单(0c 条目) + +## 验证结果(2026-09-28 实测) + +- 行号核对:设计文档引用的 14 处 文件:行 全部与真实代码一致(sed 逐处核对:stream/types.go:68、pinative.go:102、manager.go:364、loop.go:178-183、backend.go:38、runner.mjs:600/24、sse.go:476、workflow.go:543、useTaskRunAttempts.ts:474、taskRuns.ts:113、session.go:624、phase_snapshot.go:225 等) +- 内链核对:文档内 21 个代码文件相对链接、6 个 .doc 文档引用全部存在 +- 术语修正:stripThinkBlocks 剥离对象修正为 `…`(原文误写为特殊符号) +- 无硬编码模型名(grep MiniMax/M2.7 零命中于方案正文,仅协议描述提及 provider 家族作为 reasoning_content 语义来源) +- 产品 PI 测试 NOT REQUIRED(依据:验证契约「范围判定」——纯设计文档,无运行模板/提示词/业务变更;本任务不产生产品代码) + +## 五态收口 + +- review_state: done(设计文档自检完成;待用户评审确认) +- integration_state: not_applicable(不合入代码;文档随本任务分支提交) +- deployment: not_applicable +- cleanup: pending(worktree 待独立维护者复查后清理) +- remote_sync: not_applicable(未授权 push) diff --git a/.doc/system-design/2026-09-28-agent-process-streaming-and-subagent-view-design.md b/.doc/system-design/2026-09-28-agent-process-streaming-and-subagent-view-design.md new file mode 100644 index 00000000..f15130b2 --- /dev/null +++ b/.doc/system-design/2026-09-28-agent-process-streaming-and-subagent-view-design.md @@ -0,0 +1,460 @@ +# 智能体工作过程流式展示与 subagent 子页面设计 + +> 版本:v1.0(设计稿,未实施) +> 日期:2026-09-28 +> 任务:subagent-process-view-design-2026-09-28 +> 作者:Cursor(owner),三路并行代码探索 + 主线调研交叉验证 +> 交付物性质:系统设计文档,**不含任何代码实施**(用户明示"可以先不实现") + +--- + +## A. 目标与参照 + +### A.1 用户需求 + +1. 让助手的工作过程像豆包、kimi、codex 等工具一样对外输出:**思考(thinking)、工具调用(tool call)、过程内容输出**三类信息; +2. 使用**流式输出**持续更新助手的工作过程,而非终态一次性呈现; +3. 多智能体小队设计 **subagent 子页面**承载上述展示(先设计,不实现)。 + +### A.2 参照产品的展示形态拆解 + +| 产品 | 思考展示 | 工具调用展示 | 增量输出 | 交互模式 | +|---|---|---|---|---| +| 豆包 | "深度思考"折叠块:思考中呼吸态滚动,完成后折叠为一行摘要,可点击展开全文 | 工具卡片:图标+工具名+入参摘要+结果摘要+耗时 | 正文逐 token 流式 | 主流内嵌折叠,不打断阅读 | +| kimi | 推理过程折叠区,默认收起,进行中有进度感 | 搜索/工具步骤条 | 逐段流式 | 同上 | +| codex(CLI/Web) | 思考行内灰字流式 | 命令执行块(命令+输出+退出码) | 流式 | 工程向,密度更高 | + +共同设计语言:**默认折叠(低认知负荷)→ 状态可感知(进行中动效)→ 按需展开(全文/详情)→ 归档后可回放**。 + +### A.3 本仓已有基座与约束 + +本设计不是从零发明,而是延伸既有三层信息架构(2026-09-08 streaming-events 设计,已归档但语义仍有效): + +- **L1 phase/status**("现在在干嘛"):已实现并持久化(`task_run_events`); +- **L2 summary/delta/briefing**("结论是什么"):已实现(delta 为整段伪流式); +- **L3 detail**("推演依据"):**协议已定义(`DetailPayload.category: agent_thought/tool_call/step_output/log`),但无生产者、双重丢弃**——本设计的主战场。 + +既有 UX 约束必须继承: + +1. **不打扰原则**:detail 默认折叠,不默认展开思考链/工具日志(2026-09-09 chat-wait-error-retry 设计 P1 明确); +2. **透明可追溯**:详细思考与草稿不丢失,按需可查(工作区文件留存兜底); +3. **治理红线**:过程展示只是可视化,**不改变验收证据边界**——PI 叙事、模型输出不能冒充 PASS/测试证据(AGENTS.md 安全与证据)。 + +### A.4 概念区分(重要) + +本设计的 "subagent" 指**产品内小队成员**(`internal/squad` 的 AgentSpec:organizer/regulatory/writer/leader 等 8 标准专员 + ad_hoc 派生专员),**不是**本仓开发流程的工程工具 PI 子任务派发(`scripts/pi_subagents.py` + `.doc/ops/2026-09-24-pi-subagent-executor.md`)。两者名字相近但完全不同域,实施时不得混用数据源。 + +--- + +## B. 过程事件协议扩展 + +### B.1 现状协议回顾 + +`internal/stream/types.go` 的 `StreamEvent` 已定义 12 种事件类型;其中 `DetailPayload`: + +```go +// internal/stream/types.go:68 +type DetailPayload struct { + Category string `json:"category"` // agent_thought, step_output, tool_call, log + StepID string `json:"step_id,omitempty"` // 步骤标识 (如 tasks/01_regulatory) + Role string `json:"role,omitempty"` // 对应角色 + Title string `json:"title"` + Content string `json:"content"` // 完整推演正文或技术草稿 + Collapsible bool `json:"collapsible"` +} +``` + +现状问题: + +1. 只有 `Content` 全文单帧,无增量模式(思考链逐 token 需要数百帧,全文帧会放大 N 倍流量); +2. 无 `RunID` 冗余(事件重放时按 run 已隔离,可省;但 live 合并需要前端从通道上下文取); +3. 并行步骤下 `Role`/`StepID` 已有,但前端折叠键没有用它们(见 B.4)。 + +### B.2 DetailPayload 扩展(增量模式) + +```go +// 设计草案(不实施) +type DetailPayload struct { + Category string `json:"category"` // agent_thought | tool_call | step_output | log(沿用四类) + StepID string `json:"step_id,omitempty"` // 步骤标识(如 tasks/03_regulatory) + Role string `json:"role,omitempty"` // 成员角色 key(如 regulatory) + RunID string `json:"run_id,omitempty"` // 冗余 run 标识(重放通道按 run 已隔离,可选) + Seq int64 `json:"seq,omitempty"` // 同一 (category, step_id, role, item_id) 流内的序号 + ItemID string `json:"item_id,omitempty"` // 聚合项 ID:一个思考块/一次工具调用/一份产出 + // 增量模式三选一: + Delta string `json:"delta,omitempty"` // 增量帧:追加到 ItemID 项 + Content string `json:"content,omitempty"` // 终帧/快照帧:ItemID 项的完整内容(或无增量能力的发送方) + Done bool `json:"done,omitempty"` // 终帧标记(ItemID 项结束) + Title string `json:"title,omitempty"` + Collapsible bool `json:"collapsible"` + // tool_call 特化字段(Category == "tool_call" 时): + Tool *ToolCallInfo `json:"tool,omitempty"` +} + +type ToolCallInfo struct { + Name string `json:"name"` // write_file / read_file / grep ... + Status string `json:"status"` // calling | succeeded | failed + Input string `json:"input,omitempty"` // 入参摘要(JSON,可能分帧) + Output string `json:"output,omitempty"` // 结果摘要(可能分帧) + DurationMs int64 `json:"duration_ms,omitempty"` +} +``` + +**增量语义**(关键设计决策): + +- 发送方优先逐帧 `Delta`;`Content` 用于两类场景:(a) 无流能力的发送方(如既有 `step_output` 整段产出),(b) 周期性**快照对账帧**(防止增量丢帧后内容漂移); +- 客户端按 `(StepID, Role, Category, ItemID)` 聚合:`Delta` 追加、`Content` 覆盖、`Done` 定稿; +- 节流(发送方义务,见 F.1):逐 token 事件合并为 ~100–300ms 批帧,单帧上限(如 8KB)超限分帧。 + +### B.3 llm 层事件扩展(reasoning 链) + +`internal/agentx/llm` 现有事件只有 `text_delta/tool_call_delta/message_end/error`。OpenAI 兼容国产模型(MiniMax/DeepSeek 系)在 delta 中返回 `reasoning_content` 字段(思考链)。扩展: + +```go +// internal/agentx/llm/types.go 设计草案 +const ( + KindTextDelta EventKind = "text_delta" + KindReasoningDelta EventKind = "reasoning_delta" // 新增 + KindToolCallDelta EventKind = "tool_call_delta" + ... +) + +type Event struct { + Kind EventKind + Text string + ReasoningDelta string // 新增:reasoning_content 增量 + ... +} +``` + +`ParseSSE`(internal/agentx/llm/sse.go)补充解析 `delta.reasoning_content` → `KindReasoningDelta`。 + +**容器侧对应**:`deploy/pi-agent/runner.mjs:24` 的 `stripThinkBlocks` 目前把 `…` 块从产物中剥离(丢弃)。设计上不改变"正文不含 think"的既有约定,而是在**剥离前**把 think 块作为 reasoning 事件分流转发(见 C.5)。 + +### B.4 并行步骤归属路由(修复既有缺陷) + +现状 `PhasePayload` 折叠键为 `phase_id#step_index`([taskrun/phase_snapshot.go:225](../../../internal/taskrun/phase_snapshot.go) 与 [web/src/engine/phaseEventMapper.ts:65](../../../web/src/engine/phaseEventMapper.ts)),**并行组成员**(多个 role 同时 `step_executing`)若无 step_index 会互相覆盖/误折叠。 + +设计:折叠键升级为 `phase_id#step_index@role`(role 存在时必选参与键)。前端 `applyPhaseEvent` 与后端 `FoldPhaseEvents` 同步修改,语义保持对齐(两处折叠逻辑本就要求一致,见 phase_snapshot.go 注释)。detail 事件天然带 `role/step_id`,聚合不受影响。 + +### B.5 通道选择:沿用 stream/StreamEvent,不新增域事件 + +理由: + +1. `events.StaticContracts`(internal/events/registry.go)是静态 allowlist(13 种业务事件 + YAML parity 测试),加 agent 过程事件需改 Go+YAML+测试三处,且域事件总线面向**业务投影**(通知/阶段推进),不面向用户实时流; +2. `task_run_events`(JSONB payload)+ `after_seq` 重放已为过程事件准备好持久化与断线恢复; +3. chat SSE 通道(链路 A)无持久化,但 B 轨参谋对话的过程展示可接受"live-only + 终态落库"(chat_turns 记录终态),与现状一致。 + +--- + +## C. 后端链路打通设计(七断点逐条) + +### C.0 总览 + +``` +【链路 B:长任务(本设计主通道,先做)】 +worker goroutine (taskrun.Manager.runWorker) + └ bizdev.ExecuteTask → squad.WorkflowDriver.runOneStep + └ agent.Backend.Execute(OnEvent 回调) ← 断点 4 + ├ pi-native: agentx.Run(OnEvent) ← 断点 2/3(直通) + │ └ llm.Stream: reasoning/text/tool delta ← 断点 1 + └ pi-agent: POST runner /v1/run/stream ← 断点 5(NDJSON 流) + └ Streamer.EmitDetail(category, stepID, role, ...) ← 断点 6 之前:detail 已能出 Streamer + └ Manager.appendEvent → task_run_events (seq) ← 持久化(已就绪) + └ SSE: GET /tasks/runs/{id}/events ← 透传(已就绪) + └ 前端 detail 渲染分支 ← 断点 7 + +【链路 A:聊天 SSE(P4 再做)】 +chat.Session → coordinator.generateDialogueReply + └ dialogueBackend.Execute(OnEvent) → token 级 EmitDelta(替换一次性 EmitDelta 伪流式) +``` + +### C.1 断点 1:reasoning_content 解析 + +- 文件:[internal/agentx/llm/sse.go](../../../internal/agentx/llm/sse.go)(`sseChoice.Delta` 增 `ReasoningContent string \`json:"reasoning_content"\``)、[internal/agentx/llm/types.go](../../../internal/agentx/llm/types.go)(`Event` 增 `ReasoningDelta` + `KindReasoningDelta`) +- 语义:reasoning 与 text 分开累计;`message_end` 的 `Message.Content` 仍只含 text(与 runner `stripThinkBlocks` 语义对齐:正文无 think)。 +- 兼容性:无 reasoning 的模型零行为变化(字段空即无事件)。 + +### C.2 断点 2:agentx 循环转发 tool_call_delta + +- 文件:[internal/agentx/loop.go:178-183](../../../internal/agentx/loop.go) `consume()`:`case llm.KindToolCallDelta` 增加转发(emit 一个 `tool_call_delta` Event,带 ToolName/ArgumentsDelta);`KindReasoningDelta` 同理转发 `reasoning_delta`。 +- `agentx.Event`(loop.go:19)增字段:`Kind string`(llm 层事件细分)或复用 `Type` 前缀约定(`text_delta`/`reasoning_delta`/`tool_call_delta`/`tool_execution_start`/`tool_execution_end`…已够用,推荐复用 Type)。 + +### C.3 断点 3:Backend 接口过程回调 + +**设计取舍**:不改 `Backend.Execute` 签名(改签名会波及 stub/fallback/limiter 全部实现与调用方),改为 **Request 增可选回调**: + +```go +// internal/agent/backend.go 设计草案 +type Request struct { + ... + // OnEvent 过程事件回调(nil = 不需要过程,行为与现状完全一致) + // 实现方必须保证:回调在 Execute 返回前完成(同步或等待收尾),无泄漏 goroutine。 + OnEvent func(ev ProcessEvent) +} + +// ProcessEvent 对 agentx.Event 与 runner NDJSON 事件的统一投影 +type ProcessEvent struct { + Type string // text_delta | reasoning_delta | tool_call_delta | tool_execution_start | tool_execution_end | message_end | ... + Text string // delta 文本(Type 为 *delta 时) + Tool string // 工具名 + Detail string // 工具参数/结果摘要(tool_execution_* 时) +} +``` + +- pi-native([internal/agent/pinative.go](../../../internal/agent/pinative.go)):`agentx.Run` 的 `OnEvent` 双写——既拼 `execution.log`(现状保留),又投影到 `req.OnEvent`; +- `WrapWithConcurrencyLimit` 透传 Request 即透传回调,无需改; +- fallback/stub:fallback 转发 primary 的回调事件即可;stub 无事件。 + +### C.4 断点 4:squad 层桥接 Streamer + +[internal/squad/workflow.go](../../../internal/squad/workflow.go) `runOneStep`(构造 `agent.Request` 处,约 :543): + +```go +// 设计草案 +if req.OnEvent == nil { + req.OnEvent = func(ev agent.ProcessEvent) { /* 仅拼日志(现状) */ } +} +// 注入流式桥(streamer 可为 nil,安全) +req.OnEvent = chainEvents(req.OnEvent, squadDetailBridge(streamer, step)) +``` + +`squadDetailBridge`(新函数,~40 行)把 `ProcessEvent` 翻译为 `EmitDetail`: + +| agentx/runner 事件 | EmitDetail 映射 | +|---|---| +| `reasoning_delta` | category=agent_thought, delta 帧, ItemID=`step.ID#thought` | +| `text_delta` | category=agent_thought 的草稿子项(或 P2 可选:不改,任务正文走终态 step_output,避免与最终 deliverable 重复)→ **推荐 P0 不发 text_delta,仅 reasoning + tool** | +| `tool_call_delta` | category=tool_call, delta 帧(入参增量), ItemID=`step.ID#tool` | +| `tool_execution_start` | tool_call 项 status=calling 帧 | +| `tool_execution_end` | tool_call 项 status=succeeded/failed + Output 摘要 + duration 帧 | +| `message_end` | 对应 ItemID 的 done 帧 | + +节流在 bridge 内做(100–300ms 合并、8KB 分帧、工具输出截断 N KB——工具结果可能巨大,只发首尾摘要)。 + +**B 轨参谋(链路 A,P4)**:[internal/coordinator/dialogue_llm.go](../../../internal/coordinator/dialogue_llm.go) `generateDialogueReply` 同样注入 `OnEvent`,把 `text_delta` 逐帧转 `streamer.EmitDelta`(替换 [internal/chat/session.go:624](../../../internal/chat/session.go) 一次性 `EmitDelta(NaturalReply)`),`reasoning_delta` 转 `EmitDetail(agent_thought)`。注意 chat SSE 无持久化——断线后靠 `chat_turns` 终态查询(现状机制),过程帧不可恢复(可接受,与豆包/kimi 行为一致)。 + +### C.5 断点 5:远程 runner 流式端点 + +[deploy/pi-agent/runner.mjs](../../../deploy/pi-agent/runner.mjs) 增加端点(不动 `/v1/run` 既有契约——`ResultV1` typed 校验、wire 预算 8MB 等全部保留): + +``` +POST /v1/run/stream Content-Type: application/x-ndjson(chunked) +请求体 = /v1/run 同构 + stream: true +响应 = 逐行 NDJSON: + {"type":"proc","ev":{...pi RPC stdout 事件行...}} // 过程事件(原样转发已解析的行) + ...(执行中持续 flush) + {"type":"result","body":{...与 /v1/run 完全相同的 typed 结果...}} // 终帧 +``` + +- runner 侧改动集中在 `handleRun`:stdout `on("data")` 里既有的逐行 JSON 解析循环(runner.mjs:365-388)旁路一个转发器;`stripThinkBlocks` 之前先把 think 块作为 `{"type":"reasoning_delta"}` proc 帧发出; +- Go 侧 [internal/agent/piagent.go](../../../internal/agent/piagent.go) `executeRemote`:`req.OnEvent != nil` 时改走流式端点——`http.Client` 无总超时的流式变体(`Timeout` 由 ctx 控制),bufio 逐行读:`proc` 帧 → `req.OnEvent` 投影;`result` 帧 → 走既有 `DecodeRemoteRun` 校验路径(**终态校验逻辑零改动**,保证治理边界不放松); +- 降级:流式端点 404(旧 runner)→ 回落 `/v1/run` 一次性调用 + 无过程事件(`capabilities` 探测亦可,但 404 回落更简单)。 + +### C.6 断点 6:includeDetails 参数化 + +两处硬编码/丢弃: + +1. [internal/taskrun/manager.go:364](../../../internal/taskrun/manager.go) `stream.WithListener(ctx, false, listener)`——worker 侧 Streamer 是否发 detail 由**创建时**决定。设计:`CreateAndStart`/`StartClaimedRun` 透传 `IncludeDetails`(`RunRecord` 或 `CreateOptions` 增字段;默认 false 保持现状)。**注意**:detail 是否持久化与是否推送解耦——建议 **detail 一律持久化**(`task_run_events` JSONB 足够),`includeDetails` 只控制 live 推送与 chat 通道;重放端点用 query `include_details=0` 过滤,历史 subagent 页面仍可全量回放。取舍理由:持久化膨胀可控(F.1 节流后估算),而"刷新后过程消失"违背透明可追溯原则; +2. [internal/server/sse.go:476](../../../internal/server/sse.go) `_ = includeDetails`——`StreamChatRequest.IncludeDetails` 已存在(chat 通道)但 task 通道读取后丢弃;接线到 worker 创建。 + +### C.7 断点 7:前端协议与分支 + +- [web/src/api/chatStream.ts](../../../web/src/api/chatStream.ts) `StreamEvent` 增 `detail?: { category?: string; step_id?: string; role?: string; item_id?: string; seq?: number; delta?: string; content?: string; done?: boolean; title?: string; collapsible?: boolean; tool?: { name?: string; status?: string; input?: string; output?: string; duration_ms?: number } }`; +- [web/src/hooks/useTaskRunAttempts.ts](../../../web/src/hooks/useTaskRunAttempts.ts) `onEvent` switch 增 `case 'detail'`:聚合进消息的 `processItems`(见 D.4),不进正文。 + +--- + +## D. subagent 子页面设计(重点) + +### D.1 入口与路由 + +**决策:扩展 hash 路由,不引入 react-router。** + +理由: + +| 方案 | 优点 | 代价 | +|---|---|---| +| A. hash 扩展(推荐) | 复用 [web/src/engine/anchorNavigator.ts](../../../web/src/engine/anchorNavigator.ts) 既有解析/监听先例(`#/s/{sessionId}/m/{messageId}`);零新依赖;与"工作台为唯一应用视图"的产品形态匹配 | 顶层 view 状态机要加一态;深链参数解析手写 | +| B. react-router | 标准 | 新依赖 + 全仓路由心智迁移;App.tsx 1758 行重构风险;本需求只有一个子页面,收益不成比例 | + +路由形态: + +``` +#/s/{sessionId}/run/{runId}/agents → 子页面(默认选中当前 running/最近完成成员) +#/s/{sessionId}/run/{runId}/agents/{roleId} → 子页面(选中指定成员) +``` + +实现骨架(设计草案): + +- App.tsx 顶层 view 状态机扩展:`'workbench' | 'admin' | 'subagents'`;`parseAnchorHash` 扩展识别 `/run/{runId}/agents` 段; +- `handleSubagentsView` 由 TaskRunCard 的"查看小队过程"入口触发(写 hash 触发 hashchange,与消息深链同一机制); +- 子页面经 `OnDemand`([web/src/components/lazy/OnDemand.tsx](../../../web/src/components/lazy/OnDemand.tsx))懒加载,**props 最小化**:仅 `runId`、`sessionId`、`onExit`(返回 workbench)——规避 App.tsx props 钻透:子页面自持 SSE 订阅与状态,不依赖父容器消息流。 + +### D.2 页面信息架构 + +``` +┌──────────────────────────────────────────────────────────────┐ +│ 小队过程 · {任务标题} · run:{runId 截断} [刷新] [返回对话] │ +├──────────────┬───────────────────────────────────────────────┤ +│ 成员卡列表 │ 选中成员(role: regulatory 法规专员) │ +│ │ ┌ 状态条: ⚙️ 正在执行 · 已用 2m13s · 第2/5步 ┐ │ +│ 👤 organizer │ └──────────────────────────────────────────┘ │ +│ ✅ 完成 1m2s│ ┌ ThinkingBlock(默认折叠)──────────────┐ │ +│ 👤 searcher │ │ 💭 思考中…(流式增量,呼吸态) │ │ +│ ✅ 完成 3m4s│ └───────────────────────────────────────┘ │ +│ 👤 regulatory│ ┌ ToolCallCard ─────────────────────────┐ │ +│ ⚙️ 执行中 │ │ 🔧 read_file tasks/01.../output.md │ │ +│ 👤 writer │ │ ✅ 213ms · 12KB │ │ +│ ⏳ 等待 │ └──────────────────────────────────────┘ │ +│ 👤 leader │ ┌ ThinkingBlock(第二段)──────────────┐ │ +│ ⏳ 等待 │ ... │ │ +│ │ ┌ 步骤产出 output.md(终态 step_output)┐ │ +│ [成员详情] │ │ 📄 阶段性产出(可展开全文) │ │ +│ │ └──────────────────────────────────────┘ │ +├──────────────┴───────────────────────────────────────────────┤ +│ 事件时间线(跨成员 phase 事件折叠,复用 applyPhaseEvent 语义) │ +└──────────────────────────────────────────────────────────────┘ +``` + +- **左栏数据源**:`squad.json`(成员编制:role/Name/Responsibilities——工作区文件,经新只读端点或既有 workspace API 投影)+ phase/detail 事件聚合出状态与耗时(`step_executing`/`step_completed`/`step_failed` by role); +- **右栏数据源**:detail 事件流按 `role == selected` 过滤,聚合为 `processItems`(见 D.4); +- **底部时间线**:全量 phase 事件(复用 `TaskRunCard` 折叠逻辑与 `PhaseEvent` 类型)。 + +### D.3 三类过程组件 + +| 组件 | 状态 | 内容来源 | 交互 | +|---|---|---|---| +| `ThinkingBlock` | thinking(呼吸态+增量滚动)/ done(折叠为一行摘要:前 40 字 + 字数)/ error | `agent_thought` 增量帧聚合 | 默认折叠;点击展开全文(Markdown 渲染);进行中可"实时展开" | +| `ToolCallCard` | calling(转轮)/ succeeded(绿勾+耗时)/ failed(红叹+错误摘要) | `tool_call` 帧聚合(name/input/output/status/duration) | 入参/结果默认摘要(N KB 截断),点击展开 JSON/代码(**需补块级渲染**,见 D.6) | +| `ProcessStepOutput` | done | 既有 `step_output` 整段 content | 默认折叠,展开渲染 Markdown | + +组件均为纯展示组件(props 驱动),放置 `web/src/components/subagents/`,配 `node:test` 静态断言测试(与仓内组件测试形态一致)。 + +### D.4 前端数据流与状态 + +```typescript +// 设计草案:web/src/hooks/useRunProcessStream.ts +type ProcessItem = { + key: string; // `${stepId}/${itemId}` + category: 'agent_thought' | 'tool_call' | 'step_output' | 'log'; + role: string; + stepId: string; + title?: string; + text: string; // delta 累计或 content 快照 + status: 'streaming' | 'done' | 'error'; + tool?: { name: string; status: string; input?: string; output?: string; durationMs?: number }; + updatedAt: number; +}; + +function applyDetailEvent(items: Record, ev: DetailEvent): Record +// 纯函数(与 applyPhaseEvent 同风格,node:test 可测): +// delta → 追加 text、status=streaming +// content → 覆盖 text(快照对账) +// done → status=done +// tool 帧 → 合并 tool 字段 +``` + +订阅策略(复用既有骨架): + +- **live**:`subscribeTaskRunEvents(runId, { onEvent, afterSeq })`([web/src/api/taskRuns.ts:113](../../../web/src/api/taskRuns.ts)),事件分发:`phase` → 左栏状态 + 底部时间线;`detail` → `applyDetailEvent`; +- **刷新/进入已结束 run**:先 `readTaskRunSSEOnce(runId, 0, …, { replayOnly: true })` 全量重放(事件已持久化,见 C.6),再决定是否续订 live; +- **超量回放保护**:`after_seq` 分页重放(既有能力),detail 大 run 可先只重放 phase + 按需拉成员 detail(`GET /runs/{id}/events?role=xxx&include_details=1` 过滤参数为可选增强,P2.5)。 + +### D.5 与主对话气泡的联动 + +- 气泡内 `TaskRunCard` 保持现状(L1 时间线),新增两处: + 1. 运行中:卡片底部一行"查看小队过程 →"(写 hash 深链进子页面); + 2. 完成后:L3 摘要条(如"5 名专员 · 12 次工具调用 · 3 段推演"),点击同样进子页面; +- **不把全量 detail 渲染进气泡**——认知负荷原则:气泡只承载 L1/L2,L3 全文在子页面。`MessageBubble` 现有 `message.phaseEvents` 通道不动。 + +### D.6 前端基础设施缺口补齐(实施前置) + +1. **块级 Markdown + 代码高亮**:现状 `renderInlineMarkdown` 仅行内语法([web/src/engine/renderMarkdown.ts](../../../web/src/engine/renderMarkdown.ts))。工具调用入参/结果需要 fenced code block + JSON 高亮。方案:`renderMarkdown` 增块级分支(heading/fence/list——解析器自研或引入 `marked`/`react-markdown`;倾向自研轻量扩展,避免新依赖与 XSS 面扩大,沿用现有 escape 纪律); +2. **高频增量渲染性能**:processItems 更新频率受后端节流(100–300ms 批帧)保护;子页面右栏**不用虚拟列表**(成员过程项数量级 ~10²),但聚合函数必须纯函数 + `React.memo` 行项; +3. **通用折叠组件**:自研 `CollapsibleBlock`(受控 `open` + `Summary` 插槽),三组件复用。 + +### D.7 后端配套只读端点(成员编制) + +子页面左栏需要 `squad.json`。设计(P2 内最小): + +``` +GET /api/v1/tasks/runs/{run_id}/squad +→ { members: [{ role, name, responsibilities, source }], task_key, title } +``` + +实现:taskrun Manager 读 `record.WorkspaceID` → 工作区 `squad.json` 反序列化(只读投影,权限沿用 run 事件的 `RequireProjectAccess`)。 + +--- + +## E. 分期实施路线 + +| 期 | 内容 | 验证形态(按验证契约) | 依赖 | +|---|---|---|---| +| **P0 协议+直通** | B.2/B.3 协议扩展;C.1–C.4(pi-native 全链路);C.6 includeDetails 参数化 + detail 持久化;前端 chatStream 类型 + detail 分支(仅聚合不渲染) | Go 单测(llm sse_test 增 reasoning case、squad bridge 节流测试);stub/pi-native E2E;真实 PI Runner(dev 环境)冒烟 | 无 | +| **P1 气泡过程块** | D.5 气泡内 L3 摘要 + 折叠 ThinkingBlock(最小渲染,块级 markdown 只做 fence+JSON) | 前端 node:test + 真实浏览器验收(交互变更) | P0 | +| **P2 subagent 子页面** | D.1–D.7 全量(hash 路由、成员卡、过程流、squad 端点) | 前端单测 + Playwright spec(新增 e2e/subagents-view.spec.ts)+ 真实 PI 全链路验收 | P1 | +| **P2.5(可选)** | 事件过滤参数(role/include_details query);大 run 分页回放 | 接口测试 | P2 | +| **P3 runner 流式** | C.5 `/v1/run/stream` + piagent 流式消费 + 404 回落 | runner 单测(mjs 侧 settle-policy 风格)+ 真实容器验收(dev) | P0(可并行) | +| **P4 B 轨参谋** | C.4 尾段:dialogue_llm OnEvent → token 级 EmitDelta + reasoning detail | 真实 PI 对话链路验收(chat SSE) | P0 | +| **P5(演进)** | 历史回放端点(transcript.jsonl/execution.log 投影 API);per-member 持久化表(如 `task_run_step_events`,仅当 JSONB 容量实测超限) | 视实测 | P2/P3 | + +每期独立合入(Conventional Commits:`feat(stream)/feat(web)` 前后端分原子提交),P0 即可演示 pi-native 路径的"思考流+工具卡"。 + +--- + +## F. 风险与边界 + +### F.1 事件量与持久化膨胀 + +- **节流义务在 bridge**(C.4):reasoning/text delta 合并 100–300ms 批帧;单帧 ≤8KB;工具输出截断(如 4KB 首部 + 省略标记);`step_output` 保持现状单帧(终态产物已有 `deliverable` 通道与文档存储,不重复全文持久化时可评估只存摘要——P0 先全文,P2.5 实测再收紧); +- **容量估算**:单 run 5 成员 × (1 思考块 2–8KB + 6 工具调用 × 1KB 摘要) ≈ 50KB/run 量级(节流后),`task_run_events` JSONB 单行无硬限,可控; +- **兜底**:`execution.log`/`transcript.jsonl` 仍全量落盘工作区(现状不变),P5 投影 API 作历史回放。 + +### F.2 认知负荷与安全 + +- detail 默认折叠贯穿三处(气泡摘要、子页面 ThinkingBlock、ToolCallCard 入参/结果摘要)——继承 2026-09-09 设计 P1 约束; +- **脱敏**:bridge 层过滤——不透传 prompt/AGENTS.md 全文(这些是任务输入不是过程);工具入参只发 workspace 相对路径相关字段;配置/密钥字段(provider/key)永不进事件(`ProcessEvent` 设计上就不含这些来源); +- **治理红线**:过程事件不写 `events` 域总线、不参与验收判定;测试/审查结论仍以报告路径与 receipt 为准(pi-subagents 规则),本设计纯可视化。 + +### F.3 兼容与降级 + +- 旧 runner(无 `/v1/run/stream`)→ 404 回落一次性调用,功能退化为 L1 时间线(现状); +- `OnEvent == nil` → 所有后端行为与现状完全一致(回调是纯增量); +- 前端不识别 detail(旧前端 + 新后端)→ JSON 字段忽略,无破坏; +- `includeDetails` 默认 false → chat 通道现状不变。 + +### F.4 并行正确性 + +- 并行组多成员同时发 detail:`Streamer.Emit` 已有 RWMutex;bridge 无共享状态(每 step 独立闭包); +- 前端聚合键含 role/stepID(B.4),并行不串扰; +- `step_output` 终态帧在并行完成回调内发出(现状 workflow.go:746 位置不变)。 + +### F.5 明确不做 + +- 不引入 WebSocket(SSE + after_seq 重放已覆盖需求); +- 不做过程事件的域总线登记(B.5 理由); +- 不做 B 轨 chat 过程帧的持久化与断线重放(P4 live-only,终态落库现状); +- 本期不实现任何代码(用户授权边界)。 + +--- + +## G. 附录:断点-文件-方案对照表 + +| # | 断点 | 文件:行 | 方案章节 | +|---|---|---|---| +| 1 | reasoning_content 未解析 | internal/agentx/llm/sse.go、types.go | C.1 / B.3 | +| 2 | agentx 丢弃 tool_call_delta | internal/agentx/loop.go:178-183 | C.2 | +| 3 | Backend 黑盒无回调 | internal/agent/backend.go:38、pinative.go:102 | C.3 | +| 4 | squad 层无成员过程流 | internal/squad/workflow.go:543(runOneStep) | C.4 | +| 5 | runner /v1/run 一次性 | deploy/pi-agent/runner.mjs:600 | C.5 | +| 6 | includeDetails 硬编码 false / 丢弃 | internal/taskrun/manager.go:364、server/sse.go:476 | C.6 | +| 7 | 前端无 detail 协议与分支 | web/src/api/chatStream.ts、hooks/useTaskRunAttempts.ts:474 | C.7 | + +## H. 参考文件 + +- 三层信息架构基座:`.doc/archive/design/superpowers/specs/2026-09-08-streaming-events-and-digestible-ux-design.md` +- 认知负荷约束:`.doc/archive/design/superpowers/specs/2026-09-09-chat-wait-error-retry-ux-design.md` +- 小队编排(含 §18.5 演进方向):`.doc/system-design/agent-squad/02-智能体小队编排-设计-思路-架构-实现-难点-解决方案-亮点与优势.md` +- 长任务 resume:`.doc/archive/design/superpowers/specs/2026-09-09-longtask-phase-live-resume-ux-design.md` +- 任务记录:`.doc/ops/short-term/2026-09-28-subagent-process-view-design.md` diff --git a/.doc/system-design/README.md b/.doc/system-design/README.md index b6a2f936..70c64361 100644 --- a/.doc/system-design/README.md +++ b/.doc/system-design/README.md @@ -7,6 +7,14 @@ ## 📋 报告清单 +### 0c. 小队过程流式展示与 subagent 子页面(2026-09-28) + +| 项 | 链接 | +|-----|------| +| 主设计 | [2026-09-28-agent-process-streaming-and-subagent-view-design.md](2026-09-28-agent-process-streaming-and-subagent-view-design.md) | +| 任务 | [../ops/short-term/2026-09-28-subagent-process-view-design.md](../ops/short-term/2026-09-28-subagent-process-view-design.md) | +| 一句话总结 | 思考/工具调用/过程内容七断点打通(reasoning 解析→Backend 回调→runner 流式→detail 持久化)+ subagent 子页面(hash 路由 + 成员卡 + 过程流组件);设计稿未实施 | + ### 0b. 自由协作与认领提醒(2026-09-24) | 项 | 链接 |