@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.
- package/README.en.md +10 -2
- package/README.md +17 -6
- package/lib/client.js +1980 -616
- package/lib/index.js +303 -293
- package/package.json +6 -2
- package/plugin-src/client/access-policy-settings.js +6 -0
- package/plugin-src/client/channels/feishu/index.js +16 -4
- package/plugin-src/client/channels/wecom-app/api.js +124 -0
- package/plugin-src/client/channels/wecom-app/index.js +612 -0
- package/plugin-src/client/channels/wecom-app/styles.js +25 -0
- package/plugin-src/client/delivery-settings.js +7 -0
- package/plugin-src/client/i18n.js +64 -1
- package/plugin-src/client/index.js +21 -0
- package/plugin-src/client/model-setting.js +135 -69
- package/plugin-src/client/session-channel-logos.js +241 -0
- package/plugin-src/client/styles.js +22 -7
- package/plugin-src/host/channels/dingtalk/index.mjs +8 -15
- package/plugin-src/host/channels/discord/index.mjs +8 -10
- package/plugin-src/host/channels/feishu/index.mjs +8 -15
- package/plugin-src/host/channels/office/index.mjs +9 -5
- package/plugin-src/host/channels/qq/index.mjs +8 -15
- package/plugin-src/host/channels/shared/access-policy-production.mjs +1 -1
- package/plugin-src/host/channels/shared/model-setting-rpc.mjs +1 -2
- package/plugin-src/host/channels/shared/startup-error.mjs +53 -0
- package/plugin-src/host/channels/shared/startup.mjs +47 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +1 -0
- package/plugin-src/host/channels/slack/index.mjs +8 -10
- package/plugin-src/host/channels/telegram/index.mjs +8 -10
- package/plugin-src/host/channels/wecom/index.mjs +8 -15
- package/plugin-src/host/channels/wecom/rpc.mjs +6 -1
- package/plugin-src/host/channels/wecom-app/index.mjs +33 -0
- package/plugin-src/host/channels/wecom-app/production.mjs +217 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +225 -0
- package/plugin-src/host/channels/weixin/index.mjs +8 -15
- package/plugin-src/host/channels/whatsapp/index.mjs +8 -15
- package/plugin-src/host/delivery-adapter.mjs +4 -0
- package/plugin-src/host/delivery-suggestions.mjs +4 -0
- package/plugin-src/host/index.mjs +21 -2
- package/plugin-src/host/session-title-prefix.mjs +122 -0
- package/scripts/verify-model-setting.mjs +54 -0
- package/scripts/verify-session-channel-logos.mjs +59 -0
- package/scripts/verify-session-title-prefix.mjs +149 -0
- package/src/channels/feishu/bridge.mjs +305 -12
- package/src/channels/feishu/feishu-cards.mjs +38 -0
- package/src/channels/feishu/feishu-channel.mjs +88 -20
- package/src/channels/feishu/plugin-controller.mjs +1 -0
- package/src/channels/feishu/repair-manager.mjs +3 -2
- package/src/channels/shared/bot-workspace-store.mjs +11 -3
- package/src/channels/shared/command-catalog.mjs +102 -0
- package/src/channels/shared/i18n-en/feishu.mjs +14 -2
- package/src/channels/shared/i18n-en/shared-a.mjs +3 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +9 -0
- package/src/channels/shared/i18n-en/wecom-app.mjs +33 -0
- package/src/channels/shared/i18n-en.mjs +2 -0
- package/src/channels/shared/model-setting.mjs +43 -8
- package/src/channels/shared/session-channel-labels.mjs +26 -0
- package/src/channels/shared/text-harness-bridge.mjs +2 -26
- package/src/channels/telegram/telegram-api.mjs +18 -6
- package/src/channels/telegram/telegram-runtime.mjs +34 -32
- package/src/channels/wecom/send-error.mjs +31 -0
- package/src/channels/wecom/wecom-bridge.mjs +59 -17
- package/src/channels/wecom-app/callback-server.mjs +471 -0
- package/src/channels/wecom-app/config-store.mjs +240 -0
- package/src/channels/wecom-app/harness-client.mjs +11 -0
- package/src/channels/wecom-app/state-store.mjs +107 -0
- package/src/channels/wecom-app/wecom-app-api.mjs +432 -0
- package/src/channels/wecom-app/wecom-app-bridge.mjs +1059 -0
- package/src/channels/wecom-app/wecom-app-controller.mjs +440 -0
- 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
|
-
|
|
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 {
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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)
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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 (
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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 =
|
|
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
|