让多端同步看到 OpenClaw tool 事件:两种无需大改源码的实现方案

近期在围绕鼎道 PSUIP 等核心产品研发时,我们一直通过 OpenClaw 提升研发效率和产品体验,其灵活的事件分发能力为开发提供了关键支撑。但在实际落地适配中,常会观察到下面这种现象:

  • 某条 WS 连接发起一条消息后,这条连接自己能收到 stream: "tool" 的事件
  • 另一个独立的 WS 连接,即使连的是同一个 gateway、同一个 session,也可能收不到这条 tool 事件
  • 但这个独立 WS 连接通常仍然能收到:
    • assistant
    • lifecycle
    • chat
    • agent

这会让人误以为:

  • 是前端渲染出了问题
  • 是当前连接 gateway 的 WS 客户端没有正确解析 tool
  • 或者不同 WS 连接应该天然共享同一条 run 的所有事件

这一问题直接影响前端调试需要攻克的核心卡点。本文结合实际研发场景,深度解析该现象的底层逻辑,并给出适配我们业务场景的解决方案。

核心结论:OpenClaw 事件分发的默认语义

结合鼎道的研发场景拆解,我先明确核心结论:OpenClaw 后端对不同事件的分发策略存在本质差异,这也是导致上述问题的核心原因,而非鼎道产品层的代码问题:

  • 普通消息事件,例如 assistantlifecyclechat
    • 走广播逻辑
  • tool 事件
    • 走定向分发逻辑
    • 默认只发给"发起这次 run 的那条 WS 连接"

所以:

  • A 连接发起 chat.send
    • A 能收到这次 run 的 tool
    • B 通常收不到
  • B 连接发起 chat.send
    • B 能收到这次 run 的 tool
    • A 通常收不到

这并非异常,而是 OpenClaw 的默认设计语义,也是我们适配鼎道产品时需要重点关注的底层逻辑。

根本原因:从源码到鼎道场景的落地分析

tool 事件不走广播逻辑

OpenClaw 在处理 agent 事件时,会先判断事件类型是否为 tool,并分流处理:

ts 复制代码
const isToolEvent = evt.stream === "tool";

然后按两种路径处理:

  • 如果是 tool 事件,调用 broadcastToConnIds(...) 定向发送;
  • 如果不是 tool 事件,调用 broadcast(...) 广播。

这意味着,在 DingOS 和 PSUIP 的研发场景中,tool 是"连接级定向消息",而其他事件是"会话级广播消息",二者的分发范围天然不同。

tool 仅发给已注册的连接

OpenClaw 在处理 chat.send(对应 DingOS 智能助手的交互触发、PSUIP 的前端指令提交)时,会将发起请求的 connId 注册为本次 runIdtool 事件接收方,注册条件为:

  • 当前连接存在有效 connId
  • 连接在 connect.caps 中声明了 tool-events

满足条件时执行:

ts 复制代码
registerToolEventRecipient(runId, connId);

后续该 runId 产生的 tool 事件,仅会发送给这个注册的 connId------这也是鼎道不同研发页面收不到跨连接 tool 事件的核心原因。

tool 只发给已注册的 recipient

OpenClaw 在处理 chat.send 启动一次 run 时,把当前发起请求的 connId 注册到本次 runId 的 tool 事件接收方,注册条件为:。

  • 当前连接存在有效 connId
  • 连接在 connect.caps 里声明了 tool-events

只有满足这两个条件,后端才会执行:

ts 复制代码
registerToolEventRecipient(runId, connId)

后续该 runId 产生的 tool 事件,仅会发送给这个注册的 connId,这也是鼎道不同研发页面收不到跨连接 tool 事件的核心原因。

新 WS 连接会生成新 connId

在 OpenClaw 网关源码中,每次新建 WS 连接都会生成全新的 connId(UUID):

ts 复制代码
const connId = randomUUID();

这意味着,即便 DingOS 控制台和 PSUIP 调试页面使用相同的 gatewayUrltokendevice.idclient.id,只要是两次独立的 WS 连接,就会生成不同的 connId。例如:

  • DingOS 控制台的 WS 连接:connId=fd95xxxx
  • PSUIP 调试页面的 WS 连接:connId=abf396xxxx

二者属于完全独立的连接,自然无法共享定向分发的 tool 事件。

研发场景下的源码链路参考

结合 OpenClaw 的实际适配,我梳理了与该问题直接相关的核心源码位置,便于定位和调整:

chat.send 时注册 tool 接收方

文件:src/gateway/server-methods/chat.ts

关键逻辑:

ts 复制代码
const connId = typeof client?.connId === "string" ? client.connId : undefined;
const wantsToolEvents = hasGatewayClientCap(
  client?.connect?.caps,
  GATEWAY_CLIENT_CAPS.TOOL_EVENTS,
);
if (connId && wantsToolEvents) {
  context.registerToolEventRecipient(runId, connId);
}

在 DingOS 场景中,这段逻辑决定了"哪条连接发起智能助手交互,哪条连接就会被注册为 tool 接收方"。

tool 与普通消息分开发送

文件:src/gateway/server-chat.ts

关键逻辑:

ts 复制代码
const isToolEvent = evt.stream === "tool";
if (isToolEvent) {
  const recipients = toolEventRecipients.get(evt.runId);
  if (recipients && recipients.size > 0) {
    broadcastToConnIds("agent", toolPayload, recipients);
  }
} else {
  broadcast("agent", agentPayload);
}

这也是鼎道多端场景下,tool 事件仅单端可见的核心代码逻辑。

定向发送的核心实现

文件:src/gateway/server-broadcast.ts

关键逻辑:

ts 复制代码
const broadcastToConnIds = (event, payload, connIds, opts) => {
  if (connIds.size === 0) {
    return;
  }
  broadcastInternal(event, payload, opts, connIds);
};

该方法仅向指定 connIds 发送消息,而非所有在线客户端,直接导致多端无法共享 tool 事件。

为什么普通消息在多端能正常同步?

在 调试中,stream=assistantstream=lifecycleevent chat 等普通消息能正常同步,核心原因是这些事件走广播路径,不受 connId 限制。这也解释了研发过程中"文本回复多端可见,但工具调用仅单端可见"的现象------并非我们前端代码问题,而是 OpenClaw 后端分发策略的差异。

在研发场景下的日志排查方法

在调试相关功能时,可通过以下日志特征定位 tool 事件问题:

判断 tool 是否定向发送

日志中若出现:

text 复制代码
stream=tool ... targeted clients=2 targets=1

说明当前网关下有 2 条在线连接,但该 tool 仅定向发送给 1 条,符合默认逻辑。

定位发起 run 的连接

日志中若出现:

text 复制代码
[ws] ⇄ res ✓ chat.send ... conn=fd95...

说明本次 chat.sendfd95... 连接发起,后续 tool 仅会发送给该连接。

确认前端连接的 connId

日志中若出现:

text 复制代码
[ws] webchat connected conn=abf396...

说明当前调试页面的连接 ID 为 abf396...,若本次 run 由 fd95... 发起,则调试页面仅能收到广播事件,无法收到 tool

研发过程中的常见误区(鼎道场景适配)

误区一:同一 sessionKey 应共享 tool 事件

实际:OpenClaw 中 toolrunId -> connId 绑定分发,而非仅按 sessionKey 广播,需针对性调整。

误区二:相同 token/device/client 参数复用 connId

实际:connId 是连接级 UUID,每次新建连接都会重新生成,即便 调试页面参数完全一致,也会生成不同 connId

误区三:前端未显示 tool 就是渲染 Bug

实际:更常见的原因是后端仅将 tool 发送给发起 run 的连接,当前前端并非该连接,因此天然收不到。

公司业务场景下的解决方案

针对公司业务场景需求,我们梳理了三种适配方案,兼顾"无源码修改""快速魔改""长期适配"的不同诉求:

无源码修改:利用 session.tool 订阅能力

若不想修改 OpenClaw 核心源码,可借助 OpenClaw 源码层面的 session.tool 事件能力(非官方文档明确特性),适配 DingOS/PSUIP 的多端观测需求:

  1. WS 连接成功后,客户端主动调用订阅指令:
json 复制代码
{
  "method": "sessions.subscribe",
  "params": {}
}
  1. 前端监听 session.tool 事件(而非原生 tool 事件)。

该方案已在鼎道研发环境验证有效:调试页面通过订阅 session.tool,可同步接收 run 产生的工具生命周期事件,无需修改后端源码,快速解决联调过程中的信息断层问题。

快速魔改:调整 tool 事件分发逻辑

可直接修改 OpenClaw 安装包中的事件分发逻辑,让 tool 既定向发送又广播:

js 复制代码
if (isToolEvent) {
  if ((typeof evt.data?.phase === "string" ? evt.data.phase : "") === "start" && isControlUiVisible && sessionKey && !isAborted) {
    flushBufferedChatDeltaIfNeeded(sessionKey, clientRunId, evt.runId, evt.seq);
  }
  const recipients = toolEventRecipients.get(evt.runId);
  if (recipients && recipients.size > 0) {
    broadcastToConnIds("agent", sessionKey ? {
      ...toolPayload,
      ...buildSessionEventSnapshot(sessionKey)
    } : toolPayload, recipients);
  }
  // 新增广播逻辑,适配鼎道多端场景
  broadcast("agent", toolPayload);
}

该修改已在鼎道测试环境验证:

  • 原始逻辑下,只有发起当前 run 的连接能收到 tool
  • 打开 broadcast("agent", toolPayload) 后,其他 WS 客户端也能同步收到 tool

这说明当前问题并不是客户端无法解析 tool,而是后端默认没有把 tool 做广播。

总结

在鼎道各项产品的研发过程中,"不同 WS 连接无法共享 OpenClaw tool 事件"的问题,核心根源是 OpenClaw 对 tool 事件的定向分发设计------tool 仅发送给发起 run 的连接,且每次新 WS 连接都会生成新 connId

我们研发团队通过深入解析 OpenClaw 源码,结合自身业务场景,梳理出"无源码修改""快速魔改"等解决方案,既解决了当下多端调试的痛点,也为后续产品迭代提供了技术支撑。未来,鼎道智联将持续深耕智能交互、前端一体化等领域,结合 OpenClaw 等优秀开源技术的适配经验,不断优化 DingOS、PSUIP 等产品的研发效率与用户体验,同时积极回馈开源社区,推动技术生态的共同发展。

相关推荐
河阿里1 小时前
WebSocket:从零开始到实战项目
网络·websocket·网络协议
第404块砖头2 小时前
WorkBuddy清理Claw历史会话指南
ai·openclaw·workbuddy
组合缺一3 小时前
OpenClaw vs SolonCode:绑定飞书与钉钉,到底谁更简单?
ai·钉钉·飞书·ai编程·数字员工·openclaw·soloncode
七夜zippoe3 小时前
OpenClaw Browser:浏览器控制入门
ai·自动化·浏览器·browser·openclaw
晓杰'12 小时前
从0到1实现 Balatro 游戏后端(2):NestJS框架搭建与项目结构设计
后端·websocket·typescript·node.js·游戏开发·项目实战·nestjs
小新同学^O^15 小时前
简单学习 --> WebSocket
java·websocket·网络协议·学习
Walter先生1 天前
中金所股指期货主力合约自动识别:一个接口搞定 IF/IC/IH 连续合约合成
后端·websocket·架构·实时行情数据源
周易宅1 天前
2026年自主智能体系统架构演进:OpenClaw与Hermes Agent在现代软件生态中的定位、机制与应用
ai·系统架构·openclaw·hermes
带刺的坐椅1 天前
OpenClaw vs SolonCode:绑定飞书与钉钉,到底谁更简单?
钉钉·飞书·openclaw·soloncode