飞书(Feishu/Lark)机器人集成
概述
Codev 内置飞书(国内版)和 Lark(国际版)机器人集成,允许用户在即时通讯中与 AI CLI 进行对话。机器人通过 @larksuite/channel SDK 建立长连接 WebSocket,支持私聊、群聊、语音回复、引用回复等功能。
架构
Feishu WebSocket (LarkChannel)
│
▼
FeishuService (singleton)
├── PendingQueue (600ms 去抖)
├── Access Control (DM/Group 策略)
├── Slash Command Router (/stop, /reset, /status, /help)
├── Quoted Context Fetcher
└── TTS Engine (Edge TTS / VoxCPM)
│
▼
messageQueueManager.enqueue()
└── origin: { kind: 'channel', server: 'feishu' }
│
▼
useFeishuBridge (React hook)
├── 收集 AI 回复
└── sendMarkdown() / sendVoice() 返回飞书
连接方式
采用 WebSocket 长连接(非 Webhook),基于 @larksuite/channel 包:
- 国内版:
https://open.feishu.cn - 国际版 (Lark):
https://open.larksuite.com - 通过配置中的
tenant字段选择端点('lark'使用国际版)
Channel 配置
| 参数 | 默认值 | 说明 |
|---|---|---|
respectProxyEnv |
true |
遵循 HTTP_PROXY/HTTPS_PROXY |
pingTimeout |
3 |
SDK ping 看门狗 |
handshakeTimeoutMs |
8000 |
连接握手超时 |
httpTimeoutMs |
30000 |
API 调用超时 |
policy.dmMode |
'open' |
SDK 级 DM 策略(上层另有自定义) |
safety.chatQueue.enabled |
false |
禁用 SDK 内部队列,使用自定义 PendingQueue |
生命周期事件
message— 收到消息error— 连接错误reconnecting— 重连中reconnected— 重连成功
消息处理流水线
Feishu WS → NormalizedMessage → PendingQueue → Access Control → Slash Router → Inbound Listeners → enqueue()
PendingQueue(去抖队列)
文件:vendor/bot/pending-queue.ts
- 每个
chatId独立队列 - 消息静默 600ms 后批量提交
- 支持
block(scope)/unblock(scope)— AI 回复期间阻塞新消息 - 以
/开头的命令跳过队列立即处理
Access Control(访问控制)
文件:FeishuService.ts
DM 权限(canUseDm):
- 机器人拥有者(Owner)— 始终允许
- 管理员(
admins[])— 始终允许 allowedUsers[]为空(默认)— 所有人可 DMallowedUsers[]有值 — 仅白名单用户可 DM
群聊权限(canUseGroup):
- 拥有者和管理员始终允许
- 仅
allowedChats[]中的群被允许
@ 提及策略:
requireMentionInGroup默认为true— 群聊需要 @bot- 可配置为
false响应所有群消息
内置命令
| 命令 | 说明 |
|---|---|
/stop |
停止当前处理 |
/reset |
重置会话 |
/status |
显示机器人状态、App ID、Owner、策略 |
/help |
显示帮助信息 |
未知命令自动传递给 AI 处理。
引用回复
当用户回复某条历史消息时,fetchQuotedContext()(vendor/bot/quote.ts)会通过 channel.fetchRawMessage() 获取原文,包装为 XML 注入 AI 上下文:
<quoted_message id="..." sender_id="..." sender_name="..." type="text">
Original message content here
</quoted_message>
支持类型:纯文本、合并转发、交互式卡片(CardKit v1 和 v2)。
消息格式输出
文本
sendText(chatId, text) — 发送纯文本。
Markdown(主要方式)
sendMarkdown(chatId, markdown) — 发送前通过 feishuMarkdown.ts 优化:
- 标题降级: H1→H4, H2-H6→H5(飞书卡片 H1-H3 渲染有 bug)
- Schema 2.0 间距: 在连续标题、表格前后、代码块周围插入
<br> - 图片过滤: 非
img_*图片移除(防 CardKit 200570 错误) - 表格限制: 超过 3 个表格降级为代码块(防 230099/11310 错误)
语音
sendVoice(chatId, text) — 启用 TTS 时自动调用。
语音(TTS)系统
飞书语音为单向输出(AI 回复 → 语音),用户始终通过文字输入。
Edge TTS(默认)
- 使用
edge-ttsCLI 工具 - 默认语音:
zh-CN-XiaoxiaoNeural - 输出转码为 OGG (Opus) 后发送
VoxCPM(自定义语音克隆)
- 使用
.venv/bin/voxcpmCLI - 需要
ttsReferenceAudio(WAV/MP3 参考音频) - 长文本按 ~150 字在句边界分块
- 每块独立合成,WAV→OGG 转码,ffmpeg concat 合并
TTS 配置
| 字段 | 类型 | 说明 |
|---|---|---|
ttsEnabled |
boolean | 主开关 |
ttsProvider |
'edge' | 'voxcpm' |
引擎选择 |
ttsVoice |
string | Edge TTS 语音名称 |
ttsReferenceAudio |
string | VoxCPM 参考音频路径 |
QR 码注册向导
支持一键创建飞书应用,无需手动在开发者后台操作:
- 调用
@larksuite/channel的registerApp({ source: 'codev' }) - 返回 QR 码 URL,终端用 ASCII 渲染
- 用户使用飞书手机端扫码授权
client_id和client_secret自动保存到配置- 机器人立即启动
配置
文件路径: ~/.claude/adapters.json(feishu 键下)
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
appId |
string | ✅ | 飞书开放平台 App ID |
appSecret |
string | ✅ | 飞书开放平台 App Secret |
tenant |
'feishu' | 'lark' |
❌ | API 端点选择 |
encryptKey |
string | ❌ | 事件加密 |
verificationToken |
string | ❌ | 事件 URL 验证 |
allowedUsers |
string[] | ❌ | DM 白名单(空=开放) |
admins |
string[] | ❌ | 管理员 open_id |
allowedChats |
string[] | ❌ | 群聊白名单 |
requireMentionInGroup |
boolean | ❌ | 默认 true |
ttsEnabled |
boolean | ❌ | 语音回复开关 |
ttsProvider |
'edge' | 'voxcpm' |
❌ | TTS 引擎 |
ttsVoice |
string | ❌ | Edge TTS 语音 |
ttsReferenceAudio |
string | ❌ | VoxCPM 参考音频 |
Keepalive(保活机制)
文件:vendor/bot/keepalive.ts
独立于 SDK 内部 ping 的防御性看门狗:
- 间隔: 每 15 秒
- 防风暴: 5 秒内跳过重复 tick
- 睡眠检测: 距上次 tick 超过 30 秒重置计数器
- HTTP 探针: 重连前 HEAD 请求检测网络可达性
- 死连接阈值: 连续 3 个 tick 确认 WS 断开才强制重连
桥接 Hook
useFeishuBridge(src/hooks/useFeishuBridge.ts)
React hook,负责:
- 订阅 FeishuService 的入站事件
- 监听
messages数组,识别飞书来源的消息(origin.kind === 'channel' && origin.server === 'feishu') - 收集后续 AI 回复
- 在
isLoading从 true→false 时:- 主:
sendMarkdown()发送完整回复 - 次:
sendVoice()发送 TTS 语音
- 主:
与 FriendService 的对比
| 方面 | FeishuService | FriendService |
|---|---|---|
| 通信方式 | WebSocket (@larksuite/channel) |
同进程 HTTP + SSE |
| 消息输入 | 文字(飞书 IM) | 文字 / 语音(VAD + STT) |
| 语音方向 | 仅输出(TTS) | 双向(VAD → STT → AI → TTS) |
| TTS 引擎 | Edge TTS, VoxCPM | Edge TTS, Qwen TTS |
| 配置存储 | ~/.claude/adapters.json |
getPrefs() |
| 服务位置 | src/services/feishu/ |
src/friend/ |