
1. 多端 WS 连接收不到 OpenClaw tool 事件问题到底出在哪如果你正在用 OpenClaw 做多端联调大概率遇到过这个场景A 端发起一次对话A 端能实时看到stream: tool的工具调用事件B 端明明连的是同一个 gateway、同一个 session却只收到assistant、lifecycle、chat这些消息tool 事件像凭空消失了一样。前端同学第一反应往往是「渲染逻辑写错了」或者「B 端的 WS 客户端没解析 tool」但排查半天发现代码没问题。这个现象的本质是 OpenClaw 后端对不同事件采用了两套完全不同的分发策略。普通消息事件走的是广播逻辑只要连着同一个 gateway 就能收到而 tool 事件走的是定向分发逻辑默认只发给「发起这次 run 的那条 WS 连接」。换句话说tool 是连接级定向消息其他事件是会话级广播消息分发范围天然不同。再往深一层看OpenClaw 网关在每次新建 WS 连接时都会生成一个全新的connIdUUID。哪怕你两个页面用的是相同的gatewayUrl、相同的 token、相同的device.id和client.id只要是两次独立的 WS 连接拿到的connId就完全不同。A 端发起chat.send时后端会把 A 的connId注册为本次runId的 tool 事件接收方后续这个 run 产生的 tool 事件就只发给 AB 自然收不到。所以这不是 bug而是 OpenClaw 的默认设计语义。理解这一点之后解决思路就清晰了要么让 B 端也能订阅到 tool 事件要么让后端把 tool 事件也广播出去。下面我会给出两种无需大改源码的实现方案并配上可复制的配置片段和验证动作。整个过程中多端接入的凭据管理我会统一走 TaoToken 的 Key 通道避免每个端各配一套 token 带来的混乱。2. 前置准备用 TaoToken 统一多端 Key 与 API 通道在动手改配置之前先把多端的调用凭据统一掉。多端联调最烦的就是 A 端一套 key、B 端一套 key改一个参数要同步好几个地方还容易因为 key 不一致导致某些端请求直接 401。我的做法是让所有端共用同一个 TaoToken 通道集中管理调用凭据。TaoToken 的定位是一个统一的模型调用入口你可以把它理解成「一个 Key 走通多端」的凭据中枢。它本身不替代你的编辑器或网关而是负责把调用凭据收敛到一处。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个地址不加 UTM 参数直接用于代码里的 Base URL。具体操作分三步。第一步登录后在控制台创建 API Key入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建完把 Key 复制出来注意它只完整显示一次。第二步如果你要管理多个 Key 或者给不同端分配不同权限可以在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 里统一维护。第三步把 Base URL 和 Key 写进各端的配置里Model ID 按你实际要调用的模型填。这里有个关键点多端共用同一个 Key 时务必确认各端的connId是独立的不要试图通过复用 token 来复用连接。connId是连接级 UUID跟 Key 没有关系Key 只负责鉴权连接身份由网关自己生成。把这两件事分清楚后面排查问题会省很多力气。如果你还想先验证一下 Key 是否可用可以直接在模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 发一条测试消息确认通道通了再往下做多端配置。对于长期跑编码 Agent 的场景也可以了解下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 把额度集中管理。3. 方案一不改源码用 sessions.subscribe 订阅 session.tool 事件第一种方案适合「不想动 OpenClaw 核心源码」的团队。核心思路是B 端不去抢 tool 事件的定向接收权而是主动订阅session.tool事件。这个能力在 OpenClaw 源码层面是存在的只是官方文档没有明确写出来属于可以用的隐藏特性。先看配置。B 端 WS 连接建立成功后客户端主动发一条订阅指令JSON 结构如下{ method: sessions.subscribe, params: {} }这条指令的作用是让当前连接订阅 session 级别的事件流。订阅成功后前端监听的事件名要从原生的tool改成session.tool。也就是说A 端继续监听tool它是发起 run 的连接天然能收到定向事件B 端监听session.tool两边都能拿到工具生命周期事件。前端监听部分的伪代码大概是这样// B 端连接建立后先订阅 ws.onopen () { ws.send(JSON.stringify({ method: sessions.subscribe, params: {} })); }; // B 端监听 session.tool 而不是 tool ws.onmessage (event) { const msg JSON.parse(event.data); if (msg.stream session.tool) { console.log(B 端收到工具事件:, msg.data); renderToolEvent(msg.data); } // 其他广播事件照常处理 if (msg.stream assistant || msg.stream lifecycle) { renderChatEvent(msg); } };这里要注意一个细节sessions.subscribe的params传空对象表示订阅当前 session 下的所有事件。如果你的业务需要按sessionKey过滤可以在params里带上sessionKey字段具体字段名以你实际使用的 OpenClaw 版本为准建议先在测试环境打印一下订阅返回的确认消息。这个方案的优势是零源码改动B 端不需要知道 A 端的connId也不需要后端做任何广播改造。实测下来在同一个 gateway、同一个 session 下A 端发起 runB 端通过session.tool能同步收到工具的开始、进行、结束各个阶段事件。踩过的坑是订阅指令必须在连接建立后、发起任何chat.send之前发送否则可能错过早期事件。4. 方案二快速魔改让 tool 事件同时走定向与广播第二种方案适合「可以接受小幅修改安装包」的团队。思路很直接在 tool 事件的分发逻辑里除了原有的定向发送再补一条广播调用。这样发起 run 的连接照常收到定向事件其他连接也能通过广播收到。先定位到事件分发的位置。在 OpenClaw 网关源码里处理 agent 事件的核心文件是src/gateway/server-chat.ts关键逻辑是判断evt.stream tool后走broadcastToConnIds否则走broadcast。我们要改的就是 tool 分支在定向发送之后补一条广播。修改后的代码片段如下const isToolEvent evt.stream tool; if (isToolEvent) { const recipients toolEventRecipients.get(evt.runId); if (recipients recipients.size 0) { broadcastToConnIds( agent, sessionKey ? { ...toolPayload, ...buildSessionEventSnapshot(sessionKey) } : toolPayload, recipients ); } // 新增让 tool 事件也广播给所有在线连接 broadcast(agent, toolPayload); } else { broadcast(agent, agentPayload); }改动只有一行broadcast(agent, toolPayload);但效果很明显。改完之后A 端作为定向接收方照常收到 toolB 端作为广播接收方也能收到同一份 tool 事件。注意broadcast的第二个参数用的是toolPayload不要误用agentPayload否则 B 端收到的数据结构会不对。改完源码后需要重新构建并重启网关。如果你用的是打包好的安装包找到对应的构建产物替换即可。重启后建议先做一次单端验证只开 A 端发起一次带工具调用的对话确认 A 端仍能正常收到 tool 事件说明定向逻辑没被破坏。然后再开 B 端重复同样的操作确认 B 端也能收到。这个方案的代价是每次升级 OpenClaw 版本时需要重新应用这处改动。建议把改动做成 patch 文件或者记录在团队的升级清单里避免升级后忘记。另外广播会让所有在线连接都收到 tool 事件如果你的 gateway 上连接数很多要注意前端做去重和过滤避免重复渲染。5. 验证请求与成功结果核对 connId 与事件到达顺序配置改完之后必须做一轮完整的验证否则你无法确认到底是配置生效了还是碰巧某条连接收到了事件。验证的核心是核对各端的connId和事件到达顺序。第一步启动多端连接。开两个浏览器标签页或者两个客户端分别连到同一个 gateway。连接建立后在网关日志里找类似这样的行[ws] webchat connected connfd95xxxx... [ws] webchat connected connabf396xxxx...这两行说明有两条独立连接connId分别是fd95xxxx和abf396xxxx。记下这两个 ID后面核对要用。第二步触发 tool 事件。在 A 端假设是fd95xxxx发起一次会调用工具的对话比如让它执行一个需要调用外部工具的任务。此时观察日志应该能看到[ws] ⇄ res ✓ chat.send ... connfd95... streamtool ... targeted clients2 targets1targeted clients2表示当前有 2 条在线连接targets1表示定向发送只给了 1 条也就是 A 端。如果你用了方案二这之后还应该看到一条广播记录说明 tool 事件已经广播给所有连接。第三步核对各端收到的事件。A 端应该同时收到定向的tool事件和广播的tool事件方案二下B 端应该收到广播的tool事件方案二或者session.tool事件方案一。事件到达顺序上定向事件通常先到广播事件紧随其后两者携带的runId和seq应该一致。一个成功的验证结果是A 端和 B 端都能在控制台打印出同一runId下的工具调用开始、进行、结束三个阶段且seq序列连续。如果 B 端只收到部分阶段检查一下是不是订阅指令发晚了或者广播逻辑里toolPayload被覆盖了。6. 本篇常见错误排查401、local proxy failed 与 OAuth 报错多端联调时除了 tool 事件收不到还容易撞上几类鉴权和连接错误。这里按真实报错逐条对照。401 Unauthorized最常见的原因是各端 Key 不一致或者 Key 失效。如果你按第 2 节用了 TaoToken 统一通道先确认所有端的 Base URL 都指向https://taotoken.net/apiKey 都来自同一个控制台。如果只有某一端 401重点查那一端的配置文件看 Key 是不是复制时多了空格或者少了字符。另外注意Key 和connId是两回事401 是鉴权失败跟连接身份无关。local proxy failed这个报错通常出现在本地起了代理层或者网关转发配置不对的时候。检查你的 WS 连接地址是不是被某个本地代理拦截了确认gatewayUrl直连的是 OpenClaw 网关本身而不是中间多了一层转发。如果你在配置里同时写了代理和直连优先用直连地址测试。reading choices 报错这类报错一般出现在解析模型返回结构时choices字段读不到。多端场景下如果 B 端收到的是广播的toolPayload而不是完整的模型响应前端却按模型响应的结构去解析就会报这个错。解决方法是让前端按stream字段分流处理tool和session.tool走工具事件渲染逻辑assistant走消息渲染逻辑不要混用同一套解析函数。OAuth 相关报错如果你在接入 Claude Code 或类似需要 OAuth 的客户端报错往往出在回调地址或者 token 交换环节。这类场景建议直接走 API Key 模式用 TaoToken 的 Key 通道替代 OAuth 流程配置更简单。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各客户端的 Base URL、Key、Model ID 三件套写法。排查时有个通用技巧先在网关日志里搜conn把所有连接的connId列出来再搜runId看这次 run 的 tool 事件定向给了哪个connId。两边一对就能判断是「B 端没订阅」还是「后端没广播」而不是盲目怀疑前端渲染。7. 多端接入的凭据与通道管理建议两种方案落地之后多端同步看 tool 事件的问题基本就解决了。最后补充几点关于凭据和通道管理的经验这些是我在实际多端联调里踩出来的。第一Key 集中管理连接身份各自独立。多端共用同一个 TaoToken Key 没问题但不要试图通过复用 Key 来复用connId。每次新建 WS 连接网关都会生成新的connId这是设计使然。你要做的是让各端在日志里能清楚打印自己的connId方便排查。第二方案选择看团队约束。如果团队不允许改 OpenClaw 源码方案一的sessions.subscribe是首选零改动、可回退。如果团队有自己的构建流程能接受维护一个 patch方案二的广播改造更彻底B 端不需要额外订阅逻辑前端改动更小。第三长期跑 Agent 场景建议把额度也统一。多端联调往往伴随大量工具调用如果每个端各配一套额度很容易出现某个端额度耗尽导致联调中断。Coding Plan 可以把额度集中管理配合统一的 Key 通道多端接入的配置成本能压到最低。第四验证动作要固化成脚本。每次改完配置手动开两个端、发消息、看日志很费时间。建议把「启动双端连接 → 触发 tool → 核对 connId 和事件到达」写成一个可重复执行的检查清单改配置后跑一遍确认没有回归。到这里两种方案的配置、验证和排障都覆盖了。你可以先从方案一入手确认session.tool能收到事件再决定要不要上方案二做广播改造。多端同步这件事核心不是改多少代码而是先把connId和事件分发语义搞清楚剩下的就是配置和验证的功夫。