@openclaw-china/wecom 2026.3.18 → 2026.3.19

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.d.ts CHANGED
@@ -476,6 +476,10 @@ interface PluginRuntime {
476
476
  mediaUrl?: string;
477
477
  mediaUrls?: string[];
478
478
  }) => Promise<void>;
479
+ onSkip?: (payload: unknown, info: {
480
+ kind: string;
481
+ reason: string;
482
+ }) => void;
479
483
  onError?: (err: unknown, info: {
480
484
  kind: string;
481
485
  }) => void;
package/dist/index.js CHANGED
@@ -1729,6 +1729,7 @@ var CHANNEL_ORDER = [
1729
1729
  "wecom",
1730
1730
  "wecom-app",
1731
1731
  "wecom-kf",
1732
+ "wechat-mp",
1732
1733
  "feishu-china"
1733
1734
  ];
1734
1735
  var CHANNEL_DISPLAY_LABELS = {
@@ -1737,6 +1738,7 @@ var CHANNEL_DISPLAY_LABELS = {
1737
1738
  wecom: "WeCom\uFF08\u4F01\u4E1A\u5FAE\u4FE1-\u667A\u80FD\u673A\u5668\u4EBA\uFF09",
1738
1739
  "wecom-app": "WeCom App\uFF08\u81EA\u5EFA\u5E94\u7528-\u53EF\u63A5\u5165\u5FAE\u4FE1\uFF09",
1739
1740
  "wecom-kf": "WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09",
1741
+ "wechat-mp": "WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09",
1740
1742
  qqbot: "QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09"
1741
1743
  };
1742
1744
  var CHANNEL_GUIDE_LINKS = {
@@ -1745,6 +1747,7 @@ var CHANNEL_GUIDE_LINKS = {
1745
1747
  wecom: `${GUIDES_BASE}/wecom/configuration.md`,
1746
1748
  "wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
1747
1749
  "wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
1750
+ "wechat-mp": `${GUIDES_BASE}/wechat-mp/configuration.md`,
1748
1751
  qqbot: `${GUIDES_BASE}/qqbot/configuration.md`
1749
1752
  };
1750
1753
  var CHINA_CLI_STATE_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-cli-state");
@@ -1954,7 +1957,9 @@ function isChannelConfigured(cfg, channelId) {
1954
1957
  case "wecom-app":
1955
1958
  return hasTokenPair(channelCfg);
1956
1959
  case "wecom-kf":
1957
- return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.corpSecret) && hasNonEmptyString(channelCfg.token) && hasNonEmptyString(channelCfg.encodingAESKey);
1960
+ return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.token) && hasNonEmptyString(channelCfg.encodingAESKey);
1961
+ case "wechat-mp":
1962
+ return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.token);
1958
1963
  default:
1959
1964
  return false;
1960
1965
  }
@@ -2215,6 +2220,15 @@ async function configureWecomKf(prompter, cfg) {
2215
2220
  section("\u914D\u7F6E WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09");
2216
2221
  showGuideLink("wecom-kf");
2217
2222
  const existing = getChannelConfig(cfg, "wecom-kf");
2223
+ Ve(
2224
+ [
2225
+ "\u5411\u5BFC\u987A\u5E8F\uFF1AwebhookPath / token / encodingAESKey / corpId / open_kfid / corpSecret",
2226
+ "\u57FA\u7840\u5FC5\u586B\uFF1AcorpId / token / encodingAESKey / open_kfid",
2227
+ "corpSecret \u4F1A\u4F5C\u4E3A\u6700\u540E\u4E00\u4E2A\u53C2\u6570\u8BE2\u95EE\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF0C\u5F85\u56DE\u8C03 URL \u6821\u9A8C\u901A\u8FC7\u5E76\u70B9\u51FB\u201C\u5F00\u59CB\u4F7F\u7528\u201D\u540E\u518D\u8865",
2228
+ "webhookPath \u9ED8\u8BA4\u503C\uFF1A/wecom-kf"
2229
+ ].join("\n"),
2230
+ "\u53C2\u6570\u8BF4\u660E"
2231
+ );
2218
2232
  const webhookPath = await prompter.askText({
2219
2233
  label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wecom-kf\uFF09",
2220
2234
  defaultValue: toTrimmedString(existing.webhookPath) ?? "/wecom-kf",
@@ -2235,19 +2249,14 @@ async function configureWecomKf(prompter, cfg) {
2235
2249
  defaultValue: toTrimmedString(existing.corpId),
2236
2250
  required: true
2237
2251
  });
2238
- const corpSecret = await prompter.askSecret({
2239
- label: "\u5FAE\u4FE1\u5BA2\u670D Secret",
2240
- existingValue: toTrimmedString(existing.corpSecret),
2241
- required: true
2242
- });
2243
2252
  const openKfId = await prompter.askText({
2244
2253
  label: "open_kfid",
2245
2254
  defaultValue: toTrimmedString(existing.openKfId),
2246
2255
  required: true
2247
2256
  });
2248
- const welcomeText = await prompter.askText({
2249
- label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
2250
- defaultValue: toTrimmedString(existing.welcomeText),
2257
+ const corpSecret = await prompter.askSecret({
2258
+ label: "\u5FAE\u4FE1\u5BA2\u670D Secret\uFF08\u6700\u540E\u586B\u5199\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF09",
2259
+ existingValue: toTrimmedString(existing.corpSecret),
2251
2260
  required: false
2252
2261
  });
2253
2262
  return mergeChannelConfig(cfg, "wecom-kf", {
@@ -2255,8 +2264,72 @@ async function configureWecomKf(prompter, cfg) {
2255
2264
  token,
2256
2265
  encodingAESKey,
2257
2266
  corpId,
2258
- corpSecret,
2259
2267
  openKfId,
2268
+ corpSecret: corpSecret || void 0
2269
+ });
2270
+ }
2271
+ async function configureWechatMp(prompter, cfg) {
2272
+ section("\u914D\u7F6E WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09");
2273
+ showGuideLink("wechat-mp");
2274
+ const existing = getChannelConfig(cfg, "wechat-mp");
2275
+ const webhookPath = await prompter.askText({
2276
+ label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wechat-mp\uFF09",
2277
+ defaultValue: toTrimmedString(existing.webhookPath) ?? "/wechat-mp",
2278
+ required: true
2279
+ });
2280
+ const appId = await prompter.askText({
2281
+ label: "\u516C\u4F17\u53F7 appId",
2282
+ defaultValue: toTrimmedString(existing.appId),
2283
+ required: true
2284
+ });
2285
+ const appSecret = await prompter.askSecret({
2286
+ label: "\u516C\u4F17\u53F7 appSecret\uFF08\u4E3B\u52A8\u53D1\u9001\u9700\u8981\uFF09",
2287
+ existingValue: toTrimmedString(existing.appSecret),
2288
+ required: false
2289
+ });
2290
+ const token = await prompter.askSecret({
2291
+ label: "\u670D\u52A1\u5668\u914D\u7F6E token",
2292
+ existingValue: toTrimmedString(existing.token),
2293
+ required: true
2294
+ });
2295
+ const messageMode = await prompter.askSelect(
2296
+ "\u6D88\u606F\u52A0\u89E3\u5BC6\u6A21\u5F0F",
2297
+ [
2298
+ { value: "plain", label: "plain\uFF08\u660E\u6587\uFF09" },
2299
+ { value: "safe", label: "safe\uFF08\u5B89\u5168\u6A21\u5F0F\uFF09" },
2300
+ { value: "compat", label: "compat\uFF08\u517C\u5BB9\u6A21\u5F0F\uFF09" }
2301
+ ],
2302
+ toTrimmedString(existing.messageMode) ?? "safe"
2303
+ );
2304
+ let encodingAESKey = toTrimmedString(existing.encodingAESKey);
2305
+ if (messageMode !== "plain") {
2306
+ encodingAESKey = await prompter.askSecret({
2307
+ label: "EncodingAESKey\uFF08safe/compat \u5FC5\u586B\uFF09",
2308
+ existingValue: encodingAESKey,
2309
+ required: true
2310
+ });
2311
+ }
2312
+ const replyMode = await prompter.askSelect(
2313
+ "\u56DE\u590D\u6A21\u5F0F",
2314
+ [
2315
+ { value: "passive", label: "passive\uFF085 \u79D2\u5185\u88AB\u52A8\u56DE\u590D\uFF09" },
2316
+ { value: "active", label: "active\uFF08\u5BA2\u670D\u6D88\u606F\u4E3B\u52A8\u53D1\u9001\uFF09" }
2317
+ ],
2318
+ toTrimmedString(existing.replyMode) ?? "passive"
2319
+ );
2320
+ const welcomeText = await prompter.askText({
2321
+ label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
2322
+ defaultValue: toTrimmedString(existing.welcomeText),
2323
+ required: false
2324
+ });
2325
+ return mergeChannelConfig(cfg, "wechat-mp", {
2326
+ webhookPath,
2327
+ appId,
2328
+ appSecret: appSecret || void 0,
2329
+ token,
2330
+ encodingAESKey: messageMode === "plain" ? void 0 : encodingAESKey,
2331
+ messageMode,
2332
+ replyMode,
2260
2333
  welcomeText: welcomeText || void 0
2261
2334
  });
2262
2335
  }
@@ -2318,6 +2391,8 @@ async function configureSingleChannel(channel, prompter, cfg) {
2318
2391
  return configureWecomApp(prompter, cfg);
2319
2392
  case "wecom-kf":
2320
2393
  return configureWecomKf(prompter, cfg);
2394
+ case "wechat-mp":
2395
+ return configureWechatMp(prompter, cfg);
2321
2396
  case "qqbot":
2322
2397
  return configureQQBot(prompter, cfg);
2323
2398
  default:
@@ -2459,6 +2534,7 @@ var SUPPORTED_CHANNELS = [
2459
2534
  "wecom",
2460
2535
  "wecom-app",
2461
2536
  "wecom-kf",
2537
+ "wechat-mp",
2462
2538
  "qqbot"
2463
2539
  ];
2464
2540
  var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
@@ -7181,6 +7257,8 @@ function normalizeWecomWsCallback(frame) {
7181
7257
  var MESSAGE_CONTEXT_TTL_MS = 6 * 60 * 1e3;
7182
7258
  var EVENT_CONTEXT_TTL_MS = 10 * 1e3;
7183
7259
  var STREAM_FINISH_GRACE_MS = 2500;
7260
+ var WECOM_WS_THINKING_MESSAGE = "<think></think>";
7261
+ var WECOM_WS_FINISH_FALLBACK_MESSAGE = "\u2705 \u5904\u7406\u5B8C\u6210\u3002";
7184
7262
  var messageContexts = /* @__PURE__ */ new Map();
7185
7263
  var eventContexts = /* @__PURE__ */ new Map();
7186
7264
  var messageBySessionKey = /* @__PURE__ */ new Map();
@@ -7342,6 +7420,8 @@ function registerWecomWsMessageContext(params) {
7342
7420
  pendingAutoImagePaths: [],
7343
7421
  createdAt: now2(),
7344
7422
  updatedAt: now2(),
7423
+ placeholderContent: "",
7424
+ suppressVisibleFallback: false,
7345
7425
  started: false,
7346
7426
  finished: false,
7347
7427
  queue: Promise.resolve(),
@@ -7367,6 +7447,7 @@ async function sendWecomWsMessagePlaceholder(params) {
7367
7447
  finish: false
7368
7448
  })
7369
7449
  );
7450
+ context.placeholderContent = content;
7370
7451
  context.started = true;
7371
7452
  context.updatedAt = now2();
7372
7453
  });
@@ -7405,6 +7486,16 @@ function bindWecomWsRouteContext(params) {
7405
7486
  }
7406
7487
  context.updatedAt = now2();
7407
7488
  }
7489
+ function markWecomWsMessageContextSkipped(params) {
7490
+ pruneMessageContexts();
7491
+ const key = messageKey(params.accountId.trim(), params.reqId.trim());
7492
+ const context = messageContexts.get(key);
7493
+ if (!context || context.finished) return;
7494
+ const reason = String(params.reason ?? "").trim();
7495
+ if (!reason) return;
7496
+ context.suppressVisibleFallback = true;
7497
+ context.updatedAt = now2();
7498
+ }
7408
7499
  async function appendWecomWsActiveStreamChunk(params) {
7409
7500
  const result = await appendWecomWsActiveStreamReply({
7410
7501
  accountId: params.accountId,
@@ -7465,6 +7556,7 @@ async function appendWecomWsActiveStreamReply(params) {
7465
7556
  context.msgItems.push(...acceptedMsgItems);
7466
7557
  }
7467
7558
  if (chunk.trim()) {
7559
+ context.placeholderContent = "";
7468
7560
  context.content = appendStreamSnapshotContent(context.content, chunk);
7469
7561
  await context.send(
7470
7562
  buildWecomWsRespondMessageCommand({
@@ -7520,13 +7612,15 @@ async function finishWecomWsMessageContext(params) {
7520
7612
  const finalContent = errorMessage ? context.content ? `${context.content}
7521
7613
 
7522
7614
  ${errorMessage}` : errorMessage : context.content;
7523
- const sendFinish = context.started || Boolean(finalContent) || context.msgItems.length > 0;
7615
+ const fallbackContent = !finalContent && !context.suppressVisibleFallback && context.placeholderContent === WECOM_WS_THINKING_MESSAGE ? WECOM_WS_FINISH_FALLBACK_MESSAGE : void 0;
7616
+ const finishContent = finalContent || fallbackContent;
7617
+ const sendFinish = context.started || Boolean(finishContent) || context.msgItems.length > 0;
7524
7618
  if (sendFinish) {
7525
7619
  await context.send(
7526
7620
  buildWecomWsRespondMessageCommand({
7527
7621
  reqId: context.reqId,
7528
7622
  streamId: context.streamId,
7529
- content: finalContent || void 0,
7623
+ content: finishContent,
7530
7624
  finish: true,
7531
7625
  msgItems: context.msgItems
7532
7626
  })
@@ -8056,6 +8150,9 @@ async function dispatchWecomMessage(params) {
8056
8150
  if (!normalized.trim()) return;
8057
8151
  await hooks.onChunk(normalized);
8058
8152
  },
8153
+ onSkip: (_payload, info) => {
8154
+ hooks.onSkip?.(info);
8155
+ },
8059
8156
  onError: (err, info) => {
8060
8157
  hooks.onError?.(err);
8061
8158
  logger.error(`${info.kind} reply failed: ${String(err)}`);
@@ -9378,6 +9475,7 @@ async function fetchAndSaveWecomDocMcpConfig(params) {
9378
9475
  var activeConnections = /* @__PURE__ */ new Map();
9379
9476
  var processedMessageIds = /* @__PURE__ */ new Map();
9380
9477
  var PROCESSED_MESSAGE_TTL_MS = 10 * 60 * 1e3;
9478
+ var WECOM_WS_SHUTDOWN_GRACE_MS = 1e3;
9381
9479
  var activatedTargets = /* @__PURE__ */ new Map();
9382
9480
  function getOrCreateConnection(accountId) {
9383
9481
  let conn = activeConnections.get(accountId);
@@ -9493,7 +9591,11 @@ function summarizeWecomReplyFrame(frame) {
9493
9591
  }
9494
9592
  return JSON.stringify(summary);
9495
9593
  }
9496
- function createSdkLogger(logger) {
9594
+ function isExpectedShutdownWsLog(message) {
9595
+ const lowered = message.toLowerCase();
9596
+ return lowered.includes("invalid websocket frame") || lowered.includes("invalid opcode") || lowered.includes("websocket connection closed: code: 1006");
9597
+ }
9598
+ function createSdkLogger(logger, opts) {
9497
9599
  return {
9498
9600
  debug(message, ...args) {
9499
9601
  logger.debug(formatLogMessage(message, args));
@@ -9502,13 +9604,27 @@ function createSdkLogger(logger) {
9502
9604
  logger.info(formatLogMessage(message, args));
9503
9605
  },
9504
9606
  warn(message, ...args) {
9505
- logger.warn(formatLogMessage(message, args));
9607
+ const formatted = formatLogMessage(message, args);
9608
+ if (opts?.isShuttingDown?.() && isExpectedShutdownWsLog(formatted)) {
9609
+ logger.debug(`wecom ws shutdown noise suppressed: ${formatted}`);
9610
+ return;
9611
+ }
9612
+ logger.warn(formatted);
9506
9613
  },
9507
9614
  error(message, ...args) {
9508
- logger.error(formatLogMessage(message, args));
9615
+ const formatted = formatLogMessage(message, args);
9616
+ if (opts?.isShuttingDown?.() && isExpectedShutdownWsLog(formatted)) {
9617
+ logger.debug(`wecom ws shutdown noise suppressed: ${formatted}`);
9618
+ return;
9619
+ }
9620
+ logger.error(formatted);
9509
9621
  }
9510
9622
  };
9511
9623
  }
9624
+ function isExpectedShutdownWsError(error) {
9625
+ const message = error.message.toLowerCase();
9626
+ return message.includes("invalid websocket frame") || message.includes("invalid opcode");
9627
+ }
9512
9628
  function requireActiveClient(accountId) {
9513
9629
  const conn = activeConnections.get(accountId);
9514
9630
  if (!conn?.client) {
@@ -9634,6 +9750,8 @@ async function startWecomWsGateway(opts) {
9634
9750
  }
9635
9751
  conn.promise = new Promise((resolve4, reject) => {
9636
9752
  let finished = false;
9753
+ let shuttingDown = false;
9754
+ let shutdownTimer = null;
9637
9755
  const client = new WSClient({
9638
9756
  botId: account.botId ?? "",
9639
9757
  secret: account.secret ?? "",
@@ -9641,18 +9759,18 @@ async function startWecomWsGateway(opts) {
9641
9759
  heartbeatInterval: account.heartbeatIntervalMs,
9642
9760
  reconnectInterval: account.reconnectInitialDelayMs,
9643
9761
  maxReconnectAttempts: -1,
9644
- logger: createSdkLogger(logger)
9762
+ logger: createSdkLogger(logger, { isShuttingDown: () => shuttingDown })
9645
9763
  });
9646
9764
  conn.client = client;
9647
- const finish = (err) => {
9765
+ const cleanup = (err) => {
9648
9766
  if (finished) return;
9649
9767
  finished = true;
9768
+ if (shutdownTimer) {
9769
+ clearTimeout(shutdownTimer);
9770
+ shutdownTimer = null;
9771
+ }
9650
9772
  abortSignal?.removeEventListener("abort", onAbort);
9651
9773
  client.removeAllListeners();
9652
- try {
9653
- client.disconnect();
9654
- } catch {
9655
- }
9656
9774
  clearWecomWsReplyContextsForAccount(account.accountId);
9657
9775
  clearActivatedTargetsForAccount(account.accountId);
9658
9776
  conn.client = null;
@@ -9668,9 +9786,27 @@ async function startWecomWsGateway(opts) {
9668
9786
  if (err) reject(err);
9669
9787
  else resolve4();
9670
9788
  };
9789
+ const beginShutdown = (err) => {
9790
+ if (finished) return;
9791
+ if (shuttingDown) {
9792
+ return;
9793
+ }
9794
+ shuttingDown = true;
9795
+ abortSignal?.removeEventListener("abort", onAbort);
9796
+ shutdownTimer = setTimeout(() => {
9797
+ logger.warn(`wecom ws shutdown timed out for account ${account.accountId}; forcing cleanup`);
9798
+ cleanup(err);
9799
+ }, WECOM_WS_SHUTDOWN_GRACE_MS);
9800
+ shutdownTimer.unref?.();
9801
+ try {
9802
+ client.disconnect();
9803
+ } catch (disconnectErr) {
9804
+ cleanup(disconnectErr);
9805
+ }
9806
+ };
9671
9807
  const onAbort = () => {
9672
9808
  logger.info("abort signal received, stopping wecom ws gateway");
9673
- finish();
9809
+ beginShutdown();
9674
9810
  };
9675
9811
  const handleMessageCallback = (frame) => {
9676
9812
  const callback = normalizeWecomWsCallback(toWecomWsFrame(frame));
@@ -9717,7 +9853,7 @@ async function startWecomWsGateway(opts) {
9717
9853
  void sendWecomWsMessagePlaceholder({
9718
9854
  accountId: account.accountId,
9719
9855
  reqId: callback.reqId,
9720
- content: "\u23F3"
9856
+ content: WECOM_WS_THINKING_MESSAGE
9721
9857
  }).catch((err) => {
9722
9858
  logger.warn(`wecom ws placeholder ack failed: ${String(err)}`);
9723
9859
  });
@@ -9730,6 +9866,13 @@ async function startWecomWsGateway(opts) {
9730
9866
  runId: context.runId
9731
9867
  });
9732
9868
  },
9869
+ onSkip: (info) => {
9870
+ markWecomWsMessageContextSkipped({
9871
+ accountId: account.accountId,
9872
+ reqId: callback.reqId,
9873
+ reason: info.reason
9874
+ });
9875
+ },
9733
9876
  onChunk: async (text) => {
9734
9877
  await appendWecomWsActiveStreamChunk({
9735
9878
  accountId: account.accountId,
@@ -9919,13 +10062,20 @@ async function startWecomWsGateway(opts) {
9919
10062
  setStatus?.({
9920
10063
  accountId: account.accountId,
9921
10064
  mode: "ws",
9922
- running: true,
10065
+ running: !shuttingDown,
9923
10066
  connectionState: "disconnected",
9924
10067
  lastDisconnectAt: Date.now(),
9925
10068
  lastDisconnectReason: reason
9926
10069
  });
10070
+ if (shuttingDown) {
10071
+ cleanup();
10072
+ }
9927
10073
  });
9928
10074
  client.on("error", (error) => {
10075
+ if (shuttingDown && isExpectedShutdownWsError(error)) {
10076
+ logger.debug(`wecom ws shutdown noise suppressed: ${error.message}`);
10077
+ return;
10078
+ }
9929
10079
  logger.error(`wecom ws sdk error: ${error.message}`);
9930
10080
  setStatus?.({
9931
10081
  accountId: account.accountId,
@@ -9935,17 +10085,17 @@ async function startWecomWsGateway(opts) {
9935
10085
  });
9936
10086
  });
9937
10087
  conn.stop = () => {
9938
- finish();
10088
+ beginShutdown();
9939
10089
  };
9940
10090
  if (abortSignal?.aborted) {
9941
- finish();
10091
+ beginShutdown();
9942
10092
  return;
9943
10093
  }
9944
10094
  abortSignal?.addEventListener("abort", onAbort, { once: true });
9945
10095
  try {
9946
10096
  client.connect();
9947
10097
  } catch (err) {
9948
- finish(err);
10098
+ cleanup(err);
9949
10099
  }
9950
10100
  });
9951
10101
  return conn.promise;