OpenClaw / Feishu Channel / Agent Runtime 三者关系
最近虎sir在docker容器以及vmware 虚拟机中安装了openclaw,解决了一些实际问题,聊天通道为飞书,配置飞书机器人与后台Agent进行交互,下面就用户与openclaw的交互做一个简要概述。

一、三层角色
┌────────────────────────────────────────────────────────────────────────────┐│ Layer 1: 用户层 — Feishu Backend (飞书开放平台) ││ domain = "open.feishu.cn" | https://open.feishu.cn/open-apis/... ││ 协议:WebSocket (LarkWSClient) + HTTPS REST ││ 持有:appId/appSecret、事件订阅、消息存储、卡片数据 │└──────────────────────────────┬─────────────────────────────────────────────┘│ WebSocket 事件推送 + HTTPS API 调用▼┌────────────────────────────────────────────────────────────────────────────┐│ Layer 2: 通道层 — Feishu Channel Plugin (openclaw-lark 插件) ││ 位置: /config/.openclaw/extensions/openclaw-lark/ ││ 进程: 嵌入 openclaw-gateway (PID 30112, 单一 Node 进程) ││ 职责: 收消息 → 解析 → 安全闸门 → 富化 → 转发给 Agent ││ 关键模块: ││ src/channel/monitor.js ← WebSocket 入口, 创建 LarkClient + WSClient ││ src/channel/event-handlers.js ← 注册飞书事件路由 ││ src/channel/plugin.js ← ChannelPlugin 接口实现 ││ src/messaging/inbound/handler.js ← 7 阶段流水线 ││ src/messaging/inbound/dispatch.js ← 调用 runtime.channel.reply ││ src/messaging/outbound/* ← 发送消息/卡片/媒体 ││ src/core/lark-client.js ← SDK 封装 ││ src/tools/oapi/index.js ← 注册 OAPI 工具 (calendar, task) ││ src/tools/mcp/doc/index.js ← 注册 MCP 文档工具 │└──────────────────────────────┬─────────────────────────────────────────────┘│ dispatchToAgent → core.channel.reply.dispatch*│ (进程内函数调用 + 事件总线)▼┌────────────────────────────────────────────────────────────────────────────┐│ Layer 3: Agent Runtime — OpenClaw Gateway ││ 进程: openclaw-gateway (PID 30112) ││ Agents: ││ /config/.openclaw/agents/main ← 我 (当前 webchat session) ││ /config/.openclaw/agents/joshua-work ← 飞书 "work" 账号绑定的 agent ││ /config/.openclaw/agents/joshua-life ← 飞书 "life" 账号绑定的 agent ││ /config/.openclaw/agents/joshua-invest ← 飞书 "invest" 账号绑定的 agent ││ 每个 agent 持有: agent/ 目录、sessions/ 历史、模型配置 (models.json) ││ 凭据: /config/.openclaw/credentials/lark.secrets.json (4 个 appSecret) │└────────────────────────────────────────────────────────────────────────────┘
二、协议 / 数据流详解
入站:你发消息给我
[你]│ 1. 在飞书 DM 发文本消息 "查余额"│▼ 飞书 IM 服务器│ 2. 通过 WebSocket (Lark/Event v2 envelope) 推送事件│ URL: wss://open.feishu.cn/... (resolveBrand('feishu'))│ 协议: larksuite open SDK WSClient 内部的长连接│ 负载示例 (简化):│ {│ "schema": "2.0",│ "header": { "app_id": "cli_xxx", "event_type": "im.message.receive_v1" },│ "event": {│ "message": { "message_id": "om_...", "chat_id": "oc_...", "content": "{\"text\":\"查余额\"}" },│ "sender": { "sender_id": { "open_id": "ou_..." } }│ }│ }│▼ [Feishu Channel Plugin - monitor.js]│ 3. LarkClient.startWS() 创建 WSClient,│ EventDispatcher.register({ "im.message.receive_v1": handleMessageEvent })│ 入站事件分发到 handleMessageEvent│▼ [event-handlers.js]│ 4. isEventOwnershipValid 检查 app_id 匹配 (防串号)│ MessageDedup 去重 (TTL 缓存, 防 WS 重连导致重复投递)│ 用 chat_id (+ thread_id) 走 chat-queue.js 串行化 (避免同一个 chat 并发混乱)│▼ [handler.js - 7 阶段流水线]│ 5. ① 解析账号 (accounts.getLarkAccount → joshua-work/life/invest)│ ② parseMessageEvent → MessageContext (合并转发消息展开)│ ③ enrich.resolveSenderInfo (批量预取用户姓名)│ ④ gate.checkMessageGate ← 权限闸门:│ - 白名单 allowFrom│ - 群策略 requireMention (群消息必须 @机器人)│ - 多账号隔离: accountScopedCfg 替换 cfg.channels.feishu│ ⑤ 富化 (内容解析, 媒体下载, 引用消息展开)│ ⑥ chat-queue 排队│ ⑦ dispatch.dispatchToAgent → 构造 agent envelope│▼ [dispatch.js]│ 6. dc.core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({│ ctx: { from, to, rawBody, senderId, chatType, accountId, ... },│ cfg: accountScopedCfg,│ replyOptions: { skillFilter } ← 决定 agent 能用哪些 skill│ })│ 这一步是进程内调用 + 事件总线, 走 OpenClaw plugin-sdk 的│ "channel.reply" 子系统│▼ [Agent Runtime - openclaw-gateway]│ 7. runtime 找到目标 agent (按 accountId 绑定到 joshua-* agent)│ agent 加载自己的:│ - workspace (AGENTS.md, SOUL.md, USER.md, IDENTITY.md, BOOTSTRAP.md)│ - sessions 历史│ - skills (本会话上下文已加载: feishu-*, htsc-*)│ - 模型 (models.json)│ 8. agent 调 LLM (我用的是 minimax/MiniMax-M3)│ 9. agent 决定调工具: 例如 feishu_im_user_message (以用户身份发),│ 或我装好的 htsc a-share-paper-trading│▼ [工具调用路径 - 任选]│ A. 飞书 OAPI 工具 (feishu_*) → lark-client.js → HTTPS POST│ https://open.feishu.cn/open-apis/...│ Authorization: Bearer <tenant_access_token | user_access_token>│ B. 飞书 MCP 工具 (doc/wiki/drive) → MCP 协议 (model-context-protocol)│ C. 本地/扩展 skill (htsc-*) → 直接 HTTP POST 后端 (例如 ai.zhangle.com)│▼ [Outbound 写回]│ 10. agent 的回复被 reply-dispatcher 接管│ - 流式卡片 (Streaming Card) → updateCardFeishu (PATCH card)│ - 普通文本 → sendMessageFeishu / sendTextLark│ - 富卡片 → sendCardLark│ - 图片/文件/音频 → uploadImageLark + sendImageLark 等│▼ [飞书 IM 服务器 → 你的飞书客户端]11. 卡片/文本/图片渲染在你的飞书里
三、协议清单(按层)
|
|
|
|
|
|---|---|---|---|
|
|
|
wss://open.feishu.cn/... |
|
/open-apis/im/v1/... |
|
https://open.feishu.cn/open-apis/im/v1/messages |
|
/open-apis/authen/v2/oauth/token |
|
https://open.feishu.cn/open-apis/authen/v2/oauth/token |
|
/open-apis/doc/v2/... |
|
|
|
/open-apis/bitable/v1/... |
|
|
|
/open-apis/calendar/v4/... |
|
|
|
/open-apis/task/v2/... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
https://ai.zangle.com
|
|
|
|
|
|
|
四、关键约束(从代码里挖出来的)
1.单进程:所有 plugin + agents + WebSocket 连接都在一个 openclaw-gateway (PID 30112) 里跑(cwd 是 /home/joshua)。2.多账号 = 多 agent:4 个飞书 app 配置(main / work / life / invest),凭据存 /config/.openclaw/credentials/lark.secrets.json,每个账号绑定一个独立 agent(joshua-work 等)。账号之间通过 accountScopedCfg 严格隔离配置。3.WebSocket 模式:connectionMode 默认且当前唯一支持的是 websocket;webhook 模式 README 标注未实现。4.串行化:同一个 chat_id + thread_id 通过 chat-queue.js 排队,避免同一会话并发引发乱序。5.去重:MessageDedup 在 WS 重连时按 message_id 去重(默认 TTL 默认上限)。6.安全闸门:gate.js 强制 requireMention(群消息不 @ 机器人就丢)+ allowFrom 白名单。7.两套工具注册: OAPI 工具(calendar/task)走飞书 Open API MCP 工具(doc/wiki/drive)走 Model Context Protocol OAuth 工具单独注册8.卡片 vs 文本:回复走流式卡片(dispatchReplyWithBufferedBlockDispatcher),支持 Thinking/Generating/Complete 三态 + 流式打字效果
五、用一句话总结
Feishu Backend 通过 WebSocket 推事件给 openclaw-lark 插件的 WSClient,插件在进程内用 7 阶段流水线(解析 → 富化 → 权限闸门 → 排队)处理后,通过 runtime.channel.reply.dispatch* 把消息塞进对应 agent(按账号绑定到 joshua-work/life/invest),agent 在自己 workspace 里跑 LLM + 工具(OAPI / MCP / 本地 skill),最终回复经 reply-dispatcher 流式卡片或文本回写飞书 IM。
夜雨聆风