深入OpenClaw源代码(5):消息传递的完整旅程 —— 以飞书插件为例
前四篇我们剖析了 Gateway 中枢(OpenClaw Gateway 启动全流程解析(以设计模式为骨架的深度剖析))、插件/工具/技能体系(深入OpenClaw源代码(2):Plugin、Tool、Skill的架构设计)、智能体 Agent(深入OpenClaw源代码(4):读懂 OpenClaw 的 System Prompt——一份“智能体操作系统”的说明书) 以及 System Prompt 的构造(深入OpenClaw源代码(4):读懂 OpenClaw 的 System Prompt——一份“智能体操作系统”的说明书)。本篇将聚焦消息在 OpenClaw 中的流转路径,从飞书平台接收消息,到智能体处理,再到回复返回用户,一图胜千言,带你看清代码背后的设计哲学。

一、消息传递的总体架构
在 OpenClaw 中,消息的传递遵循“插件适配,核心处理”的分层原则:
-
渠道插件(如飞书)负责与外部平台建立连接、接收原始事件、转换为内部统一格式,并将智能体生成的回复发送回平台。
-
Gateway 主进程是系统的中枢,提供 WebSocket 服务供所有插件和客户端连接,统一处理消息路由、会话管理、智能体调度。
-
自动回复核心(auto-reply 模块)封装了完整的 Agent 处理流程,包括上下文构建、模型调用、工具执行、回复生成等。
整个架构可以概括为:插件接收消息 → 通过 WebSocket RPC 发送给 Gateway → Gateway 调度智能体处理 → 回复通过同一 WebSocket 连接推回插件 → 插件发送到平台。
本文以飞书插件为例,一步步拆解这个过程,并给出对应的源代码文件。
二、飞书插件:从注册到连接
2.1 插件的“身份证”:channel.ts
每个渠道插件都有自己的入口文件(如飞书插件位于 extensions/feishu/src/channel.ts),在该文件中导出一个符合 ChannelPlugin 接口的对象,用于向 OpenClaw 核心描述插件的元数据、配置、启动逻辑等。
export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {id: "feishu",meta: { ... },// 网关启动时的回调gateway: {startAccount: async (ctx) => {const { monitorFeishuProvider } = await import("./monitor.js");return monitorFeishuProvider({ ... });},},// 其他配置...};
当 OpenClaw 启动且配置了飞书账号时,核心会调用 startAccount,进而启动 monitorFeishuProvider,开始监听消息。
2.2 与飞书平台建立连接:monitor.transport.ts
飞书插件支持两种连接模式:WebSocket 长连接(推荐)和 Webhook。在 WebSocket 模式下,monitorWebSocket 函数通过飞书 SDK 的 WSClient 建立连接。
export async function monitorWebSocket({ account, eventDispatcher }) {const wsClient = createFeishuWSClient(account);wsClient.start({ eventDispatcher });}
-
createFeishuWSClient使用官方 SDK 的WSClient发起 WebSocket 连接,连接目标是飞书开放平台。 -
eventDispatcher用于注册事件处理器,当飞书推送消息时,会触发对应回调。
三、消息接收与转换
3.1 事件分发与预处理:monitor.account.ts
飞书 SDK 的 eventDispatcher 注册了 im.message.receive_v1 事件处理器,当收到消息时,会执行去重和防抖逻辑。
eventDispatcher.register({"im.message.receive_v1": async (data) => {const event = data as FeishuMessageEvent;if (!tryBeginFeishuMessageProcessing(messageId, accountId)) return; // 去重await inboundDebouncer.enqueue(event); // 防抖队列},});
-
去重:防止飞书可能重复推送同一消息。
-
防抖:将短时间内来自同一用户的连续文本消息合并,避免智能体频繁触发。
防抖队列刷新后,最终会调用 handleFeishuMessage(位于 bot.ts)。
3.2 消息解析与上下文构建:bot.ts
handleFeishuMessage 是整个插件的业务核心,它负责将飞书原始事件转换为 OpenClaw 内部统一的 MsgContext。
export async function handleFeishuMessage(params) {// 1. 解析消息内容let ctx = parseFeishuMessageEvent(event, botOpenId);// 2. 处理合并转发等特殊类型if (event.message.message_type === "merge_forward") {// 通过 API 拉取完整内容}// 3. 获取发送者显示名(可选)if (feishuCfg?.resolveSenderNames ?? true) {const senderResult = await resolveFeishuSenderName({...});ctx.senderName = senderResult.name;}// 4. 访问控制与策略if (isGroup) {// 群聊允许列表、是否需要@机器人等} else {// 私聊允许列表、配对机制}// 5. 构建智能体上下文const body = core.channel.reply.formatAgentEnvelope({channel: "Feishu",from: envelopeFrom,timestamp: new Date(),body: messageBody,});// 6. 创建回复分发器const { dispatcher, replyOptions, markDispatchIdle } = createFeishuReplyDispatcher({...});// 7. 将消息提交给智能体处理await core.channel.reply.dispatchReplyFromConfig({ctx: agentCtx,cfg,dispatcher,replyOptions,});}
关键函数:
-
parseFeishuMessageEvent:提取消息文本、提及、媒体等信息。 -
formatAgentEnvelope:给消息加上渠道信封(时间戳、发送者等),便于智能体区分上下文。 -
createFeishuReplyDispatcher:创建一个回复分发器,用于后续接收智能体的回复。 -
dispatchReplyFromConfig:核心 API,将消息提交给智能体。
四、Gateway 内部处理链
dispatchReplyFromConfig 是 OpenClaw 核心库提供的函数(位于 src/auto-reply/reply/dispatch-from-config.ts),它通过插件与 Gateway 主进程之间的 WebSocket 连接发送 RPC 请求,方法名为 chat.send。
Gateway 主进程的 WebSocket 服务器收到请求后,路由到 src/gateway/server-methods/chat.ts 中的 chatHandlers["chat.send"] 处理:
// chat.ts 中简化的处理逻辑async function handleChatSend(params) {const ctx = buildMsgContext(...);const dispatcher = createReplyDispatcher(...);await dispatchInboundMessage({ ctx, cfg, dispatcher });}
-
dispatchInboundMessage(位于src/auto-reply/dispatch.ts)通过withReplyDispatcher管理资源生命周期,最终调用dispatchReplyFromConfig。 -
dispatchReplyFromConfig解析配置、决定路由(是否跨渠道),并调用getReplyFromConfig(位于src/auto-reply/reply/get-reply.ts)。 -
getReplyFromConfig负责加载会话状态、模型配置,运行真正的 Agent 循环(模型推理 + 工具调用)。
在 Agent 运行过程中,产生的中间结果(流式块、工具结果)通过回调(onBlockReply、onToolResult)返回给 dispatch-from-config.ts,后者通过 dispatcher 将内容通过 WebSocket 推送给插件。
五、回复返回:从 Gateway 到飞书用户
5.1 插件接收回复:dispatcher 的 deliver 回调
插件在调用 createFeishuReplyDispatcher 时,传递了一个 deliver 回调。当 Gateway 推送回复事件时,这个回调会被触发。
// reply-dispatcher.ts 中的 deliver 实现deliver: async (payload: ReplyPayload, info) => {const text = payload.text ?? "";const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);const useCard = renderMode === "card" || (renderMode === "auto" && shouldUseCard(text));if (useCard) {await sendStructuredCardFeishu({...}); // 发送消息卡片} else {await sendChunkedTextReply({ text, useCard: false }); // 发送普通文本(支持分块)}for (const mediaUrl of mediaList) {await sendMediaFeishu({...}); // 发送媒体文件}}
-
对于长文本,
sendChunkedTextReply会根据飞书平台限制(4000 字符)自动分块。 -
对于 Markdown 或表格等富文本,可以选择发送为飞书卡片,提升阅读体验。
-
最终通过飞书 SDK 的
sendMessageFeishu等函数将消息发送回用户。
5.2 整个流程的序列图
下面用 Mermaid 序列图完整展示从飞书用户发送消息到收到回复的全过程,包括插件内部、Gateway 内部的关键节点。

六、总结:插件开发的黄金模式
飞书插件的实现清晰地展示了 OpenClaw 渠道插件的标准架构:

这种分层设计带来的好处:
-
解耦:插件只需关心消息格式转换和平台 API 调用,无需理解智能体内部逻辑。
-
复用:所有渠道共享相同的智能体处理流程(
dispatch.ts、get-reply.ts等)。 -
扩展性:新增渠道只需实现上述四部分,即可享受 OpenClaw 的全部智能体能力。
通过本文,我们完整地走了一遍从飞书消息到智能体回复的旅程。相信您对 OpenClaw 的消息流转机制有了更直观的认识。下篇我们将深入探讨 OpenClaw 的会话管理与记忆机制,敬请期待。
夜雨聆风