openclaw-plugin-wecom 0.3.2 → 0.3.3

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 CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  ### Option 1: Docker Deployment (Recommended)
19
19
 
20
- This repository provides a complete Docker deployment solution with automated plugin installation.
20
+ This repository provides a complete Docker deployment solution that **deploys OpenClaw + WeCom plugin in one step**, with automated installation and configuration.
21
21
 
22
22
  ```bash
23
23
  # 1. Clone the repository
@@ -83,11 +83,11 @@ Then add to your OpenClaw configuration:
83
83
  {
84
84
  "plugins": {
85
85
  "entries": {
86
- "openclaw-plugin-wecom": { "enabled": true }
86
+ "wecom": { "enabled": true }
87
87
  }
88
88
  },
89
89
  "channels": {
90
- "wxwork": {
90
+ "wecom": {
91
91
  "enabled": true,
92
92
  "token": "Your Token",
93
93
  "encodingAesKey": "Your EncodingAESKey"
@@ -99,7 +99,7 @@ Then add to your OpenClaw configuration:
99
99
  ### WeCom Backend Setup
100
100
 
101
101
  1. Create an "Intelligent Bot" in WeCom Admin Console.
102
- 2. Set the "Receive Message" URL to your service address (e.g., `https://your-domain.com/webhooks/wxwork`).
102
+ 2. Set the "Receive Message" URL to your service address (e.g., `https://your-domain.com/webhooks/wecom`).
103
103
  3. Enter the corresponding Token and EncodingAESKey.
104
104
 
105
105
  ## 📂 Project Structure
@@ -127,13 +127,13 @@ openclaw-plugin-wecom/
127
127
  The plugin implements per-user/per-group isolation:
128
128
 
129
129
  1. On message arrival, generates a deterministic `agentId`:
130
- - DM: `wxwork-dm-<userId>`
131
- - Group: `wxwork-group-<chatId>`
130
+ - DM: `wecom-dm-<userId>`
131
+ - Group: `wecom-group-<chatId>`
132
132
  2. OpenClaw automatically creates/reuses the corresponding Agent workspace.
133
133
 
134
134
  ### Configuration Options
135
135
 
136
- Under `channels.wxwork`:
136
+ Under `channels.wecom`:
137
137
 
138
138
  | Option | Type | Default | Description |
139
139
  |--------|------|---------|-------------|
@@ -147,7 +147,7 @@ To route all messages to the default Agent:
147
147
  ```json
148
148
  {
149
149
  "channels": {
150
- "wxwork": {
150
+ "wecom": {
151
151
  "dynamicAgents": { "enabled": false }
152
152
  }
153
153
  }
@@ -163,7 +163,7 @@ To prevent regular users from executing sensitive Gateway management commands vi
163
163
  ```json
164
164
  {
165
165
  "channels": {
166
- "wxwork": {
166
+ "wecom": {
167
167
  "commands": {
168
168
  "enabled": true,
169
169
  "allowlist": ["/new", "/status", "/help", "/compact"]
@@ -182,18 +182,6 @@ To prevent regular users from executing sensitive Gateway management commands vi
182
182
 
183
183
  > ⚠️ **Security Note**: Do not add `/gateway`, `/plugins`, or other management commands to the whitelist to prevent regular users from gaining Gateway instance admin privileges.
184
184
 
185
- ## 🏗️ Building Custom Images
186
-
187
- To build a custom OpenClaw image:
188
-
189
- ```bash
190
- # Build image (uses China npm mirror)
191
- ./local.sh v2026.1.29
192
-
193
- # Build and push image
194
- ./local.sh v2026.1.29 push
195
- ```
196
-
197
185
  ## 🤝 Contributing
198
186
 
199
187
  We welcome contributions! Please submit Issues or Pull Requests for bugs or feature suggestions.
package/README_ZH.md CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  ### 方式一:Docker 一键部署(推荐)
19
19
 
20
- 本仓库提供了完整的 Docker 部署方案,包含自动化的插件安装和配置。
20
+ 本仓库提供了完整的 Docker 部署方案,**一键部署 OpenClaw + 企业微信插件**,包含自动化安装和配置。
21
21
 
22
22
  ```bash
23
23
  # 1. 克隆仓库
@@ -83,11 +83,11 @@ npm install openclaw-plugin-wecom
83
83
  {
84
84
  "plugins": {
85
85
  "entries": {
86
- "openclaw-plugin-wecom": { "enabled": true }
86
+ "wecom": { "enabled": true }
87
87
  }
88
88
  },
89
89
  "channels": {
90
- "wxwork": {
90
+ "wecom": {
91
91
  "enabled": true,
92
92
  "token": "你的 Token",
93
93
  "encodingAesKey": "你的 EncodingAESKey"
@@ -99,7 +99,7 @@ npm install openclaw-plugin-wecom
99
99
  ### 企业微信后台设置
100
100
 
101
101
  1. 在企业微信管理后台创建一个"智能机器人"。
102
- 2. 将机器人的"接收消息配置"中的 URL 设置为你的服务地址(例如:`https://your-domain.com/webhooks/wxwork`)。
102
+ 2. 将机器人的"接收消息配置"中的 URL 设置为你的服务地址(例如:`https://your-domain.com/webhooks/wecom`)。
103
103
  3. 填入对应的 Token 和 EncodingAESKey。
104
104
 
105
105
  ## 📂 项目结构
@@ -127,13 +127,13 @@ openclaw-plugin-wecom/
127
127
  OpenClaw 会通过解析 `SessionKey` 来决定本次消息由哪个 Agent 处理。本插件实现"按人/按群隔离":
128
128
 
129
129
  1. 企业微信消息到达后,插件生成确定性的 `agentId`:
130
- - 私聊:`wxwork-dm-<userId>`
131
- - 群聊:`wxwork-group-<chatId>`
130
+ - 私聊:`wecom-dm-<userId>`
131
+ - 群聊:`wecom-group-<chatId>`
132
132
  2. OpenClaw 自动创建/复用对应的 Agent 工作区。
133
133
 
134
134
  ### 配置选项
135
135
 
136
- 配置在 `channels.wxwork` 下:
136
+ 配置在 `channels.wecom` 下:
137
137
 
138
138
  | 配置项 | 类型 | 默认值 | 说明 |
139
139
  |--------|------|--------|------|
@@ -147,7 +147,7 @@ OpenClaw 会通过解析 `SessionKey` 来决定本次消息由哪个 Agent 处
147
147
  ```json
148
148
  {
149
149
  "channels": {
150
- "wxwork": {
150
+ "wecom": {
151
151
  "dynamicAgents": { "enabled": false }
152
152
  }
153
153
  }
@@ -163,7 +163,7 @@ OpenClaw 会通过解析 `SessionKey` 来决定本次消息由哪个 Agent 处
163
163
  ```json
164
164
  {
165
165
  "channels": {
166
- "wxwork": {
166
+ "wecom": {
167
167
  "commands": {
168
168
  "enabled": true,
169
169
  "allowlist": ["/new", "/status", "/help", "/compact"]
@@ -182,18 +182,6 @@ OpenClaw 会通过解析 `SessionKey` 来决定本次消息由哪个 Agent 处
182
182
 
183
183
  > ⚠️ **安全提示**:不要将 `/gateway`、`/plugins` 等管理指令添加到白名单,避免普通用户获得 Gateway 实例的管理权限。
184
184
 
185
- ## 🏗️ 本地构建镜像
186
-
187
- 如果需要自定义构建 OpenClaw 镜像:
188
-
189
- ```bash
190
- # 构建镜像(使用国内 npm 源)
191
- ./local.sh v2026.1.29
192
-
193
- # 构建并推送镜像
194
- ./local.sh v2026.1.29 push
195
- ```
196
-
197
185
  ## 🤝 贡献规范
198
186
 
199
187
  我们非常欢迎开发者参与贡献!如果你发现了 Bug 或有更好的功能建议,请提交 Issue 或 Pull Request。
package/client.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * WxWork AI Bot Client
2
+ * WeCom AI Bot Client
3
3
  * 智能机器人专用 - 只使用 response_url 回复,不需要 access_token
4
4
  * https://developer.work.weixin.qq.com/document/path/101039
5
5
  */
6
6
 
7
7
  import { logger } from "./logger.js";
8
- import { withRetry, parseWxWorkError, CONSTANTS } from "./utils.js";
8
+ import { withRetry, parseWecomError, CONSTANTS } from "./utils.js";
9
9
 
10
10
  /**
11
11
  * 通过 response_url 主动回复消息
@@ -32,7 +32,7 @@ export async function sendReplyMessage(responseUrl, message) {
32
32
 
33
33
  const data = await res.json();
34
34
  if (data.errcode !== 0) {
35
- const errorInfo = parseWxWorkError(data.errcode, data.errmsg);
35
+ const errorInfo = parseWecomError(data.errcode, data.errmsg);
36
36
  throw new Error(`Response failed: [${data.errcode}] ${errorInfo.message}`);
37
37
  }
38
38
 
@@ -108,7 +108,7 @@ export async function sendStreamChunk(responseUrl, streamId, content, isFinished
108
108
 
109
109
  const data = await res.json();
110
110
  if (data.errcode !== 0) {
111
- const errorInfo = parseWxWorkError(data.errcode, data.errmsg);
111
+ const errorInfo = parseWecomError(data.errcode, data.errmsg);
112
112
  throw new Error(`Stream response failed: [${data.errcode}] ${errorInfo.message}`);
113
113
  }
114
114
 
package/crypto.js CHANGED
@@ -7,7 +7,7 @@ import { logger } from "./logger.js";
7
7
  * Enterprise WeChat Intelligent Robot Crypto Implementation
8
8
  * Simplified for AI Bot mode (no corpId validation)
9
9
  */
10
- export class WxWorkCrypto {
10
+ export class WecomCrypto {
11
11
  token;
12
12
  encodingAesKey;
13
13
  aesKey;
@@ -24,7 +24,7 @@ export class WxWorkCrypto {
24
24
  this.encodingAesKey = encodingAesKey;
25
25
  this.aesKey = Buffer.from(encodingAesKey + "=", "base64");
26
26
  this.iv = this.aesKey.subarray(0, 16);
27
- logger.debug("WxWorkCrypto initialized (AI Bot mode)");
27
+ logger.debug("WecomCrypto initialized (AI Bot mode)");
28
28
  }
29
29
 
30
30
  getSignature(timestamp, nonce, encrypt) {
package/dynamic-agent.js CHANGED
@@ -16,7 +16,7 @@ import { logger } from "./logger.js";
16
16
 
17
17
  /**
18
18
  * 生成 AgentId
19
- * 规范:wxwork-dm-{userId} 或 wxwork-group-{groupId}
19
+ * 规范:wecom-dm-{userId} 或 wecom-group-{groupId}
20
20
  *
21
21
  * @param {string} chatType - "dm" 或 "group"
22
22
  * @param {string} peerId - userId 或 groupId
@@ -25,28 +25,28 @@ import { logger } from "./logger.js";
25
25
  export function generateAgentId(chatType, peerId) {
26
26
  const sanitizedId = String(peerId).toLowerCase().replace(/[^a-z0-9_-]/g, "_");
27
27
  if (chatType === "group") {
28
- return `wxwork-group-${sanitizedId}`;
28
+ return `wecom-group-${sanitizedId}`;
29
29
  }
30
- return `wxwork-dm-${sanitizedId}`;
30
+ return `wecom-dm-${sanitizedId}`;
31
31
  }
32
32
 
33
33
  /**
34
34
  * 获取动态 Agent 配置
35
35
  */
36
36
  export function getDynamicAgentConfig(config) {
37
- const wxwork = config?.channels?.wxwork || {};
37
+ const wecom = config?.channels?.wecom || {};
38
38
  return {
39
- enabled: wxwork.dynamicAgents?.enabled !== false,
39
+ enabled: wecom.dynamicAgents?.enabled !== false,
40
40
 
41
41
  // 私聊配置
42
- dmCreateAgent: wxwork.dm?.createAgentOnFirstMessage !== false,
42
+ dmCreateAgent: wecom.dm?.createAgentOnFirstMessage !== false,
43
43
 
44
44
  // 群聊配置
45
- groupEnabled: wxwork.groupChat?.enabled !== false,
46
- groupRequireMention: wxwork.groupChat?.requireMention !== false,
47
- groupMentionPatterns: wxwork.groupChat?.mentionPatterns || ["@"],
48
- groupCreateAgent: wxwork.groupChat?.createAgentOnFirstMessage !== false,
49
- groupHistoryLimit: wxwork.groupChat?.historyLimit || 10,
45
+ groupEnabled: wecom.groupChat?.enabled !== false,
46
+ groupRequireMention: wecom.groupChat?.requireMention !== false,
47
+ groupMentionPatterns: wecom.groupChat?.mentionPatterns || ["@"],
48
+ groupCreateAgent: wecom.groupChat?.createAgentOnFirstMessage !== false,
49
+ groupHistoryLimit: wecom.groupChat?.historyLimit || 10,
50
50
  };
51
51
  }
52
52
 
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { WxWorkWebhook } from "./webhook.js";
1
+ import { WecomWebhook } from "./webhook.js";
2
2
  import { logger } from "./logger.js";
3
3
  import { streamManager } from "./stream-manager.js";
4
4
  import {
@@ -36,8 +36,8 @@ const DEFAULT_COMMAND_BLOCK_MESSAGE = `⚠️ 该命令不可用。
36
36
  * 获取命令白名单配置
37
37
  */
38
38
  function getCommandConfig(config) {
39
- const wxwork = config?.channels?.wxwork || {};
40
- const commands = wxwork.commands || {};
39
+ const wecom = config?.channels?.wecom || {};
40
+ const commands = wecom.commands || {};
41
41
  return {
42
42
  allowlist: commands.allowlist || DEFAULT_COMMAND_ALLOWLIST,
43
43
  blockMessage: commands.blockMessage || DEFAULT_COMMAND_BLOCK_MESSAGE,
@@ -90,7 +90,7 @@ function setRuntime(runtime) {
90
90
 
91
91
  function getRuntime() {
92
92
  if (!_runtime) {
93
- throw new Error("[wxwork] Runtime not initialized");
93
+ throw new Error("[wecom] Runtime not initialized");
94
94
  }
95
95
  return _runtime;
96
96
  }
@@ -102,19 +102,19 @@ const webhookTargets = new Map();
102
102
  // can be added to the correct stream instead of using response_url
103
103
  const activeStreams = new Map();
104
104
 
105
- function normalizeWxWorkAllowFromEntry(raw) {
105
+ function normalizeWecomAllowFromEntry(raw) {
106
106
  const trimmed = String(raw ?? "").trim();
107
107
  if (!trimmed) return null;
108
108
  if (trimmed === "*") return "*";
109
- return trimmed.replace(/^(wxwork|wecom|wework):/i, "").replace(/^user:/i, "").toLowerCase();
109
+ return trimmed.replace(/^(wecom|wework):/i, "").replace(/^user:/i, "").toLowerCase();
110
110
  }
111
111
 
112
- function resolveWxWorkAllowFrom(cfg, accountId) {
113
- const wxwork = cfg?.channels?.wxwork;
114
- if (!wxwork) return [];
112
+ function resolveWecomAllowFrom(cfg, accountId) {
113
+ const wecom = cfg?.channels?.wecom;
114
+ if (!wecom) return [];
115
115
 
116
116
  const normalizedAccountId = String(accountId || DEFAULT_ACCOUNT_ID).trim().toLowerCase();
117
- const accounts = wxwork.accounts;
117
+ const accounts = wecom.accounts;
118
118
  const account =
119
119
  accounts && typeof accounts === "object"
120
120
  ? accounts[accountId] ??
@@ -124,20 +124,20 @@ function resolveWxWorkAllowFrom(cfg, accountId) {
124
124
  : undefined;
125
125
 
126
126
  const allowFromRaw =
127
- account?.dm?.allowFrom ?? account?.allowFrom ?? wxwork.dm?.allowFrom ?? wxwork.allowFrom ?? [];
127
+ account?.dm?.allowFrom ?? account?.allowFrom ?? wecom.dm?.allowFrom ?? wecom.allowFrom ?? [];
128
128
 
129
129
  if (!Array.isArray(allowFromRaw)) return [];
130
130
 
131
131
  return allowFromRaw
132
- .map(normalizeWxWorkAllowFromEntry)
132
+ .map(normalizeWecomAllowFromEntry)
133
133
  .filter((entry) => Boolean(entry));
134
134
  }
135
135
 
136
- function resolveWxWorkCommandAuthorized({ cfg, accountId, senderId }) {
136
+ function resolveWecomCommandAuthorized({ cfg, accountId, senderId }) {
137
137
  const sender = String(senderId ?? "").trim().toLowerCase();
138
138
  if (!sender) return false;
139
139
 
140
- const allowFrom = resolveWxWorkAllowFrom(cfg, accountId);
140
+ const allowFrom = resolveWecomAllowFrom(cfg, accountId);
141
141
  if (allowFrom.includes("*") || allowFrom.length === 0) return true;
142
142
  return allowFrom.includes(sender);
143
143
  }
@@ -170,13 +170,13 @@ function registerWebhookTarget(target) {
170
170
  // Channel Plugin Definition
171
171
  // =============================================================================
172
172
 
173
- const wxworkChannelPlugin = {
174
- id: "wxwork",
173
+ const wecomChannelPlugin = {
174
+ id: "wecom",
175
175
  meta: {
176
- id: "wxwork",
176
+ id: "wecom",
177
177
  label: "Enterprise WeChat",
178
178
  selectionLabel: "Enterprise WeChat (AI Bot)",
179
- docsPath: "/channels/wxwork",
179
+ docsPath: "/channels/wecom",
180
180
  blurb: "Enterprise WeChat AI Bot channel plugin.",
181
181
  aliases: ["wecom", "wework"],
182
182
  },
@@ -186,41 +186,41 @@ const wxworkChannelPlugin = {
186
186
  threads: false,
187
187
  media: false,
188
188
  nativeCommands: false,
189
- blockStreaming: true, // WxWork AI Bot uses stream response format
189
+ blockStreaming: true, // WeCom AI Bot uses stream response format
190
190
  },
191
- reload: { configPrefixes: ["channels.wxwork"] },
191
+ reload: { configPrefixes: ["channels.wecom"] },
192
192
  config: {
193
193
  listAccountIds: (cfg) => {
194
- const wxwork = cfg?.channels?.wxwork;
195
- if (!wxwork || !wxwork.enabled) return [];
194
+ const wecom = cfg?.channels?.wecom;
195
+ if (!wecom || !wecom.enabled) return [];
196
196
  return [DEFAULT_ACCOUNT_ID];
197
197
  },
198
198
  resolveAccount: (cfg, accountId) => {
199
- const wxwork = cfg?.channels?.wxwork;
200
- if (!wxwork) return null;
199
+ const wecom = cfg?.channels?.wecom;
200
+ if (!wecom) return null;
201
201
  return {
202
202
  id: accountId || DEFAULT_ACCOUNT_ID,
203
203
  accountId: accountId || DEFAULT_ACCOUNT_ID,
204
- enabled: wxwork.enabled !== false,
205
- token: wxwork.token || "",
206
- encodingAesKey: wxwork.encodingAesKey || "",
207
- webhookPath: wxwork.webhookPath || "/webhooks/wxwork",
208
- config: wxwork,
204
+ enabled: wecom.enabled !== false,
205
+ token: wecom.token || "",
206
+ encodingAesKey: wecom.encodingAesKey || "",
207
+ webhookPath: wecom.webhookPath || "/webhooks/wecom",
208
+ config: wecom,
209
209
  };
210
210
  },
211
211
  defaultAccountId: (cfg) => {
212
- const wxwork = cfg?.channels?.wxwork;
213
- if (!wxwork || !wxwork.enabled) return null;
212
+ const wecom = cfg?.channels?.wecom;
213
+ if (!wecom || !wecom.enabled) return null;
214
214
  return DEFAULT_ACCOUNT_ID;
215
215
  },
216
216
  setAccountEnabled: ({ cfg, accountId, enabled }) => {
217
217
  if (!cfg.channels) cfg.channels = {};
218
- if (!cfg.channels.wxwork) cfg.channels.wxwork = {};
219
- cfg.channels.wxwork.enabled = enabled;
218
+ if (!cfg.channels.wecom) cfg.channels.wecom = {};
219
+ cfg.channels.wecom.enabled = enabled;
220
220
  return cfg;
221
221
  },
222
222
  deleteAccount: ({ cfg, accountId }) => {
223
- if (cfg.channels?.wxwork) delete cfg.channels.wxwork;
223
+ if (cfg.channels?.wecom) delete cfg.channels.wecom;
224
224
  return cfg;
225
225
  },
226
226
  },
@@ -232,8 +232,8 @@ const wxworkChannelPlugin = {
232
232
  // Outbound adapter: Send messages via stream (all messages go through stream now)
233
233
  outbound: {
234
234
  sendText: async ({ cfg, to, text, accountId }) => {
235
- // to格式: "wxwork:userid" 或 "userid"
236
- const userId = to.replace(/^wxwork:/, "");
235
+ // to格式: \"wecom:userid\" 或 \"userid\"
236
+ const userId = to.replace(/^wecom:/, "");
237
237
 
238
238
  // 获取该用户当前活跃的 streamId
239
239
  const streamId = activeStreams.get(userId);
@@ -246,21 +246,21 @@ const wxworkChannelPlugin = {
246
246
  streamManager.appendStream(streamId, separator + text);
247
247
 
248
248
  return {
249
- channel: "wxwork",
249
+ channel: "wecom",
250
250
  messageId: `msg_stream_${Date.now()}`,
251
251
  };
252
252
  }
253
253
 
254
254
  // 如果没有活跃的流,记录警告
255
- logger.warn("WxWork outbound: no active stream for user", { userId });
255
+ logger.warn("WeCom outbound: no active stream for user", { userId });
256
256
 
257
257
  return {
258
- channel: "wxwork",
258
+ channel: "wecom",
259
259
  messageId: `fake_${Date.now()}`,
260
260
  };
261
261
  },
262
262
  sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => {
263
- const userId = to.replace(/^wxwork:/, "");
263
+ const userId = to.replace(/^wecom:/, "");
264
264
  const streamId = activeStreams.get(userId);
265
265
 
266
266
  if (streamId && streamManager.hasStream(streamId)) {
@@ -272,15 +272,15 @@ const wxworkChannelPlugin = {
272
272
  streamManager.appendStream(streamId, separator + content);
273
273
 
274
274
  return {
275
- channel: "wxwork",
275
+ channel: "wecom",
276
276
  messageId: `msg_stream_${Date.now()}`,
277
277
  };
278
278
  }
279
279
 
280
- logger.warn("WxWork outbound sendMedia: no active stream", { userId });
280
+ logger.warn("WeCom outbound sendMedia: no active stream", { userId });
281
281
 
282
282
  return {
283
- channel: "wxwork",
283
+ channel: "wecom",
284
284
  messageId: `fake_${Date.now()}`,
285
285
  };
286
286
  },
@@ -288,17 +288,17 @@ const wxworkChannelPlugin = {
288
288
  gateway: {
289
289
  startAccount: async (ctx) => {
290
290
  const account = ctx.account;
291
- logger.info("WxWork gateway starting", { accountId: account.accountId, webhookPath: account.webhookPath });
291
+ logger.info("WeCom gateway starting", { accountId: account.accountId, webhookPath: account.webhookPath });
292
292
 
293
293
  const unregister = registerWebhookTarget({
294
- path: account.webhookPath || "/webhooks/wxwork",
294
+ path: account.webhookPath || "/webhooks/wecom",
295
295
  account,
296
296
  config: ctx.cfg,
297
297
  });
298
298
 
299
299
  return {
300
300
  shutdown: async () => {
301
- logger.info("WxWork gateway shutting down");
301
+ logger.info("WeCom gateway shutting down");
302
302
  unregister();
303
303
  },
304
304
  };
@@ -310,7 +310,7 @@ const wxworkChannelPlugin = {
310
310
  // HTTP Webhook Handler
311
311
  // =============================================================================
312
312
 
313
- async function wxworkHttpHandler(req, res) {
313
+ async function wecomHttpHandler(req, res) {
314
314
  const url = new URL(req.url || "", "http://localhost");
315
315
  const path = normalizeWebhookPath(url.pathname);
316
316
  const targets = webhookTargets.get(path);
@@ -320,7 +320,7 @@ async function wxworkHttpHandler(req, res) {
320
320
  }
321
321
 
322
322
  const query = Object.fromEntries(url.searchParams);
323
- logger.debug("WxWork HTTP request", { method: req.method, path });
323
+ logger.debug("WeCom HTTP request", { method: req.method, path });
324
324
 
325
325
  // GET: URL Verification
326
326
  if (req.method === "GET") {
@@ -331,7 +331,7 @@ async function wxworkHttpHandler(req, res) {
331
331
  return true;
332
332
  }
333
333
 
334
- const webhook = new WxWorkWebhook({
334
+ const webhook = new WecomWebhook({
335
335
  token: target.account.token,
336
336
  encodingAesKey: target.account.encodingAesKey,
337
337
  });
@@ -340,13 +340,13 @@ async function wxworkHttpHandler(req, res) {
340
340
  if (echo) {
341
341
  res.writeHead(200, { "Content-Type": "text/plain" });
342
342
  res.end(echo);
343
- logger.info("WxWork URL verification successful");
343
+ logger.info("WeCom URL verification successful");
344
344
  return true;
345
345
  }
346
346
 
347
347
  res.writeHead(403, { "Content-Type": "text/plain" });
348
348
  res.end("Verification failed");
349
- logger.warn("WxWork URL verification failed");
349
+ logger.warn("WeCom URL verification failed");
350
350
  return true;
351
351
  }
352
352
 
@@ -365,9 +365,9 @@ async function wxworkHttpHandler(req, res) {
365
365
  chunks.push(chunk);
366
366
  }
367
367
  const body = Buffer.concat(chunks).toString("utf-8");
368
- logger.debug("WxWork message received", { bodyLength: body.length });
368
+ logger.debug("WeCom message received", { bodyLength: body.length });
369
369
 
370
- const webhook = new WxWorkWebhook({
370
+ const webhook = new WecomWebhook({
371
371
  token: target.account.token,
372
372
  encodingAesKey: target.account.encodingAesKey,
373
373
  });
@@ -413,7 +413,7 @@ async function wxworkHttpHandler(req, res) {
413
413
  account: target.account,
414
414
  config: target.config,
415
415
  }).catch((err) => {
416
- logger.error("WxWork message processing failed", { error: err.message });
416
+ logger.error("WeCom message processing failed", { error: err.message });
417
417
  // 即使失败也要标记流为完成
418
418
  streamManager.finishStream(streamId);
419
419
  });
@@ -474,7 +474,7 @@ async function wxworkHttpHandler(req, res) {
474
474
 
475
475
  // Handle event
476
476
  if (result.event) {
477
- logger.info("WxWork event received", { event: result.event });
477
+ logger.info("WeCom event received", { event: result.event });
478
478
 
479
479
  // 处理进入会话事件 - 发送欢迎语
480
480
  if (result.event?.event_type === "enter_chat") {
@@ -544,7 +544,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
544
544
  // 确定 peerId:群聊用 chatId,私聊用 senderId
545
545
  const peerId = isGroupChat ? chatId : senderId;
546
546
  const peerKind = isGroupChat ? "group" : "dm";
547
- const conversationId = isGroupChat ? `wxwork:group:${chatId}` : `wxwork:${senderId}`;
547
+ const conversationId = isGroupChat ? `wecom:group:${chatId}` : `wecom:${senderId}`;
548
548
 
549
549
  // 设置用户当前活跃的 streamId,供 outbound.sendText 使用
550
550
  // 群聊时用 chatId 作为 key
@@ -557,21 +557,21 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
557
557
  let rawBody = rawContent;
558
558
  if (isGroupChat) {
559
559
  if (!shouldTriggerGroupResponse(rawContent, config)) {
560
- logger.debug("WxWork: group message ignored (no mention)", { chatId, senderId });
560
+ logger.debug("WeCom: group message ignored (no mention)", { chatId, senderId });
561
561
  return;
562
562
  }
563
563
  // 提取实际内容(移除 @提及)
564
564
  rawBody = extractGroupMessageContent(rawContent, config);
565
565
  }
566
566
 
567
- const commandAuthorized = resolveWxWorkCommandAuthorized({
567
+ const commandAuthorized = resolveWecomCommandAuthorized({
568
568
  cfg: config,
569
569
  accountId: account.accountId,
570
570
  senderId,
571
571
  });
572
572
 
573
573
  if (!rawBody.trim()) {
574
- logger.debug("WxWork: empty message, skipping");
574
+ logger.debug("WeCom: empty message, skipping");
575
575
  return;
576
576
  }
577
577
 
@@ -583,7 +583,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
583
583
  if (commandCheck.isCommand && !commandCheck.allowed) {
584
584
  // 命令不在白名单中,返回拒绝消息
585
585
  const cmdConfig = getCommandConfig(config);
586
- logger.warn("WxWork: blocked command", {
586
+ logger.warn("WeCom: blocked command", {
587
587
  command: commandCheck.command,
588
588
  from: senderId,
589
589
  chatType: peerKind
@@ -598,7 +598,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
598
598
  return;
599
599
  }
600
600
 
601
- logger.info("WxWork processing message", {
601
+ logger.info("WeCom processing message", {
602
602
  from: senderId,
603
603
  chatType: peerKind,
604
604
  peerId,
@@ -626,7 +626,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
626
626
  // ========================================================================
627
627
  const route = core.routing.resolveAgentRoute({
628
628
  cfg: config,
629
- channel: "wxwork",
629
+ channel: "wecom",
630
630
  accountId: account.accountId,
631
631
  peer: {
632
632
  kind: peerKind,
@@ -666,7 +666,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
666
666
  Body: body,
667
667
  RawBody: rawBody,
668
668
  CommandBody: rawBody,
669
- From: `wxwork:${senderId}`,
669
+ From: `wecom:${senderId}`,
670
670
  To: conversationId,
671
671
  SessionKey: route.sessionKey,
672
672
  AccountId: route.accountId,
@@ -675,9 +675,9 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
675
675
  SenderName: senderId,
676
676
  SenderId: senderId,
677
677
  GroupId: isGroupChat ? chatId : undefined,
678
- Provider: "wxwork",
679
- Surface: "wxwork",
680
- OriginatingChannel: "wxwork",
678
+ Provider: "wecom",
679
+ Surface: "wecom",
680
+ OriginatingChannel: "wecom",
681
681
  OriginatingTo: conversationId,
682
682
  CommandAuthorized: commandAuthorized,
683
683
  });
@@ -688,7 +688,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
688
688
  sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
689
689
  ctx: ctxPayload,
690
690
  }).catch((err) => {
691
- logger.error("WxWork: failed updating session meta", { error: err.message });
691
+ logger.error("WeCom: failed updating session meta", { error: err.message });
692
692
  });
693
693
 
694
694
  // Dispatch reply with AI processing
@@ -703,7 +703,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
703
703
  textPreview: (payload.text || "").substring(0, 50),
704
704
  });
705
705
 
706
- await deliverWxWorkReply({
706
+ await deliverWecomReply({
707
707
  payload,
708
708
  account,
709
709
  responseUrl,
@@ -714,11 +714,11 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
714
714
  // 如果是最终回复,标记流为完成
715
715
  if (streamId && info.kind === "final") {
716
716
  streamManager.finishStream(streamId);
717
- logger.info("WxWork stream finished", { streamId });
717
+ logger.info("WeCom stream finished", { streamId });
718
718
  }
719
719
  },
720
720
  onError: (err, info) => {
721
- logger.error("WxWork reply failed", { error: err.message, kind: info.kind });
721
+ logger.error("WeCom reply failed", { error: err.message, kind: info.kind });
722
722
  // 发生错误时也标记流为完成
723
723
  if (streamId) {
724
724
  streamManager.finishStream(streamId);
@@ -731,7 +731,7 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
731
731
  if (streamId) {
732
732
  streamManager.finishStream(streamId);
733
733
  activeStreams.delete(streamKey); // 清理活跃流映射
734
- logger.info("WxWork stream finished (dispatch complete)", { streamId });
734
+ logger.info("WeCom stream finished (dispatch complete)", { streamId });
735
735
  }
736
736
  }
737
737
 
@@ -739,10 +739,10 @@ async function processInboundMessage({ message, streamId, timestamp, nonce, acco
739
739
  // Outbound Reply Delivery (Stream-only mode)
740
740
  // =============================================================================
741
741
 
742
- async function deliverWxWorkReply({ payload, account, responseUrl, senderId, streamId }) {
742
+ async function deliverWecomReply({ payload, account, responseUrl, senderId, streamId }) {
743
743
  const text = payload.text || "";
744
744
 
745
- logger.debug("deliverWxWorkReply called", {
745
+ logger.debug("deliverWecomReply called", {
746
746
  hasText: !!text.trim(),
747
747
  textPreview: text.substring(0, 50),
748
748
  streamId,
@@ -751,7 +751,7 @@ async function deliverWxWorkReply({ payload, account, responseUrl, senderId, str
751
751
 
752
752
  // 所有消息都通过流式发送
753
753
  if (!text.trim()) {
754
- logger.debug("WxWork: empty block, skipping stream update");
754
+ logger.debug("WeCom: empty block, skipping stream update");
755
755
  return;
756
756
  }
757
757
 
@@ -762,7 +762,7 @@ async function deliverWxWorkReply({ payload, account, responseUrl, senderId, str
762
762
 
763
763
  // 去重:检查流内容是否已包含此消息(避免 block + final 重复)
764
764
  if (stream.content.includes(content.trim())) {
765
- logger.debug("WxWork: duplicate content, skipping", {
765
+ logger.debug("WeCom: duplicate content, skipping", {
766
766
  streamId: targetStreamId,
767
767
  contentPreview: content.substring(0, 30)
768
768
  });
@@ -779,23 +779,23 @@ async function deliverWxWorkReply({ payload, account, responseUrl, senderId, str
779
779
  const activeStreamId = activeStreams.get(senderId);
780
780
  if (activeStreamId && streamManager.hasStream(activeStreamId)) {
781
781
  appendToStream(activeStreamId, text);
782
- logger.debug("WxWork stream appended (via activeStreams)", {
782
+ logger.debug("WeCom stream appended (via activeStreams)", {
783
783
  streamId: activeStreamId,
784
784
  contentLength: text.length,
785
785
  });
786
786
  return;
787
787
  }
788
- logger.warn("WxWork: no active stream for this message", { senderId });
788
+ logger.warn("WeCom: no active stream for this message", { senderId });
789
789
  return;
790
790
  }
791
791
 
792
792
  if (!streamManager.hasStream(streamId)) {
793
- logger.warn("WxWork: stream not found, cannot update", { streamId });
793
+ logger.warn("WeCom: stream not found, cannot update", { streamId });
794
794
  return;
795
795
  }
796
796
 
797
797
  appendToStream(streamId, text);
798
- logger.debug("WxWork stream appended", {
798
+ logger.debug("WeCom stream appended", {
799
799
  streamId,
800
800
  contentLength: text.length,
801
801
  to: senderId
@@ -807,24 +807,24 @@ async function deliverWxWorkReply({ payload, account, responseUrl, senderId, str
807
807
  // =============================================================================
808
808
 
809
809
  const plugin = {
810
- id: "openclaw-plugin-wecom",
810
+ id: "wecom",
811
811
  name: "Enterprise WeChat",
812
812
  description: "Enterprise WeChat AI Bot channel plugin for OpenClaw",
813
813
  configSchema: { type: "object", additionalProperties: false, properties: {} },
814
814
  register(api) {
815
- logger.info("WxWork plugin registering...");
815
+ logger.info("WeCom plugin registering...");
816
816
 
817
817
  // Save runtime for message processing
818
818
  setRuntime(api.runtime);
819
819
  _openclawConfig = api.config;
820
820
 
821
821
  // Register channel
822
- api.registerChannel({ plugin: wxworkChannelPlugin });
823
- logger.info("WxWork channel registered");
822
+ api.registerChannel({ plugin: wecomChannelPlugin });
823
+ logger.info("WeCom channel registered");
824
824
 
825
825
  // Register HTTP handler for webhooks
826
- api.registerHttpHandler(wxworkHttpHandler);
827
- logger.info("WxWork HTTP handler registered");
826
+ api.registerHttpHandler(wecomHttpHandler);
827
+ logger.info("WeCom HTTP handler registered");
828
828
  },
829
829
  };
830
830
 
package/logger.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Structured logging for WxWork plugin
2
+ * Structured logging for WeCom plugin
3
3
  */
4
4
  export class Logger {
5
5
  prefix;
6
- constructor(prefix = "[wxwork]") {
6
+ constructor(prefix = "[wecom]") {
7
7
  this.prefix = prefix;
8
8
  }
9
9
  log(level, message, context) {
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "id": "openclaw-plugin-wecom",
3
- "name": "OpenClaw WeCom (WxWork)",
4
- "description": "Enterprise WeChat (WeCom/WxWork) messaging channel plugin for OpenClaw",
5
- "channels": ["wxwork"],
3
+ "name": "OpenClaw WeCom",
4
+ "description": "Enterprise WeChat (WeCom) messaging channel plugin for OpenClaw",
5
+ "channels": [
6
+ "wecom"
7
+ ],
6
8
  "configSchema": {
7
9
  "type": "object",
8
10
  "additionalProperties": false,
9
11
  "properties": {}
10
12
  }
11
- }
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-plugin-wecom",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Enterprise WeChat AI Bot channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -27,11 +27,11 @@
27
27
  "./index.js"
28
28
  ],
29
29
  "channel": {
30
- "id": "wxwork",
30
+ "id": "wecom",
31
31
  "label": "Enterprise WeChat",
32
32
  "selectionLabel": "Enterprise WeChat (AI Bot)",
33
- "docsPath": "/channels/wxwork",
34
- "docsLabel": "wxwork",
33
+ "docsPath": "/channels/wecom",
34
+ "docsLabel": "wecom",
35
35
  "blurb": "Support for Enterprise WeChat (WeCom) AI Bot integration",
36
36
  "order": 90,
37
37
  "aliases": [
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "keywords": [
48
48
  "openclaw",
49
- "wxwork",
49
+ "wecom",
50
50
  "wecom",
51
51
  "chat",
52
52
  "plugin"
package/utils.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Utility functions and helpers for WxWork plugin
2
+ * Utility functions and helpers for WeCom plugin
3
3
  */
4
4
  export class TTLCache {
5
5
  options;
@@ -137,7 +137,7 @@ export class MessageDeduplicator {
137
137
  this.seen.set(msgId, true);
138
138
  }
139
139
  }
140
- export function parseWxWorkError(errcode, errmsg) {
140
+ export function parseWecomError(errcode, errmsg) {
141
141
  // Reference: https://developer.work.weixin.qq.com/document/path/96213
142
142
  switch (errcode) {
143
143
  case -1:
@@ -198,7 +198,7 @@ export function parseWxWorkError(errcode, errmsg) {
198
198
  }
199
199
  }
200
200
  export function shouldRetryError(errcode) {
201
- const info = parseWxWorkError(errcode, "");
201
+ const info = parseWecomError(errcode, "");
202
202
  return info.retryable;
203
203
  }
204
204
  // ============================================================================
package/webhook.js CHANGED
@@ -1,9 +1,9 @@
1
- import { WxWorkCrypto } from "./crypto.js";
1
+ import { WecomCrypto } from "./crypto.js";
2
2
  import { logger } from "./logger.js";
3
3
  import { MessageDeduplicator, randomString } from "./utils.js";
4
4
 
5
5
  /**
6
- * WxWork AI Bot Webhook Handler
6
+ * WeCom AI Bot Webhook Handler
7
7
  * Based on official demo: https://developer.work.weixin.qq.com/document/path/101039
8
8
  *
9
9
  * Key differences from legacy mode:
@@ -11,15 +11,15 @@ import { MessageDeduplicator, randomString } from "./utils.js";
11
11
  * - receiveid is empty string for AI Bot
12
12
  * - Response uses stream message format
13
13
  */
14
- export class WxWorkWebhook {
14
+ export class WecomWebhook {
15
15
  config;
16
16
  crypto;
17
17
  deduplicator = new MessageDeduplicator();
18
18
 
19
19
  constructor(config) {
20
20
  this.config = config;
21
- this.crypto = new WxWorkCrypto(config.token, config.encodingAesKey);
22
- logger.debug("WxWorkWebhook initialized (AI Bot mode)");
21
+ this.crypto = new WecomCrypto(config.token, config.encodingAesKey);
22
+ logger.debug("WecomWebhook initialized (AI Bot mode)");
23
23
  }
24
24
 
25
25
  // =========================================================================
@@ -180,7 +180,7 @@ export class WxWorkWebhook {
180
180
  };
181
181
  }
182
182
  else if (msgtype === "stream") {
183
- // Stream continuation request from WxWork
183
+ // Stream continuation request from WeCom
184
184
  const streamId = data.stream?.id;
185
185
  logger.debug("Received stream refresh request", { streamId });
186
186
  return {