AI 智能体消息桥梁,连接飞书/钉钉/企微,实现异步通知与确认。发送消息到飞书群、等待用户回复、会话切换到飞书。在用户提到飞书、钉钉、企微、消息通知、审批确认、会话切换时使用。灵魂级约束:使用本 skill 进行飞书会话时必守「保持会话维持、用户终端永不消逝的电波」——收到回复后必须再发下一条到飞书,超时后必须 --heartbeat 拉起,仅用户说「结束」「切回」时退出;执行发消息并等回复时进程必须在前台等待,不得切入后台。
Install
npx skillscat add ziweiaxis/qingniao Install via the SkillsCat registry.
MessageBridge Skill(青鸟)
AI 智能体的消息桥梁,连接飞书/钉钉/企微,实现异步通知与确认。对外可称「青鸟」,项目/仓库名为 qingniao,npm 包名为 @zwa/qingniao。
灵魂级约束(每次使用本 skill 必守,新会话也不遗忘)
保持会话维持,用户终端永不消逝的电波。 只要用户通过本 skill 进入飞书会话(「到飞书」「切到飞书」「切换到青鸟」等),则:
- 收到飞书回复后必须再发下一条到飞书并继续等,不得只在当前对话里回一句就停;停即断线。
- 单轮超时后必须按 sessionHint 执行
npx @zwa/qingniao --heartbeat重新拉起;若心跳返回的仍是 timeout,继续再发心跳(或再发内容),不得只发一次心跳就停,不得因超时结束会话。 - 仅当用户在飞书中明确说「结束」或「切回」时方可退出循环;此时必须执行
npx @zwa/qingniao session close(或hook unregister)清空当前会话 channel,使 cursor-stop-hook 不再触发。 - 执行
npx @zwa/qingniao/npm run turn/npm run dev:cli等发消息并等回复时,进程必须在前台等待飞书回复,不得切入后台。 调用方(如 Cursor、脚本)须使用足够长的超时或阻塞直至命令返回,不得因短超时将进程挂到后台;否则无法拿到飞书回复的 JSON。 - (Cursor Agent 必守) 切换青鸟后,每次「发消息并等回复」的工具调用必须设置 足够长的 timeout(建议 ≥600000ms,即 10 分钟),且同一轮对话内:若收到的飞书回复不是「结束」「切回」,必须立即再发起一次「发消息并等回复」把下一条内容发到飞书并继续等待,不得在只发一条、只收一条后就在 Cursor 里回一句话结束回合;否则用户会感到「没等到青鸟回复就结束对话了」。只有飞书回复含「结束」「切回」时才执行 session close 并结束。
- 本约束在每次使用本 skill 时生效,不因新会话、新对话而遗忘。
对外唯一用法:无需安装,发内容到飞书并等回复只需一条命令,会话切换时循环调用同一条:
npx @zwa/qingniao "<内容>"
# 可选超时:npx @zwa/qingniao "<内容>" --timeout=3600其余(自检、配置、首次配对等)都是该命令的内部逻辑,见下文「命令内部逻辑与配置」。需在 Cursor/Codex 内做「会话切换飞书」闭环时,可将本仓克隆到 skill 目录并执行 npm run turn -- "<内容>"。安装方式见 INSTALL.md。
Quick Start
无需 npm install,任意目录执行(npx 会从 npm 拉取并运行):
npx @zwa/qingniao "<要发到飞书的内容>"- 把内容发到飞书并等待用户回复;stdout 输出单行 JSON(
status、reply、sessionHint等)。 - 「到飞书」/ 会话切换:循环执行「AI 生成回复 →
npx @zwa/qingniao "<内容>"→ 解析回复 → 再生成 → 再调用」,直到用户说「结束」或「切回」。 - 若未配置飞书,命令内部会自检并给出引导,按提示完成配置后再用同一条命令即可(配置与首次配对见下文「命令内部逻辑与配置」)。
1. Skill 配置
- 包名:
@zwa/qingniao(bin 保留别名skill-message-bridge兼容)。 - 对外用法:
npx @zwa/qingniao "<内容>"(发到飞书并等回复);自检、config、connect 等为命令内部逻辑,见 §4.2。 - 飞书配置:环境变量或
~/.message-bridge/config.json;完整引导见 飞书 Onboarding。
2. 功能
- ✅ 发送消息到飞书群聊
- ✅ 发送消息并等待用户回复
- ✅ 长连接实时接收(飞书事件订阅)
- ✅ 超时处理
- ✅ 任务队列管理
3. Channel 配置总览
| Channel | 状态 | 环境变量 | 说明与文档 |
|---|---|---|---|
| 飞书 Feishu | ✅ 已支持 | FEISHU_APP_ID、FEISHU_APP_SECRET、FEISHU_CHAT_ID(或 DITING_FEISHU_*) |
长连接收消息;完整从创建应用到获取 chat_id 见 飞书 Onboarding |
| 钉钉 DingTalk | 📌 规划中 | (待定) | 见 CONTRIBUTING.md |
| 企微 WeCom | 📌 规划中 | (待定) | 同上 |
当前仅飞书可用;钉钉/企微欢迎按 CONTRIBUTING 接入。
4. 使用方式
4.1 对外唯一用法(AI / 调用方只认这一条)
npx @zwa/qingniao "<内容>"
npx @zwa/qingniao "<内容>" --timeout=3600 # 可选:单轮超时秒数- 把
<内容>发到飞书并等待用户回复;stdout 仅输出一行 JSON(status、reply、replyUser、sessionHint等),便于 agent 解析、避免历史噪音;[MessageBridge] 等日志在 stderr,解析时只认 stdout 该行 JSON 即可。 - 消息也可从 stdin 传入:
echo "内容" | npx @zwa/qingniao。 - 「到飞书」/ 会话切换:只循环调用这一条命令,由 AI 维持会话,直到用户说「结束」或「切回」。
不要使用 require("@skills/message-bridge");包名为 @zwa/qingniao,代码中 require("@zwa/qingniao")。
4.2 命令内部逻辑与配置(非对外用法)
以下均为命令内部或用户首次配置时涉及的行为,AI 不需要根据场景选择「用 check-env 还是 connect 还是 send」;只需用 npx @zwa/qingniao "<内容>",未配置时命令会自检并给出引导。
| 内部/配置用途 | 说明 |
|---|---|
| 自检 | 命令执行时会检查环境变量或 ~/.message-bridge/config.json;缺项时输出引导,不直接发消息。 |
| 写入配置 | 用户首次配置:npx @zwa/qingniao config set feishu --app-id=xxx --app-secret=xxx 写入配置文件;交互式引导需用户本机终端执行(TTY),npx 代跑多为非 TTY。 |
| 首次配对(chat_id) | 仅缺 Chat ID 时:用户在本机终端执行 npx @zwa/qingniao connect,在飞书发一条消息后终端输出 chat_id,再执行 config set feishu --chat-id=oc_xxx 保存。connect 仅用于此次配对,AI 在「到飞书」时不要调用 connect,只循环调用 npx @zwa/qingniao "<内容>"。 |
| 仅发不等 | 内部对应 send 子命令;对外统一用「发内容并等回复」这一条即可,需要「只发不等」时可由调用方发完即忽略回复或设极短超时。 |
| 心跳(--heartbeat) | 仅等待飞书下一条消息,不向飞书推送任何内容;用于单轮超时后把会话重新拉起、继续等飞书。用法:npx @zwa/qingniao --heartbeat [--timeout=N]。心跳可连续调用:若本次 --heartbeat 返回的仍是 status:"timeout",必须再次执行 --heartbeat(或发一条新内容),不能只发一次心跳就停;循环直到收到 replied 或用户说「结束」「切回」。输出格式与 "<内容>" 一致,超时同样为 status:"timeout"。 |
| 当前会话 channel | 发消息到飞书时自动在项目根 .cursor/message-bridge-channel.json 写入当前 channel(如 feishu)。cursor-stop-hook 仅当 channel 非空时才向 Cursor 输出 followup_message 以续写;channel 为空则不触发。 |
| 关闭会话(session close) | 用户说「结束」「切回」后必须执行:npx @zwa/qingniao session close 或 npx @zwa/qingniao hook unregister,清空 channel 并移除 stop hook;此后 cursor-stop-hook 不再触发会话续写。 |
| 帮助 | npx @zwa/qingniao --help、config show、config path 等为调试/运维用,非会话闭环所需。 |
完整飞书配置与权限步骤见 飞书 Onboarding。
4.3 在代码中调用(可选)
需要在自己写的 Node 脚本里调用时,可安装后 require:
npm install @zwa/qingniaoconst messageBridge = require("@zwa/qingniao");
// 或在本仓库根目录开发时: require("./dist/index.js")(需先 npm run build)4.4 环境变量(飞书)
export FEISHU_APP_ID="cli_xxx"
export FEISHU_APP_SECRET="xxx"
export FEISHU_CHAT_ID="oc_xxx"(也支持 DITING_FEISHU_*。如何获取 chat_id 见 飞书 Onboarding。)
4.5 在 Node.js 中使用
const messageBridge = require("@zwa/qingniao"); // 从 npm 安装时
// 或 const messageBridge = require("./dist/index.js"); // 在本仓库根目录时(需先 npm run build)
// 发送消息并等待回复
const result = await messageBridge.notify({
message: "需要你确认一下这个操作",
timeout: 60,
});
if (result.status === "replied") {
console.log("用户回复:", result.reply);
} else if (result.status === "timeout") {
console.log("超时未回复");
}
await messageBridge.send({ message: "任务已完成!" });4.6 在 OpenClaw / 其他 AI 智能体中
const { notify, send } = require("@zwa/qingniao");
const result = await notify({
message: "检测到异常,是否继续?",
timeout: 120,
});
if (result.status === "replied" && result.reply.includes("继续")) {
console.log("用户确认,继续执行");
} else {
console.log("用户拒绝或超时,停止执行");
}API 摘要
- notify({ message, timeout? }):发消息并等待用户回复;返回
status: "replied"|"timeout"|"error"、reply、replyUser。 - send({ message }):仅发送消息,不等待回复;返回
messageId。
参数与返回值详见 reference.md。
使用场景
1. 审批流程
const result = await notify({
message: "检测到高风险操作,需要审批\n\n操作:删除生产数据库\n请回复「批准」或「拒绝」",
timeout: 300, // 5分钟
});
if (result.status === "replied" && result.reply === "批准") {
// 执行操作
}2. 异常告警
await send({
message: "⚠️ 服务器 CPU 使用率超过 90%\n\n时间: " + new Date().toISOString(),
});3. 任务完成通知
await send({
message: "✅ 数据备份完成\n\n文件数: 1234\n大小: 5.6GB",
});4. 交互式确认
const result = await notify({
message: "发现 3 个待处理的问题,是否立即处理?",
timeout: 60,
});
if (result.status === "replied" && result.reply.includes("是")) {
// 处理问题
}首次使用与配置引导(命令内部逻辑)
(AI 必读)当用户说「切到飞书」「切换到飞书」「发消息到飞书」时:你只需循环调用 npx @zwa/qingniao "<内容>"。 不需要先跑 check-env、再选 config 或 connect;自检与配置是命令内部逻辑,未配置时命令会报错或提示,你再按下面引导用户即可。
若调用 npx @zwa/qingniao "<内容>" 失败或提示未配置,按缺项顺序引导用户(以下均为命令内部/用户侧操作,不是你多调几种子命令):
- 缺少 App ID 或 App Secret:引导用户到 飞书开放平台 创建应用并复制凭证,再让用户执行
npx @zwa/qingniao config set feishu --app-id=xxx --app-secret=xxx保存;完整步骤见 docs/ONBOARDING-FEISHU.md。 - 仅缺 Chat ID:需在飞书后台配置事件订阅(长连接 +
im.message.receive_v1),再让用户在本机终端执行npx @zwa/qingniao connect,在群聊或私聊给机器人发一条消息后终端会输出 chat_id,再执行config set feishu --chat-id=oc_xxx保存。connect 仅此次配对用,由用户本机执行,AI 不要代跑;「到飞书」时只循环npx @zwa/qingniao "<内容>"。
配置完成后用户再说「切到飞书」即可正常循环上述唯一命令。
- 其他 Channel(钉钉/企微):当前未实现,可到 Issues 搜索或新建需求。
会话切换到飞书 = 飞书长周期对话闭环
(AI 必读)除用户明确发出结束信号(如在飞书说「结束」「切回」)外,会话永不结束。 单轮超时、任务完成、长时间无回复等任何场景均不结束会话;超时后用心跳把会话重新拉起,继续等飞书下一条。
当用户说**「会话切换到飞书」**时,进入长周期对话:AI 的输出发到飞书 → 飞书用户回复 → AI 基于回复再生成 → 再发飞书,循环;仅在用户说「结束」「切回」时退出,不设轮数上限。
唯一用法(与上文 4.1 一致):
npx @zwa/qingniao "<AI 的回复内容>"
npx @zwa/qingniao "<内容>" --timeout=3600 # 可选
# 若在 skill 目录内:npm run turn -- "<内容>"- 把内容发到飞书并等待用户回复;stdout 输出单行 JSON:
{"status":"replied","reply":"用户回复", "replyUser":"?", "sessionHint":"..."},超时为"status":"timeout"。 - sessionHint:通常为「用户未说「结束」或「切回」前请继续循环发消息,勿退出飞书会话」。当 status 为 "timeout" 时,sessionHint 会改为明确指令:「本轮超时,请执行 npx @zwa/qingniao --heartbeat [--timeout=N] 重新拉起会话;若返回仍为 timeout,继续再发 --heartbeat,勿只发一次就停,勿退出。」调用方按此执行即可。
- stderr 会提示:
[MessageBridge] 请根据 sessionHint 继续发下一条消息到飞书,勿退出会话。 - 超时:默认约 12 小时(或
FEISHU_TURN_TIMEOUT);可用--timeout=N覆盖。单轮超时后永不退出:用 心跳npx @zwa/qingniao --heartbeat [--timeout=N]把会话重新拉起,挂起等飞书下一条(不推送任何内容);或发「等待超时,如需继续请直接回复」后再发一条"<内容>"。超时≠结束会话。
闭环:循环「AI 生成回复 → npx @zwa/qingniao "<内容>" → 解析 reply、sessionHint;若本轮超时 → 用 npx @zwa/qingniao --heartbeat [--timeout=N] 重新拉起;若心跳返回的仍是 timeout,继续再发心跳(或再发内容),不要只发一次心跳就停 → 直到收到 replied 再生成 → 再调用」。仅当用户发出明确结束信号(如「结束」「切回」)时退出;任务结束、超时、无回复等任何其他场景一律不退出。
结束会话时必做:当飞书回复中含「结束」「切回」时,执行 npx @zwa/qingniao session close(或 hook unregister),清空当前会话 channel 并移除 stop hook。此后 cursor-stop-hook 读取到 channel 为空,不再向渠道触发续写;下次用户说「到飞书」再发消息时会重新写入 channel。可选:向飞书发收尾 npx @zwa/qingniao send "会话已结束。要再聊请到 Cursor 说「切到飞书」。" 或执行 scripts/cursor-feishu-end.sh。
为何会自动断掉:闭环在 Cursor 单次回复里跑,有工具调用/上下文上限,跑一段时间就会结束当次回复。解决:要不依赖 Cursor 的持久对话,可运行常驻进程 npm run conversation(即 node scripts/feishu-conversation.js,需配置 AI_REPLY_URL 或 OPENAI_API_KEY),在飞书里一直聊直到你说结束;若继续用 Cursor 闭环,断掉后说「继续飞书」即恢复。
为何会「没等到青鸟回复就结束对话」:常见原因:(1)调用「发消息并等回复」时 timeout 过短,未等飞书用户回复就超时,命令被中止;(2)执行环境将长时间运行的命令 挂到后台,拿不到 stdout 的 JSON;(3)Agent 只发一条、收一条后未再发下一条,就在 Cursor 里结束回合。对应做法:用 ≥10 分钟 timeout、不 background、且每次收到飞书回复(非结束/切回)后必须再发一条到飞书并等待。
可选:纯飞书端对话(scripts/feishu-conversation.js)
不经过 Cursor、只在飞书里和机器人对话时,可单独运行:
npm run conversation # 或 node scripts/feishu-conversation.js需配置 AI_REPLY_URL 或 OPENAI_API_KEY(+ OPENAI_BASE_URL、OPENAI_MODEL)。飞书需已订阅 im.message.receive_v1(长连接)。
测试
验证唯一用法(发到飞书并等回复,需飞书群内有人回):
npx @zwa/qingniao "请回复测试"自检、只发不等等为命令内部/调试用(见 §4.2)。在仓库内还可运行:npm test、npm run test:quick、npm run test:complete、npm run test:session-bridge。
更多参考
- API 参数与返回值、npm 发布与验证、技术实现、限制与未来计划:见 reference.md。