--- title: Session Tree 运行时 created: 2026-04-21 updated: 2026-04-26 type: concept tags: [runtime, agent, backend, database] sources: [packages/backend/src/modules/netaclaw/session-tree/provider.ts, packages/backend/src/modules/netaclaw/session-tree/types.ts, packages/backend/src/modules/netaclaw/session-tree/snapshot.ts, packages/backend/src/modules/netaclaw/session-tree/context_builder.ts, packages/backend/src/modules/netaclaw/session-tree/mysql_provider.ts, packages/backend/src/modules/netaclaw/session-tree/file_provider.ts, packages/backend/src/modules/netaclaw/gateway/session.ts, packages/frontend/src/modules/agent/store/chat.ts] --- # Session Tree 运行时 Session Tree 是 NetaClaw 新 Agent runtime 的会话状态模型。它把对话从线性消息列表升级为“节点树 + 当前叶子 + 活动路径 + 运行时上下文”的结构,使刷新恢复、分支、压缩、子 Agent 批次、标签、模型切换都能进入同一套持久化和投影机制。 它是 [[agent-runtime]]、[[context-compaction]]、[[subagent-session]] 和 [[frontend-architecture]] 的共同底座。 ## Provider 抽象 `SessionTreeProvider` 定义统一接口,当前有 file 和 mysql 两种 provider。 主要能力包括: - `createSession` - `getSession` - `updateSession` - `deleteSession` - `listEntries` - `appendEntry` - `appendMessage` - `appendThinkingLevelChange` - `appendModelChange` - `appendCompaction` - `appendBranchSummary` - `appendLabelChange` - `appendSessionInfo` - `updateEntry` - `switchLeaf` - `resetLeaf` - `createBranchedSession` - `getActivePath` - `getSnapshot` 这层抽象让运行时不直接绑定 MySQL 或本地文件,也为未来多 workspace、本地优先存储、导入导出保留空间。 ## Snapshot 结构 `SessionTreeSnapshot` 是前后端对齐的核心投影: | 字段 | 含义 | | --- | --- | | `session` | 会话元信息,包括 provider、rootEntryId、leafEntryId、cwd、agentId 等 | | `entries` | 当前会话所有树节点 | | `activePath` | 从 root 到 leaf 的当前上下文路径 | | `childrenByParentId` | 前端重建树结构所需的父子索引 | | `labelsByEntryId` | 节点标签状态 | | `runtimeContext` | 供模型调用使用的消息、thinking level、模型引用 | 前端刷新恢复应优先加载 snapshot,而不是依赖 localStorage 保存完整聊天记录。 ## Entry 类型 当前树节点类型包括: | 类型 | 用途 | | --- | --- | | `message` | system/user/assistant/tool 模型消息 | | `thinking_level_change` | 会话内 thinking level 切换 | | `model_change` | 会话内模型切换 | | `compaction` | 压缩摘要节点,详见 [[context-compaction]] | | `branch_summary` | 分支摘要 | | `custom` | 不直接展示的自定义数据 | | `custom_message` | 可展示的自定义消息 | | `label` | 对目标节点设置或清除标签 | | `session_info` | 会话信息变更 | | `subagent_batch` | 子 Agent 批次节点,详见 [[subagent-session]] | | `subagent_result` | 子 Agent 批次结果节点 | ## Active Path 与上下文构建 模型上下文不再等于“所有历史消息”。运行时会从当前 `leafEntryId` 回溯得到 `activePath`,再由 `context_builder.ts` 转成 `runtimeContext.messages`。 这带来几个结果: - 分支切换只需要切换 leaf。 - 压缩摘要可以替代被压缩的历史段。 - 子 Agent、分支摘要、custom message 可以参与展示,但不一定进入模型上下文。 - thinking/model 变更可以沿路径生效。 ## 持久化位置 Session Tree 支持两类持久化: - MySQL provider:适合生产和多端共享。 - File provider:适合本地优先、单机 workspace、调试和导入导出。 浏览器 localStorage 只保存最近 session/agent 之类的轻量指针,不是对话历史主存储。 ## 删除语义 Session Tree 删除必须按会话实际所属的 provider 执行: - 前端 `chat.ts` 删除请求优先携带会话列表中该 session 的 `agentId`。 - 后端 `gateway/session.ts` 在删除前先从 `netaclaw_agent_session` 反查 `sessionId` 所属 `agentId`,再解析该 Agent 的 session backend。 - MySQL provider 删除 `netaclaw_agent_session_entry` 和 `netaclaw_agent_session`;file provider 删除对应 JSONL 文件。 - 随后后端再清理 legacy session/message、subagent_session 和 session-tree 兼容表记录。 这避免了 MySQL 会话在当前选中 Agent 不一致、或前端缺少 `agentId` 时走默认 file provider,导致“点击删除没有效果”。 ## 前端消费 `packages/frontend/src/modules/agent/store/chat.ts` 负责消费 snapshot: - `sessionMeta` 保存会话元信息。 - `entries` 和 `entryById` 保存节点。 - `childrenByParentId` 支持树结构恢复。 - `activePathIds` 控制当前可见路径。 - `visibleEntries` 将树节点投影成对话 UI。 - `subagentRuntimeByBatchId` 和 `toolRuntimeRoutesByBatchId` 记录子 Agent 工具路由展示数据。 对话页滚动、刷新恢复、历史批次展示都应该围绕这些状态实现。 ## 相关页面 - [[agent-runtime]] - [[subagent-session]] - [[context-compaction]] - [[frontend-architecture]] - [[websocket-gateway]] - [[netaclaw-module]] - [[frontend-architecture]] ## 2026-04-22 Subagent Result Metadata `subagent_result` carries durable metadata needed to replay subagent execution after refresh. - `metadata.processEvents`: normalized worker event timeline for UI replay. - `metadata.evidenceSummaries`: structured evidence projection built from subagent tool results. - `metadata.toolRuntimeRoutes`: tool routing diagnostics for the delegated batch. The frontend store should project these fields into dedicated maps keyed by entry id, rather than letting view components parse raw metadata directly. ## 2026-04-23 Continue-From-Entry And Projection Contract Session Tree 现在不仅负责“存什么”,还决定“从哪里继续对话”: - 前端 `chat.ts` 维护 `selectedEntryId`、`switchingLeafEntryId`、`pendingLeafConfirmation`,允许用户选中任意节点后继续发送,而不是只能沿当前 leaf 末尾追加。 - 如果选中的是普通 user 节点,继续发送会基于它的父节点重放该条 user message,形成新的分支。 - 如果选中的是 `branch_summary`、`compaction` 或非当前 leaf 节点,前端会先切换 leaf,再沿该路径继续。 - 这些交互都以 `session.leafEntryId`、`activePath` 和 snapshot 中的树结构为依据,不依赖前端自行缓存一份线性历史。 对子 Agent 相关节点,当前 snapshot 契约也更明确: - `subagent_result.metadata.subagentProjection` 是会话树边界输出的 canonical projection。 - projection 内的 `diagnostics.selectedSource`、`inputSources`、`fallbackUsed` 用来说明当前展示究竟来自 `subagent_result`、旧 tool message,还是历史 metadata fallback。 - 这让 Session Tree 不再只是原始数据容器,而是把“兼容历史记录”和“为当前 UI 供给稳定形态”一起纳入运行时模型。