@xmanrui/dsh-im 4.14.0 → 4.16.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.
Files changed (69) hide show
  1. package/README.en.md +10 -2
  2. package/README.md +17 -6
  3. package/lib/client.js +1980 -616
  4. package/lib/index.js +303 -293
  5. package/package.json +6 -2
  6. package/plugin-src/client/access-policy-settings.js +6 -0
  7. package/plugin-src/client/channels/feishu/index.js +16 -4
  8. package/plugin-src/client/channels/wecom-app/api.js +124 -0
  9. package/plugin-src/client/channels/wecom-app/index.js +612 -0
  10. package/plugin-src/client/channels/wecom-app/styles.js +25 -0
  11. package/plugin-src/client/delivery-settings.js +7 -0
  12. package/plugin-src/client/i18n.js +64 -1
  13. package/plugin-src/client/index.js +21 -0
  14. package/plugin-src/client/model-setting.js +135 -69
  15. package/plugin-src/client/session-channel-logos.js +241 -0
  16. package/plugin-src/client/styles.js +22 -7
  17. package/plugin-src/host/channels/dingtalk/index.mjs +8 -15
  18. package/plugin-src/host/channels/discord/index.mjs +8 -10
  19. package/plugin-src/host/channels/feishu/index.mjs +8 -15
  20. package/plugin-src/host/channels/office/index.mjs +9 -5
  21. package/plugin-src/host/channels/qq/index.mjs +8 -15
  22. package/plugin-src/host/channels/shared/access-policy-production.mjs +1 -1
  23. package/plugin-src/host/channels/shared/model-setting-rpc.mjs +1 -2
  24. package/plugin-src/host/channels/shared/startup-error.mjs +53 -0
  25. package/plugin-src/host/channels/shared/startup.mjs +47 -0
  26. package/plugin-src/host/channels/shared/workspace-rpc.mjs +1 -0
  27. package/plugin-src/host/channels/slack/index.mjs +8 -10
  28. package/plugin-src/host/channels/telegram/index.mjs +8 -10
  29. package/plugin-src/host/channels/wecom/index.mjs +8 -15
  30. package/plugin-src/host/channels/wecom/rpc.mjs +6 -1
  31. package/plugin-src/host/channels/wecom-app/index.mjs +33 -0
  32. package/plugin-src/host/channels/wecom-app/production.mjs +217 -0
  33. package/plugin-src/host/channels/wecom-app/rpc.mjs +225 -0
  34. package/plugin-src/host/channels/weixin/index.mjs +8 -15
  35. package/plugin-src/host/channels/whatsapp/index.mjs +8 -15
  36. package/plugin-src/host/delivery-adapter.mjs +4 -0
  37. package/plugin-src/host/delivery-suggestions.mjs +4 -0
  38. package/plugin-src/host/index.mjs +21 -2
  39. package/plugin-src/host/session-title-prefix.mjs +122 -0
  40. package/scripts/verify-model-setting.mjs +54 -0
  41. package/scripts/verify-session-channel-logos.mjs +59 -0
  42. package/scripts/verify-session-title-prefix.mjs +149 -0
  43. package/src/channels/feishu/bridge.mjs +305 -12
  44. package/src/channels/feishu/feishu-cards.mjs +38 -0
  45. package/src/channels/feishu/feishu-channel.mjs +88 -20
  46. package/src/channels/feishu/plugin-controller.mjs +1 -0
  47. package/src/channels/feishu/repair-manager.mjs +3 -2
  48. package/src/channels/shared/bot-workspace-store.mjs +11 -3
  49. package/src/channels/shared/command-catalog.mjs +102 -0
  50. package/src/channels/shared/i18n-en/feishu.mjs +14 -2
  51. package/src/channels/shared/i18n-en/shared-a.mjs +3 -0
  52. package/src/channels/shared/i18n-en/shared-c.mjs +9 -0
  53. package/src/channels/shared/i18n-en/wecom-app.mjs +33 -0
  54. package/src/channels/shared/i18n-en.mjs +2 -0
  55. package/src/channels/shared/model-setting.mjs +43 -8
  56. package/src/channels/shared/session-channel-labels.mjs +26 -0
  57. package/src/channels/shared/text-harness-bridge.mjs +2 -26
  58. package/src/channels/telegram/telegram-api.mjs +18 -6
  59. package/src/channels/telegram/telegram-runtime.mjs +34 -32
  60. package/src/channels/wecom/send-error.mjs +31 -0
  61. package/src/channels/wecom/wecom-bridge.mjs +59 -17
  62. package/src/channels/wecom-app/callback-server.mjs +471 -0
  63. package/src/channels/wecom-app/config-store.mjs +240 -0
  64. package/src/channels/wecom-app/harness-client.mjs +11 -0
  65. package/src/channels/wecom-app/state-store.mjs +107 -0
  66. package/src/channels/wecom-app/wecom-app-api.mjs +432 -0
  67. package/src/channels/wecom-app/wecom-app-bridge.mjs +1059 -0
  68. package/src/channels/wecom-app/wecom-app-controller.mjs +440 -0
  69. package/src/channels/wecom-app/wecom-app-runtime.mjs +221 -0
@@ -96,8 +96,16 @@ function validBotCommand(value) {
96
96
  return Boolean(value)
97
97
  && typeof value === 'object' && !Array.isArray(value)
98
98
  && typeof value.command === 'string' && TELEGRAM_COMMAND_NAME.test(value.command)
99
- && typeof value.description === 'string' && value.description.length >= 1
100
- && value.description.length <= 256;
99
+ && typeof value.description === 'string' && value.description.trim().length >= 1
100
+ && [...value.description].length <= 256;
101
+ }
102
+
103
+ export function validateTelegramCommands(commands, { allowEmpty = false } = {}) {
104
+ if (!Array.isArray(commands) || (!allowEmpty && commands.length === 0)
105
+ || commands.length > 100 || commands.some((command) => !validBotCommand(command))
106
+ || new Set(commands.map((item) => item.command)).size !== commands.length) {
107
+ throw new TypeError('Telegram bot commands are invalid');
108
+ }
101
109
  }
102
110
 
103
111
  export const COMMANDS_MENU_BUTTON = Object.freeze({ type: 'commands' });
@@ -334,10 +342,7 @@ export class TelegramApi {
334
342
  }
335
343
 
336
344
  async setMyCommands({ commands, scope, languageCode, signal } = {}) {
337
- if (!Array.isArray(commands) || commands.length === 0
338
- || commands.some((command) => !validBotCommand(command))) {
339
- throw new TypeError('Telegram bot commands are invalid');
340
- }
345
+ validateTelegramCommands(commands);
341
346
  return this.#call('setMyCommands', {
342
347
  commands,
343
348
  ...(scope ? { scope } : {}),
@@ -345,6 +350,13 @@ export class TelegramApi {
345
350
  }, { signal });
346
351
  }
347
352
 
353
+ async deleteMyCommands({ scope, languageCode, signal } = {}) {
354
+ return this.#call('deleteMyCommands', {
355
+ ...(scope ? { scope } : {}),
356
+ ...(cleanString(languageCode) ? { language_code: cleanString(languageCode) } : {}),
357
+ }, { signal });
358
+ }
359
+
348
360
  async setChatMenuButton({ menuButton = COMMANDS_MENU_BUTTON, signal } = {}) {
349
361
  if (!menuButton || typeof menuButton !== 'object' || Array.isArray(menuButton)) {
350
362
  throw new TypeError('Telegram menu button is invalid');
@@ -5,7 +5,8 @@ import { createTextDeliveryBlock } from '../shared/semantic/delivery.mjs';
5
5
  import { t } from '../shared/i18n.mjs';
6
6
  import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
7
7
  import { recoverAssistantTextByTimestamp } from '../shared/session-reply-recovery.mjs';
8
- import { COMMANDS_MENU_BUTTON, TelegramApi } from './telegram-api.mjs';
8
+ import { SHARED_COMMAND_CATALOG, commandsForChannel } from '../shared/command-catalog.mjs';
9
+ import { COMMANDS_MENU_BUTTON, TelegramApi, validateTelegramCommands } from './telegram-api.mjs';
9
10
  import { createTelegramHttpTransport } from './telegram-http.mjs';
10
11
  import { createTelegramBridgeStatus, TelegramHarnessBridge } from './telegram-bridge.mjs';
11
12
  import {
@@ -17,39 +18,30 @@ import {
17
18
  TELEGRAM_ACCESS_MODES,
18
19
  } from './config-store.mjs';
19
20
 
20
- export const TELEGRAM_COMMAND_MENU = Object.freeze([
21
- { command: 'new', description: '开启一个全新会话' },
22
- { command: 'compact', description: '压缩当前会话的较早上下文' },
23
- { command: 'workspace', description: '切换工作区' },
24
- { command: 'ws', description: '切换工作区' },
25
- { command: 'workspacelist', description: '列出工作区绝对路径' },
26
- { command: 'workspaces', description: '列出工作区绝对路径' },
27
- { command: 'wsl', description: '列出工作区绝对路径' },
28
- { command: 'sessionlist', description: '列出会话 ID 和标题' },
29
- { command: 'sessions', description: '列出会话 ID 和标题' },
30
- { command: 'session', description: '将当前聊天绑定到指定会话' },
31
- { command: 'models', description: '按序号列出所有可用模型' },
32
- { command: 'model', description: '查看或切换当前会话模型' },
33
- { command: 'presetlist', description: '列出可用 Agent Preset' },
34
- { command: 'presets', description: '列出可用 Agent Preset' },
35
- { command: 'preset', description: '查看或设置新会话 Agent Preset' },
36
- { command: 'stop', description: '停止当前任务' },
37
- { command: 'steer', description: '纠偏当前任务' },
38
- { command: 'batch', description: '开始批量输入(仅私聊)' },
39
- { command: 'send', description: '提交当前批次' },
40
- { command: 'cancel', description: '取消当前批次' },
41
- { command: 'status', description: '检查连接状态' },
42
- { command: 'version', description: '查看插件版本' },
43
- { command: 'help', description: '显示帮助' },
44
- ]);
21
+ function commandMenuEntries(catalog, translate) {
22
+ return commandsForChannel('telegram', catalog)
23
+ .filter((item) => item.menuVisible !== false)
24
+ .flatMap((item) => [item, ...(item.aliases ?? [])]
25
+ .filter((entry) => entry.menuVisible !== false && entry.enabled !== false
26
+ && (!entry.channels || entry.channels.includes('telegram')))
27
+ .map((entry) => ({
28
+ command: entry.name,
29
+ description: translate(entry.description ?? item.description),
30
+ })));
31
+ }
45
32
 
46
- export function telegramCommandMenu() {
47
- return TELEGRAM_COMMAND_MENU.map((item) => ({
48
- ...item,
49
- description: t(item.description),
50
- }));
33
+ export function telegramCommandMenu(catalog = SHARED_COMMAND_CATALOG) {
34
+ const commands = commandMenuEntries(catalog, t);
35
+ validateTelegramCommands(commands, { allowEmpty: true });
36
+ return commands;
51
37
  }
52
38
 
39
+ // Retain the source-language export for existing consumers, derived from the
40
+ // catalog. Runtime synchronization always generates a fresh, localized list.
41
+ export const TELEGRAM_COMMAND_MENU = Object.freeze(
42
+ commandMenuEntries(SHARED_COMMAND_CATALOG, (text) => text).map((item) => Object.freeze(item)),
43
+ );
44
+
53
45
  function escaped(value) {
54
46
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
55
47
  }
@@ -772,6 +764,7 @@ export class TelegramRuntime {
772
764
  #replyTimeoutMs;
773
765
  #createApi;
774
766
  #createHttpTransport;
767
+ #commandCatalog;
775
768
  #status = createTelegramRuntimeStatus();
776
769
  #httpTransport = null;
777
770
  #api = null;
@@ -791,6 +784,7 @@ export class TelegramRuntime {
791
784
  replyTimeoutMs = 600_000,
792
785
  createApi = (options) => new TelegramApi(options),
793
786
  createHttpTransport = createTelegramHttpTransport,
787
+ commandCatalog = SHARED_COMMAND_CATALOG,
794
788
  }) {
795
789
  if (!config || !token || !harness || !state) {
796
790
  throw new TypeError('TelegramRuntime requires config, token, Harness, and state');
@@ -805,6 +799,7 @@ export class TelegramRuntime {
805
799
  this.#replyTimeoutMs = replyTimeoutMs;
806
800
  this.#createApi = createApi;
807
801
  this.#createHttpTransport = createHttpTransport;
802
+ this.#commandCatalog = commandCatalog;
808
803
  }
809
804
 
810
805
  get status() {
@@ -883,7 +878,14 @@ export class TelegramRuntime {
883
878
  throw error;
884
879
  }
885
880
  try {
886
- await api.setMyCommands({ commands: telegramCommandMenu(), signal: controller.signal });
881
+ const commands = telegramCommandMenu(this.#commandCatalog);
882
+ // Both operations use the existing default scope and language. Sending
883
+ // the full list replaces old entries; an empty catalog clears that list.
884
+ if (commands.length > 0) {
885
+ await api.setMyCommands({ commands, signal: controller.signal });
886
+ } else {
887
+ await api.deleteMyCommands({ signal: controller.signal });
888
+ }
887
889
  await api.setChatMenuButton({ menuButton: COMMANDS_MENU_BUTTON, signal: controller.signal });
888
890
  } catch (error) {
889
891
  this.#logger.warn?.(
@@ -0,0 +1,31 @@
1
+ import { channelDeliveryFailure } from '../shared/message-failure.mjs';
2
+
3
+ // Normalize only errors from SDK send calls, never Harness or menu-building errors.
4
+ export function wecomSendError(error, operation) {
5
+ if (error?.wecomOperation || error?.name === 'AbortError'
6
+ || error instanceof TypeError || error instanceof ReferenceError || error instanceof SyntaxError) return error;
7
+ const value = error?.errcode ?? error?.body?.errcode ?? error?.providerCode;
8
+ const providerCode = typeof value === 'number'
9
+ || (typeof value === 'string' && /^-?\d+$/u.test(value)) ? Number(value) : NaN;
10
+ const rejected = Number.isSafeInteger(providerCode) && providerCode !== 0;
11
+ const disconnected = error?.message === 'WebSocket not connected, unable to send data';
12
+ const wrapped = channelDeliveryFailure(error, { uncertain: !rejected && !disconnected });
13
+ // Keep the same provider mappings as wecomArtifactError.
14
+ if (providerCode === 48002) wrapped.code = 'channel-permission';
15
+ if (providerCode === 45009) wrapped.code = 'channel-rate-limit';
16
+ if (rejected) wrapped.providerCode = providerCode;
17
+ wrapped.wecomOperation = operation;
18
+ wrapped.message = `Enterprise WeChat ${operation} failed (${wrapped.code}, provider=${wrapped.providerCode ?? '-'})`;
19
+ return wrapped;
20
+ }
21
+
22
+ // The SDK rejection frame can contain private payloads. Log only diagnostic fields.
23
+ export function wecomSendDiagnostic(error) {
24
+ if (!error?.wecomOperation) return undefined;
25
+ return {
26
+ operation: error.wecomOperation,
27
+ code: error.code,
28
+ providerCode: error.providerCode,
29
+ status: error.status,
30
+ };
31
+ }
@@ -1,6 +1,7 @@
1
1
  import { createDeferredDeliveryCoordinator, deferredOutcomeText } from '../shared/deferred-delivery-coordinator.mjs';
2
2
  import { generateReqId } from '@wecom/aibot-node-sdk';
3
3
  import { randomUUID } from 'node:crypto';
4
+ import { wecomSendDiagnostic, wecomSendError } from './send-error.mjs';
4
5
  import {
5
6
  parseWecomMenu, wecomList, wecomMenu, wecomMenuText, wecomSettings, wecomTemplateCard,
6
7
  } from './wecom-cards.mjs';
@@ -54,7 +55,6 @@ import {
54
55
  createDeliveryReceipt,
55
56
  } from '../shared/semantic/delivery.mjs';
56
57
  import {
57
- channelDeliveryFailure,
58
58
  clearLastMessageFailure,
59
59
  messageFailureText,
60
60
  setLastMessageFailure,
@@ -616,6 +616,7 @@ export class WecomHarnessBridge {
616
616
  }
617
617
 
618
618
  async #showMain(frame, { welcome = false } = {}) {
619
+ const previousFailure = this.#status.lastMessageError;
619
620
  const key = conversationKey(frame);
620
621
  const workspace = this.#harness.currentWorkspace?.();
621
622
  const sessionId = this.#state.sessionFor(key);
@@ -650,10 +651,21 @@ export class WecomHarnessBridge {
650
651
  presetLabel: settings?.agentPresetCatalog?.items.find((item) => item.id === currentPreset)?.label,
651
652
  });
652
653
  await this.#sendMenu(frame, settingsMenu, { active: welcome });
653
- if (welcome) return;
654
- await this.#sendMenu(frame, wecomMenu({ workspace,
655
- workspaces: (paths?.paths ?? (workspace ? [workspace] : [])).map((path) => [path, `/workspace ${path}`]),
656
- }), { active: true });
654
+ if (!welcome) {
655
+ await this.#sendMenu(frame, wecomMenu({ workspace,
656
+ workspaces: (paths?.paths ?? (workspace ? [workspace] : [])).map((path) => [path, `/workspace ${path}`]),
657
+ }), { active: true });
658
+ }
659
+ this.#clearMenuFailure(previousFailure);
660
+ }
661
+
662
+ #clearMenuFailure(previousFailure) {
663
+ // Do not clear a model failure or one recorded by a concurrent request.
664
+ if (previousFailure?.reason === 'WECOM_MENU_DELIVERY'
665
+ && this.#status.lastMessageError === previousFailure) {
666
+ clearLastMessageFailure(this.#status);
667
+ this.#status.lastError = null;
668
+ }
657
669
  }
658
670
 
659
671
  #rememberMenu(frame, menu) {
@@ -675,6 +687,8 @@ export class WecomHarnessBridge {
675
687
  const body = bodyOf(frame);
676
688
  const chatId = body.chattype === 'group' ? body.chatid : body.from.userid;
677
689
  const card = this.#rememberMenu(frame, menu);
690
+ const operation = welcome ? 'replyWelcome'
691
+ : active || this.#cardFrames.has(frame) ? 'sendMessage' : 'replyTemplateCard';
678
692
  try {
679
693
  if (welcome) {
680
694
  await this.#client.replyWelcome(frame, { msgtype: 'template_card', template_card: card });
@@ -683,11 +697,20 @@ export class WecomHarnessBridge {
683
697
  } else {
684
698
  await this.#client.replyTemplateCard(frame, card);
685
699
  }
686
- } catch (error) {
687
- this.#menus.delete(card.task_id);
688
- this.#logger.warn?.('[dsh-im:wecom] menu delivery failed; using text:', error);
700
+ } catch (cause) {
701
+ const error = wecomSendError(cause, operation);
702
+ // A timed-out card may already be visible and must remain usable.
703
+ if (error.code !== 'channel-delivery-uncertain') this.#menus.delete(card.task_id);
704
+ // Only a definite card rejection can safely fall back to text.
705
+ if (error.code !== 'channel-delivery-failed' || error.providerCode === undefined) throw error;
706
+ this.#logger.warn?.('[dsh-im:wecom] menu delivery failed; using text:', wecomSendDiagnostic(error));
689
707
  if (welcome) {
690
- await this.#client.replyWelcome(frame, { msgtype: 'text', text: { content: wecomMenuText(menu) } });
708
+ const content = wecomMenuText(menu);
709
+ try {
710
+ await this.#client.replyWelcome(frame, { msgtype: 'text', text: { content } });
711
+ } catch (cause) {
712
+ throw wecomSendError(cause, 'replyWelcome');
713
+ }
691
714
  } else {
692
715
  await this.#sendImmediate(frame, chatId, wecomMenuText(menu));
693
716
  }
@@ -705,7 +728,10 @@ export class WecomHarnessBridge {
705
728
  task = this.#processEvent(frame).catch((error) => {
706
729
  if (this.#signal?.aborted) return;
707
730
  this.#status.lastError = error?.message ?? String(error);
708
- this.#logger.warn?.('[dsh-im:wecom] menu event failed:', error);
731
+ const failure = setLastMessageFailure(this.#status, error, {
732
+ reason: error?.wecomOperation ? 'wecom-menu-delivery' : undefined,
733
+ });
734
+ this.#logger.warn?.(`[dsh-im:wecom] menu event failed [${failure.referenceId}]`, wecomSendDiagnostic(error));
709
735
  }).finally(() => {
710
736
  this.#eventIds.delete(id);
711
737
  this.#commandTasks.delete(task);
@@ -802,6 +828,7 @@ export class WecomHarnessBridge {
802
828
  const menu = parseWecomMenu(text);
803
829
  const options = { signal: this.#signal };
804
830
  if (menu) {
831
+ const previousFailure = this.#status.lastMessageError;
805
832
  let content;
806
833
  if (menu.section === 'main') {
807
834
  await this.#showMain(frame);
@@ -846,6 +873,7 @@ export class WecomHarnessBridge {
846
873
  content = wecomList({ title, entries, description, ...menu });
847
874
  }
848
875
  await this.#sendMenu(frame, content);
876
+ this.#clearMenuFailure(previousFailure);
849
877
  return { messages: [] };
850
878
  }
851
879
  const command = text.toLowerCase();
@@ -960,10 +988,15 @@ export class WecomHarnessBridge {
960
988
  ).catch((error) => {
961
989
  if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
962
990
  this.#status.lastError = error?.message ?? String(error);
963
- const failure = setLastMessageFailure(this.#status, error);
991
+ const failure = setLastMessageFailure(this.#status, error, {
992
+ reason: parseWecomMenu(commandText) && error?.wecomOperation ? 'wecom-menu-delivery' : undefined,
993
+ });
964
994
  this.#logger.error?.(
965
995
  `[dsh-im:wecom] failed to process a command [${failure.referenceId}]`,
996
+ wecomSendDiagnostic(error),
966
997
  );
998
+ if (this.#cardFrames.has(frame) && error?.wecomOperation
999
+ && error.code !== 'channel-delivery-failed') return;
967
1000
  return this.#sendImmediate(frame, chatId, messageFailureText(failure))
968
1001
  .catch(() => undefined);
969
1002
  }).finally(() => {
@@ -1149,6 +1182,7 @@ export class WecomHarnessBridge {
1149
1182
  async #processFastCommand(frame, messageId, chatId, key, message, runner) {
1150
1183
  this.#signal?.throwIfAborted();
1151
1184
  if (this.#state.hasSeen(messageId)) return;
1185
+ const previousFailure = this.#status.lastMessageError;
1152
1186
  await this.#state.markSeen(messageId);
1153
1187
  this.#status.messagesReceived += 1;
1154
1188
  this.#status.lastMessageAt = new Date().toISOString();
@@ -1171,17 +1205,24 @@ export class WecomHarnessBridge {
1171
1205
  for (const reply of result?.messages ?? [result?.message]) {
1172
1206
  if (reply) await this.#sendImmediate(frame, chatId, reply);
1173
1207
  }
1174
- this.#status.lastError = null;
1208
+ if (!this.#status.lastMessageError || this.#status.lastMessageError === previousFailure) {
1209
+ this.#status.lastError = null;
1210
+ }
1175
1211
  }
1176
1212
 
1177
1213
  async #sendActive(chatId, text) {
1178
1214
  const providerMessageIds = [];
1179
1215
  for (const chunk of splitUtf8(text)) {
1180
1216
  this.#signal?.throwIfAborted();
1181
- const result = await this.#client.sendMessage(
1182
- chatId,
1183
- { msgtype: 'markdown', markdown: { content: chunk } },
1184
- );
1217
+ let result;
1218
+ try {
1219
+ result = await this.#client.sendMessage(
1220
+ chatId,
1221
+ { msgtype: 'markdown', markdown: { content: chunk } },
1222
+ );
1223
+ } catch (error) {
1224
+ throw wecomSendError(error, 'sendMessage');
1225
+ }
1185
1226
  const messageId = providerMessageId(result);
1186
1227
  if (messageId) providerMessageIds.push(messageId);
1187
1228
  }
@@ -1434,7 +1475,7 @@ export class WecomHarnessBridge {
1434
1475
  });
1435
1476
  }
1436
1477
  } catch (error) {
1437
- textSendError = channelDeliveryFailure(error);
1478
+ textSendError = wecomSendError(error, 'sendMessage');
1438
1479
  this.#logger.warn?.(
1439
1480
  '[dsh-im:wecom] final text delivery failed; continuing with result files:',
1440
1481
  error,
@@ -1491,6 +1532,7 @@ export class WecomHarnessBridge {
1491
1532
  });
1492
1533
  this.#logger.error?.(
1493
1534
  `[dsh-im:wecom] failed to process an inbound message [${failure.referenceId}]`,
1535
+ wecomSendDiagnostic(error),
1494
1536
  );
1495
1537
  const errorText = messageFailureText(failure);
1496
1538
  const visibleError = batchFailureMessage