一、飞书消息任务流程分析
把整个系统想象成一个「餐厅」
用户 ──────► 飞书服务器 ──► OpenClaw Gateway ──► AI Agent ──► 飞书服务器 ──► 用户
(外卖平台) (餐厅前台) (后厨) (外卖平台)
1. 消息接收:「接单」
场景: 顾客在外卖平台下单
用户发送消息
│
▼
飞书服务器 ──收到订单──► OpenClaw Gateway
(外卖平台) (餐厅前台)
做了什么:
- 飞书服务器收到用户消息
- 通过 WebSocket 或 Webhook 推送给 OpenClaw
- 就像外卖平台把订单推送给餐厅
关键文件: extensions/feishu/src/monitor.transport.ts
2. 消息验证:「验单」
场景: 前台收到订单,检查订单是否有效
inboundDebouncer.enqueue() ── 消息去重/防抖
│
▼
handleFeishuMessage() ── 解析消息 + 权限检查
│
├── 是群聊?需要 @ 机器人吗?
├── 用户在白名单吗?
└── 机器人有没有回复权限?
关键文件: extensions/feishu/src/bot.ts
3. 路由到 Agent:「分单到后厨」
场景: 前台确认订单有效,交给后厨
dispatchReplyFromConfig() ── 消息分发给 AI
│
▼
getReplyFromConfig() ── 准备做饭(加载会话、解析指令)
│
▼
runPreparedReply() ── 把食材准备好
关键文件: src/auto-reply/reply/dispatch-from-config.ts
4. AI 处理:「炒菜」
场景: 后厨开始做菜
runReplyAgent() ── 启动做菜流程
│
▼
runEmbeddedPiAgent() ── 调用 AI 模型
│
▼
┌─────────────────────────────┐
│ AI 模型 │
│ "分析消息,生成回复" │
└─────────────────────────────┘
AI 做了什么:
- 理解用户说的话
- 查看对话历史(记忆)
- 生成合适的回复
关键文件: src/agents/pi-embedded.ts
5. 回复发送:「出餐」
场景: 菜做好了,交给外卖小哥
ReplyPayload 生成 ── 装盘
│
▼
sendMessageFeishu() ── 发送文本消息
或
sendCardFeishu() ── 发送卡片消息(带格式)
或
FeishuStreamingSession ── 流式发送(边做边上菜)
│
▼
飞书 API ── 外卖小哥取餐
│
▼
用户收到回复
关键文件: extensions/feishu/src/send.ts
完整流程图
用户: "帮我翻译 Hello"
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第一步:接单(消息接收) │
│ │
│ 飞书服务器 ─── WebSocket/Webhook ───► OpenClaw Gateway │
│ │
│ 📁 extensions/feishu/src/monitor.transport.ts │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第二步:验单(权限检查) │
│ │
│ - 是群聊吗?需要 @ 我吗? │
│ - 用户有没有权限? │
│ - 机器人能不能回复? │
│ │
│ 📁 extensions/feishu/src/bot.ts │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第三步:分单(路由到 Agent) │
│ │
│ dispatchReplyFromConfig() │
│ │
│ 📁 src/auto-reply/reply/dispatch-from-config.ts │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第四步:炒菜(AI 处理) │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ AI 模型: "Hello" → "你好" │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 📁 src/agents/pi-embedded.ts │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第五步:出餐(发送回复) │
│ │
│ sendMessageFeishu() ───► 飞书 API ───► 用户 │
│ │
│ 📁 extensions/feishu/src/send.ts │
└─────────────────────────────────────────────────────────────┘
│
▼
用户: "你好"
二、飞书消息任务源码分析
1. 飞书 Extension 入口和消息接收点
关键文件:
extensions/feishu/src/channel.ts- 飞书 channel 插件主入口extensions/feishu/src/monitor.ts- 消息监控入口extensions/feishu/src/monitor.transport.ts- WebSocket/Webhook 传输层extensions/feishu/src/monitor.account.ts- 单账号消息处理
消息接收流程:
Gateway 启动
│
▼
monitorFeishuProvider() [monitor.ts:31]
│
├── monitorSingleAccount() [monitor.account.ts:634]
│ │
│ ├── fetchBotIdentityForMonitor() - 获取机器人身份
│ ├── warmupDedupFromDisk() - 加载去重缓存
│ ├── createEventDispatcher() - 创建 Lark SDK 事件分发器
│ ├── registerEventHandlers() - 注册事件处理器
│ │
│ └── 根据 connectionMode 选择传输方式:
│ │
│ ├── WebSocket: monitorWebSocket() [monitor.transport.ts:84]
│ │ └── wsClient.start({ eventDispatcher })
│ │
│ └── Webhook: monitorWebhook() [monitor.transport.ts:129]
│ └── HTTP Server 处理 webhook 请求
事件注册点 [monitor.account.ts:410-619]:
eventDispatcher.register({
"im.message.receive_v1": ... // 接收消息
"im.message.reaction.created_v1": ... // 表情反应
"im.message.reaction.deleted_v1": ... // 删除反应
"application.bot.menu_v6": ... // Bot 菜单
"card.action.trigger": ... // 卡片交互
})
2. 消息如何路由到 Agents
关键文件:
extensions/feishu/src/bot.ts- 消息处理和路由核心逻辑extensions/feishu/src/policy.ts- 权限策略检查extensions/feishu/src/session-route.ts- Session 路由解析
路由流程:
im.message.receive_v1 事件
│
▼
inboundDebouncer.enqueue() [monitor.account.ts:419] - 消息去重/防抖
│
▼
handleFeishuMessage() [bot.ts:221]
│
├── finalizeFeishuMessageProcessing() - 去重确认
├── parseFeishuMessageEvent() - 解析消息内容
│
├── 权限检查:
│ ├── 群组检查: isFeishuGroupAllowed() - 检查 groupAllowFrom
│ ├── DM 检查: resolveFeishuAllowlistMatch() - 检查 dmPolicy
│ └── 配对检查: createChannelPairingController() - 处理未配对用户
│
├── 消息路由:
│ ├── resolveAgentRoute() - 解析 agent 路由
│ ├── resolveConfiguredBindingRoute() - ACP 会话绑定
│ └── buildFeishuAgentBody() - 构建发送给 agent 的消息体
│
└── dispatchReplyFromConfig() [dispatch-from-config.ts:149]
│
└── getReplyFromConfig() [get-reply.ts:106]
│
└── 传递给 Agent 执行
消息解析 [bot.ts:130-174]:
parseFeishuMessageEvent(event, botOpenId, botName)
├── parseMessageContent() - 解析消息内容
├── checkBotMentioned() - 检查是否 @机器人
├── normalizeMentions() - 规范化提及
└── 返回 FeishuMessageContext
权限策略 [policy.ts]:
groupPolicy: "open" | "allowlist" - 群组策略dmPolicy: "open" | "pairing" | "allowlist" - DM 策略requireMention: 群组是否需要 @机器人才响应
3. Agent 如何处理并生成回复
关键文件:
src/auto-reply/reply/get-reply.ts- 回复生成入口src/auto-reply/reply/get-reply-run.ts- 回复运行逻辑src/auto-reply/reply/agent-runner.ts- Agent 执行器src/auto-reply/reply/agent-runner-execution.ts- 实际 LLM 调用src/agents/pi-embedded.ts- Embedded PI Agent
Agent 处理流程:
dispatchReplyFromConfig()
│
├── 快速中止检查: tryFastAbortFromMessage()
├── 发送策略检查: resolveSendPolicy()
├── ACP 分发: tryDispatchAcpReply() - ACP 会话处理
│
└── getReplyFromConfig() [get-reply.ts:106]
│
├── finalizeInboundContext() - 规范化上下文
├── applyMediaUnderstandingIfNeeded() - 媒体理解
├── applyLinkUnderstandingIfNeeded() - 链接理解
├── initSessionState() - 初始化会话状态
├── resolveReplyDirectives() - 解析指令
│ ├── /think - 思考级别
│ ├── /verbose - 详细模式
│ └── /reset - 重置会话
│
└── runPreparedReply() [get-reply-run.ts:204]
│
├── 构建 prompt
├── 解析队列设置
└── runReplyAgent() [agent-runner.ts:110]
│
└── runAgentTurnWithFallback() [agent-runner-execution.ts:84]
│
└── runEmbeddedPiAgent() [pi-embedded.ts]
│
└── 调用 LLM API 生成回复
消息格式化 [bot.ts:176-219]:
buildFeishuAgentBody()
├── 添加引用内容 [Replying to: "..."]
├── 添加发送者标识 "sender: message"
├── 添加提及提示 [System: ...]
└── 添加消息 ID [message_id: xxx]
4. 回复如何传回飞书
关键文件:
extensions/feishu/src/reply-dispatcher.ts- 回复分发器extensions/feishu/src/send.ts- 消息发送extensions/feishu/src/outbound.ts- 出站适配器extensions/feishu/src/streaming-card.ts- 流式卡片
回复发送流程:
Agent 生成 ReplyPayload
│
▼
createFeishuReplyDispatcher() [reply-dispatcher.ts:95]
│
├── 创建 typing controller (打字指示器)
└── 创建 ReplyDispatcher
│
├── dispatcher.sendToolResult() - 工具结果
├── dispatcher.sendBlockReply() - 块回复(流式)
└── dispatcher.sendFinalReply() - 最终回复
│
▼
reply-dispatcher.ts deliver()
│
├── 流式卡片: streaming.start() / streaming.update() / streaming.close()
│ 或
├── 文本卡片: sendStructuredCardFeishu()
│ 或
└── 纯文本: sendMessageFeishu()
│
▼
sendReplyOrFallbackDirect() [send.ts:121]
│
├── 尝试回复: client.im.message.reply()
└── 失败时直接发送: client.im.message.create()
发送函数链 [send.ts]:
sendMessageFeishu()
→ sendReplyOrFallbackDirect()
→ client.im.message.reply() (回复)
→ client.im.message.create() (直接发送)
sendStructuredCardFeishu()
→ sendCardFeishu()
→ sendReplyOrFallbackDirect()
sendMediaFeishu() [media.ts]
→ 上传媒体到飞书
→ 发送消息
流式卡片 [streaming-card.ts]:
FeishuStreamingSession
├── start() - 创建卡片消息
├── update() - 更新卡片内容(partial)
└── close() - 关闭并发送最终内容
完整消息流时序图
┌─────────────────────────────────────────────────────────────────────────┐
│ 飞书服务器 │
│ im.message.receive_v1 event ──────────────────────────────────────┐ │
└─────────────────────────────────────────────────────────────────────┼───┘
│
WebSocket / Webhook ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ OpenClaw Gateway │
│ │
│ monitor.ts: monitorFeishuProvider() │
│ │ │
│ ▼ │
│ monitor.account.ts: monitorSingleAccount() │
│ │ │
│ ▼ │
│ monitor.account.ts: registerEventHandlers() │
│ │ 注册 im.message.receive_v1 │
│ ▼ │
│ monitor.account.ts: inboundDebouncer.enqueue() │
│ │ 消息防抖/去重 │
│ ▼ │
│ bot.ts: handleFeishuMessage() │
│ │ - 解析消息 │
│ │ - 权限检查 │
│ │ - 路由解析 │
│ ▼ │
│ dispatch-from-config.ts: dispatchReplyFromConfig() │
│ │ - 插件钩子 │
│ │ - 快速中止检查 │
│ │ - ACP 分发 │
│ ▼ │
│ get-reply.ts: getReplyFromConfig() │
│ │ - 会话初始化 │
│ │ - 指令解析 │
│ ▼ │
│ get-reply-run.ts: runPreparedReply() │
│ │ - 构建 prompt │
│ ▼ │
│ agent-runner.ts: runReplyAgent() │
│ │ │
│ ▼ │
│ agent-runner-execution.ts: runAgentTurnWithFallback() │
│ │ - 模型选择 │
│ │ - 错误重试 │
│ ▼ │
│ pi-embedded.ts: runEmbeddedPiAgent() │
│ │ LLM API 调用 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ AI Model │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ReplyPayload 生成 │
│ │ │
│ ▼ │
│ reply-dispatcher.ts: createFeishuReplyDispatcher() │
│ │ - 创建 typing controller │
│ │ - 处理流式/块/最终回复 │
│ ▼ │
│ send.ts: sendMessageFeishu() / sendStructuredCardFeishu() │
│ │ │
│ ▼ │
│ client.im.message.reply() / client.im.message.create() │
│ │ 调用飞书 API │
└───────────────────────────────────────────────────────────────────────────┘
│
飞书 API ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ 飞书服务器 │
│ 消息发送到用户 │
└───────────────────────────────────────────────────────────────────────────┘
关键文件路径汇总
| 阶段 | 文件路径 | 核心函数 |
|---|---|---|
| Channel 入口 | extensions/feishu/src/channel.ts | feishuPlugin - 插件定义 |
| 监控启动 | extensions/feishu/src/monitor.ts | monitorFeishuProvider() |
| 单账号监控 | extensions/feishu/src/monitor.account.ts | monitorSingleAccount(), registerEventHandlers() |
| 传输层 | extensions/feishu/src/monitor.transport.ts | monitorWebSocket(), monitorWebhook() |
| 消息处理 | extensions/feishu/src/bot.ts | handleFeishuMessage(), parseFeishuMessageEvent() |
| 权限策略 | extensions/feishu/src/policy.ts | resolveFeishuReplyPolicy(), isFeishuGroupAllowed() |
| 回复分发 | extensions/feishu/src/reply-dispatcher.ts | createFeishuReplyDispatcher() |
| 消息发送 | extensions/feishu/src/send.ts | sendMessageFeishu(), sendCardFeishu() |
| 出站适配 | extensions/feishu/src/outbound.ts | feishuOutbound |
| 流式卡片 | extensions/feishu/src/streaming-card.ts | FeishuStreamingSession |
| 核心分发 | src/auto-reply/reply/dispatch-from-config.ts | dispatchReplyFromConfig() |
| 回复生成 | src/auto-reply/reply/get-reply.ts | getReplyFromConfig() |
| Agent 运行 | src/auto-reply/reply/agent-runner.ts | runReplyAgent() |
| LLM 执行 | src/auto-reply/reply/agent-runner-execution.ts | runAgentTurnWithFallback() |