skill-message-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ # 贡献指南 / Contributing
2
+
3
+ 欢迎参与 MessageBridge 的共建。本项目**不限定单一渠道**,当前已实现飞书,欢迎社区补全钉钉、企微等其它渠道。
4
+
5
+ We welcome contributions. The project supports multiple channels (Feishu implemented; DingTalk, WeCom, etc. welcome).
6
+
7
+ ---
8
+
9
+ ## 一、贡献流程 / How to Contribute
10
+
11
+ ### 目标
12
+ - 代码、文档、单测符合规范,便于维护与 AI 协作。
13
+
14
+ ### 步骤
15
+ 1. Fork 本仓库,在个人 fork 上创建分支(如 `feature/dingtalk` 或 `fix/xxx`)。
16
+ 2. 修改后在本机运行 `npm run build:dist`(若有改 src)及现有测试(如 `node test-quick.js`)。
17
+ 3. 提交信息建议格式:`类型: 简短描述`(如 `feat: 钉钉发送与等待回复`、`docs: README 英文版`)。
18
+ 4. 向本仓库发起 Pull Request,描述变更与自测结果。
19
+ 5. 维护者 Review 后合并。
20
+
21
+ ### 验收标准
22
+ - [ ] 新代码有对应单测或已通过现有测试。
23
+ - [ ] 文档/README 如有涉及则同步更新(中英双语优先)。
24
+ - [ ] PR 描述说明「做了什么、如何验证」。
25
+
26
+ ---
27
+
28
+ ## 二、新渠道接入 / Adding a New Channel
29
+
30
+ ### 目标
31
+ - 在不破坏现有飞书能力的前提下,新增一个 IM 渠道(如钉钉、企微),实现「发消息」与「等回复」。
32
+
33
+ ### 步骤与可执行清单(供人工或 AI 逐条完成)
34
+
35
+ - [ ] **1. 平台适配器**
36
+ 在 `src/platforms/` 下新增 `<channel>.ts`(参考 `feishu.ts`),实现:
37
+ - 获取 access token(或该渠道等价物)。
38
+ - 建立长连接/订阅(若需收消息)。
39
+ - `sendMessage(task)`:发送一条消息并返回消息 ID。
40
+ - 在收到用户消息时,调用 `messageQueue.resolveTask(taskId, { reply, replyUser })` 与队列对接。
41
+ - [ ] **2. 注册适配器**
42
+ 在 `src/index.ts`(或统一平台注册处)根据 `platform` 参数选择对应 adapter,保持 `notify(params)` / `send(params)` 的 `platform` 入参生效。
43
+ - [ ] **3. Turn 脚本**
44
+ 新增 `dist/<channel>-turn.js`(可参考 `feishu-turn.js`):读环境变量、调 `notify({ message, timeout })`、stdout 最后一行为单行 JSON `{"status":"replied"|"timeout"|"error","reply":"...","replyUser":"?"}`。
45
+ - [ ] **4. 环境变量**
46
+ 在 README 与本文档中说明新渠道所需环境变量(如 `DINGTALK_APP_KEY`、`DINGTALK_APP_SECRET`、会话 ID 等)。
47
+ - [ ] **5. 单测**
48
+ 为该渠道增加至少 1 个可运行的测试(发送或 mock 收消息),保证 CI/本地 `npm test` 或等价命令可覆盖。
49
+ - [ ] **6. 文档**
50
+ - README「支持的渠道」中列出新渠道及配置说明(中英双语)。
51
+ - 本文档「新渠道接入」下可补充该渠道的简要说明或链接。
52
+
53
+ ### 验收标准
54
+ - [ ] 新渠道可通过 `notify` 发消息并拿到用户回复(或 timeout)。
55
+ - [ ] 新渠道的 turn 脚本可在项目根目录执行并输出约定 JSON。
56
+ - [ ] 单测通过;README/CONTRIBUTING 已更新。
57
+
58
+ ---
59
+
60
+ ## 三、单测要求 / Testing
61
+
62
+ - **新功能**:应有对应单测(发送、收回复、超时、错误处理等,可 mock 网络)。
63
+ - **新渠道**:至少覆盖「发送成功」或「模拟收到回复」一条路径。
64
+ - **运行方式**:在项目根目录执行 `node test-quick.js` 或 `npm test`(以 package.json 为准);CI 可在此基础上增加流水线。
65
+ - **单测写法**:可参考现有 `test-quick.js`、`test-complete.js`;保持用例独立、不依赖外部未 mock 的服务。
66
+
67
+ ---
68
+
69
+ ## 四、文档与 AI 友好 / Docs & AI-Friendly
70
+
71
+ 为方便人类与 AI 贡献者按步骤完成工作,文档约定如下:
72
+
73
+ - **结构化**:每个主题包含「目标 / 步骤 / 验收标准」三块,便于按步骤执行并自检。
74
+ - **可执行清单**:如「新渠道接入」采用 checkbox 列表,可逐条勾选完成。
75
+ - **格式与示例**:目录命名、单测风格、PR 描述格式在本文档中给出示例,贡献者可按示例仿写。
76
+ - **中英双语**:README、CONTRIBUTING 及与贡献者相关的说明尽量提供中英版本(可在同一文件中用标题分隔)。
77
+
78
+ ---
79
+
80
+ ## 五、给 AI 贡献者 / For AI Contributors
81
+
82
+ 本项目欢迎由 AI 按文档完成的贡献(代码、文档、单测)。建议:
83
+
84
+ - **推荐起点**:从「新渠道接入」或「文档/README 中英双语补全」类 issue 或任务入手。
85
+ - **必读文件**:`README.md`、`CONTRIBUTING.md`(本文)、`src/platforms/feishu.ts`、`src/index.ts`、`feishu-turn.js` / `dist/feishu-turn.js`。
86
+ - **执行顺序**:按 CONTRIBUTING 中「步骤」与「可执行清单」逐条完成,并在 PR 中说明已完成项与验收结果。
87
+ - **提交与 PR**:提交信息与 PR 描述请写清「做了什么、如何验证」,便于维护者 Review。
88
+
89
+ ---
90
+
91
+ ## 六、发布到 GitHub / Publishing to GitHub
92
+
93
+ 若你希望将本仓库发布到自己的 GitHub 账号下:
94
+
95
+ 1. 在本机安装 [GitHub CLI](https://cli.github.com/):`sudo apt install -y gh`(或 `brew install gh` / 见官方文档)。
96
+ 2. 登录:`gh auth login`,按提示完成 GitHub 授权。
97
+ 3. 在**本项目目录**下(若尚未初始化 git):
98
+ ```bash
99
+ git init
100
+ git add .
101
+ git commit -m "Initial commit"
102
+ gh repo create message-bridge --public --source=. --push
103
+ ```
104
+ 4. 若仓库已存在,则:`git remote add origin <your-repo-url>` 后 `git push -u origin main`。
105
+
106
+ (此节仅作说明,不要求贡献者必须执行。)
107
+
108
+ ---
109
+
110
+ ## 发布到 npm / Publish to npm
111
+
112
+ 维护者可将本仓库发布为 npm 包,便于用户 `npm install skill-message-bridge` 或 `npx skill-message-bridge "..."` 使用。
113
+
114
+ 1. 在 [npmjs.com](https://www.npmjs.com/) 注册账号并登录;本机执行 `npm login`。
115
+ 2. 在仓库根目录执行 `npm run build:dist`(或由 `prepublishOnly` 自动执行),确认 `dist/` 已更新。
116
+ 3. 执行 `npm publish`(首次发布);若使用 scope 如 `@hulk-yin/message-bridge`,需在 package.json 中改 `name` 并执行 `npm publish --access public`。
117
+ 4. 发布后用户可通过 `npm install skill-message-bridge` 安装;未发布前可从 GitHub 安装:`npm install github:hulk-yin/message-bridge`。
118
+
119
+ ---
120
+
121
+ # English (summary)
122
+
123
+ - **Contribution flow**: Fork → branch → implement & test → commit (message: `type: short desc`) → open PR with description and self-check.
124
+ - **New channel**: Add adapter in `src/platforms/`, register in index, add turn script and env docs, add tests, update README (EN+ZH).
125
+ - **Testing**: New code should have tests; new channels need at least one test covering send or mock reply.
126
+ - **Docs**: Structured (goal / steps / acceptance), checklists, examples; bilingual (EN+ZH) preferred.
127
+ - **AI contributors**: Start from "new channel" or "docs i18n"; read README, CONTRIBUTING, `src/platforms/feishu.ts`, `src/index.ts`, turn script; follow checklists and state completion in PR.
package/INSTALL.md ADDED
@@ -0,0 +1,140 @@
1
+ # 安装为 Skill / Install as Skill
2
+
3
+ 本仓库可作为 **Skill** 被 Cursor、Codex、Claude Code 等 AI 编码环境加载,实现「会话切换到飞书/钉钉」、发消息、等回复等能力。支持 **npm 包** 与 **Git 克隆** 两种方式。
4
+
5
+ This repo can be installed as a **skill** in Cursor, Codex, Claude Code, etc. You can use **npm** or **Git clone**.
6
+
7
+ ---
8
+
9
+ ## 方式一:npm 包 / Via npm
10
+
11
+ 适合:在任意 Node 项目里直接依赖、或通过 `npx` 调用,无需克隆仓库。Skill 描述(SKILL.md)会随包安装到 `node_modules/skill-message-bridge/`,部分环境可从该路径读取。
12
+
13
+ ### 安装
14
+
15
+ ```bash
16
+ # 项目依赖
17
+ npm install skill-message-bridge
18
+
19
+ # 或全局(可随处运行 message-bridge-turn)
20
+ npm install -g skill-message-bridge
21
+ ```
22
+
23
+ ### 使用
24
+
25
+ ```javascript
26
+ // 在代码中
27
+ const messageBridge = require("skill-message-bridge");
28
+ const result = await messageBridge.notify({ message: "请确认", timeout: 60 });
29
+ ```
30
+
31
+ ```bash
32
+ # 命令行单轮(发到飞书并等回复)
33
+ npx skill-message-bridge "你要发的内容"
34
+ # 或全局安装后
35
+ skill-message-bridge "你要发的内容"
36
+ ```
37
+
38
+ 配置好环境变量 `FEISHU_APP_ID`、`FEISHU_APP_SECRET`、`FEISHU_CHAT_ID` 后即可使用。若需在 Cursor/Codex 中作为「可加载的 skill」(含 SKILL.md 说明),需将 skill 目录指向 `node_modules/skill-message-bridge`,或同时用下面的 Git 方式安装 SKILL 到 `.cursor/skills/`。
39
+
40
+ **发布状态**:包名 `skill-message-bridge`,发布到 npm 后可直接 `npm install`。当前若未发布,可用 `npm install github:hulk-yin/message-bridge` 从 GitHub 安装。
41
+
42
+ ---
43
+
44
+ ## 方式二:Git 克隆(完整 Skill)/ Via Git clone
45
+
46
+ 安装后,**所有命令均在 skill 目录(即本仓库根目录)执行**。适合需要完整 SKILL.md + 源码、或需改代码贡献的场景。
47
+
48
+ ### Cursor
49
+
50
+ Cursor 从项目的 `.cursor/skills/<name>/` 或用户级 skills 目录加载 skill;本仓库包含 `SKILL.md` + 实现代码,**整仓克隆到 skill 目录即可**。
51
+
52
+ ### 方式一:项目内安装(仅当前项目可用)
53
+
54
+ 在**项目根目录**执行:
55
+
56
+ ```bash
57
+ git clone https://github.com/hulk-yin/message-bridge.git .cursor/skills/message-bridge
58
+ cd .cursor/skills/message-bridge && npm install && npm run build:dist
59
+ ```
60
+
61
+ 之后 Cursor 会识别 `.cursor/skills/message-bridge/SKILL.md`,实现代码即同目录下的 `index.js`、`dist/feishu-turn.js` 等;AI 执行 `npm run turn` 时在 **`.cursor/skills/message-bridge`** 下执行即可。
62
+
63
+ ### 方式二:用户级安装(所有项目可用)
64
+
65
+ 若希望所有 Cursor 项目都能用,可克隆到 Cursor 用户级 skills 目录(具体路径以 Cursor 文档为准,常见为 `~/.cursor/skills/` 或 Cursor 设置中的 “Skills path”):
66
+
67
+ ```bash
68
+ mkdir -p ~/.cursor/skills
69
+ git clone https://github.com/hulk-yin/message-bridge.git ~/.cursor/skills/message-bridge
70
+ cd ~/.cursor/skills/message-bridge && npm install && npm run build:dist
71
+ ```
72
+
73
+ 安装后**重启 Cursor** 或重新加载技能,配置好 `FEISHU_APP_ID` / `FEISHU_APP_SECRET` / `FEISHU_CHAT_ID` 即可使用。
74
+
75
+ ---
76
+
77
+ ## Codex (OpenAI)
78
+
79
+ Codex 使用 `$CODEX_HOME/skills`(默认 `~/.codex/skills`),通过 **skill-installer** 从 GitHub 安装。若你已安装 [skill-installer](https://github.com/openai/skills) 的脚本,在具备网络权限的环境执行:
80
+
81
+ ```bash
82
+ # 从 Codex 的 skill-installer 目录执行(或把 install-skill-from-github.py 放到 PATH)
83
+ scripts/install-skill-from-github.py --repo hulk-yin/message-bridge --path .
84
+ ```
85
+
86
+ 将安装到 `$CODEX_HOME/skills/message-bridge`,目录内包含 `SKILL.md` 与完整实现。安装后**重启 Codex** 以加载新 skill。需在 skill 目录执行 `npm install` 与 `npm run build:dist` 后,`npm run turn` 等命令才可用。
87
+
88
+ 若无法使用上述脚本,可手动克隆:
89
+
90
+ ```bash
91
+ git clone https://github.com/hulk-yin/message-bridge.git "${CODEX_HOME:-$HOME/.codex}/skills/message-bridge"
92
+ cd "${CODEX_HOME:-$HOME/.codex}/skills/message-bridge" && npm install && npm run build:dist
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Claude Code / 其他环境
98
+
99
+ 只要该环境支持「从某目录加载 skill(读取 SKILL.md 或等价描述)」,即可将本仓库克隆到该目录:
100
+
101
+ ```bash
102
+ # 将 <SKILLS_ROOT> 替换为环境的 skill 根目录
103
+ git clone https://github.com/hulk-yin/message-bridge.git <SKILLS_ROOT>/message-bridge
104
+ cd <SKILLS_ROOT>/message-bridge && npm install && npm run build:dist
105
+ ```
106
+
107
+ 约定:
108
+
109
+ - **Skill 根目录** = 本仓库根目录 = 包含 `SKILL.md` 与 `package.json` 的目录。
110
+ - 所有文档中的 `npm run turn`、`node dist/feishu-turn.js` 等命令,均在 **该目录** 下执行。
111
+ - 环境变量 `FEISHU_APP_ID`、`FEISHU_APP_SECRET`、`FEISHU_CHAT_ID` 需在运行前配置(或使用 `DITING_FEISHU_*`)。
112
+
113
+ ---
114
+
115
+ ## 安装后自检
116
+
117
+ 在 **skill 根目录**(即本仓库根)执行:
118
+
119
+ ```bash
120
+ npm install
121
+ npm run build:dist
122
+ # 配置 FEISHU_* 后做一次快速测试(会真实发飞书)
123
+ node test-quick.js
124
+ ```
125
+
126
+ 若测试通过,即可在 Cursor/Codex/Claude 中通过「会话切换到飞书」或对应指令使用本 skill。
127
+
128
+ ---
129
+
130
+ ## 小结
131
+
132
+ | 方式 | 环境 | 安装方式 | 说明 |
133
+ |------|-------------|----------|------|
134
+ | **npm** | 任意 Node 项目 | `npm install skill-message-bridge` | 代码中 `require("skill-message-bridge")`;命令行 `npx skill-message-bridge "..."`;未发布前可用 `npm install github:hulk-yin/message-bridge` |
135
+ | **Git** | Cursor 项目 | `git clone ... .cursor/skills/message-bridge` | 项目内 `.cursor/skills/message-bridge` |
136
+ | **Git** | Cursor 全局 | `git clone ... ~/.cursor/skills/message-bridge` | `~/.cursor/skills/message-bridge` |
137
+ | **Git** | Codex | `install-skill-from-github.py --repo hulk-yin/message-bridge --path .` 或手动 clone | `$CODEX_HOME/skills/message-bridge` |
138
+ | **Git** | 其他 | `git clone ... <SKILLS_ROOT>/message-bridge` | `<SKILLS_ROOT>/message-bridge` |
139
+
140
+ **统一约定**:Git 方式下,实现与命令均以「本仓库根目录」为当前目录;npm 方式下,命令在安装目录 `node_modules/skill-message-bridge` 或通过 `npx` 运行。
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # MessageBridge
2
+
3
+ AI 智能体的多渠道消息桥梁,实现「发消息」与「等回复」,支持与 AI 对话闭环。**当前已实现飞书;钉钉、企微等欢迎社区共建。**
4
+
5
+ A multi-channel message bridge for AI agents: send messages and wait for replies. **Feishu is implemented; DingTalk, WeCom, etc. welcome community contributions.**
6
+
7
+ ---
8
+
9
+ ## 如何对接不同渠道 / Supported Channels
10
+
11
+ | 渠道 Channel | 状态 Status | 说明 |
12
+ |-------------|-------------|------|
13
+ | 飞书 Feishu | ✅ 已实现 | 需配置 `FEISHU_APP_ID` / `FEISHU_APP_SECRET` / `FEISHU_CHAT_ID`(或 `DITING_FEISHU_*`),长连接收消息。 |
14
+ | 钉钉 DingTalk | 📌 待共建 | 接口形态类似:发消息 + 收回复;接入步骤见 [CONTRIBUTING.md](./CONTRIBUTING.md#二新渠道接入--adding-a-new-channel)。 |
15
+ | 企微 WeCom | 📌 待共建 | 同上,欢迎按 CONTRIBUTING 清单提交适配。 |
16
+
17
+ 扩展新渠道:在 `src/platforms/` 增加适配器并实现「发消息 + 将用户回复回填到队列」,详见 [CONTRIBUTING](./CONTRIBUTING.md)。
18
+
19
+ ---
20
+
21
+ ## 参与共建 / Community
22
+
23
+ 欢迎补全其它 IM 渠道、补全文档与单测、或改进现有实现。请阅读 [CONTRIBUTING.md](./CONTRIBUTING.md),按「新渠道接入」清单或「贡献流程」提 PR;**欢迎 AI 按文档参与贡献**(见 CONTRIBUTING「给 AI 贡献者」)。
24
+
25
+ ---
26
+
27
+ ## 快速开始 / Quick Start
28
+
29
+ ```bash
30
+ # 1. 安装依赖
31
+ npm install
32
+
33
+ # 2. 配置环境变量(飞书示例,请替换为你的应用与群聊 ID)
34
+ export FEISHU_APP_ID="your_app_id"
35
+ export FEISHU_APP_SECRET="your_app_secret"
36
+ export FEISHU_CHAT_ID="oc_xxx"
37
+
38
+ # 3. 构建(若改过 src)
39
+ npm run build:dist
40
+
41
+ # 4. 运行测试
42
+ node test-quick.js
43
+ ```
44
+
45
+ ## 功能特性
46
+
47
+ ✅ **消息发送** - 发送消息到飞书群聊
48
+ ✅ **等待回复** - 发送消息并等待用户回复
49
+ ✅ **实时接收** - WebSocket 长链接实时接收消息
50
+ ✅ **超时处理** - 可配置超时时间
51
+ ✅ **任务队列** - 支持多任务管理
52
+
53
+ ## 使用示例
54
+
55
+ ```javascript
56
+ const messageBridge = require("./index.js");
57
+
58
+ // 发送消息并等待回复
59
+ const result = await messageBridge.notify({
60
+ message: "需要你确认一下",
61
+ timeout: 60,
62
+ });
63
+
64
+ if (result.status === "replied") {
65
+ console.log("用户回复:", result.reply);
66
+ }
67
+
68
+ // 仅发送消息
69
+ await messageBridge.send({
70
+ message: "任务完成!",
71
+ });
72
+ ```
73
+
74
+ ## 文档 / Docs
75
+
76
+ - [INSTALL.md](./INSTALL.md) - **安装为 Cursor / Codex / Claude Code Skill**(中英)
77
+ - [CONTRIBUTING.md](./CONTRIBUTING.md) - 贡献流程、新渠道接入、单测与 AI 友好说明(中英)
78
+ - [SKILL.md](./SKILL.md) - 与 AI 技能/闭环使用相关的详细说明
79
+
80
+ ## 开发进度
81
+
82
+ 详细进度请查看 [PROGRESS.md](./PROGRESS.md)
83
+
84
+ ## 测试
85
+
86
+ - `test.js` - 基础消息发送测试
87
+ - `test-sdk.js` - SDK 功能测试
88
+ - `test-ws-debug.js` - WebSocket 调试测试
89
+ - `test-complete.js` - 完整功能测试
90
+ - `test-quick.js` - 快速测试
91
+
92
+ ## 技术栈
93
+
94
+ - Node.js
95
+ - @larksuiteoapi/node-sdk
96
+ - WebSocket 长链接
97
+
98
+ ## 作者
99
+
100
+ 7号智创 - "7号,启航!"
101
+
102
+ ## 许可 / License
103
+
104
+ MIT
105
+
106
+ ---
107
+
108
+ ## 安装方式 / Install
109
+
110
+ - **npm**:`npm install skill-message-bridge`(发布后);未发布可 `npm install github:hulk-yin/message-bridge`。代码中 `require("skill-message-bridge")`,命令行 `npx skill-message-bridge "..."`。
111
+ - **Skill(Cursor / Codex / Claude)**:见 **[INSTALL.md](./INSTALL.md)**,支持 Git 克隆到各环境 skill 目录或从 npm 安装后使用。
112
+
113
+ ## English (short)
114
+
115
+ - **What**: Send messages and wait for user replies over IM (Feishu implemented; other channels welcome).
116
+ - **Quick start**: `npm install` → set `FEISHU_APP_ID` / `FEISHU_APP_SECRET` / `FEISHU_CHAT_ID` → `npm run build:dist` → `node test-quick.js`.
117
+ - **API**: `notify({ message, timeout })` returns `{ status: "replied"|"timeout"|"error", reply, replyUser }`; `send({ message })` for fire-and-forget.
118
+ - **Contributing**: See [CONTRIBUTING.md](./CONTRIBUTING.md) for new channels, tests, and AI-friendly checklists.
package/SKILL.md ADDED
@@ -0,0 +1,238 @@
1
+ # MessageBridge Skill
2
+
3
+ AI 智能体的消息桥梁,连接飞书/钉钉/企微,实现异步通知与确认。
4
+
5
+ **实现位置**:本仓库根目录即为 skill 实现目录。安装到 Cursor/Codex/Claude 等后,所有命令(如 `npm run turn`、`node dist/feishu-turn.js`)均在**该 skill 目录**下执行,不依赖绝对路径。安装方式见 [INSTALL.md](./INSTALL.md)。
6
+
7
+ ## 功能
8
+
9
+ - ✅ 发送消息到飞书群聊
10
+ - ✅ 发送消息并等待用户回复
11
+ - ✅ WebSocket 长链接实时接收
12
+ - ✅ 超时处理
13
+ - ✅ 任务队列管理
14
+
15
+ ## 使用方式
16
+
17
+ ### 1. 环境变量配置(本 skill 无其他外部依赖)
18
+
19
+ ```bash
20
+ export FEISHU_APP_ID="cli_xxx"
21
+ export FEISHU_APP_SECRET="xxx"
22
+ export FEISHU_CHAT_ID="oc_xxx"
23
+ ```
24
+
25
+ (也支持 `DITING_FEISHU_*` 命名,便于与使用 Diting 的项目共用同一套配置。)
26
+
27
+ ### 2. 在 Node.js 中使用
28
+
29
+ ```javascript
30
+ const messageBridge = require("./index.js");
31
+
32
+ // 发送消息并等待回复
33
+ const result = await messageBridge.notify({
34
+ message: "需要你确认一下这个操作",
35
+ timeout: 60, // 60秒超时
36
+ });
37
+
38
+ if (result.status === "replied") {
39
+ console.log("用户回复:", result.reply);
40
+ console.log("回复用户:", result.replyUser);
41
+ } else if (result.status === "timeout") {
42
+ console.log("超时未回复");
43
+ }
44
+
45
+ // 仅发送消息(不等待回复)
46
+ await messageBridge.send({
47
+ message: "任务已完成!",
48
+ });
49
+ ```
50
+
51
+ ### 3. 在 OpenClaw 中使用
52
+
53
+ ```javascript
54
+ // 在其他 AI 智能体中调用
55
+ const { notify, send } = require("@skills/message-bridge");
56
+
57
+ // 发送通知并等待确认
58
+ const result = await notify({
59
+ message: "检测到异常,是否继续?",
60
+ timeout: 120,
61
+ });
62
+
63
+ if (result.status === "replied" && result.reply.includes("继续")) {
64
+ // 用户确认继续
65
+ console.log("用户确认,继续执行");
66
+ } else {
67
+ // 用户拒绝或超时
68
+ console.log("用户拒绝或超时,停止执行");
69
+ }
70
+ ```
71
+
72
+ ## API
73
+
74
+ ### notify(params)
75
+
76
+ 发送消息并等待用户回复。
77
+
78
+ **参数:**
79
+ - `message` (string, 必需) - 消息内容
80
+ - `timeout` (number, 可选) - 超时时间(秒),默认 60
81
+ - `platform` (string, 可选) - 平台类型,默认 "feishu"
82
+ - `userId` (string, 可选) - 用户 ID
83
+ - `groupId` (string, 可选) - 群聊 ID
84
+
85
+ **返回:**
86
+ ```javascript
87
+ {
88
+ success: true,
89
+ status: "replied" | "timeout" | "error",
90
+ reply: "用户回复内容",
91
+ replyUser: "ou_xxx",
92
+ timestamp: "2026-02-10T09:00:00.000Z"
93
+ }
94
+ ```
95
+
96
+ ### send(params)
97
+
98
+ 仅发送消息,不等待回复。
99
+
100
+ **参数:**
101
+ - `message` (string, 必需) - 消息内容
102
+ - `platform` (string, 可选) - 平台类型,默认 "feishu"
103
+ - `userId` (string, 可选) - 用户 ID
104
+ - `groupId` (string, 可选) - 群聊 ID
105
+
106
+ **返回:**
107
+ ```javascript
108
+ {
109
+ success: true,
110
+ messageId: "om_xxx"
111
+ }
112
+ ```
113
+
114
+ ## 使用场景
115
+
116
+ ### 1. 审批流程
117
+ ```javascript
118
+ const result = await notify({
119
+ message: "检测到高风险操作,需要审批\n\n操作:删除生产数据库\n请回复「批准」或「拒绝」",
120
+ timeout: 300, // 5分钟
121
+ });
122
+
123
+ if (result.status === "replied" && result.reply === "批准") {
124
+ // 执行操作
125
+ }
126
+ ```
127
+
128
+ ### 2. 异常告警
129
+ ```javascript
130
+ await send({
131
+ message: "⚠️ 服务器 CPU 使用率超过 90%\n\n时间: " + new Date().toISOString(),
132
+ });
133
+ ```
134
+
135
+ ### 3. 任务完成通知
136
+ ```javascript
137
+ await send({
138
+ message: "✅ 数据备份完成\n\n文件数: 1234\n大小: 5.6GB",
139
+ });
140
+ ```
141
+
142
+ ### 4. 交互式确认
143
+ ```javascript
144
+ const result = await notify({
145
+ message: "发现 3 个待处理的问题,是否立即处理?",
146
+ timeout: 60,
147
+ });
148
+
149
+ if (result.status === "replied" && result.reply.includes("是")) {
150
+ // 处理问题
151
+ }
152
+ ```
153
+
154
+ ## 配置要求
155
+
156
+ ### 飞书开放平台配置
157
+
158
+ 1. **创建企业自建应用**
159
+ 2. **开通权限:**
160
+ - `im:message` - 发送消息
161
+ - `im:message:send_as_bot` - 以机器人身份发送
162
+ 3. **事件订阅:**
163
+ - 订阅方式:选择「长连接」
164
+ - 订阅事件:`im.message.receive_v1`
165
+ 4. **发布应用**
166
+
167
+ ## 会话切换到飞书 = 飞书长周期对话闭环
168
+
169
+ 当调用方(如 Cursor AI)收到用户说**「会话切换到飞书」**时,应进入**你与 skill 的长周期对话环境**:非飞书渠道启动 → AI 的输出发到飞书 → 飞书用户回复 → AI 基于回复继续思考 → 结果再发飞书,循环执行;**仅**在用户说「结束」「切回」时退出,不设轮数上限,不主动结束则永远 loop。
170
+
171
+ **单轮工具**(供 AI 循环调用):
172
+
173
+ ```bash
174
+ npm run turn -- "<AI 的回复内容>" 或 node dist/feishu-turn.js "<AI 的回复内容>"(需先 npm run build:dist)
175
+ ```
176
+
177
+ - 把内容发到飞书并等待用户回复。
178
+ - 输出单行 JSON:`{"status":"replied","reply":"用户回复", "replyUser":"?"}`;超时为 `"status":"timeout"`。
179
+ - 超时秒数:`FEISHU_TURN_TIMEOUT`(秒,默认 3600;飞书回复可能数小时可设 86400)。单轮超时后不要退出闭环,发「等待超时,如需继续请直接回复」并再次等待。
180
+
181
+ **闭环**:循环执行「AI 生成回复 → 调用 npm run turn 或 node dist/feishu-turn.js → 解析 reply → 再生成 → 再调用」;仅用户说「结束」/「切回」时退出,不设轮数上限,永远 loop。
182
+
183
+ **为何会自动断掉**:闭环在 Cursor 单次回复里跑,有工具调用/上下文上限,跑一段时间就会结束当次回复。**解决**:要不依赖 Cursor 的持久对话,可运行常驻进程 `node feishu-conversation.js`(配置 AI_REPLY_URL 或 OPENAI_API_KEY),在飞书里一直聊直到你说结束;若继续用 Cursor 闭环,断掉后说「继续飞书」即恢复。
184
+
185
+ ## 可选:纯飞书端对话(feishu-conversation.js)
186
+
187
+ 不经过 Cursor、只在飞书里和机器人对话时,可单独运行:
188
+
189
+ ```bash
190
+ node feishu-conversation.js # 或 npm run conversation
191
+ ```
192
+
193
+ 需配置 **AI_REPLY_URL** 或 **OPENAI_API_KEY**(+ OPENAI_BASE_URL、OPENAI_MODEL)。飞书需已订阅 `im.message.receive_v1`(长连接)。
194
+
195
+ ## 测试
196
+
197
+ ```bash
198
+ # 基础测试
199
+ node test.js
200
+
201
+ # 完整功能测试
202
+ node test-complete.js
203
+
204
+ # WebSocket 调试
205
+ node test-ws-debug.js
206
+
207
+ # 快速测试
208
+ node test-quick.js
209
+ ```
210
+
211
+ ## 技术实现
212
+
213
+ - **SDK**: `@larksuiteoapi/node-sdk`
214
+ - **连接方式**: WebSocket 长链接
215
+ - **消息格式**: JSON
216
+ - **超时处理**: Promise + setTimeout
217
+
218
+ ## 限制
219
+
220
+ - 当前仅支持飞书平台
221
+ - 仅支持文本消息
222
+ - 简单的消息匹配逻辑(按时间顺序)
223
+
224
+ ## 未来计划
225
+
226
+ - [ ] 支持钉钉、企业微信
227
+ - [ ] 支持富文本、卡片消息
228
+ - [ ] 改进消息匹配逻辑(基于 message_id)
229
+ - [ ] 支持多用户并发
230
+ - [ ] 添加消息历史记录
231
+
232
+ ## 作者
233
+
234
+ 7号智创 - "7号,启航!"
235
+
236
+ ## 许可
237
+
238
+ MIT
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 飞书单轮:把「你的回复」发到飞书,等待用户回复,输出用户回复(供 AI 在闭环中循环调用)。
4
+ * 用法:node feishu-turn.js "你要发到飞书的内容"
5
+ * 或 echo "内容" | node feishu-turn.js
6
+ * 输出:单行 JSON {"status":"replied"|"timeout"|"error", "reply":"用户回复", "replyUser": "?"}
7
+ * 超时:FEISHU_TURN_TIMEOUT 秒,默认 3600(1 小时);飞书回复可能较久甚至数小时时可设为 86400 等。
8
+ */
9
+ const mb = require("./index.js");
10
+
11
+ const timeout = parseInt(process.env.FEISHU_TURN_TIMEOUT || "3600", 10);
12
+
13
+ function getMessage() {
14
+ const arg = process.argv[2];
15
+ if (arg !== undefined && arg !== "") return arg;
16
+ const fs = require("fs");
17
+ if (!process.stdin.isTTY) {
18
+ return fs.readFileSync(0, "utf8").trim();
19
+ }
20
+ return "";
21
+ }
22
+
23
+ async function main() {
24
+ const message = getMessage();
25
+ if (!message) {
26
+ console.log(JSON.stringify({ status: "error", reply: "", error: "empty message" }));
27
+ process.exit(1);
28
+ }
29
+
30
+ try {
31
+ const result = await mb.notify({ message, timeout });
32
+ const out = {
33
+ status: result.status || "error",
34
+ reply: result.reply || "",
35
+ replyUser: result.replyUser || "",
36
+ };
37
+ if (result.error) out.error = result.error;
38
+ console.log(JSON.stringify(out));
39
+ process.exit(result.status === "replied" ? 0 : 1);
40
+ } catch (err) {
41
+ console.log(JSON.stringify({ status: "error", reply: "", error: err.message }));
42
+ process.exit(1);
43
+ } finally {
44
+ mb.close();
45
+ }
46
+ }
47
+
48
+ main();
package/dist/index.js ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * MessageBridge Skill - 完整实现(修复版)
3
+ *
4
+ * 使用飞书 WebSocket 长链接实现消息发送和接收
5
+ */
6
+
7
+ const lark = require("@larksuiteoapi/node-sdk");
8
+
9
+ // 配置
10
+ const config = {
11
+ appId: process.env.FEISHU_APP_ID || process.env.DITING_FEISHU_APP_ID || "",
12
+ appSecret: process.env.FEISHU_APP_SECRET || process.env.DITING_FEISHU_APP_SECRET || "",
13
+ chatId: process.env.FEISHU_CHAT_ID || process.env.DITING_FEISHU_CHAT_ID || "",
14
+ };
15
+
16
+ // 全局客户端
17
+ let httpClient = null;
18
+ let wsClient = null;
19
+ let eventDispatcher = null;
20
+ let isInitialized = false;
21
+
22
+ // 待处理的任务队列
23
+ const pendingTasks = new Map();
24
+
25
+ /**
26
+ * 初始化 MessageBridge
27
+ */
28
+ async function init() {
29
+ if (isInitialized) {
30
+ return;
31
+ }
32
+
33
+ console.log("[MessageBridge] 初始化...");
34
+
35
+ // 创建 HTTP 客户端
36
+ httpClient = new lark.Client({
37
+ appId: config.appId,
38
+ appSecret: config.appSecret,
39
+ appType: lark.AppType.SelfBuild,
40
+ domain: lark.Domain.Feishu,
41
+ });
42
+
43
+ // 创建事件处理器
44
+ eventDispatcher = new lark.EventDispatcher({}).register({
45
+ "im.message.receive_v1": async (data) => {
46
+ const message = data.message;
47
+
48
+ try {
49
+ const content = JSON.parse(message.content);
50
+ const senderId = message.sender?.sender_id?.open_id || message.sender?.sender_id?.user_id || "unknown";
51
+ const text = content.text || "";
52
+
53
+ console.log(`[MessageBridge] 收到消息: ${text} (from ${senderId})`);
54
+
55
+ // 查找等待回复的任务
56
+ for (const [taskId, task] of pendingTasks.entries()) {
57
+ if (task.status === "pending") {
58
+ // 简单匹配:回复最近的任务
59
+ task.reply = text;
60
+ task.replyUser = senderId;
61
+ task.status = "resolved";
62
+ task.repliedAt = new Date();
63
+
64
+ // 触发回调
65
+ if (task.resolve) {
66
+ task.resolve(task);
67
+ }
68
+
69
+ console.log(`[MessageBridge] 任务 ${taskId} 已解决`);
70
+ break;
71
+ }
72
+ }
73
+ } catch (error) {
74
+ console.error("[MessageBridge] 处理消息失败:", error.message);
75
+ }
76
+
77
+ return { code: 0 };
78
+ },
79
+ });
80
+
81
+ // 创建 WebSocket 客户端
82
+ wsClient = new lark.WSClient({
83
+ appId: config.appId,
84
+ appSecret: config.appSecret,
85
+ loggerLevel: lark.LoggerLevel.error, // 只显示错误
86
+ });
87
+
88
+ // 启动 WebSocket
89
+ wsClient.start({ eventDispatcher });
90
+
91
+ // 等待连接建立
92
+ await new Promise(resolve => setTimeout(resolve, 2000));
93
+
94
+ isInitialized = true;
95
+ console.log("[MessageBridge] 初始化完成");
96
+ }
97
+
98
+ /**
99
+ * 发送消息并等待回复
100
+ */
101
+ async function notify(params) {
102
+ const {
103
+ message,
104
+ platform = "feishu",
105
+ userId,
106
+ groupId,
107
+ timeout = 60, // 默认 60 秒超时
108
+ } = params;
109
+
110
+ // 确保已初始化
111
+ await init();
112
+
113
+ // 创建任务
114
+ const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
115
+ const task = {
116
+ taskId,
117
+ message,
118
+ platform,
119
+ userId,
120
+ groupId,
121
+ timeout,
122
+ status: "pending",
123
+ createdAt: new Date(),
124
+ reply: null,
125
+ replyUser: null,
126
+ repliedAt: null,
127
+ };
128
+
129
+ pendingTasks.set(taskId, task);
130
+
131
+ try {
132
+ // 确定接收者类型和 ID
133
+ const targetId = groupId || config.chatId || userId;
134
+ const receiveIdType = groupId || config.chatId ? "chat_id" : "open_id";
135
+
136
+ // 发送消息
137
+ console.log(`[MessageBridge] 发送消息 (${receiveIdType}): ${message}`);
138
+ const res = await httpClient.im.message.create({
139
+ params: {
140
+ receive_id_type: receiveIdType,
141
+ },
142
+ data: {
143
+ receive_id: targetId,
144
+ msg_type: "text",
145
+ content: JSON.stringify({ text: message }),
146
+ },
147
+ });
148
+
149
+ if (res.code !== 0) {
150
+ throw new Error(`发送失败: ${res.msg}`);
151
+ }
152
+
153
+ console.log(`[MessageBridge] 消息已发送: ${res.data.message_id}`);
154
+
155
+ // 等待回复
156
+ const result = await new Promise((resolve, reject) => {
157
+ task.resolve = resolve;
158
+ task.reject = reject;
159
+
160
+ // 超时处理
161
+ setTimeout(() => {
162
+ if (task.status === "pending") {
163
+ task.status = "timeout";
164
+ reject(new Error("Timeout waiting for reply"));
165
+ }
166
+ }, timeout * 1000);
167
+ });
168
+
169
+ // 清理任务
170
+ pendingTasks.delete(taskId);
171
+
172
+ return {
173
+ success: true,
174
+ reply: result.reply,
175
+ replyUser: result.replyUser,
176
+ timestamp: result.repliedAt?.toISOString(),
177
+ status: "replied",
178
+ };
179
+ } catch (error) {
180
+ // 清理任务
181
+ pendingTasks.delete(taskId);
182
+
183
+ if (error.message.includes("Timeout")) {
184
+ return {
185
+ success: true,
186
+ status: "timeout",
187
+ error: "Timeout waiting for user reply",
188
+ };
189
+ }
190
+
191
+ return {
192
+ success: false,
193
+ status: "error",
194
+ error: error.message,
195
+ };
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 仅发送消息,不等待回复
201
+ */
202
+ async function send(params) {
203
+ const {
204
+ message,
205
+ platform = "feishu",
206
+ userId,
207
+ groupId,
208
+ } = params;
209
+
210
+ // 确保已初始化
211
+ await init();
212
+
213
+ try {
214
+ // 确定接收者类型和 ID
215
+ const targetId = groupId || config.chatId || userId;
216
+ const receiveIdType = groupId || config.chatId ? "chat_id" : "open_id";
217
+
218
+ console.log(`[MessageBridge] 发送消息 (${receiveIdType}): ${message}`);
219
+ const res = await httpClient.im.message.create({
220
+ params: {
221
+ receive_id_type: receiveIdType,
222
+ },
223
+ data: {
224
+ receive_id: targetId,
225
+ msg_type: "text",
226
+ content: JSON.stringify({ text: message }),
227
+ },
228
+ });
229
+
230
+ if (res.code !== 0) {
231
+ throw new Error(`发送失败: ${res.msg}`);
232
+ }
233
+
234
+ console.log(`[MessageBridge] 消息已发送: ${res.data.message_id}`);
235
+
236
+ return {
237
+ success: true,
238
+ messageId: res.data.message_id,
239
+ };
240
+ } catch (error) {
241
+ return {
242
+ success: false,
243
+ error: error.message,
244
+ };
245
+ }
246
+ }
247
+
248
+ /**
249
+ * 关闭连接
250
+ */
251
+ function close() {
252
+ if (wsClient) {
253
+ // WebSocket 客户端没有 close 方法,会自动管理
254
+ console.log("[MessageBridge] 关闭连接");
255
+ }
256
+ isInitialized = false;
257
+ }
258
+
259
+ module.exports = {
260
+ init,
261
+ notify,
262
+ send,
263
+ close,
264
+ name: "messageBridge",
265
+ description: "AI 智能体的消息桥梁,连接飞书/钉钉/企微,实现异步通知与确认",
266
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "skill-message-bridge",
3
+ "version": "0.1.0",
4
+ "description": "AI 智能体的消息桥梁,连接飞书/钉钉/企微,实现异步通知与确认",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "ts-node src/index.ts",
10
+ "test": "node test.js",
11
+ "conversation": "node feishu-conversation.js",
12
+ "build:dist": "node scripts/copy-to-dist.js",
13
+ "turn": "node dist/feishu-turn.js",
14
+ "prepublishOnly": "npm run build:dist"
15
+ },
16
+ "bin": {
17
+ "skill-message-bridge": "dist/feishu-turn.js",
18
+ "message-bridge-turn": "dist/feishu-turn.js"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "SKILL.md",
24
+ "INSTALL.md",
25
+ "CONTRIBUTING.md"
26
+ ],
27
+ "keywords": [
28
+ "message-bridge",
29
+ "feishu",
30
+ "dingtalk",
31
+ "wecom",
32
+ "ai-agent",
33
+ "notification",
34
+ "skill",
35
+ "cursor",
36
+ "codex"
37
+ ],
38
+ "author": "7号智创",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/hulk-yin/message-bridge.git"
43
+ },
44
+ "homepage": "https://github.com/hulk-yin/message-bridge#readme",
45
+ "dependencies": {
46
+ "@larksuiteoapi/lark-mcp": "^0.5.0",
47
+ "@larksuiteoapi/node-sdk": "^1.58.0",
48
+ "node-fetch": "^2.7.0",
49
+ "ws": "^8.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.0.0",
53
+ "@types/ws": "^8.0.0",
54
+ "ts-node": "^10.0.0",
55
+ "typescript": "^5.0.0"
56
+ }
57
+ }