# 飞书(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[]` 为空(默认)— 所有人可 DM - `allowedUsers[]` 有值 — 仅白名单用户可 DM **群聊权限**(`canUseGroup`): - 拥有者和管理员始终允许 - 仅 `allowedChats[]` 中的群被允许 **@ 提及策略**: - `requireMentionInGroup` 默认为 `true` — 群聊需要 @bot - 可配置为 `false` 响应所有群消息 ### 内置命令 | 命令 | 说明 | |------|------| | `/stop` | 停止当前处理 | | `/reset` | 重置会话 | | `/status` | 显示机器人状态、App ID、Owner、策略 | | `/help` | 显示帮助信息 | 未知命令自动传递给 AI 处理。 --- ## 引用回复 当用户回复某条历史消息时,`fetchQuotedContext()`(`vendor/bot/quote.ts`)会通过 `channel.fetchRawMessage()` 获取原文,包装为 XML 注入 AI 上下文: ```xml Original message content here ``` 支持类型:纯文本、合并转发、交互式卡片(CardKit v1 和 v2)。 --- ## 消息格式输出 ### 文本 `sendText(chatId, text)` — 发送纯文本。 ### Markdown(主要方式) `sendMarkdown(chatId, markdown)` — 发送前通过 `feishuMarkdown.ts` 优化: - **标题降级**: H1→H4, H2-H6→H5(飞书卡片 H1-H3 渲染有 bug) - **Schema 2.0 间距**: 在连续标题、表格前后、代码块周围插入 `
` - **图片过滤**: 非 `img_*` 图片移除(防 CardKit 200570 错误) - **表格限制**: 超过 3 个表格降级为代码块(防 230099/11310 错误) ### 语音 `sendVoice(chatId, text)` — 启用 TTS 时自动调用。 --- ## 语音(TTS)系统 飞书语音为**单向输出**(AI 回复 → 语音),用户始终通过文字输入。 ### Edge TTS(默认) - 使用 `edge-tts` CLI 工具 - 默认语音:`zh-CN-XiaoxiaoNeural` - 输出转码为 OGG (Opus) 后发送 ### VoxCPM(自定义语音克隆) - 使用 `.venv/bin/voxcpm` CLI - 需要 `ttsReferenceAudio`(WAV/MP3 参考音频) - 长文本按 ~150 字在句边界分块 - 每块独立合成,WAV→OGG 转码,ffmpeg concat 合并 ### TTS 配置 | 字段 | 类型 | 说明 | |------|------|------| | `ttsEnabled` | boolean | 主开关 | | `ttsProvider` | `'edge' \| 'voxcpm'` | 引擎选择 | | `ttsVoice` | string | Edge TTS 语音名称 | | `ttsReferenceAudio` | string | VoxCPM 参考音频路径 | --- ## QR 码注册向导 支持一键创建飞书应用,无需手动在开发者后台操作: 1. 调用 `@larksuite/channel` 的 `registerApp({ source: 'codev' })` 2. 返回 QR 码 URL,终端用 ASCII 渲染 3. 用户使用飞书手机端扫码授权 4. `client_id` 和 `client_secret` 自动保存到配置 5. 机器人立即启动 --- ## 配置 文件路径: `~/.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,负责: 1. 订阅 FeishuService 的入站事件 2. 监听 `messages` 数组,识别飞书来源的消息(`origin.kind === 'channel' && origin.server === 'feishu'`) 3. 收集后续 AI 回复 4. 在 `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/` |