调用链 · 核心模块 · 回调身份决策 · 反馈环风险 · 2026-06-25
三层结构:MCP 客户端 → bridge daemon → Claude CLI + 飞书回调
两种接入方式,生产用 HTTP daemon 模式共享任务注册表
一个进程服务一个 MCP 客户端。进程退出,所有任务记录消失(内存注册表,不持久化)。适合 Cursor / Claude Desktop 单机接入。
node bin/claude-code-mcp-bridge.mjs mcp \
--cwd-root ~/projects
所有客户端共享同一个任务注册表 + Session Store,跨 Hermes / Pi 可见。launchd 守护,监听 127.0.0.1:8787。
| 路径 | 用途 |
|---|---|
/mcp | MCP JSON-RPC 入口 |
/api/state | 任务 + session 快照 |
/ | Dashboard(实时轮询) |
/healthz | 存活探针 |
4 个模块分工明确,Notifier 是回调身份问题的根源所在
封装 claude CLI 进程,把 stdout 转成 AgentEvent 异步流。
内存注册表。每个 task 独立事件缓冲(最多 5000 条)+ waiters 队列,供 claude_wait 长轮询。
running → done/error/cancelled → 触发 Notifier → 释放所有 waiters
持久化 Claude session ID 到 sessions.json。重启后可用 session_id 续传对话。
任务完成/失败时 fire-and-forget 调 lark-cli。身份由 as_identity + lark_home 共同决定。
as_identity 默认 "user"(notifier.ts:38)。不传 lark_home 则用 daemon 自己的环境,大概率触发 230002。
从 Hermes 发起到飞书收到通知的全流程
mcp_cc_claude_run传入 prompt、cwd、model、session_id(续传)、notify_target(回调配置)
校验 cwd-root 约束 → 生成 UUID task_id → 调用 TaskRegistry.start()
claude -p <prompt> --output-format stream-json,进程 stdout 持续推送 JSON 事件
task_id(不阻塞)Hermes 收到 task_id 后可用 claude_status / claude_wait 轮询进展
text_delta 累加文本 · tool_use 更新 currentTool · agent_end 触发状态变 done/error
状态变 terminal(done/error/cancelled)→ fireFeishuNotification() → spawn lark-cli → 飞书收到卡片
通知卡片内容(两种时机)
🚀 派出通知(notify_on_start: true 时发)
task_id · cwd · model
✅/❌/⛔ 结束通知(状态变 terminal 时必发)
duration · tools 数 · tokens · cost · 最后 1500 字输出
as_identity + lark_home 共同决定飞书里谁在发通知
lark_home: "~/.hermes" + as_identity: "user"lark_home: "~/.pi" + as_identity: "user"as_identity: "bot"(任意 lark_home)lark_home,as_identity 走默认 "user"as_identity: "user" + 主聊 + 群在 gateway 监听消息路由规则(notifier.ts 实现顺序,anchor 优先于 chat_id)
| 条件 | lark-cli 命令 | 效果 |
|---|---|---|
有 anchor_msg_id + reply_in_thread: true |
+messages-reply --reply-in-thread |
进飞书话题(thread)✅ |
有 anchor_msg_id,无 reply_in_thread |
+messages-reply |
reply 那条消息,主聊流 ✅ |
无 anchor,有 chat_id |
+messages-send --chat-id |
直接发群主聊 ✅ |
| anchor 和 chat_id 都没有 | — | 静默丢弃,只打 warn log ⚠️ |
三个参数同时命中 → CC 通知被 gateway 当新指令 → 半夜吵醒爸爸
① as_identity: "user" ② reply_in_thread: false(发主聊) ③ 目标群在 gateway 监听
01:31:37 Hermes 派 CC 跑 pi-yunzhan loose-threads
notify_target 三条撞了:
① as_identity = "user"
② reply_in_thread = false
③ chat_id = pi-yunzhan 群(gateway 监听中)
01:31:41 飞书 gateway 收到"爸爸在群里发了消息"
→ 开新 session 20260625_013140_e0ab8582
→ 把 CC 自报的"🚀 任务已派出..."当成指令
01:34:34 Hermes 懵逼查了 thread / task / session
→ 主动回复爸爸"这是 CC notify..."
→ 爸爸半夜被叫醒
通知进话题,gateway 通常不监听 thread 子消息,不会误触发
gateway 识别为自家 bot 消息,直接 ignore,不开新 session
reply_in_thread=false(发主聊)时,必须配 as_identity="bot"。禁止 user + 主聊 + 被监听群三条同时出现。
异步任务模型:run 立即返回,status/wait 轮询,cancel 终止
| 工具 | 功能 | 关键参数 |
|---|---|---|
claude_run | 启动异步任务,立即返回 task_id | prompt · cwd · model · session_id · notify_target |
claude_status | 获取任务快照(文本/状态/计数器) | task_id |
claude_wait | 长轮询新事件,chunk-by-chunk 流式 | task_id · from_seq · timeout_ms |
claude_cancel | SIGTERM → SIGKILL 终止任务 | task_id |
claude_list | 列出所有任务(运行中 + 已结束) | — |
claude_forget | 从内存删除已结束任务记录 | task_id |
claude_sessions | 列出持久化 session,可续传 | — |