跳转至

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 级过滤。

修复

...(agentId ? { agentId } : {})

根因 2:旧代码路径被 Jiti 缓存

源文件已改成"覆盖式更新",但运行时仍表现为"叠加式更新"。

原因:OpenClaw 实际加载的是 extensions/ 目录旧代码,Jiti cache 里仍是旧逻辑。

正确逻辑:entry.streamText = text(全量替换)

根因 3:thinking.start 缺少 agentId

导致切换机器人时,"思考中"状态从上一个机器人继承到下一个。

重复消息问题

现象:流式文本不叠加了,但会发两条相同消息

根因: - stream.resumeisComplete=true 时生成临时 ID:resumed-${Date.now()} - 最终 message.send 带真实 messageId - 两者 ID 不同,客户端无法去重

修复stream.resume 需要携带真实 messageId

解决方案

协议层字段完整性修复

历史隔离三层: 1. 服务端存储按 agentId 标记 2. history.sync 回包带 agentId 3. 客户端本地缓存恢复也按 agentId 过滤

流式输出隔离: - text.deltaagentId - thinking.startagentId - stream.resumemessageId

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.synctext.deltathinking.startagentId,会直接导致串台。

连接复用必须以 server/channel/chat 为边界

否则多服务器、多 agent 场景必然污染。

断线恢复应由协议层支持

stream.resume 是正确方向,而不是前端自己缓存重试。

Client-Web 复杂度过高时,先降维验证

去掉 React / SW / IndexedDB / Relay 等复杂变量,用 H5 直连验证协议问题。

Service Worker / Jiti cache / extensions 路径

都可能制造"代码改了但运行没变"的假象。排查问题时必须确认: 1. 代码是否已 build 2. dist 是否真的包含修复 3. 浏览器是否真的加载新 bundle