# Neta Pi-First Agent Platform 重构设计 ## 背景 当前 Neta Agent 能力已经具备以下基础: - 线性会话消息流 - 上下文压缩 - Session Subagent 委派 - Skill 安装、扫描与运行时注入 - WebSocket 实时推送 但这些能力目前建立在线性消息模型之上,导致几个根本限制: - 会话历史不是树状结构,无法像 Pi 一样从任意历史节点自然分叉继续 - Compaction 是线性消息流上的附加机制,而不是树模型中的正式节点 - Subagent 运行在同一后端进程内,主要靠 prompt 和工具白名单做软隔离 - Subagent 结果主要聚合在 assistant message metadata,不利于重放、审计和分支切换 - Skill 更偏后台安装项,而不是统一的运行时资源系统 本次重构目标不是兼容升级,而是以 Pi 的设计思想对 Neta Agent Runtime 做一次彻底重建。 ## 目标 构建一个全新的 Agent Platform 子系统,具备以下能力: 1. 使用树状会话模型替代现有线性消息模型。 2. 使用 leaf 驱动上下文重建,而不是按时间顺序回放所有消息。 3. 将 compaction、branch summary、subagent result 都纳入正式的会话树节点。 4. 将 subagent 改为 Pi 风格的进程级隔离运行。 5. 将 skill 改为统一的资源发现、索引和运行时装载系统。 6. 会话存储同时支持 file 和 MySQL 两种 provider,并允许在 agent 配置中选择。 7. 前后端统一消费新的树状 session 协议,不兼容旧接口。 8. 在 Agent 对话页面引入完整流式通信,包含 assistant、tool、subagent 事件。 9. 重构 Agent 相关的管理页面,使其适配 runtime、provider、树状会话和流式状态。 10. 将模型管理纳入统一的平台管理视图。 11. 旧历史数据允许直接删除,不做兼容迁移。 ## 非目标 本次重构不包含以下目标: - 不保留旧消息接口兼容性 - 不保留旧历史数据结构 - 不实现“旧消息表到新树模型”的迁移脚本 - 不以“最小改动”复用现有 session/message 模型 - 不保留 subagent 聚合在 assistant metadata 的旧展示方式 - 不要求本地 provider 必须使用 SQLite - 不要求一次性实现所有历史后台页面的视觉重设计,但要求结构与能力适配新平台模型 ## 方案选择 已讨论的候选路线如下: ### 方案 A:Pi-first 全量重构 直接引入新的 session entry tree 模型,所有会话内容都变为树节点;Agent 仅持有当前 leaf;subagent 使用独立 worker/子进程运行;skill 按统一资源模型参与主子 runtime。 优点: - 架构最干净 - 与 Pi 的树状对话、branch summary、进程隔离 subagent 设计一致 - 后续导出、审计、回放、树导航都基于同一事实源 缺点: - 改动范围最大 - 数据库、后端 runtime、前端 store、协议层都要重写 ### 方案 B:Pi Core + Neta Channel 底层会话树和 subagent runtime 向 Pi 靠拢,但保留较多现有 Neta 网关和 service 分层。 优点: - 对现有工程组织侵入较小 缺点: - 旧结构约束仍然存在,长期不够彻底 ### 方案 C:独立 Agent Kernel 将新会话树、subagent、skill 全部放入一个新的独立 kernel 子系统,对外以 RPC/API 暴露。 优点: - 边界最清晰 缺点: - 首期工程量和组织成本最高 ### 结论 采用方案 A:Pi-first 全量重构。 原因如下: - 用户明确要求不兼容旧接口,接受删除旧历史数据 - 用户希望在会话树、subagent 通信、skill 机制上全量参考 Pi - 若继续沿用旧线性消息模型,会在 branch summary、tree export、subagent 回放等能力上持续受限 ## 总体架构 新系统建议拆分为 8 个核心子系统: ### 1. 会话树 Provider 层 职责: - 定义树状会话存储抽象接口 - 隔离 file 和 MySQL 两种具体存储实现 - 提供 branch、branch summary、compaction、snapshot 相关读写接口 实现: - `SessionTreeProvider` - `FileSessionTreeProvider` - `MySqlSessionTreeProvider` 说明: - 会话的唯一事实源不再是 message 列表,而是 entry tree - 当前上下文由 `session.leaf_entry_id` 决定 - 上层 runtime 不允许直接依赖文件系统或 MySQL 细节 ### 2. Agent 会话运行时 职责: - 基于 session + leaf 构建当前上下文 - 驱动主 agent 的一次运行 - 流式写入 assistant、tool call、tool result 等 entry - 触发 compaction、branch summary、subagent dispatch 说明: - Runtime 不负责前端状态拼装 - Runtime 输出统一事件 ### 3. 子 Agent 进程编排层 职责: - 拉起独立 subagent worker 子进程 - 向子进程发送任务 envelope - 监听 JSONL 事件 - 记录 run 和 event - 在主会话树中落地 subagent batch/result 节点 说明: - 子进程不共享主 runtime 内存 - 主子通信仅依赖结构化协议 ### 4. Skill 资源系统 职责: - 发现、校验、索引和注册 skill 资源 - 构建 `` prompt 索引 - 提供 `read_skill` / `read_skill_file` 运行时读取能力 - 统一主 agent 与 subagent 的 skill 装载策略 说明: - skill 是运行时资源,不再只是后台安装项 ### 5. 实时会话网关 职责: - 对前端暴露 session snapshot 和增量事件 - 统一推送会话树、branch、compaction、subagent 事件 - 提供完整流式通信能力 说明: - 前端不再消费旧线性 message stream - 前端统一消费 session tree 协议 - 流式粒度覆盖 assistant 文本、tool 状态与 subagent 事件 ### 6. 前端会话 Store 职责: - 维护前端 session snapshot、entry graph、active path 与 streaming state - 向聊天主视图提供当前 active path 的消息投影视图 - 向树侧栏提供完整树结构和节点状态 说明: - 布局采用“聊天为主,树为辅” - UI 主视图不是线性消息事实源,而是树状 state 的投影 ### 7. Agent 管理资源控制层 职责: - 统一承载 skill 管理、tool 管理、agent 管理、agent 会话管理、模型管理 - 面向新的 platform resource 模型展示配置、作用域、provider、状态和审计信息 - 作为后台管理入口消费新的平台 API,而不是旧页面零散调用旧接口 ### 8. 平台资源 API 层 职责: - 为会话、agent、tool、skill、model 等资源提供统一管理接口 - 暴露前台聊天页和后台管理页共用的查询与操作能力 - 提供 provider 感知、运行时能力感知、状态聚合与诊断信息 ## 会话存储 Provider 设计 ### 总体原则 Neta 的会话树存储必须通过统一 provider 抽象暴露,不能绑定单一后端。 支持的 provider: - `file` - `mysql` 默认 provider: - `file` 选择方式: - 在 agent 配置中指定会话存储后端类型 ### `SessionTreeProvider` 接口职责 建议接口覆盖以下能力: - 创建 session - 读取 session meta - 读取当前 leaf - 读取 branch path - 追加 entry - 更新 entry - 切换 leaf - 写入 branch summary / compaction / subagent 节点 - 生成 session snapshot - 删除 session ### `FileSessionTreeProvider` 设计要求: - 采用 Pi 风格文件会话方案 - 实现时优先复用 Pi 的文件会话、树路径构建、branch 与 compaction 相关源码 - 保持 append-only / append-friendly,适合流式写入和会话重放 - 文件会话采用 Pi 的单文件 JSONL 语义:第一行是 session header,后续每行是一条 session entry - 不采用 `session.json + entries.jsonl` 双文件结构,避免与 Pi 的 `SessionManager` 语义产生分叉 架构要求: - Provider 对外暴露统一树语义,不向 runtime 泄漏文件格式细节 - 允许使用 Pi 风格 session file / entry append 方式 - 本地后端不要求必须使用 SQLite - `file` provider 优先高强度移植 Pi 的 `SessionManager` 纯逻辑和文件协议,再包一层 Neta provider 适配 ### `MySqlSessionTreeProvider` 设计要求: - 用 MySQL 实现与 file provider 完全等价的树语义 - 上层行为与 file provider 保持一致 - 不得因为底层是数据库而改变 branch、compaction、subagent 节点语义 ### 存储 Provider 一致性约束 两种 provider 必须通过同一组契约测试,至少覆盖: - session 创建与删除 - branch path 重建 - 从历史节点继续分叉 - branch summary 写入 - compaction 节点写入与上下文重建 - subagent batch/result 节点写入 - snapshot 一致性 - streaming 过程中的 entry 更新 ## 数据模型 ### `neta_agent_session` 核心字段建议: - `id` - `session_id` - `root_entry_id` - `leaf_entry_id` - `provider` - `cwd` - `session_file` - `parent_session_id` - `agent_id` - `user_id` - `title` - `status` - `metadata` - `created_at` - `updated_at` 职责: - 表示一个树状会话容器 - 当前会话运行位置由 `leaf_entry_id` 定义 ### `neta_agent_session_entry` 核心字段建议: - `id` - `session_id` - `entry_id` - `parent_entry_id` - `type` - `role` - `message` - `content` - `custom_type` - `display` - `target_entry_id` - `label` - `provider` - `model_id` - `first_kept_entry_id` - `tokens_before` - `tool_name` - `tool_call_id` - `usage` - `model` - `thinking` - `summary` - `details` - `source_runtime` - `created_at` 说明: - 所有会话内容都存为 entry - 每个 entry 都属于一个 session,并有明确父子关系 建议的 `type` 枚举: - `message` - `thinking_level_change` - `model_change` - `compaction` - `branch_summary` - `custom` - `custom_message` - `label` - `session_info` - `subagent_batch` - `subagent_result` 说明: - `tool_call` 和 `tool_result` 可以作为前端流式投影事件或 message 内部 block,但不建议作为第一版树模型的独立 entry 类型强行拆出,避免破坏 Pi 的 `AgentMessage` / `toolResult` 语义 - 若后续确实需要把 tool 拆成独立 entry,必须先定义与模型上下文转换的双向映射,不得在运行时内核计划中隐式改变消息语义 ### `neta_agent_subprocess_run` 核心字段建议: - `id` - `session_id` - `parent_entry_id` - `run_id` - `mode` - `status` - `agent_name` - `agent_id` - `pid` - `started_at` - `ended_at` - `error` 职责: - 保存一个 subagent 进程运行实例 - 支持取消、审计和回放 ### `neta_agent_subprocess_event` 核心字段建议: - `id` - `run_id` - `event_seq` - `event_type` - `payload` - `created_at` 职责: - 保存 subagent 过程事件 - 支持历史回放与问题定位 ### `neta_skill_resource` 核心字段建议: - `id` - `name` - `scope` - `source` - `source_info` - `version` - `fingerprint` - `entry_file` - `status` - `created_at` - `updated_at` 职责: - 统一注册 skill 资源 - 支持 agent 与 subagent 的统一 skill 装载 ## 上下文构建与运行时规划 ### 1. 叶子节点驱动上下文重建 定义一个统一的上下文构建入口: `buildSessionContext(sessionId, leafEntryId)` 规则: 1. 从 `leaf` 向上回溯到 `root` 2. 收集当前路径上的所有 entry 3. 不读取 sibling branch 4. 将 path 上的 entry 转换为模型上下文 ### 2. 用户发送消息 处理流程: 1. 读取当前 session 的 `leaf_entry_id` 2. 基于当前 leaf 构建上下文 3. 新建一个 `user message entry`,父节点指向当前 leaf 4. 新建一个 assistant in-flight entry,父节点指向新的 user entry 5. 更新 session leaf 指向 assistant in-flight entry 6. 启动 runtime 进行流式推理 ### 3. 流式执行 在一次主 agent 运行中: - assistant 文本输出更新当前 assistant entry - 工具调用优先写入 assistant message 的 tool call block,并通过 realtime 事件投影为前端 tool 状态 - 工具结果优先保留为 Pi 兼容的 `AgentMessage` / tool result payload,并通过 realtime 事件投影为前端 tool 结果 - thinking、usage、model 信息写入对应节点 说明: - runtime 不再直接维护 message 列表 - 所有运行中状态都映射到树节点 - tool 的 UI 展示可以是独立组件,但第一版会话事实源不强行拆成 `tool_call/tool_result` 独立 entry,避免破坏模型上下文转换 ### 4. 从历史节点继续对话 规则: - 若选择的是 user 节点,则将 leaf 切到该 user 节点的父节点,并把 user 文本回填前端输入框;提交后形成新 branch - 若选择的是 assistant、compaction、branch summary 等非 user 节点,则直接将 leaf 切到该节点,并从该点继续 ### 5. 分支摘要 当用户从一个 branch 切到另一个 branch 时: 1. 计算当前 leaf 与目标节点的 common ancestor 2. 识别被放弃的路径 3. 根据策略决定是否生成摘要 4. 若生成,则写入 `branch_summary entry` 要求: - branch summary 是正式节点,而不是 message metadata - branch summary 进入后续上下文构建 ### 6. Compaction Compaction 也作为正式节点存在。 `compaction entry` 需要记录: - 摘要内容 - 压缩后 token 规模 - 保留起点 - 生成时模型信息 - 可选扩展 `details` 上下文构建遇到 compaction entry 时: - 注入 compaction summary - 根据 `first_kept_entry_id` 找到保留边界 - 注入 compaction summary 后,只拼接保留边界之后的真实上下文节点 - 若存在多个 compaction,以 active path 上最近的 compaction 为准,并保留 `details` 中的文件读写轨迹 ## 子 Agent 进程级隔离设计 ### 设计原则 Neta 的 subagent 按 Pi 风格使用独立进程运行,不采用线程内共享上下文或同进程 runtime 软隔离。 ### 主进程职责 - 构建 `SubagentTaskEnvelope` - 启动独立 worker 子进程 - 维护 `run_id -> process handle` - 监听 stdout JSONL 事件 - 持久化 run 和 event - 将最终结果折叠为主会话树 entry ### 子进程职责 - 初始化独立 runtime - 读取受限 skill 和 tool policy - 基于 envelope 中的上下文摘要执行任务 - 输出标准 JSONL 事件 ### 主子通信边界 必须满足: - 不共享主进程内存状态 - 不允许子进程直接写主 session tree - 不允许子进程直接操作 session leaf - 子进程只输出结构化事件和最终结果 ### 子进程输入 Envelope 建议包含: - `runId` - `sessionId` - `parentEntryId` - `task` - `restrictedSystemPrompt` - `toolPolicy` - `allowedSkills` - `contextSummary` - `runtimeConfig` ### 子进程输出事件 建议使用 JSONL 事件流,包含: - `run_start` - `assistant_delta` - `tool_call` - `tool_result` - `run_end` - `run_error` ### 主会话树中的子 Agent 节点 建议: - 每一批委派生成 `subagent_batch entry` - 每个任务结果生成 `subagent_result entry` 说明: - Subagent 不再主要依赖 assistant message metadata 展示 - 前端直接将 subagent 节点作为树的一等公民渲染 ## Pi 源码复用策略 ### 可以直接复用或高强度移植 - 文件 session / entry append 相关实现 - tree path 构建 - branch / common ancestor / compaction 相关纯逻辑 - skill discovery 规则 - web-ui 中的事件驱动更新模式 - web-ui 中稳定消息区与 streaming 容器分离思路 - web-ui 中工具渲染注册机制的组织方式 优先来源: - `packages/coding-agent/src/core/session-manager.ts` - `packages/coding-agent/src/core/compaction/*` - `packages/coding-agent/src/core/skills.ts` - `packages/web-ui/src/components/AgentInterface.ts` - `packages/web-ui/src/components/StreamingMessageContainer.ts` - `packages/web-ui/src/ChatPanel.ts` ### 只能借鉴设计,不直接复制实现 - subagent extension 示例 - interactive mode / TUI tree selector - 直接依赖 `pi` CLI 的进程启动逻辑 - 前端本地 Agent 会话对象 - web-ui 中直接依赖前端本地 `Agent` 对象的会话管理方式 - web-ui 的浏览器本地 session 存储实现 ### 禁止复制的部分 - TUI/CLI 键位与交互层 - 本地终端渲染逻辑 - 任何直接依赖 Pi 扩展 API 的胶水代码 原则: - 复制核心抽象和纯逻辑 - 重写 Neta 的平台适配层 ## 页面与管理后台适配范围 本次重构不仅覆盖 runtime 和对话页,还必须覆盖以下页面: - Agent 对话页面 - Skill 管理页面 - Tool 管理页面 - Agent 管理页面 - Agent 会话页面 - 模型管理页面 ### 1. Skill 管理页面 需要适配: - 新的 skill resource registry - scope/source/sourceInfo/fingerprint 展示 - 子进程可见性与 policy 状态 - provider 无关的运行时可用性 可参考 Pi: - `skills.ts` 的 discovery 与 source 信息组织方式 - 资源冲突与优先级策略 不能直接照搬的部分: - Pi 的本地 skill 目录假设 - Pi 的纯本地用户级资源开关方式 ### 2. Tool 管理页面 需要适配: - tool catalog 与 runtime tool policy 的分层 - 主 agent / subagent 的工具可见性和限制规则 - provider、模型能力、skill 条件对工具可用性的影响 可参考 Pi: - 工具注册与渲染的边界设计 - 工具结果的稳定区与 streaming 区分离展示思路 ### 3. Agent 管理页面 需要适配: - agent 与 session provider 选择 - subagent policy - skill policy - model policy - compaction / branch / streaming 相关运行时配置 要求: - agent 配置必须能显式选择 `file` 或 `mysql` - agent 配置必须能看出 subagent 是否启用进程级隔离 ### 4. Agent 会话页面 需要适配: - 树状会话列表与 session meta - 当前 leaf、provider 类型、branch 数量、compaction 数量 - session snapshot 拉取与重连 - 会话删除、重置、诊断和回放入口 可参考 Pi: - session tree / branch / compaction 的概念模型 ### 5. 模型管理页面 需要适配: - 模型元信息和 provider 管理 - context window、thinking 能力、tool 能力等运行时能力展示 - agent 配置与 runtime provider 的关联关系 要求: - 模型管理不再只是静态配置页 - 必须能支撑新 runtime 的能力判断和前端展示 ## Skill 资源系统设计 ### Skill 发现 规则建议参考 Pi: - 扫描 `project / workspace / user` 三类 skill 根目录 - 一旦目录命中 `SKILL.md`,视为 skill root,不再向下递归 - skill name、description、frontmatter 必须严格校验 - 每个 skill 生成统一 `sourceInfo` ### Skill 注册层 发现结果写入 `neta_skill_resource`。 职责: - 维护当前可用 skill 集合 - 不负责 prompt 注入 ### Skill Prompt 索引 主 agent 与 subagent 的 runtime 启动时: 1. 构建 `` 轻量索引 2. 让模型先判断是否需要读取 skill 3. 通过 `read_skill` / `read_skill_file` 按需装载完整内容 ### Skill 运行时策略 主 agent 与 subagent 共享同一 skill 装载模型,但 policy 不同: - 主 agent 可访问完整允许范围的 skill - subagent 只访问受限 scope 内的 skill - 需要高权限或用户交互的 skill 默认禁止对子进程开放 ### Skill 导入 Neta 现有 GitHub / ZIP / 本地安装能力可以保留,但必须下沉到 resource import 层。 运行时永远只面向 registry/discovery 结果,而不是面向安装来源。 ## 前端设计 ### 状态模型 前端 agent store 建议按以下结构重做: #### 1. 会话元信息 - 当前 session id - 当前 leaf entry id - 当前选中的 tree node - 当前 tree filter mode -- branch switch / summary / compaction 状态 #### 2. 会话节点层 - `entryById` - `childrenByParentId` - `activePath` - `visibleTreeNodes` - `branchSummaryEntries` - `compactionEntries` - `subagentEntries` #### 3. 流式运行状态 - 当前 in-flight assistant entry - 当前 tool 执行节点 - 当前 subagent batch 实时事件 - 当前是否允许中断、切 branch、继续输入 #### 4. 编辑器状态 - 预填文本 - 是否进入“从该点继续”模式 - branch summary 提示选项 ### 关键交互 前端第一版应支持: - 树侧栏导航 - 从任意 user 节点继续并创建新 branch - 从任意非 user 节点直接切到该点继续 - branch summary 提示 - compaction 节点折叠/展开 - subagent 节点实时展示和历史回放 ### 主布局 新对话页采用“聊天为主,树为辅”的布局: - 主区域为当前 active path 的聊天视图 - 树作为可展开侧栏 - 用户大多数时间停留在聊天视图,需要切分支时再展开 ### 对话 UI 组件重构范围 本次重构不只是替换接口,还要求重构 Agent 对话页面的核心 UI 组件,以适配树状会话和完整流式通信。 需要纳入重构范围的组件类型包括: - 会话主容器 - 消息列表容器 - 流式消息容器 - 输入编辑器 - 工具调用展示组件 - 工具结果展示组件 - subagent 运行展示组件 - compaction 节点展示组件 - branch summary 展示组件 - session tree 侧栏与节点组件 - 上下文统计与运行状态组件 - 任务规划与执行状态组件 - Todo / 计划 / 进度类组件 - clarify / 提问 / 等待用户响应类组件 - thinking / reasoning 展示组件 - 错误态、重试态、取消态组件 ### 组件重构目标 前端 UI 组件必须从“线性消息列表驱动”改为“树状 session state 投影驱动”。 具体要求: 1. 主聊天区只负责渲染当前 active path 的消息投影视图。 2. 树侧栏负责完整 entry tree 的浏览、选择和切换。 3. streaming 区域独立于稳定消息区。 4. tool、subagent、compaction、branch summary 都必须有正式节点组件,而不是仅挂在消息 metadata 上。 5. 输入框需要支持“从历史 user 节点继续”时的预填与分支提示。 6. 任务规划、Todo、thinking、clarify 等运行时子组件必须统一视觉语言和状态表达。 ### 建议的前端组件分层 建议拆成 4 层: #### 1. 容器层 - `AgentChatPage` - `AgentConversationPanel` - `SessionTreeSidebar` 职责: - 页面布局 - 响应式布局切换 - 容器间状态编排 #### 2. 会话投影视图层 - `ConversationEntryList` - `StreamingAssistantPanel` - `StreamingToolPanel` - `StreamingSubagentPanel` - `PlanningPanel` - `ThinkingPanel` - `ClarifyPanel` 职责: - 将 active path 投影为聊天视图 - 将 streaming state 渲染为增量 UI - 将任务规划、thinking、clarify 等运行时状态渲染为统一子视图 #### 3. 树节点渲染层 - `SessionTreeNode` - `BranchSummaryNode` - `CompactionNode` - `SubagentBatchNode` - `SubagentResultNode` 职责: - 渲染不同 entry type 的树节点外观与交互 #### 4. 输入与操作层 - `ConversationComposer` - `BranchContinueBanner` - `ContextStatsBar` - `RuntimeStatusBar` - `PlanProgressBar` - `ActionRetryBar` 职责: - 输入消息 - 展示当前继续分支上下文 - 展示 token、运行态、流式状态 - 展示计划进度、重试、取消与错误恢复入口 ### 与 Pi Web UI 的借鉴关系 可以借鉴 Pi Web UI 的原则: - 会话状态与视图组件解耦 - 稳定消息区和 streaming 区分离 - 事件驱动更新而不是全量重绘 - 工具与附件渲染使用可注册组件模型 Neta 的对话页不能直接复用 Pi 的 `AgentInterface` 作为核心组件,因为: - Pi 的 `AgentInterface` 面向前端本地 Agent 对象 - Neta 的新架构由后端 session snapshot + realtime patch 驱动 - Neta 需要额外支持 session tree sidebar、branch summary、subagent tree node 等平台能力 因此: - 可以参考 Pi Web UI 的组件边界和 streaming 渲染思路 - 但 Neta 的核心对话页面组件体系需要按新协议重构 ### 对话工作区子组件统一原则 Agent 对话页面内部的所有运行时子组件必须视为同一产品的一部分统一设计,不允许出现“主消息区一种语言、任务规划另一种语言、thinking 再一种语言”的割裂状态。 重点统一对象包括: - 任务规划 - Todo / 步骤进度 - thinking / reasoning - clarify / 等待用户输入 - tool 执行状态 - subagent 批次状态 - 失败 / 中断 / 重试提示 统一要求: - 相同状态语义使用相同视觉 token - 相同层级使用相同间距与容器 - 相同交互动作使用相同按钮与反馈方式 - 所有子组件都基于同一运行时事件模型驱动 ### 管理后台 UI 组件适配原则 后台管理页也必须适配新的平台模型,而不是仅换接口字段。 要求: - skill、tool、agent、session、model 页面都要围绕新资源模型重构数据表格、详情面板和状态展示 - 对话页与后台管理页共享统一术语和资源字段,例如 provider、sourceInfo、leaf、entry type、runtime status - 后台页面允许参考 Pi 的资源边界设计,但不能复用依赖本地 Agent 对象的前端状态模型 ### 流式渲染原则 参考 Pi Web UI 的状态消费模式,前端应分离: - 稳定 entry 列表 - 当前 streaming assistant 容器 - 当前 streaming tool 状态 - 当前 streaming subagent 状态 这样可以避免在流式过程中反复全量重绘整个聊天区域 ### 前端原则 - 不再以线性消息列表作为唯一 UI 模型 - 树是主视图,消息流是当前 active path 的投影视图 ## 新协议设计 建议废弃旧消息流接口,改为围绕 session tree 的协议: ### API / 快照 建议提供: - `session_snapshot` - 返回 session meta、entry graph、active path、runtime state 初始快照 ### WebSocket 事件 建议提供�? - `session_entry_added` - `session_entry_updated` - `session_leaf_changed` - `session_branch_summary_added` - `session_compaction_added` - `subagent_run_started` - `subagent_event` - `subagent_run_completed` - `session_context_stats` - `assistant_stream_start` - `assistant_stream_delta` - `assistant_stream_end` - `tool_call_started` - `tool_call_delta` - `tool_call_completed` - `tool_result_streamed` ### 一致性策略 - 前端首次进入会话必须先获取 `session_snapshot` - 增量事件只做 patch - 若事件丢失或序列缺失,前端主动重拉 snapshot - 稳定状态以 `session_entry_added` / `session_entry_updated` 中的 session tree entry 为准,尤其是 `subagent_batch.metadata.events`、`subagent_batch.metadata.finalResults` 和 `subagent_result.content.results` - `subagent_run_started`、`subagent_event`、`subagent_run_completed` 只作为节点 patch 到达前的瞬时 streaming projection,不作为历史回放或最终 UI 状态的事实源 - 如果 subagent realtime 事件先于 `subagent_batch` 节点或其 metadata patch 到达,前端可以暂存为 fallback;一旦持久树节点带有过程事件或最终结果,必须由树节点覆盖临时状态 ### 流式粒度要求 本次重构的流式通信不是仅推 assistant 文本,而是完整流式: - assistant 文本增量 - tool 调用状态 - tool 结果到达 - subagent 过程事件 前端必须能够同时维护稳定 entry graph 和运行中的 streaming state。 ## 错误处理与约束 ### 树结构约束 必须保证: - `session_id + entry_id` 唯一 - `parent_entry_id` 为空或指向本 session 合法节点 - 不允许环 - `leaf_entry_id` 要么为空,要么指向本 session 合法 entry ### 运行时中断 允许存在以下状态: - `in_flight` - `completed` - `aborted` - `failed` 要求: - 流式中断不能破坏整棵树 - 中断后的节点必须可审计 ### 子 Agent 失败 要求: - 子进程失败时主进程记录失败 run 和 event - 主会话树写入失败结果节点 - 子进程失败不能污染主 runtime ### Skill 安全 要求: - 子进程可见的 skill 必须白名单化 - 附属文件读取必须受资源根目录限制 - 高权限 skill 默认不对子进程开放 ## 数据库策略 用户已确认: - 旧历史数据可以直接删除 - 不要求保留旧消息结构 - 实施阶段直接使用 MCP 对数据库结构进行修改 - 不以额外手写 SQL 文件作为主路径 因此本次重构默认采用“重建新模型 + 清空旧历史”的策略。 ## 测试策略 ### 后端单元测试 覆盖: - tree builder - branch summary - compaction node 语义 - subagent protocol parser - skill discovery / registry ### 后端集成测试 覆盖: - 新建 session 并多轮对话 - 从旧节点分叉 - branch summary 生成 - compaction 后上下文重建 - subagent 单任务和并行任务 - 子进程失败与取消 ### 前端测试 覆盖: - tree selection - active path 渲染 - streaming entry patch - branch continue 交互 - subagent 节点实时更新 ## 推荐实施顺序 虽然目标是全量重构,但实现上建议分 5 个里程碑: ### 里程碑 1:Agent 运行时内核 - 新 session / entry 数据模型 - leaf 驱动上下文重建 - 基础树写入和分支继续 - dual provider 抽象与 file 默认落地 ### 里程碑 2:对话 UI 与新协议 - session snapshot - tree store - active path 视图 - branch 切换和继续 - 完整流式 assistant/tool/subagent 协议 - 对话页核心组件重构 ### 里程碑 3:子 Agent 进程编排 - 独立 worker 子进程 - run/event 持久化 - subagent tree 节点 ### 里程碑 4:Skill 资源系统 - discovery / registry / sourceInfo - 主子 runtime 统一 skill 装载 - 新版 skill policy ### 里程碑 5:管理后台适配 - skill 管理页适配 - tool 管理页适配 - agent 管理页适配 - agent 会话页适配 - 模型管理页适配 ## 风险与缓解 ### 风险 1:范围过大 缓解: - 使用明确里程碑分拆 - 每个里程碑都保持可测试、可演示 ### 风险 2:旧实现耦合过深 缓解: - 不兼容旧接口 - 新 runtime 使用新模块和新协议 - 尽量避免在旧 message 流之上继续叠逻辑 ### 风险 3:前后端事件一致性复杂 缓解: - snapshot + patch 双机制 - 事件流持久化 ### 风险 4:subagent 子进程带来运维复杂度 缓解: - 明确 envelope 和事件协议 - 对 worker 生命周期、取消、并发限制做统一管理 ## 结论 本次重构将以 Pi 的设计思想为主线,完成以下转变: - 从线性消息流转向树状会话事实源 - 从 message metadata 聚合转向正式 entry 节点建模 - 从同进程 subagent 软隔离转向进程级 subagent 强隔离 - 从 skill 安装逻辑转向 skill 运行时资源系统 - 从旧消息接口转向新的 tree snapshot + realtime patch 协议 - 从零散的后台管理页面转向围绕统一平台资源模型的管理控制台 这是一次面向长期演进的基础设施重建,而不是一次兼容式修补。 ## 当前实施进度(2026-04-21) 这一节用于同步目前已经落地、并且与本设计文档直接相关的实现进度。以下内容以当前代码状态为准,重点覆盖会话树、subagent 运行时、工具治理、Agent 配置与前端对话投影五个方向。此前文档尾部存在两版重复的“实施进度”与“下一步建议”,本次统一收口为这一版,旧条目视为已被本节覆盖。 ### 已完成 - `[done]` 会话树主链路已落地,前后端统一基于 `session snapshot + realtime patch` 消费新协议;File / MySQL 双 provider 已接入,Agent 配置可控制会话存储与历史恢复。 - `[done]` Agent 对话页已切到树状会话视图,支持 active path、分支继续、上下文压缩节点、subagent batch/result 节点;长对话与长工具结果的滚动问题已修复。 - `[done]` 前端聊天页已按 Agent 配置消费 `restoreLastSession`;当前“默认本地存储”指最近会话与最近 Agent 选择存于浏览器 `localStorage`,同时服务端 `file` provider 负责会话树落盘。 - `[done]` Agent 工具配置已从旧的 `toolsets` 粗粒度选择升级为更接近 Pi 的显式工具模型:支持 `inheritCoreTools`、`tools.enabled`、`tools.disabled`、`tools.perTool`,并支持单工具级别的 `enabled`、`promptHint`、`allowInSubagent`、`workerRoutingStrategy`。 - `[done]` 后端已形成统一 `toolManifest` / `runtimePolicy` / `presentation` 数据面: - `tools/manifest.ts` 统一推导工具 `kind`、`executionMode`、`supportedInWorker`、`workerRoutingHint`、`requiresShell`、`requiresWrite`、`requiresSkillScope` - `tool_registry` 通过统一 manifest 工厂生成治理页 `runtimeProfile` - `tool_resolver` 通过统一 manifest 工厂生成 subagent / supervisor 诊断与运行路由 - subprocess worker 的 legacy fallback 也切到同一套 manifest 事实源,避免 worker 与主进程规则漂移 - `[done]` 子代理运行时已接入 Pi 风格的 tool manifest、worker routing hint 与 main-process-proxy 基础能力;`tool_resolver` 对 subprocess worker 与 main-process-proxy 的可用性判断已统一到真实策略约束,不再只看静态支持声明。 - `[done]` 子代理 batch 持久化 metadata 已带上 `toolRuntimeRoutes`,用于树节点回放和会话刷新后的稳定展示。 - `[done]` 普通对话内 session subagent 已完成一轮专项稳定化,范围限定为 `delegate_task` / `delegate_parallel` 的 `delegationMode: "session-subagent"`,不包括 Crew: - `delegate_task` / `delegate_parallel` 已支持任务级 `workspaceRoots`,父 Agent 可显式给子代理追加允许访问的工作区 - 子代理运行前会把 `sessionCwd`、任务级 `workspaceRoots`、全局 process workspace roots 统一传入 `toolResolver.resolve()` 的 `runtimePolicy`,使预检诊断和真实 worker policy 对齐 - 任务文本包含 `desktop` 或 `桌面` 时,会自动推导当前用户桌面目录作为 workspace root,解决“会话 cwd 在 repo 内,但任务需要查桌面”的场景 - 外部证据任务会预检 `bash`、`list_dir`、`read_file`、`find_files`、`grep` 等工具是否至少有一个可执行 route;如果全部被 `workspace_root_required` 等策略阻断,会在执行前失败并给出明确错误 - 外部证据任务必须实际调用工具并产出可用 final answer;只有 `...`、空输出或没有最终结论时,不再判定为成功 - `[done]` session subagent worker 已参考 Pi 的子代理聚合方式补齐 final output 与工具证据回传: - worker 在 `run_end` 中回传 `finalOutput`,从最后一条 assistant message 中提取可展示回答,避免只依赖 `finalContent` - worker 捕获有限工具结果证据并放入 `resultPayload.toolResults`,当前上限为最多 6 条、每条最多 1200 字符,避免主上下文和 payload 无限膨胀 - 主服务会从 `finalOutput` / `finalContent` 中剥离 `...` 后再判断是否可用 - 当子代理已调用工具但没有形成最终结论时,主服务可从工具结果中保守提取图片、`.xlsx` 等文件名并生成短 summary,避免弱模型子代理因忘记总结而整体失败 - 子代理执行契约 prompt 已加强,计数类任务要求最终回答必须包含数量和用于计数的文件名或证据;无法确定时必须明确说明原因 - `[done]` session subagent 的过程事件回传已完成第一轮落地,覆盖 in-process 与 subprocess 两条执行路径: - in-process 子代理运行会记录 `run_start`、`thinking`、`token`、`tool_call`、`tool_result`、`run_end`、`run_error` - subprocess worker 的 JSONL 事件会被主进程投影为统一 `processEvents`,并保留 `proxy_tool_call`、工具调用参数、工具结果、失败详情和最终输出摘要 - `appendSubagentResultEntry()` 已支持写入 `metadata`,为后续把过程事件升级为更正式的树节点 metadata 回放模型留出接口 - 当前过程事件会进入 `resultPayload.processEvents`,前端可以在刷新后的会话树快照中继续回放,而不只依赖实时 WebSocket 事件 - `[done]` 子代理证据摘要已从 `subagent.ts` 中拆出为 `subagent_evidence.ts`,形成可测试的独立聚合层: - `normalizeSubagentToolResults()` 统一规整 worker 工具结果 - `buildSubagentEvidenceSummary()` 支持图片文件、`.xlsx` 文件等计数类证据提取,并在没有结构化计数时回退为工具证据预览 - 子代理最终输出兜底不再直接内联在 service 中,后续可继续扩展到更多证据类型 - `[done]` Agent 对话页中 subagent / delegate 工具的运行中状态展示已完成一轮误导修复: - subagent batch 的“工具路由”摘要只统计本批次任务实际请求的工具,不再把 `todo`、`write_file`、`read_skill` 等非本任务工具计入“Worker 禁用 / Worker 不支持” - 后端 `subagent_run_started` 事件只发送本批次请求工具相关的 `toolRuntimeRoutes` - streaming 工具卡片中,`delegate_task`、`delegate_parallel`、`todo` 这类主 Agent 编排工具不再展示 worker route 元信息,避免用户误以为子代理 worker 被禁用 - `[done]` Agent 对话页的 subagent / delegate 结果投影已完成一轮结构化升级: - 新增 `subagent_projection.ts`,统一从 `subagent_result` entry、tool message 内容、assistant `message.metadata.skillExecutions` 中解析 session subagent 结果 - 前端 store 派生 `subagentTaskPanelsByEntryId`、`subagentEvidenceSummariesByEntryId`、`subagentProcessEventsByEntryId`,避免视图层重复解析 payload - 对话页新增“委派结果”展示路径,可在 assistant 消息下展示每个子任务的状态、摘要、证据卡片、工具调用明细和过程事件回放 - `delegate_task` / `delegate_parallel` 的结果解析已修正为消息条目优先读取 `message.metadata`,仅在缺失时回退读取顶层 `entry.metadata`;同时保留按 `result.id` / 任务摘要的防御性去重,避免同一 payload 被解析两次 - `[done]` Windows 子进程执行体验已补齐基础处理: - 子进程 spawn 统一加上 `windowsHide: true`,避免后台 worker / shell 调用弹出窗口 - shell 配置抽象为 `process_spawn_options.ts`,Windows 优先使用 Git Bash,缺失时回退到 `ComSpec`,非 Windows 使用 `$SHELL` 或 `/bin/sh` - `[done]` 工具治理页已接入运行时重构后的核心概念,能够展示和编辑工具目录、治理状态、Worker 路由策略、是否允许子代理使用、renderer registry 绑定能力,以及工具运行约束与诊断信息;历史“工具编辑 / 开关配置”能力也已恢复。 - `[done]` 聊天页与消息项已统一接入 tool renderer registry。streaming tool 事件和 persisted tool execution 均通过同一 registry 投影,`toolLabel` 已在 realtime event、前端 store、消息投影中贯通。 - `[done]` Agent 编辑页已完成一轮稳定化重建: - 已补齐工具开关、核心工具继承、per-tool 局部覆盖、Worker 路由、子代理工具范围、会话本地文件存储配置、提示词预览等能力 - 编辑抽屉和工具配置区域已增加内部滚动,避免长工具列表或长配置内容不可见 - 工具列表、局部工具配置预览、子代理工具候选已开始优先消费后端 `runtimeDiagnostic` 投影,而不是只依赖前端本地推导 - `[done]` Agent 相关主要页面已完成一轮中文清理和历史乱码文案清理,已覆盖 `views/agent-list.vue`、`views/agent-edit.vue`、`views/tools.vue` 等核心页面。 - `[done]` 文档侧之前写成“subprocess policy 自动推导 + 工具 manifest 化”作为下一步,这一项现在已经不再属于待启动工作,而是已进入落地阶段并完成第一轮主链路接入。 ### 部分完成 - `[partial]` “浏览器本地恢复当前 Agent 最近会话”与“服务端 file provider 会话树持久化”目前都已存在,但仍是两层产品语义;实现上已经打通,用户心智模型仍需继续收口。 - `[partial]` Agent 管理页与工具治理页已基本对齐新 runtime 概念,但还未形成完整的“平台资源控制台”:工具、Agent、聊天投影已接上,session / model / skill / runtime diagnosis 还未完全统一成单一资源视图。 - `[partial]` 工具治理页已能看到 renderer 绑定、Worker 路由、最终运行诊断,但部分显示细节仍混有前端本地推导与后端 projection,需要进一步统一。 - `[partial]` Agent 对话页虽已具备滚动、历史恢复、tool 投影、subagent 投影、delegate 结果面板、工具执行明细和过程事件回放,但距离“完整 session tree sidebar + branch 操作 + compaction / branch summary 节点 UI”仍有差距。 - `[partial]` session subagent 的工具证据聚合已经抽象为独立模块,并覆盖文件计数类任务,尤其是图片和 `.xlsx`;更通用的结构化证据类型、跨工具证据合并策略和长期可审计事件模型仍需继续扩展。 ### 当前仍未完成 - `[todo]` 继续把聊天页收口为完整 session tree 主视图,补齐 branch 操作、compaction / branch summary 节点可视化、树侧栏与主消息区的一致状态表达。 - `[todo]` 继续把工具治理页、Agent 编辑页和聊天页中的 runtime 展示统一改为以后端 projection 为准,减少前端本地重复推导,尤其是 policy source、blocked reason、runtime route、renderer capability 的多处各算一遍。 - `[todo]` 将 session subagent worker 的过程事件从当前 `resultPayload.processEvents` 进一步升级为更正式的 subagent batch/result metadata 回放模型,并明确事件裁剪、长期存储和审计查询策略。 - `[todo]` 继续扩展 `SubagentEvidenceSummary`,支持更多任务类型、更多证据来源和跨工具证据合并,而不是只覆盖图片和 `.xlsx` 等文件计数场景。 - `[todo]` 继续明确 session subagent 与 Crew 的边界。普通对话委派走 session subagent,Crew 作为独立编排能力保留,但 UI 文案、工具提示和后端事件命名需要继续避免混用。 - `[todo]` 将“浏览器本地恢复历史会话”与“服务端 file provider 持久化”的产品命名和页面提示继续统一,避免继续混淆“本地存储在哪一层”。 - `[todo]` 继续清理 Agent 模块剩余页面与组件中的历史文案、残留乱码和老旧术语,重点检查 `chat.vue`、`message-item.vue` 及会话树、工具治理、模型渠道相关配套组件。 ### 对当前设计结论的修正说明 基于当前实现进度,可以确认以下设计判断已经被代码验证为正确方向: - Agent 侧工具配置不能再停留在旧的 `toolsets` 粗粒度模型,必须提升到 `manifest + policy + per-tool override`。 - 前端聊天页、后台工具治理页、Agent 编辑页必须共用一套 renderer / label / runtime routing 语言,否则无法稳定联动。 - `session.cwd` 必须进入 Agent 配置,而不能只留在后端默认值里,否则文件类工具、bash、subagent 的行为不可解释。 - `restoreLastSession` 必须作为 Agent 配置显式暴露,否则“刷新后历史是否恢复”会持续表现为不透明行为。 - 工具治理不能只做后端接入,必须同步落到 Agent 编辑页、聊天页和工具管理页,否则用户无法真正消费新 runtime 能力。 - subprocess policy 自动推导与 tool manifest 化应视为运行时内核的一部分,而不是单独附着在某个页面上的展示功能。 - session subagent 的可靠性不能只依赖子模型“自觉总结”。必须在 worker / service 外层保留 Pi 风格的 final output 提取、工具证据记录和父进程兜底聚合能力。 - UI 中的 worker route 只应展示给真正进入 worker 决策的执行工具。`delegate_task`、`delegate_parallel`、`todo` 等主 Agent 编排工具不应被展示成“Worker 禁用 / Worker 不支持”,否则会误导用户判断运行状态。 - 对话页的委派结果投影必须有单一语义来源。对于 `message` entry,`message.metadata.skillExecutions` 是优先事实源;顶层 `entry.metadata` 只能作为兼容回退,否则同一 `delegate_parallel` payload 会在前端被重复解析。 - subagent 过程事件应先作为 payload 级可回放诊断数据稳定落地,再逐步上升为正式树节点 metadata / event store。这样可以避免过早引入独立事件表,同时保持 UI 回放和问题定位能力。 ### 接下来下一步改什么更合适 1. `[next]` 优先把当前 `resultPayload.processEvents` 与 `resultPayload.evidenceSummary` 升级为更正式的 subagent batch/result metadata 回放模型。下一步最应该改的是 subagent batch/result 节点的数据模型与写入链路:明确 `tool_call`、`tool_result`、`proxy_tool_call`、`finalOutput`、证据摘要和失败原因的裁剪规则、回放规则和长期审计语义。 2. `[next]` 继续扩展通用 `SubagentEvidenceSummary` 层。当前已经从 `subagent.ts` 拆出并覆盖 `find_files` / `list_dir` / `bash` 的文件证据提取,后续应支持更多证据类型、跨工具聚合和更稳定的结构化输出 schema。 3. `[next]` 继续收口 Agent 对话页,补齐 session tree sidebar、branch 操作、compaction / branch summary 节点 UI,以及 tool / subagent / streaming 状态的统一视觉语言。这一项最直接影响用户是否真正感知到“Pi-first runtime 已经落地”。 4. `[next]` 继续收口“后端 projection 是事实源”这条架构原则,把聊天页、工具治理页、Agent 编辑页里还残留的前端本地 runtime 推导进一步替换掉,尤其是 `effectiveRuntimeProfile`、`effectiveSubagentAllowed`、`blockedReason`、`runtimeRoute`、`rendererCapability`。 5. `[next]` 在治理侧补齐策略可观察性,把 subprocess policy 的来源、阻断原因、main-process-proxy / worker-local / disabled 的最终决策路径完整展示出来,形成真正可解释的工具治理面板。 6. `[next]` 最后再统一“本地会话恢复 / file provider 落盘 / 工作目录 / 会话目录”这几个容易混淆的产品概念,把页面提示和后台配置文案彻底收口。 ## 当前实现修正:2026-04-22 本节以当前 Neta 代码实现为准,用于修正上文可能已经过期或存在编码损坏的描述。 - `[done]` session subagent / delegate 结果展示的单一语义来源已经进一步收口到后端生成的 `entry.metadata.subagentProjection`。该字段由 session tree 边界层注入,不回写 provider 中持久化的原始 entry,避免刷新、切换 leaf、实时推送时出现不同解析路径。 - `[done]` 后端新增 `subagent_projection` 投影层,统一从 `subagent_result` entry、tool message payload、assistant `message.metadata.skillExecutions` 中构建 canonical UI projection。投影内容包含 `evidenceSummaries`、`processEvents`、`taskPanels`,每个 task panel 再包含任务状态、摘要、证据、过程事件和配对后的 tool execution。 - `[done]` 对于 `message` entry,后端投影优先使用 `message.metadata.skillExecutions`,只有缺失时才回退到顶层 `entry.metadata.skillExecutions`。这条规则用于避免同一个 `delegate_parallel` payload 同时出现在 message metadata 和 entry metadata 时被重复解析。 - `[done]` 前端 `subagent_projection` store 派生逻辑已经改为优先消费 `metadata.subagentProjection`。原始 `delegate_task` / `delegate_parallel` payload 解析仅作为 legacy fallback,服务于历史 entry 或旧快照,不再作为新数据的主路径。 - `[done]` 投影协议类型已经进入后端 session tree `types.ts`,`SessionTreeEntryMetadata.subagentProjection` 是当前前后端契约。后续扩展 subagent 证据、过程事件、工具执行明细时,应优先扩展这个协议,而不是在视图层新增第二套 payload parser。 - `[done]` `subagentProjection` 已固定为 v1 协议,必须包含 `version: 1` 与 `source: "backend-projection"`。前端只把带有该版本标记的 projection 视为 canonical;缺失版本或来源不匹配时,只能进入 legacy fallback,以防旧字段或半成品 metadata 被误当作正式协议消费。 - `[done]` `subagentProjection.replay` 已开始承载回放协议元信息,当前包含 `limits` 与 `truncated`。`limits` 固定声明 evidence summaries、全局 process events、task 内 process events、tool executions 的投影上限;`truncated` 显式告诉前端和后续审计链路哪些维度发生过裁剪,避免 UI 误以为投影数据就是完整历史。 - `[done]` task panel 级别新增 `toolExecutionsTruncated`,用于精确表达单个子任务的工具执行明细是否超过投影上限。顶层 `replay.truncated.toolExecutions` 由任务级真实裁剪状态汇总而来,不再根据裁剪后的数组长度推断,避免“刚好等于上限”被误判为已裁剪。 - `[done]` 前端 task panel 已消费 `toolExecutionsTruncated`。当工具执行明细被投影上限裁剪时,UI 显示为“前 N 次”,而不是普通的“N 次”,避免用户把裁剪后的列表误读为完整调用历史;legacy fallback 路径也会按同一规则标记。 - `[done]` task panel 的任务回放已去除已经折叠进 `toolExecutions` 的 `tool_call`、`proxy_tool_call`、`tool_result` 事件,避免同一次工具调用同时出现在“工具执行”和“任务回放”里。后端 canonical projection 与前端 legacy fallback 已按同一规则对齐。 - `[done]` 新增回归测试覆盖 backend projection、snapshot 注入、frontend projection 优先级、legacy fallback、工具执行裁剪和任务回放去重,确保后续继续收口 projection 协议时不会重新引入重复解析或重复展示问题。 ### 本轮验证 - `[verified]` `pnpm --filter @neta/backend test -- --runInBand test/subagent_projection.test.ts test/subagent_projection_task_replay.test.ts test/session_tree_subagent_projection_snapshot.test.ts test/session_tree_snapshot.test.ts` - `[verified]` `pnpm --filter @neta/frontend test` - `[verified]` `pnpm --filter @neta/frontend build` - `[verified]` `pnpm --filter @neta/backend build` ### 接下来优先级修正 1. `[next]` 先提交并推送本轮 `subagentProjection v1` 收口改动,避免继续叠加功能导致 review diff 过大。 2. `[next]` 后续可继续增加 projection health / debug 信息,用于说明某个 entry 是消费 backend projection、进入 legacy fallback,还是因为协议不完整被跳过。 3. `[next]` 再推进 session tree sidebar、branch 操作、compaction / branch summary 节点 UI。原因是 subagent 结果投影的事实源已经收口,下一步更适合把完整树体验补齐,而不是继续在消息视图内堆解析逻辑。 ## 当前实现修正:2026-04-23 本节用于同步 2026-04-23 已经进入代码事实源并完成验证的文档收口,重点是 canonical metadata、projection diagnostics 与前后端契约进一步一致化。 - `[done]` `subagent_batch` 与 `subagent_result` 的 metadata 契约已经进一步显式化。当前后端 session tree 类型已经正式声明: - `SessionTreeSubagentBatchMetadata.events` - `SessionTreeSubagentBatchMetadata.finalResults` - `SessionTreeSubagentBatchMetadata.toolRuntimeRoutes` - `SessionTreeSubagentResultMetadata.finalResults` - `SessionTreeSubagentResultMetadata.processEvents` - `SessionTreeSubagentResultMetadata.evidenceSummaries` - `[done]` `subagent_result` 的 canonical projection 已明确优先读取 `metadata.finalResults`,只有缺失时才回退到旧的 `content.results`。这条规则现在已经进入后端 `subagent_projection` 主链路,不再只是前端约定。 - `[done]` 前端新增 `subagent_batch_runtime.ts`,专门负责读取 batch 级 canonical metadata,并统一派生 batch 事件、final results 与 tool runtime routes。`chat.ts` 不再直接散落读取 `metadata.events`、`metadata.finalResults`、`metadata.toolRuntimeRoutes`。 - `[done]` 前端 `SessionTreeEntry.metadata` 已从宽泛的 `Record` 收口为显式类型,当前已覆盖 `subagentProjection`、batch metadata、result metadata 三类主链路字段,减少了 store 与视图层重复做弱类型判断。 - `[done]` 前端 `subagent_projection.ts` 已增加 `getNormalizedSubagentResultMetadata()`,并把 evidence / process events 的读取统一改成“先 canonical metadata,后 legacy payload fallback”。这修复了 metadata-only `subagent_result` 之前会被跳过的问题。 - `[done]` `SubagentProjectionDiagnostics` 已成为前后端共享的正式诊断协议。当前诊断字段除原有 source / count / fallback 信息外,已新增 `projectionPath` 用于表达具体投影路径。 - `[done]` `projectionPath` 已正式进入后端 `SubagentProjectionDiagnostics` 契约。规范值目前固定为: - `canonical-backend-projection` - `metadata-only-fallback` - `legacy-payload-fallback` - `[done]` 后端生成 canonical projection 时,现已明确写出 `projectionPath: "canonical-backend-projection"`;前端 fallback 路径则按自身投影来源补齐另外两类状态,避免只用 `fallbackUsed` 做模糊表达。 - `[done]` 聊天页的 subagent 诊断区现在会直接展示 `projectionPathLabel`。用户在 UI 上已经可以直接区分当前结果是来自后端规范投影、metadata-only fallback,还是旧委派载荷 fallback。 ### 本轮设计约束修正 - `[rule]` 对于新生成的 session tree 数据,前端必须优先消费后端提供的 canonical metadata 与 `metadata.subagentProjection`,不得再把原始 `delegate_task` / `delegate_parallel` payload 当成主事实源。 - `[rule]` `subagent_batch.metadata.*` 与 `subagent_result.metadata.*` 现在应被视为稳定回放层的一部分。后续若继续扩展 subagent replay 协议,应优先扩展这些 metadata 契约,而不是新增 message-level 私有解析路径。 - `[rule]` `projectionPath` 是正式诊断语义,不只是 UI 文案。后续凡是新增 subagent 结果解析分支,都必须同步定义其 `projectionPath` 归类。 - `[rule]` 视图层不应继续手写 batch / result metadata 的读取规则。canonical metadata 的读取、标准化和 fallback 逻辑必须收敛在 store helper 中,避免再次出现多处重复解析。 ### 本轮验证 - `[verified]` `pnpm --filter @neta/frontend test` - `[verified]` `pnpm --filter @neta/frontend build` - `[verified]` `pnpm --filter @neta/backend test -- --runInBand test/subagent_projection.test.ts` - `[verified]` `pnpm --filter @neta/backend test -- --runInBand test/session_tree_chat_orchestrator.test.ts` ### 接下来优先级修正 1. `[next]` 继续把 batch / result metadata 扩成更正式的长期回放协议,尤其是 batch 级事件裁剪、finalResults 生命周期和 toolRuntimeRoutes 的审计语义。 2. `[next]` 继续减少前端 `subagent_projection.ts` 里残留的宽泛 `Record` / `any` 路径,让 canonical metadata 消费链路完全类型化。 3. `[next]` 把这套 projection diagnostics 能力继续延伸到 session tree sidebar、branch 操作和 compaction / branch summary 节点 UI,形成统一的“事实源 + fallback”可见性模型,而不是只在委派结果面板里可见。