@wecom/wecom-openclaw-plugin 1.0.0 → 1.0.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/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
- import { DEFAULT_ACCOUNT_ID, resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce, readJsonFileWithFallback, withFileLock, writeJsonFileAtomically, addWildcardAllowFrom, formatPairingApproveHint, emptyPluginConfigSchema } from 'openclaw/plugin-sdk';
2
- import { WSClient } from '@wecom/aibot-node-sdk';
1
+ import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce, readJsonFileWithFallback, withFileLock, writeJsonFileAtomically, DEFAULT_ACCOUNT_ID, addWildcardAllowFrom, formatPairingApproveHint, emptyPluginConfigSchema } from 'openclaw/plugin-sdk';
2
+ import { generateReqId, WSClient } from '@wecom/aibot-node-sdk';
3
3
  import { fileTypeFromBuffer } from 'file-type';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
@@ -77,73 +77,6 @@ const DEFAULT_MEDIA_MAX_MB = 5;
77
77
  /** 文本分块大小上限 */
78
78
  const TEXT_CHUNK_LIMIT = 4000;
79
79
 
80
- /**
81
- * 企业微信公共工具函数
82
- */
83
- const DefaultWsUrl = "wss://openws.work.weixin.qq.com";
84
- /**
85
- * 解析企业微信账户配置
86
- */
87
- function resolveWeComAccount(cfg) {
88
- const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
89
- return {
90
- accountId: DEFAULT_ACCOUNT_ID,
91
- name: wecomConfig.name ?? "企业微信",
92
- enabled: wecomConfig.enabled ?? false,
93
- websocketUrl: wecomConfig.websocketUrl || DefaultWsUrl,
94
- botId: wecomConfig.botId ?? "",
95
- secret: wecomConfig.secret ?? "",
96
- sendThinkingMessage: wecomConfig.sendThinkingMessage ?? true,
97
- config: wecomConfig,
98
- };
99
- }
100
- /**
101
- * 设置企业微信账户配置
102
- */
103
- function setWeComAccount(cfg, account) {
104
- const existing = (cfg.channels?.[CHANNEL_ID] ?? {});
105
- const merged = {
106
- enabled: account.enabled ?? existing?.enabled ?? true,
107
- botId: account.botId ?? existing?.botId ?? "",
108
- secret: account.secret ?? existing?.secret ?? "",
109
- allowFrom: account.allowFrom ?? existing?.allowFrom,
110
- dmPolicy: account.dmPolicy ?? existing?.dmPolicy,
111
- // 以下字段仅在已有配置值或显式传入时才写入,onboarding 时不主动生成
112
- ...(account.websocketUrl || existing?.websocketUrl
113
- ? { websocketUrl: account.websocketUrl ?? existing?.websocketUrl }
114
- : {}),
115
- ...(account.name || existing?.name
116
- ? { name: account.name ?? existing?.name }
117
- : {}),
118
- ...(account.sendThinkingMessage !== undefined || existing?.sendThinkingMessage !== undefined
119
- ? { sendThinkingMessage: account.sendThinkingMessage ?? existing?.sendThinkingMessage }
120
- : {}),
121
- };
122
- return {
123
- ...cfg,
124
- channels: {
125
- ...cfg.channels,
126
- [CHANNEL_ID]: merged,
127
- },
128
- };
129
- }
130
- /**
131
- * 生成随机字符串
132
- */
133
- function generateRandomString(length = 9) {
134
- return Math.random().toString(36).substr(2, length);
135
- }
136
- /**
137
- * 生成企业微信请求 ID
138
- * @param prefix - 请求类型前缀(subscribe, ping, response 等)
139
- * @returns 格式化的请求 ID
140
- */
141
- function generateReqId(prefix) {
142
- const timestamp = Date.now();
143
- const random = generateRandomString();
144
- return `${prefix}_${timestamp}_${random}`;
145
- }
146
-
147
80
  /**
148
81
  * 企业微信消息内容解析模块
149
82
  *
@@ -567,10 +500,14 @@ async function checkDmPolicy(params) {
567
500
  if (dmPolicy === "open") {
568
501
  return { allowed: true };
569
502
  }
570
- // 检查发送者是否在允许列表中
571
- const storeAllowFrom = await core.channel.pairing
503
+ // OpenClaw <= 2026.2.19 signature: readAllowFromStore(channel, env?, accountId?)
504
+ const oldStoreAllowFrom = await core.channel.pairing.readAllowFromStore('wecom', undefined, account.accountId).catch(() => []);
505
+ // Compatibility fallback for newer OpenClaw implementations.
506
+ const newStoreAllowFrom = await core.channel.pairing
572
507
  .readAllowFromStore({ channel: CHANNEL_ID, accountId: account.accountId })
573
508
  .catch(() => []);
509
+ // 检查发送者是否在允许列表中
510
+ const storeAllowFrom = [...oldStoreAllowFrom, ...newStoreAllowFrom];
574
511
  const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
575
512
  const senderAllowedResult = isSenderAllowed(senderId, effectiveAllowFrom);
576
513
  if (senderAllowedResult) {
@@ -1076,7 +1013,7 @@ function buildMessageContext(frame, account, config, text, mediaList, quoteConte
1076
1013
  MediaPaths: mediaPaths,
1077
1014
  MediaTypes: mediaTypes,
1078
1015
  MediaUrls: mediaPaths,
1079
- QuoteContent: quoteContent,
1016
+ ReplyToBody: quoteContent,
1080
1017
  });
1081
1018
  }
1082
1019
  // ============================================================================
@@ -1108,6 +1045,14 @@ async function sendThinkingReply(params) {
1108
1045
  async function routeAndDispatchMessage(params) {
1109
1046
  const { ctxPayload, config, wsClient, frame, state, runtime, onCleanup } = params;
1110
1047
  const core = getWeComRuntime();
1048
+ // 防止 onCleanup 被多次调用(onError 回调与 catch 块可能重复触发)
1049
+ let cleanedUp = false;
1050
+ const safeCleanup = () => {
1051
+ if (!cleanedUp) {
1052
+ cleanedUp = true;
1053
+ onCleanup();
1054
+ }
1055
+ };
1111
1056
  try {
1112
1057
  await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
1113
1058
  ctx: ctxPayload,
@@ -1128,7 +1073,7 @@ async function routeAndDispatchMessage(params) {
1128
1073
  },
1129
1074
  onError: (err, info) => {
1130
1075
  runtime.error?.(`[WeCom] ${info.kind} reply failed: ${String(err)}`);
1131
- onCleanup();
1076
+ // 仅记录错误,不立即 cleanup,让外层 try/catch 统一处理最终回复和 cleanup
1132
1077
  },
1133
1078
  },
1134
1079
  });
@@ -1143,11 +1088,11 @@ async function routeAndDispatchMessage(params) {
1143
1088
  streamId: state.streamId,
1144
1089
  });
1145
1090
  }
1146
- onCleanup();
1091
+ safeCleanup();
1147
1092
  }
1148
1093
  catch (err) {
1149
1094
  runtime.error?.(`[WeCom] Failed to process message: ${String(err)}`);
1150
- onCleanup();
1095
+ safeCleanup();
1151
1096
  }
1152
1097
  }
1153
1098
  /**
@@ -1362,19 +1307,72 @@ async function monitorWeComProvider(options) {
1362
1307
  runtime.error?.(`[${account.accountId}] Failed to process message: ${String(err)}`);
1363
1308
  }
1364
1309
  });
1365
- // 启动前预热 reqId 缓存
1310
+ // 启动前预热 reqId 缓存,确保完成后再建立连接,避免 getSync 在预热完成前返回 undefined
1366
1311
  warmupReqIdStore(account.accountId, (...args) => runtime.log?.(...args))
1367
1312
  .then((count) => {
1368
1313
  runtime.log?.(`[${account.accountId}] Warmed up ${count} reqId entries from disk`);
1369
1314
  })
1370
1315
  .catch((err) => {
1371
1316
  runtime.error?.(`[${account.accountId}] Failed to warmup reqId store: ${String(err)}`);
1317
+ })
1318
+ .finally(() => {
1319
+ // 无论预热成功或失败,都建立连接
1320
+ wsClient.connect();
1372
1321
  });
1373
- // 建立连接
1374
- wsClient.connect();
1375
1322
  });
1376
1323
  }
1377
1324
 
1325
+ /**
1326
+ * 企业微信公共工具函数
1327
+ */
1328
+ const DefaultWsUrl = "wss://openws.work.weixin.qq.com";
1329
+ /**
1330
+ * 解析企业微信账户配置
1331
+ */
1332
+ function resolveWeComAccount(cfg) {
1333
+ const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
1334
+ return {
1335
+ accountId: DEFAULT_ACCOUNT_ID,
1336
+ name: wecomConfig.name ?? "企业微信",
1337
+ enabled: wecomConfig.enabled ?? false,
1338
+ websocketUrl: wecomConfig.websocketUrl || DefaultWsUrl,
1339
+ botId: wecomConfig.botId ?? "",
1340
+ secret: wecomConfig.secret ?? "",
1341
+ sendThinkingMessage: wecomConfig.sendThinkingMessage ?? true,
1342
+ config: wecomConfig,
1343
+ };
1344
+ }
1345
+ /**
1346
+ * 设置企业微信账户配置
1347
+ */
1348
+ function setWeComAccount(cfg, account) {
1349
+ const existing = (cfg.channels?.[CHANNEL_ID] ?? {});
1350
+ const merged = {
1351
+ enabled: account.enabled ?? existing?.enabled ?? true,
1352
+ botId: account.botId ?? existing?.botId ?? "",
1353
+ secret: account.secret ?? existing?.secret ?? "",
1354
+ allowFrom: account.allowFrom ?? existing?.allowFrom,
1355
+ dmPolicy: account.dmPolicy ?? existing?.dmPolicy,
1356
+ // 以下字段仅在已有配置值或显式传入时才写入,onboarding 时不主动生成
1357
+ ...(account.websocketUrl || existing?.websocketUrl
1358
+ ? { websocketUrl: account.websocketUrl ?? existing?.websocketUrl }
1359
+ : {}),
1360
+ ...(account.name || existing?.name
1361
+ ? { name: account.name ?? existing?.name }
1362
+ : {}),
1363
+ ...(account.sendThinkingMessage !== undefined || existing?.sendThinkingMessage !== undefined
1364
+ ? { sendThinkingMessage: account.sendThinkingMessage ?? existing?.sendThinkingMessage }
1365
+ : {}),
1366
+ };
1367
+ return {
1368
+ ...cfg,
1369
+ channels: {
1370
+ ...cfg.channels,
1371
+ [CHANNEL_ID]: merged,
1372
+ },
1373
+ };
1374
+ }
1375
+
1378
1376
  /**
1379
1377
  * 企业微信 onboarding adapter for CLI setup wizard.
1380
1378
  */