@wecom/wecom-openclaw-plugin 1.0.0 → 1.0.3-beta.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/dist/index.cjs.js CHANGED
@@ -81,73 +81,6 @@ const DEFAULT_MEDIA_MAX_MB = 5;
81
81
  /** 文本分块大小上限 */
82
82
  const TEXT_CHUNK_LIMIT = 4000;
83
83
 
84
- /**
85
- * 企业微信公共工具函数
86
- */
87
- const DefaultWsUrl = "wss://openws.work.weixin.qq.com";
88
- /**
89
- * 解析企业微信账户配置
90
- */
91
- function resolveWeComAccount(cfg) {
92
- const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
93
- return {
94
- accountId: pluginSdk.DEFAULT_ACCOUNT_ID,
95
- name: wecomConfig.name ?? "企业微信",
96
- enabled: wecomConfig.enabled ?? false,
97
- websocketUrl: wecomConfig.websocketUrl || DefaultWsUrl,
98
- botId: wecomConfig.botId ?? "",
99
- secret: wecomConfig.secret ?? "",
100
- sendThinkingMessage: wecomConfig.sendThinkingMessage ?? true,
101
- config: wecomConfig,
102
- };
103
- }
104
- /**
105
- * 设置企业微信账户配置
106
- */
107
- function setWeComAccount(cfg, account) {
108
- const existing = (cfg.channels?.[CHANNEL_ID] ?? {});
109
- const merged = {
110
- enabled: account.enabled ?? existing?.enabled ?? true,
111
- botId: account.botId ?? existing?.botId ?? "",
112
- secret: account.secret ?? existing?.secret ?? "",
113
- allowFrom: account.allowFrom ?? existing?.allowFrom,
114
- dmPolicy: account.dmPolicy ?? existing?.dmPolicy,
115
- // 以下字段仅在已有配置值或显式传入时才写入,onboarding 时不主动生成
116
- ...(account.websocketUrl || existing?.websocketUrl
117
- ? { websocketUrl: account.websocketUrl ?? existing?.websocketUrl }
118
- : {}),
119
- ...(account.name || existing?.name
120
- ? { name: account.name ?? existing?.name }
121
- : {}),
122
- ...(account.sendThinkingMessage !== undefined || existing?.sendThinkingMessage !== undefined
123
- ? { sendThinkingMessage: account.sendThinkingMessage ?? existing?.sendThinkingMessage }
124
- : {}),
125
- };
126
- return {
127
- ...cfg,
128
- channels: {
129
- ...cfg.channels,
130
- [CHANNEL_ID]: merged,
131
- },
132
- };
133
- }
134
- /**
135
- * 生成随机字符串
136
- */
137
- function generateRandomString(length = 9) {
138
- return Math.random().toString(36).substr(2, length);
139
- }
140
- /**
141
- * 生成企业微信请求 ID
142
- * @param prefix - 请求类型前缀(subscribe, ping, response 等)
143
- * @returns 格式化的请求 ID
144
- */
145
- function generateReqId(prefix) {
146
- const timestamp = Date.now();
147
- const random = generateRandomString();
148
- return `${prefix}_${timestamp}_${random}`;
149
- }
150
-
151
84
  /**
152
85
  * 企业微信消息内容解析模块
153
86
  *
@@ -286,7 +219,7 @@ async function sendWeComReply(params) {
286
219
  if (!text) {
287
220
  return "";
288
221
  }
289
- const streamId = existingStreamId || generateReqId("stream");
222
+ const streamId = existingStreamId || aibotNodeSdk.generateReqId("stream");
290
223
  if (!wsClient.isConnected) {
291
224
  runtime.error?.(`[WeCom] WSClient not connected, cannot send reply`);
292
225
  throw new Error("WSClient not connected");
@@ -571,10 +504,14 @@ async function checkDmPolicy(params) {
571
504
  if (dmPolicy === "open") {
572
505
  return { allowed: true };
573
506
  }
574
- // 检查发送者是否在允许列表中
575
- const storeAllowFrom = await core.channel.pairing
507
+ // OpenClaw <= 2026.2.19 signature: readAllowFromStore(channel, env?, accountId?)
508
+ const oldStoreAllowFrom = await core.channel.pairing.readAllowFromStore('wecom', undefined, account.accountId).catch(() => []);
509
+ // Compatibility fallback for newer OpenClaw implementations.
510
+ const newStoreAllowFrom = await core.channel.pairing
576
511
  .readAllowFromStore({ channel: CHANNEL_ID, accountId: account.accountId })
577
512
  .catch(() => []);
513
+ // 检查发送者是否在允许列表中
514
+ const storeAllowFrom = [...oldStoreAllowFrom, ...newStoreAllowFrom];
578
515
  const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
579
516
  const senderAllowedResult = isSenderAllowed(senderId, effectiveAllowFrom);
580
517
  if (senderAllowedResult) {
@@ -1080,7 +1017,7 @@ function buildMessageContext(frame, account, config, text, mediaList, quoteConte
1080
1017
  MediaPaths: mediaPaths,
1081
1018
  MediaTypes: mediaTypes,
1082
1019
  MediaUrls: mediaPaths,
1083
- QuoteContent: quoteContent,
1020
+ ReplyToBody: quoteContent,
1084
1021
  });
1085
1022
  }
1086
1023
  // ============================================================================
@@ -1112,6 +1049,14 @@ async function sendThinkingReply(params) {
1112
1049
  async function routeAndDispatchMessage(params) {
1113
1050
  const { ctxPayload, config, wsClient, frame, state, runtime, onCleanup } = params;
1114
1051
  const core = getWeComRuntime();
1052
+ // 防止 onCleanup 被多次调用(onError 回调与 catch 块可能重复触发)
1053
+ let cleanedUp = false;
1054
+ const safeCleanup = () => {
1055
+ if (!cleanedUp) {
1056
+ cleanedUp = true;
1057
+ onCleanup();
1058
+ }
1059
+ };
1115
1060
  try {
1116
1061
  await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
1117
1062
  ctx: ctxPayload,
@@ -1132,7 +1077,7 @@ async function routeAndDispatchMessage(params) {
1132
1077
  },
1133
1078
  onError: (err, info) => {
1134
1079
  runtime.error?.(`[WeCom] ${info.kind} reply failed: ${String(err)}`);
1135
- onCleanup();
1080
+ // 仅记录错误,不立即 cleanup,让外层 try/catch 统一处理最终回复和 cleanup
1136
1081
  },
1137
1082
  },
1138
1083
  });
@@ -1147,11 +1092,11 @@ async function routeAndDispatchMessage(params) {
1147
1092
  streamId: state.streamId,
1148
1093
  });
1149
1094
  }
1150
- onCleanup();
1095
+ safeCleanup();
1151
1096
  }
1152
1097
  catch (err) {
1153
1098
  runtime.error?.(`[WeCom] Failed to process message: ${String(err)}`);
1154
- onCleanup();
1099
+ safeCleanup();
1155
1100
  }
1156
1101
  }
1157
1102
  /**
@@ -1240,7 +1185,7 @@ async function processWeComMessage(params) {
1240
1185
  const mediaList = [...imageMediaList, ...fileMediaList];
1241
1186
  // Step 5: 初始化消息状态
1242
1187
  setReqIdForChat(chatId, reqId, account.accountId);
1243
- const streamId = generateReqId("stream");
1188
+ const streamId = aibotNodeSdk.generateReqId("stream");
1244
1189
  const state = { accumulatedText: "", streamId };
1245
1190
  setMessageState(messageId, state);
1246
1191
  const cleanupState = () => {
@@ -1366,19 +1311,72 @@ async function monitorWeComProvider(options) {
1366
1311
  runtime.error?.(`[${account.accountId}] Failed to process message: ${String(err)}`);
1367
1312
  }
1368
1313
  });
1369
- // 启动前预热 reqId 缓存
1314
+ // 启动前预热 reqId 缓存,确保完成后再建立连接,避免 getSync 在预热完成前返回 undefined
1370
1315
  warmupReqIdStore(account.accountId, (...args) => runtime.log?.(...args))
1371
1316
  .then((count) => {
1372
1317
  runtime.log?.(`[${account.accountId}] Warmed up ${count} reqId entries from disk`);
1373
1318
  })
1374
1319
  .catch((err) => {
1375
1320
  runtime.error?.(`[${account.accountId}] Failed to warmup reqId store: ${String(err)}`);
1321
+ })
1322
+ .finally(() => {
1323
+ // 无论预热成功或失败,都建立连接
1324
+ wsClient.connect();
1376
1325
  });
1377
- // 建立连接
1378
- wsClient.connect();
1379
1326
  });
1380
1327
  }
1381
1328
 
1329
+ /**
1330
+ * 企业微信公共工具函数
1331
+ */
1332
+ const DefaultWsUrl = "wss://openws.work.weixin.qq.com";
1333
+ /**
1334
+ * 解析企业微信账户配置
1335
+ */
1336
+ function resolveWeComAccount(cfg) {
1337
+ const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
1338
+ return {
1339
+ accountId: pluginSdk.DEFAULT_ACCOUNT_ID,
1340
+ name: wecomConfig.name ?? "企业微信",
1341
+ enabled: wecomConfig.enabled ?? false,
1342
+ websocketUrl: wecomConfig.websocketUrl || DefaultWsUrl,
1343
+ botId: wecomConfig.botId ?? "",
1344
+ secret: wecomConfig.secret ?? "",
1345
+ sendThinkingMessage: wecomConfig.sendThinkingMessage ?? true,
1346
+ config: wecomConfig,
1347
+ };
1348
+ }
1349
+ /**
1350
+ * 设置企业微信账户配置
1351
+ */
1352
+ function setWeComAccount(cfg, account) {
1353
+ const existing = (cfg.channels?.[CHANNEL_ID] ?? {});
1354
+ const merged = {
1355
+ enabled: account.enabled ?? existing?.enabled ?? true,
1356
+ botId: account.botId ?? existing?.botId ?? "",
1357
+ secret: account.secret ?? existing?.secret ?? "",
1358
+ allowFrom: account.allowFrom ?? existing?.allowFrom,
1359
+ dmPolicy: account.dmPolicy ?? existing?.dmPolicy,
1360
+ // 以下字段仅在已有配置值或显式传入时才写入,onboarding 时不主动生成
1361
+ ...(account.websocketUrl || existing?.websocketUrl
1362
+ ? { websocketUrl: account.websocketUrl ?? existing?.websocketUrl }
1363
+ : {}),
1364
+ ...(account.name || existing?.name
1365
+ ? { name: account.name ?? existing?.name }
1366
+ : {}),
1367
+ ...(account.sendThinkingMessage !== undefined || existing?.sendThinkingMessage !== undefined
1368
+ ? { sendThinkingMessage: account.sendThinkingMessage ?? existing?.sendThinkingMessage }
1369
+ : {}),
1370
+ };
1371
+ return {
1372
+ ...cfg,
1373
+ channels: {
1374
+ ...cfg.channels,
1375
+ [CHANNEL_ID]: merged,
1376
+ },
1377
+ };
1378
+ }
1379
+
1382
1380
  /**
1383
1381
  * 企业微信 onboarding adapter for CLI setup wizard.
1384
1382
  */