@xgjktech/xg_cwork_im 1.0.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.
package/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # XG CWork IM Channel for OpenClaw
2
+
3
+ 小橙工作台 IM 机器人 Channel 插件,通过 WebSocket 长连接接收 `@` 消息,由 OpenClaw AI 处理后自动回复。
4
+
5
+ ## 工作原理
6
+
7
+ ```
8
+ IM 系统 → WebSocket → 插件(本项目) → OpenClaw AI → IM 发送接口 → IM 系统
9
+ ```
10
+
11
+ 1. 插件启动时用 `appKey` 换取 `access_token` 和 `userId`
12
+ 2. 建立 WebSocket 长连接,接收 `robotMention` 事件
13
+ 3. 将收到的消息转发给 OpenClaw 处理
14
+ 4. OpenClaw AI 回复后,插件调用 IM 接口发送消息并 `@` 原始发送者
15
+
16
+ ---
17
+
18
+ ## 前置条件
19
+
20
+ - 已安装 [OpenClaw](https://github.com/openclaw) (`>=2026.2.13`)
21
+ - 已在 IM 后台注册机器人,获取到 `appKey`
22
+
23
+ ---
24
+
25
+ ## 安装
26
+
27
+ ### 方法 A:通过 npm 包安装(推荐)
28
+
29
+ 一条命令完成安装,依赖由 OpenClaw 自动处理:
30
+
31
+ ```bash
32
+ openclaw plugins install @xgjktech/xg_cwork_im
33
+ ```
34
+
35
+ 运行 `openclaw plugins list` 确认列表中有 `xg_cwork_im` 即可。
36
+
37
+ ### 方法 B:通过本地源码安装
38
+
39
+ 如需二次开发或无法使用 npm 安装时,可克隆后以链接模式安装:
40
+
41
+ ```bash
42
+ git clone https://github.com/cwei001/openclaw-channel-xg-cwork-im.git
43
+ cd openclaw-channel-xg-cwork-im
44
+ npm install
45
+ openclaw plugins install -l .
46
+ ```
47
+
48
+ ### 方法 C:手动安装
49
+
50
+ 1. 将本仓库下载或复制到 `~/.openclaw/extensions/xg_cwork_im`(Windows 为 `%USERPROFILE%\.openclaw\extensions\xg_cwork_im`)。
51
+ 2. 确保目录内包含 `index.ts`、`openclaw.plugin.json`、`package.json`。
52
+ 3. 在该目录执行 `npm install --omit=dev` 或 `npm run install:prod` 安装依赖。
53
+ 4. 运行 `openclaw plugins list` 确认 `xg_cwork_im` 已显示。
54
+
55
+ ### 方法 D:国内网络环境(npm 镜像源)
56
+
57
+ 若执行 `openclaw plugins install @xgjktech/xg_cwork_im` 时卡在「Installing plugin dependencies...」或出现 `npm install failed`,可临时指定国内镜像源:
58
+
59
+ ```bash
60
+ NPM_CONFIG_REGISTRY=https://registry.npmmirror.com openclaw plugins install @xgjktech/xg_cwork_im
61
+ ```
62
+
63
+ 若插件已半安装(扩展目录存在但依赖未装全),可进入插件目录手动补装:
64
+
65
+ ```bash
66
+ cd ~/.openclaw/extensions/xg_cwork_im
67
+ # Windows: cd %USERPROFILE%\.openclaw\extensions\xg_cwork_im
68
+ rm -rf node_modules package-lock.json
69
+ NPM_CONFIG_REGISTRY=https://registry.npmmirror.com npm install
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 配置
75
+
76
+ ### 步骤 1:启用并信任插件
77
+
78
+ 执行以下命令,将 `xg_cwork_im` 插件添加到 OpenClaw 的信任白名单中:
79
+
80
+ ```bash
81
+ openclaw plugins enable xg_cwork_im
82
+ ```
83
+
84
+ ### 步骤 2:配置 Channel 账户信息
85
+
86
+ 编辑 `~/.openclaw/openclaw.json`,添加以下配置:
87
+
88
+ ```json
89
+ {
90
+ "channels": {
91
+ "xg_cwork_im": {
92
+ "baseUrl": "https://cwork-web-test.xgjktech.com.cn",
93
+ "wsBaseUrl": "wss://cwork-web-test.xgjktech.com.cn",
94
+ "accounts": [
95
+ { "appKey": "你的机器人 appKey", "agentId": "main", "name": "个人助手" }
96
+ ]
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ > **多账户**:如有多个机器人,在 `accounts` 数组里继续追加即可,每个账户可指定不同的 `agentId` 对应不同的 OpenClaw Agent。
103
+
104
+ ### 步骤 3:重启 Gateway
105
+
106
+ 配置完成后,重启网关使配置生效:
107
+
108
+ ```bash
109
+ openclaw gateway restart
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 完整配置参考
115
+
116
+ `~/.openclaw/openclaw.json` 参考结构(示例使用 CWork 测试/生产域名,详细请参考 `docs/im服务接口说明.md`):
117
+
118
+ ```json5
119
+ {
120
+ "plugins": {
121
+ "enabled": true,
122
+ "allow": ["xg_cwork_im"]
123
+ },
124
+ "channels": {
125
+ "xg_cwork_im": {
126
+ "baseUrl": "https://cwork-web-test.xgjktech.com.cn",
127
+ "wsBaseUrl": "wss://cwork-web-test.xgjktech.com.cn",
128
+ "groupPolicy": "mention",
129
+ "debug": false,
130
+ "accounts": [
131
+ { "appKey": "appKey_机器人A", "agentId": "main", "name": "个人助手" },
132
+ { "appKey": "appKey_机器人B", "agentId": "sales", "name": "销售助手" }
133
+ ]
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### 生产环境配置示例
140
+
141
+ ```json5
142
+ {
143
+ "channels": {
144
+ "xg_cwork_im": {
145
+ "baseUrl": "https://sg-al-cwork-web.mediportal.com.cn",
146
+ "wsBaseUrl": "wss://sg-al-cwork-web.mediportal.com.cn",
147
+ "groupPolicy": "mention",
148
+ "debug": false,
149
+ "maxConnectionAttempts": 20,
150
+ "maxReconnectDelay": 120000,
151
+ "accounts": [
152
+ { "appKey": "你的生产 appKey", "agentId": "main", "name": "个人助手" }
153
+ ]
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ ---
160
+
161
+ ## 配置项说明
162
+
163
+ ### 顶层配置(`channels.xg_cwork_im`)
164
+
165
+ | 选项 | 类型 | 默认值 | 说明 |
166
+ |---|---|---|---|
167
+ | `baseUrl` | string | **必填** | IM 服务域名(API 接口地址) |
168
+ | `wsBaseUrl` | string | — | **可选**。WebSocket 独立域名。若不填,插件将尝试自动映射。 |
169
+ | `groupPolicy` | string | `"mention"` | `open`=收全部消息;`mention`=仅 @ 机器人触发 |
170
+ | `enabled` | boolean | `true` | 是否启用 |
171
+ | `debug` | boolean | `false` | 开启调试日志 |
172
+ | `maxConnectionAttempts` | number | `10` | WebSocket 最大重连次数 |
173
+ | `initialReconnectDelay` | number | `1000` | 初始重连延迟(ms) |
174
+ | `maxReconnectDelay` | number | `60000` | 最大重连延迟(ms) |
175
+ | `reconnectJitter` | number | `0.3` | 重连抖动因子(0-1) |
176
+
177
+ ### 账户配置(`accounts[n]`)
178
+
179
+ | 选项 | 类型 | 默认值 | 说明 |
180
+ |---|---|---|---|
181
+ | `appKey` | string | **必填** | 机器人 appKey(IM 后台注册获取) |
182
+ | `agentId` | string | `"main"` | 对应的 OpenClaw Agent ID |
183
+ | `name` | string | — | 账户显示名称(仅用于日志标识) |
184
+ | `groupPolicy` | string | 继承顶层 | 可覆盖顶层的 groupPolicy |
185
+
186
+ ---
187
+
188
+ ## 故障排除
189
+
190
+ ### 收不到消息
191
+ - 确认 `appKey` 正确,且机器人已在 IM 后台激活
192
+ - 查看 OpenClaw 日志,确认 WebSocket 连接成功(`WebSocket connected successfully`)
193
+ - 群聊中确认已 `@` 机器人
194
+
195
+ ### WebSocket 频繁断连
196
+ - 检查网络稳定性
197
+ - 适当增大 `maxConnectionAttempts` 和 `maxReconnectDelay`
198
+ - 开启 `debug: true` 查看详细日志
199
+
200
+ ### token 相关报错
201
+ - 确认 `baseUrl` 域名可达
202
+ - 确认 `appKey` 有效(可在 IM 后台查看机器人状态)
203
+
204
+ ---
205
+
206
+ ## 项目结构
207
+
208
+ ```
209
+ openclaw-channel-xg-cwork-im/
210
+ ├── index.ts # 插件入口
211
+ ├── src/
212
+ │ ├── types.ts # 类型定义
213
+ │ ├── auth.ts # 认证(appKey → token)
214
+ │ ├── connection.ts # WebSocket 连接管理
215
+ │ ├── send-service.ts # IM 消息发送
216
+ │ ├── group-history-tool.ts # 拉取群消息历史的 Agent Tool
217
+ │ └── channel.ts # Channel Plugin 主定义
218
+ ├── package.json
219
+ ├── tsconfig.json
220
+ ├── openclaw.plugin.json
221
+ └── README.md
222
+ ```
223
+
224
+ ---
225
+
226
+ ## 维护者:发布到 npm
227
+
228
+ 本插件包名为 `@xgjktech/xg_cwork_im`(scoped 包)。按以下步骤发布到 npm 后,同事即可用 `openclaw plugins install @xgjktech/xg_cwork_im` 安装。
229
+
230
+ ### 1. 注册 / 登录 npm
231
+
232
+ - 无账号:打开 [https://www.npmjs.com/signup](https://www.npmjs.com/signup) 注册。
233
+ - 已有账号:在项目根目录执行:
234
+
235
+ ```bash
236
+ npm login
237
+ ```
238
+
239
+ 按提示输入 Username、Password、Email 及一次性验证码(若开启 2FA)。
240
+
241
+ ### 2. 确认 scope 权限
242
+
243
+ 包名是 `@xgjktech/xg_cwork_im`,表示属于 **scope** `@xgjktech`。两种方式二选一:
244
+
245
+ - **使用 npm 组织**:在 [npm 官网](https://www.npmjs.com/) 登录 → Organizations → Create → 创建名为 `xgjktech` 的组织,并把你的账号加入。
246
+ - **使用个人 scope**:若你希望包名是个人 scope(如 `@你的用户名/xg_cwork_im`),需把 `package.json` 里的 `"name"` 改成 `@你的用户名/xg_cwork_im`,并同步修改 README 中的安装命令。
247
+
248
+ ### 3. 发布前检查
249
+
250
+ 在仓库根目录执行:
251
+
252
+ ```bash
253
+ # 查看将要随包发布的文件(与 package.json 的 "files" 一致)
254
+ npm pack --dry-run
255
+ ```
256
+
257
+ 确认没有敏感文件(如 `.env`、密钥)。`package.json` 里已通过 `"files"` 指定只发布必要文件。
258
+
259
+ ### 4. 执行发布
260
+
261
+ **scoped 包首次发布必须加 `--access public`**,否则会变成付费的私有包:
262
+
263
+ ```bash
264
+ npm publish --access public
265
+ ```
266
+
267
+ 发布成功后可在 [https://www.npmjs.com/package/@xgjktech/xg_cwork_im](https://www.npmjs.com/package/@xgjktech/xg_cwork_im) 查看。
268
+
269
+ ### 5. 后续更新
270
+
271
+ 修改代码后,先在 `package.json` 里把 `version` 提高(如 `1.0.0` → `1.0.1`),再执行:
272
+
273
+ ```bash
274
+ npm publish --access public
275
+ ```
276
+
277
+ (若该 scope 下此前已发布过公开包,可只执行 `npm publish`。)
278
+
279
+ ### 常见问题
280
+
281
+ - **403 Forbidden**:当前账号无 `@xgjktech` 的发布权限,需在 npm 上创建/加入组织,或将包名改为你的个人 scope。
282
+ - **402 Payment Required**:未加 `--access public`,scoped 包被当成私有包,需改为 `npm publish --access public`。
283
+ - **国内网络**:可临时使用镜像登录与发布(不推荐长期用镜像发布):
284
+ `npm login --registry=https://registry.npmjs.org/`,发布时仍用默认 registry 或 `--registry=https://registry.npmjs.org/`。
package/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * XG-IM Channel Plugin for OpenClaw
3
+ *
4
+ * 插件入口:注册 XG-IM channel,并注入 PluginRuntime 供消息路由使用
5
+ */
6
+
7
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
8
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
9
+ import { setXgImRuntime, xgCworkImChannelPlugin } from "./src/channel.js";
10
+ import { buildGroupHistoryTool, extractXgCworkImConfig } from "./src/group-history-tool.js";
11
+ import { buildSendGroupMessageTool } from "./src/send-group-message-tool.js";
12
+ import type { XgCworkImPluginModule } from "./src/types.js";
13
+
14
+ const plugin: XgCworkImPluginModule = {
15
+ id: "xg_cwork_im",
16
+ name: "XG CWork IM Channel",
17
+ description: "小橙工作台 IM 机器人 Channel 插件,通过 WebSocket 长连接接入",
18
+ configSchema: emptyPluginConfigSchema(),
19
+ register(api: OpenClawPluginApi): void {
20
+ setXgImRuntime(api.runtime);
21
+ api.registerChannel({ plugin: xgCworkImChannelPlugin });
22
+ // 注册 AI 工具(静态工具对象,确保 toolNames 正确注册)
23
+ const cworkConfig = extractXgCworkImConfig(api.config);
24
+ if (cworkConfig) {
25
+ api.registerTool(buildGroupHistoryTool(cworkConfig));
26
+ api.registerTool(buildSendGroupMessageTool(cworkConfig));
27
+ } else {
28
+ console.warn("[xg_cwork_im] channel config not found, skipping tool registration");
29
+ }
30
+ },
31
+ };
32
+
33
+ export default plugin;
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "xg_cwork_im",
3
+ "channels": [
4
+ "xg_cwork_im"
5
+ ],
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": true,
9
+ "properties": {}
10
+ }
11
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@xgjktech/xg_cwork_im",
3
+ "version": "1.0.0",
4
+ "description": "XG CWork IM channel plugin for OpenClaw",
5
+ "keywords": [
6
+ "bot",
7
+ "channel",
8
+ "openclaw",
9
+ "xgim",
10
+ "im"
11
+ ],
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "main": "index.ts",
15
+ "files": [
16
+ ".npmrc",
17
+ "index.ts",
18
+ "src/*.ts",
19
+ "openclaw.plugin.json"
20
+ ],
21
+ "scripts": {
22
+ "type-check": "tsc -p tsconfig.json --noEmit",
23
+ "lint": "tsc -p tsconfig.json --noEmit",
24
+ "install:prod": "npm install --omit=dev"
25
+ },
26
+ "dependencies": {
27
+ "axios": "^1.6.0",
28
+ "ws": "^8.17.0",
29
+ "zod": "^3.22.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "@types/ws": "^8.5.10",
34
+ "typescript": "^5.3.0"
35
+ },
36
+ "peerDependencies": {
37
+ "openclaw": ">=2026.2.13"
38
+ },
39
+ "openclaw": {
40
+ "extensions": [
41
+ "./index.ts"
42
+ ],
43
+ "channels": [
44
+ "xg_cwork_im"
45
+ ],
46
+ "installDependencies": false,
47
+ "channel": {
48
+ "id": "xg_cwork_im",
49
+ "label": "XG CWork IM",
50
+ "selectionLabel": "XG CWork IM (小橙工作台)",
51
+ "blurb": "小橙工作台 IM 机器人 Channel 插件,通过 WebSocket 长连接接收消息。",
52
+ "aliases": [
53
+ "xg_cwork_im",
54
+ "xgim",
55
+ "im"
56
+ ]
57
+ },
58
+ "install": {
59
+ "localPath": ".",
60
+ "defaultChoice": "local"
61
+ }
62
+ }
63
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * XG-IM 认证模块
3
+ *
4
+ * 通过 appKey 换取 access_token 和机器人 userId。
5
+ * token 有效期一年,启动时获取一次并缓存,无需定期刷新。
6
+ */
7
+
8
+ import axios from "axios";
9
+ import type { BotIdentity, GetTokenResponse, XgImConfig } from "./types.js";
10
+
11
+ export type Log = {
12
+ info: (msg: string) => void;
13
+ warn?: (msg: string) => void;
14
+ error: (msg: string) => void;
15
+ debug?: (msg: string) => void;
16
+ };
17
+
18
+ /** 运行时 token 缓存,按 appKey 索引 */
19
+ const tokenCache = new Map<string, BotIdentity>();
20
+
21
+ /**
22
+ * 通过 appKey 获取机器人身份信息(token + userId)。
23
+ * 若缓存中已有有效 token,直接返回缓存;否则请求接口。
24
+ *
25
+ * @param config 插件配置
26
+ * @param log 日志接口
27
+ * @param force 强制重新获取(忽略缓存)
28
+ */
29
+ export async function getToken(
30
+ config: XgImConfig,
31
+ log?: Log,
32
+ force = false,
33
+ ): Promise<BotIdentity> {
34
+ if (!config.appKey) {
35
+ throw new Error("[cwork_im] appKey is required");
36
+ }
37
+ const cacheKey = `${config.baseUrl}:${config.appKey}`;
38
+
39
+ if (!force) {
40
+ const cached = tokenCache.get(cacheKey);
41
+ if (cached) {
42
+ log?.info?.(`[cwork_im] Using cached token for appKey=${config.appKey}`);
43
+ return cached;
44
+ }
45
+ }
46
+
47
+ const url = `${config.baseUrl}/user/login/appkey?appKey=${encodeURIComponent(config.appKey)}&appCode=im`;
48
+ log?.info(`[cwork_im:auth] GET ${url} appKey=${config.appKey}`);
49
+
50
+ const response = await axios.get<GetTokenResponse>(url, {
51
+ headers: { "Content-Type": "application/json" },
52
+ timeout: 10_000,
53
+ });
54
+
55
+ const body = response.data;
56
+ log?.info(`[cwork_im:auth] Response status=${response.status} resultCode=${body?.resultCode ?? "n/a"}`);
57
+ if (!body?.data?.xgToken || !body?.data?.empId) {
58
+ throw new Error(
59
+ `[cwork_im] getToken failed: unexpected response — ${JSON.stringify(body)}`,
60
+ );
61
+ }
62
+
63
+ const identity: BotIdentity = {
64
+ token: body.data.xgToken,
65
+ userId: body.data.empId,
66
+ name: body.data.userName ?? "bot",
67
+ };
68
+
69
+ tokenCache.set(cacheKey, identity);
70
+ log?.info(`[cwork_im:auth] Token acquired: userId=${identity.userId} name=${identity.name} appKey=${config.appKey}`);
71
+
72
+ return identity;
73
+ }
74
+
75
+ /** 清除指定账户的 token 缓存(用于重连时强制重新鉴权) */
76
+ export function clearTokenCache(config: XgImConfig): void {
77
+ const cacheKey = `${config.baseUrl}:${config.appKey}`;
78
+ tokenCache.delete(cacheKey);
79
+ }