@xmanrui/dsh-im 2.3.0 → 2.5.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 +9 -4
- package/README.md +9 -4
- package/lib/client.js +411 -156
- package/lib/index.js +218 -210
- package/package.json +1 -1
- package/plugin-src/client/channel-card-meta.js +36 -1
- package/plugin-src/client/channels/dingtalk/api.js +2 -0
- package/plugin-src/client/channels/dingtalk/index.js +9 -1
- package/plugin-src/client/channels/feishu/api.js +3 -0
- package/plugin-src/client/channels/feishu/index.js +46 -28
- package/plugin-src/client/channels/feishu/styles.js +26 -1
- package/plugin-src/client/channels/qq/api.js +2 -0
- package/plugin-src/client/channels/qq/index.js +9 -1
- package/plugin-src/client/channels/shared/token-api.js +2 -0
- package/plugin-src/client/channels/shared/token-channel.js +9 -1
- package/plugin-src/client/channels/wecom/api.js +2 -0
- package/plugin-src/client/channels/wecom/index.js +9 -1
- package/plugin-src/client/channels/weixin/api.js +2 -10
- package/plugin-src/client/channels/weixin/index.js +9 -5
- package/plugin-src/client/channels/whatsapp/api.js +2 -0
- package/plugin-src/client/channels/whatsapp/index.js +9 -1
- package/plugin-src/client/i18n.js +31 -25
- package/plugin-src/client/index.js +16 -2
- package/plugin-src/client/last-message-error.js +17 -0
- package/plugin-src/client/styles.js +5 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +3 -0
- package/src/channels/dingtalk/dingtalk-api.mjs +1 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +50 -16
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +2 -2
- package/src/channels/dingtalk/dingtalk-controller.mjs +2 -0
- package/src/channels/feishu/bridge.mjs +148 -70
- package/src/channels/feishu/feishu-cards.mjs +10 -6
- package/src/channels/feishu/feishu-runtime.mjs +0 -1
- package/src/channels/feishu/message-utils.mjs +1 -1
- package/src/channels/feishu/multi-bot-controller.mjs +3 -17
- package/src/channels/feishu/repair-manager.mjs +7 -2
- package/src/channels/qq/qq-bridge.mjs +84 -28
- package/src/channels/qq/qq-controller.mjs +2 -0
- package/src/channels/shared/control-command.mjs +11 -1
- package/src/channels/shared/harness-client.mjs +40 -4
- package/src/channels/shared/i18n-en/feishu.mjs +27 -25
- package/src/channels/shared/i18n-en/shared-a.mjs +78 -0
- package/src/channels/shared/i18n-en/shared-b.mjs +1 -0
- package/src/channels/shared/i18n-en/telegram.mjs +1 -0
- package/src/channels/shared/message-failure.mjs +244 -0
- package/src/channels/shared/semantic/artifact-delivery.mjs +9 -2
- package/src/channels/shared/text-harness-bridge.mjs +66 -60
- package/src/channels/shared/token-bot-controller.mjs +2 -0
- package/src/channels/slack/slack-controller.mjs +2 -0
- package/src/channels/telegram/telegram-runtime.mjs +1 -0
- package/src/channels/wecom/wecom-bridge.mjs +50 -15
- package/src/channels/wecom/wecom-controller.mjs +2 -0
- package/src/channels/weixin/weixin-api.mjs +47 -0
- package/src/channels/weixin/weixin-bridge.mjs +262 -43
- package/src/channels/weixin/weixin-controller.mjs +2 -15
- package/src/channels/weixin/weixin-runtime.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +2 -0
|
@@ -29,6 +29,7 @@ import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
|
29
29
|
import {
|
|
30
30
|
hasInboundImages,
|
|
31
31
|
ImagePromptError,
|
|
32
|
+
imagePromptDiagnostic,
|
|
32
33
|
imagePromptUserMessage,
|
|
33
34
|
promptContentForMessage,
|
|
34
35
|
} from '../shared/image-prompt.mjs';
|
|
@@ -42,6 +43,12 @@ import { deliverOutboundArtifacts } from '../shared/semantic/artifact-delivery.m
|
|
|
42
43
|
import {
|
|
43
44
|
createDeliveryReceipt,
|
|
44
45
|
} from '../shared/semantic/delivery.mjs';
|
|
46
|
+
import {
|
|
47
|
+
channelDeliveryFailure,
|
|
48
|
+
clearLastMessageFailure,
|
|
49
|
+
messageFailureText,
|
|
50
|
+
setLastMessageFailure,
|
|
51
|
+
} from '../shared/message-failure.mjs';
|
|
45
52
|
import { t } from '../shared/i18n.mjs';
|
|
46
53
|
|
|
47
54
|
const DEFAULT_FILE_UPLOAD_TIMEOUT_MS = 120_000;
|
|
@@ -73,6 +80,7 @@ function helpText() {
|
|
|
73
80
|
t('/send 提交当前批次'),
|
|
74
81
|
t('/cancel 取消当前批次'),
|
|
75
82
|
t('/status 检查连接状态'),
|
|
83
|
+
t('/version 查看插件版本'),
|
|
76
84
|
t('/help 显示本帮助'),
|
|
77
85
|
].join('\n');
|
|
78
86
|
}
|
|
@@ -459,6 +467,7 @@ export function createWecomBridgeStatus() {
|
|
|
459
467
|
lastReplyAt: null,
|
|
460
468
|
lastRejectedAt: null,
|
|
461
469
|
lastError: null,
|
|
470
|
+
lastMessageError: null,
|
|
462
471
|
};
|
|
463
472
|
}
|
|
464
473
|
|
|
@@ -588,8 +597,11 @@ export class WecomHarnessBridge {
|
|
|
588
597
|
).catch((error) => {
|
|
589
598
|
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
|
590
599
|
this.#status.lastError = error?.message ?? String(error);
|
|
591
|
-
|
|
592
|
-
|
|
600
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
601
|
+
this.#logger.error?.(
|
|
602
|
+
`[dsh-im:wecom] failed to process a command [${failure.referenceId}]`,
|
|
603
|
+
);
|
|
604
|
+
return this.#sendImmediate(frame, chatId, messageFailureText(failure))
|
|
593
605
|
.catch(() => undefined);
|
|
594
606
|
}).finally(() => {
|
|
595
607
|
this.#acceptedMessageIds.delete(messageId);
|
|
@@ -712,8 +724,11 @@ export class WecomHarnessBridge {
|
|
|
712
724
|
}).catch(async (error) => {
|
|
713
725
|
if (this.#signal?.aborted) return;
|
|
714
726
|
this.#status.lastError = error?.message ?? String(error);
|
|
715
|
-
|
|
716
|
-
|
|
727
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
728
|
+
this.#logger.error?.(
|
|
729
|
+
`[dsh-im:wecom] failed to process a batch input message [${failure.referenceId}]`,
|
|
730
|
+
);
|
|
731
|
+
await this.#sendImmediate(frame, chatId, messageFailureText(failure))
|
|
717
732
|
.catch(() => undefined);
|
|
718
733
|
}).finally(() => {
|
|
719
734
|
this.#acceptedMessageIds.delete(messageId);
|
|
@@ -790,7 +805,7 @@ export class WecomHarnessBridge {
|
|
|
790
805
|
|
|
791
806
|
async #deliverArtifacts(chatId, replyTo, artifacts = [], baseReceipt = null) {
|
|
792
807
|
if (artifacts.length === 0) {
|
|
793
|
-
return { receipt: baseReceipt, failureNoticeVisible: false };
|
|
808
|
+
return { receipt: baseReceipt, failureNoticeVisible: false, artifactSendErrors: 0 };
|
|
794
809
|
}
|
|
795
810
|
const delivery = await deliverOutboundArtifacts({
|
|
796
811
|
artifacts,
|
|
@@ -808,9 +823,13 @@ export class WecomHarnessBridge {
|
|
|
808
823
|
signal: this.#signal,
|
|
809
824
|
timeoutMs: this.#fileUploadTimeoutMs,
|
|
810
825
|
}),
|
|
811
|
-
|
|
826
|
+
onFailure: (artifact, error) => setLastMessageFailure(this.#status, error, {
|
|
827
|
+
userMessage: artifactFailureText(artifact?.fileName, error),
|
|
828
|
+
reason: error?.code,
|
|
829
|
+
}),
|
|
830
|
+
sendFailureNotice: (_artifact, _error, failure) => this.#sendActive(
|
|
812
831
|
chatId,
|
|
813
|
-
|
|
832
|
+
messageFailureText(failure),
|
|
814
833
|
),
|
|
815
834
|
logger: this.#logger,
|
|
816
835
|
});
|
|
@@ -821,6 +840,7 @@ export class WecomHarnessBridge {
|
|
|
821
840
|
return {
|
|
822
841
|
receipt: delivery.receipt,
|
|
823
842
|
failureNoticeVisible: delivery.failureNoticeVisible,
|
|
843
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
824
844
|
};
|
|
825
845
|
}
|
|
826
846
|
|
|
@@ -848,6 +868,7 @@ export class WecomHarnessBridge {
|
|
|
848
868
|
let streamId = null;
|
|
849
869
|
let streamStarted = false;
|
|
850
870
|
let batchSettled = batchSubmission === null;
|
|
871
|
+
let promptRecorded = false;
|
|
851
872
|
try {
|
|
852
873
|
if (!text && !hasImages && !hasFiles) {
|
|
853
874
|
await this.#sendImmediate(frame, chatId, t('目前支持文字、图片、文件和语音转写消息。'));
|
|
@@ -908,6 +929,8 @@ export class WecomHarnessBridge {
|
|
|
908
929
|
const content = hasImages
|
|
909
930
|
? await promptContentForMessage(message, { signal: this.#signal })
|
|
910
931
|
: undefined;
|
|
932
|
+
await this.#state.markSeen(messageId);
|
|
933
|
+
promptRecorded = true;
|
|
911
934
|
const { answer, artifacts = [] } = await askInWorkspaceSession({
|
|
912
935
|
harness: this.#harness,
|
|
913
936
|
state: this.#state,
|
|
@@ -981,7 +1004,7 @@ export class WecomHarnessBridge {
|
|
|
981
1004
|
});
|
|
982
1005
|
}
|
|
983
1006
|
} catch (error) {
|
|
984
|
-
textSendError = error;
|
|
1007
|
+
textSendError = channelDeliveryFailure(error);
|
|
985
1008
|
this.#logger.warn?.(
|
|
986
1009
|
'[dsh-im:wecom] final text delivery failed; continuing with result files:',
|
|
987
1010
|
error,
|
|
@@ -994,10 +1017,16 @@ export class WecomHarnessBridge {
|
|
|
994
1017
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
995
1018
|
throw textSendError;
|
|
996
1019
|
}
|
|
997
|
-
|
|
1020
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
1021
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
1022
|
+
}
|
|
1023
|
+
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
998
1024
|
this.#status.messagesReplied += 1;
|
|
999
1025
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
1000
1026
|
this.#status.lastError = null;
|
|
1027
|
+
if (!textSendError && delivery.artifactSendErrors === 0) {
|
|
1028
|
+
clearLastMessageFailure(this.#status);
|
|
1029
|
+
}
|
|
1001
1030
|
return delivery.receipt;
|
|
1002
1031
|
} catch (error) {
|
|
1003
1032
|
let batchFailureMessage = null;
|
|
@@ -1014,15 +1043,21 @@ export class WecomHarnessBridge {
|
|
|
1014
1043
|
await this.#client.replyStream(frame, streamId, t('已停止。'), true)
|
|
1015
1044
|
.catch(() => undefined);
|
|
1016
1045
|
}
|
|
1017
|
-
await this.#state.markSeen(messageId);
|
|
1046
|
+
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
1018
1047
|
return;
|
|
1019
1048
|
}
|
|
1020
1049
|
if (this.#signal?.aborted) return;
|
|
1021
1050
|
this.#status.lastError = error?.message ?? String(error);
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1051
|
+
const userMessage = inboundFileUserMessage(error)
|
|
1052
|
+
?? imagePromptUserMessage(error);
|
|
1053
|
+
const failure = setLastMessageFailure(this.#status, error, {
|
|
1054
|
+
userMessage,
|
|
1055
|
+
reason: imagePromptDiagnostic(error)?.reason,
|
|
1056
|
+
});
|
|
1057
|
+
this.#logger.error?.(
|
|
1058
|
+
`[dsh-im:wecom] failed to process an inbound message [${failure.referenceId}]`,
|
|
1059
|
+
);
|
|
1060
|
+
const errorText = messageFailureText(failure);
|
|
1026
1061
|
const visibleError = batchFailureMessage
|
|
1027
1062
|
? `${errorText}\n\n${batchFailureMessage}`
|
|
1028
1063
|
: errorText;
|
|
@@ -1032,7 +1067,7 @@ export class WecomHarnessBridge {
|
|
|
1032
1067
|
} else {
|
|
1033
1068
|
await this.#sendImmediate(frame, chatId, visibleError);
|
|
1034
1069
|
}
|
|
1035
|
-
await this.#state.markSeen(messageId);
|
|
1070
|
+
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
1036
1071
|
} catch {
|
|
1037
1072
|
this.#logger.error?.('[dsh-im:wecom] failed to send the safe error reply');
|
|
1038
1073
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
connectionTestTargetUnavailable,
|
|
7
7
|
} from '../shared/connection-test.mjs';
|
|
8
8
|
import { t } from '../shared/i18n.mjs';
|
|
9
|
+
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
9
10
|
|
|
10
11
|
const ACTIVE_ATTEMPT_STATES = new Set(['pending', 'connecting']);
|
|
11
12
|
const TERMINAL_ATTEMPT_STATES = new Set(['connected', 'failed', 'cancelled', 'expired']);
|
|
@@ -308,6 +309,7 @@ export class WecomController {
|
|
|
308
309
|
messagesReceived: runtimeStatus?.messagesReceived ?? 0,
|
|
309
310
|
messagesReplied: runtimeStatus?.messagesReplied ?? 0,
|
|
310
311
|
},
|
|
312
|
+
lastMessageError: publicMessageFailure(runtimeStatus?.lastMessageError),
|
|
311
313
|
error: structuredClone(this.#errors.get(config.botId) ?? null),
|
|
312
314
|
};
|
|
313
315
|
});
|
|
@@ -637,6 +637,53 @@ export function createWeixinApi({ fetchImpl = fetch } = {}) {
|
|
|
637
637
|
}
|
|
638
638
|
},
|
|
639
639
|
|
|
640
|
+
async getConfig({ baseUrl, token, toUserId, contextToken, signal }) {
|
|
641
|
+
const recipient = nonEmptyString(toUserId);
|
|
642
|
+
if (!recipient) throw new TypeError('toUserId is required');
|
|
643
|
+
const response = await requestJson(fetchImpl, {
|
|
644
|
+
method: 'POST',
|
|
645
|
+
baseUrl,
|
|
646
|
+
endpoint: 'ilink/bot/getconfig',
|
|
647
|
+
token,
|
|
648
|
+
signal,
|
|
649
|
+
timeoutMs: 10_000,
|
|
650
|
+
body: {
|
|
651
|
+
ilink_user_id: recipient,
|
|
652
|
+
...(nonEmptyString(contextToken) ? { context_token: contextToken.trim() } : {}),
|
|
653
|
+
base_info: baseInfo(),
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
if (response?.ret !== undefined && response.ret !== 0) {
|
|
657
|
+
throw new WeixinApiError('config-rejected', '微信服务拒绝了机器人配置请求。');
|
|
658
|
+
}
|
|
659
|
+
return { typingTicket: nonEmptyString(response?.typing_ticket) };
|
|
660
|
+
},
|
|
661
|
+
|
|
662
|
+
async sendTyping({ baseUrl, token, toUserId, typingTicket, status, signal }) {
|
|
663
|
+
const recipient = nonEmptyString(toUserId);
|
|
664
|
+
const ticket = nonEmptyString(typingTicket);
|
|
665
|
+
if (!recipient || !ticket) throw new TypeError('toUserId and typingTicket are required');
|
|
666
|
+
if (status !== 1 && status !== 2) throw new TypeError('typing status must be 1 or 2');
|
|
667
|
+
const response = await requestJson(fetchImpl, {
|
|
668
|
+
method: 'POST',
|
|
669
|
+
baseUrl,
|
|
670
|
+
endpoint: 'ilink/bot/sendtyping',
|
|
671
|
+
token,
|
|
672
|
+
signal,
|
|
673
|
+
timeoutMs: 10_000,
|
|
674
|
+
body: {
|
|
675
|
+
ilink_user_id: recipient,
|
|
676
|
+
typing_ticket: ticket,
|
|
677
|
+
status,
|
|
678
|
+
base_info: baseInfo(),
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
if (response?.ret !== undefined && response.ret !== 0) {
|
|
682
|
+
throw new WeixinApiError('typing-rejected', '微信服务拒绝了输入状态请求。');
|
|
683
|
+
}
|
|
684
|
+
return true;
|
|
685
|
+
},
|
|
686
|
+
|
|
640
687
|
async sendText({ baseUrl, token, toUserId, text, contextToken, runId, signal }) {
|
|
641
688
|
const recipient = nonEmptyString(toUserId);
|
|
642
689
|
const content = nonEmptyString(text);
|