第 8 章:持久化、投影与 Web 客户端
1. 本章定位
把 Host 的 durable Session 事件追到新版 Client Session、对话节点装配和 React keyed Slot。
2. 学习目标
- 区分 persistence、projection、checkpoint 与 Client snapshot。
- 解释
page()/follow()的分页和追帧模型。 - 追踪
Session、Notifier、ConversationNodeAssembler。 - 解释
useSyncExternalStore与 keyed Slot。
3. 前置知识
可把 Client Session 类比框架无关的外部 Store,把 projection 类比纯 selector,把 keyed Slot 类比插件化渲染插槽。局限是 Host Log 才是 durable truth,浏览器快照可随时丢弃重建。
4. 对应源码
packages/api/session-controller/src/index.ts:369:page()。packages/api/session-controller/src/index.ts:380:follow()。packages/api/session-controller/src/client/sessions/session.ts:81:ClientSession。packages/api/session-controller/src/client/sessions/session.ts:459:subscribe()。packages/api/session-controller/src/client/sessions/session.ts:467:getSnapshot()。packages/api/session-controller/src/client/sessions/notifier.ts:8:Notifier。packages/client/ui-conversation/src/client/conversation/assembler.ts:158:ConversationNodeAssembler。packages/client/ui-renderer/src/client/scoped-slots.tsx:287:useSyncExternalStore。
5. 工作原理
Host 用 page() 提供历史窗口,用 follow() 继续发送有序事件;Client Session 维护游标、去重和对象层状态;Assembler 把事件变成对话节点;Notifier 合批后发布引用稳定的 snapshot;React 通过 useSyncExternalStore 订阅。keyed Slot 根据 wire tool name 选择工具卡片。
6. 执行流程
无图回退:Session Log → SessionController page/follow → Client Session → Assembler/Notifier → immutable snapshot → useSyncExternalStore → conversation 与 tool card。
7. 关键源码讲解
getSnapshot() 在没有领域变化时必须返回同一引用,否则 React 会无意义重渲染。浏览器插件不应自行配对原始事件,也不应导入 Host 工具实现;Client Session 和 tool/result.meta 才是跨进程合同。
8. 调试与观察方法
按 Host seq → page/follow frame → Client cursor → assembler nodes → snapshot 引用 → React render 次数定位。常见故障:重复帧、断号、每 token 重建根对象、slot key 冲突、malformed meta 未回退。
9. 本章实践任务
在现有 Client 测试追加两个 chunk 与一个 tool/result,记录通知批次和 snapshot 引用;再注入重复/断号帧,并卸载一个 keyed Slot provider。验收:能区分“Host 已 durable 但 UI 未刷新”和“UI 有流式内容但最终 message 未形成”。
10. 常见误区
- 把浏览器 Store 当持久化真相。
- 让 React 直接消费网络帧。
- 让可选 UI 插件互相 import,绕开 Slot 生命周期。
11. 自测题
page()与follow()如何配合?- Notifier 为什么要合批?
- snapshot 引用稳定性为何重要?
- Assembler 与 React Renderer 各负责什么?
- keyed Slot 如何让 Host/Browser 解耦?