03-21~23:多端联调与协议层收敛¶
这一阶段的核心是 Clawline 流式输出 E2E 打通、多服务器隔离验证,以及关键的架构认知:协议层问题比 UI 层更重要。
Dad 的关键产品决策¶
PM 角色重新定义¶
03-21 决策:
- researcher 应是 PM + 研究员 + 测试员,不是亲自包办所有实现
- 子 agent 负责实现
- researcher 负责拆解、派工、验收测试、汇报
- "不测过不汇报"写入 AGENTS.md
附带决策:子代理尽量不用 Codex,直接用自身模型(如 claude-opus-4.6)
降维验证策略(03-23)¶
背景:Client-Web 出现消息串台、流式输出消失、连接重复等问题
Dad 决策:
暂时不要继续在 React Client-Web 上硬修,改用 ChannelBot 直连 WebSocket 模式 + H5 client 先把协议问题彻底搞清楚
目标: 1. 同页连接多个 agent 2. 多服务器切换 3. 切换 agent/服务器时的隔离 4. stop / 断开 / 重连后的消息恢复
产品要求:断线恢复¶
Dad 要求: - stop 后之前消息是否还会发 - 即使浏览器中途关闭,下次进来应能看到完整消息 - 希望支持类似"断点续传"
技术问题与根因分析¶
Client-Web 架构性故障(03-23 早晨)¶
Dad 反馈的严重问题: - 聊天体验基本不可用 - 选择 A agent 聊天,消息会出现在主 agent 或 B agent 中(串台) - 页面频繁断联重连 - 流式输出消失
历史消息串台根因¶
根因 1:ChatList 导航未正确传递 chatId
- 直接导航到 /chat/main?connectionId=xxx 历史能加载
- 从 ChatList 点击进入时,history 请求根本没发
- onOpenChat(connection.id, agent.id) 没把必要的 chatId 传下去
根因 2:服务端 history.sync 缺少 agentId
Channel 插件 sendHistorySync 返回的数据只有:
- requestId
- chatId
- messages
- timestamp
没有 agentId!
客户端过滤逻辑:只有 historyAgentId 和当前 agentId 都存在时才过滤。一旦服务端不带 agentId,所有消息都会通过。
修复:在 sendHistorySync 中补上 agentId
根因 3:客户端本地缓存未按 agentId 过滤
即使服务端历史已按 agentId 过滤,前端从 IndexedDB/本地缓存恢复消息时仍可能把别的 agent 消息带进来。
多服务器串台问题¶
现象: - 先连接 Owl,再连接 Wolf - Wolf 显示 Owl 的 agent 列表
根因:连接实例复用条件错误,把不同服务器/不同 channel 的连接混在一起
修复:
- connect() 只比较 serverUrl + chatId
- WS URL 不再错误携带 agentId
流式输出串台问题¶
现象: - A agent 正在 streaming 时切到 B,B 页面出现 A 的回复 - 甚至出现"一直在输出停不下来"
根因 1:text.delta 没有 agentId
sendStreamDelta 发出的 text.delta 只有:
- chatId
- text
- done
- timestamp
客户端无法做 agent 级过滤。
修复:
根因 2:旧代码路径被 Jiti 缓存
源文件已改成"覆盖式更新",但运行时仍表现为"叠加式更新"。
原因:OpenClaw 实际加载的是 extensions/ 目录旧代码,Jiti cache 里仍是旧逻辑。
正确逻辑:entry.streamText = text(全量替换)
根因 3:thinking.start 缺少 agentId
导致切换机器人时,"思考中"状态从上一个机器人继承到下一个。
重复消息问题¶
现象:流式文本不叠加了,但会发两条相同消息
根因:
- stream.resume 在 isComplete=true 时生成临时 ID:resumed-${Date.now()}
- 最终 message.send 带真实 messageId
- 两者 ID 不同,客户端无法去重
修复:stream.resume 需要携带真实 messageId
解决方案¶
协议层字段完整性修复¶
历史隔离三层:
1. 服务端存储按 agentId 标记
2. history.sync 回包带 agentId
3. 客户端本地缓存恢复也按 agentId 过滤
流式输出隔离:
- text.delta 带 agentId
- thinking.start 带 agentId
- stream.resume 带 messageId
SDK 抽取¶
架构决策:从 Client-Web 抽离 @clawlines/sdk
新架构:
- @clawlines/sdk:协议、连接、消息分发
- client-web:React UI 层
- channel plugin:服务端 WS/历史/流式事件
- relay gateway:中继层
断线恢复验证¶
协议层验证结果:
- 旧版本 Owl 代码没有 stream.resume
- 更新到包含 stream-state 的版本后
- 断开再重连时成功收到 stream.resume
结论:协议层已证明"断线恢复流式消息"可行。
里程碑¶
03-21¶
- [x] PM 角色重新定义
- [x] 跨 bot 派工通信链路打通(
sessions_spawn) - [x] PortalBot Dashboard 重构事故与回滚
- [x] 知识治理策略调整(代码细节转移给子 bot)
03-22¶
- [x] 三端 E2E 联调通过(Relay/Channel/Client/Gateway/Memory)
- [x] 源码目录 SSOT 治理
- [x] Client Web 列表交互重构
- [x] 文档站规划启动
03-23¶
- [x] 识别 Client-Web 架构性故障
- [x] 历史消息串台根因定位并修复
- [x] Owl/Wolf 多服务器隔离打通
- [x]
stream.resume在 H5 直连环境验证成功 - [x] 协议层字段完整性修复方案明确
关键提交¶
| 仓库 | 提交 | 说明 |
|---|---|---|
| client-web | 005c03a9 |
feat: add text.delta streaming support |
架构经验总结¶
协议字段完整性比 UI 修补更重要¶
history.sync、text.delta、thinking.start 缺 agentId,会直接导致串台。
连接复用必须以 server/channel/chat 为边界¶
否则多服务器、多 agent 场景必然污染。
断线恢复应由协议层支持¶
stream.resume 是正确方向,而不是前端自己缓存重试。
Client-Web 复杂度过高时,先降维验证¶
去掉 React / SW / IndexedDB / Relay 等复杂变量,用 H5 直连验证协议问题。
Service Worker / Jiti cache / extensions 路径¶
都可能制造"代码改了但运行没变"的假象。排查问题时必须确认: 1. 代码是否已 build 2. dist 是否真的包含修复 3. 浏览器是否真的加载新 bundle