@xmanrui/dsh-im 2.4.0 → 2.6.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 +2 -2
- package/README.md +2 -2
- package/lib/client.js +322 -109
- package/lib/index.js +218 -211
- 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 +9 -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 +4 -0
- 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 +102 -6
- package/src/channels/dingtalk/dingtalk-bridge.mjs +247 -38
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +2 -2
- package/src/channels/dingtalk/dingtalk-controller.mjs +2 -0
- package/src/channels/discord/discord-api.mjs +35 -0
- package/src/channels/discord/discord-bridge.mjs +1 -0
- package/src/channels/discord/discord-runtime.mjs +26 -0
- package/src/channels/feishu/bridge.mjs +115 -69
- package/src/channels/feishu/multi-bot-controller.mjs +2 -0
- package/src/channels/qq/qq-bridge.mjs +83 -28
- package/src/channels/qq/qq-controller.mjs +2 -0
- package/src/channels/shared/harness-client.mjs +40 -4
- package/src/channels/shared/i18n-en/shared-a.mjs +77 -0
- package/src/channels/shared/message-failure.mjs +244 -0
- package/src/channels/shared/semantic/artifact-delivery.mjs +9 -2
- package/src/channels/shared/status-reaction.mjs +107 -0
- package/src/channels/shared/text-harness-bridge.mjs +123 -66
- package/src/channels/shared/token-bot-controller.mjs +2 -0
- package/src/channels/slack/manifest.mjs +1 -0
- package/src/channels/slack/slack-api.mjs +28 -0
- package/src/channels/slack/slack-bridge.mjs +5 -0
- package/src/channels/slack/slack-controller.mjs +2 -0
- package/src/channels/slack/slack-runtime.mjs +24 -0
- package/src/channels/telegram/telegram-api.mjs +9 -0
- package/src/channels/telegram/telegram-bridge.mjs +1 -0
- package/src/channels/telegram/telegram-runtime.mjs +20 -0
- package/src/channels/wecom/wecom-bridge.mjs +49 -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 +261 -43
- package/src/channels/weixin/weixin-controller.mjs +2 -15
- package/src/channels/weixin/weixin-runtime.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-bridge.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +2 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +36 -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;
|
|
@@ -460,6 +467,7 @@ export function createWecomBridgeStatus() {
|
|
|
460
467
|
lastReplyAt: null,
|
|
461
468
|
lastRejectedAt: null,
|
|
462
469
|
lastError: null,
|
|
470
|
+
lastMessageError: null,
|
|
463
471
|
};
|
|
464
472
|
}
|
|
465
473
|
|
|
@@ -589,8 +597,11 @@ export class WecomHarnessBridge {
|
|
|
589
597
|
).catch((error) => {
|
|
590
598
|
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
|
591
599
|
this.#status.lastError = error?.message ?? String(error);
|
|
592
|
-
|
|
593
|
-
|
|
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))
|
|
594
605
|
.catch(() => undefined);
|
|
595
606
|
}).finally(() => {
|
|
596
607
|
this.#acceptedMessageIds.delete(messageId);
|
|
@@ -713,8 +724,11 @@ export class WecomHarnessBridge {
|
|
|
713
724
|
}).catch(async (error) => {
|
|
714
725
|
if (this.#signal?.aborted) return;
|
|
715
726
|
this.#status.lastError = error?.message ?? String(error);
|
|
716
|
-
|
|
717
|
-
|
|
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))
|
|
718
732
|
.catch(() => undefined);
|
|
719
733
|
}).finally(() => {
|
|
720
734
|
this.#acceptedMessageIds.delete(messageId);
|
|
@@ -791,7 +805,7 @@ export class WecomHarnessBridge {
|
|
|
791
805
|
|
|
792
806
|
async #deliverArtifacts(chatId, replyTo, artifacts = [], baseReceipt = null) {
|
|
793
807
|
if (artifacts.length === 0) {
|
|
794
|
-
return { receipt: baseReceipt, failureNoticeVisible: false };
|
|
808
|
+
return { receipt: baseReceipt, failureNoticeVisible: false, artifactSendErrors: 0 };
|
|
795
809
|
}
|
|
796
810
|
const delivery = await deliverOutboundArtifacts({
|
|
797
811
|
artifacts,
|
|
@@ -809,9 +823,13 @@ export class WecomHarnessBridge {
|
|
|
809
823
|
signal: this.#signal,
|
|
810
824
|
timeoutMs: this.#fileUploadTimeoutMs,
|
|
811
825
|
}),
|
|
812
|
-
|
|
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(
|
|
813
831
|
chatId,
|
|
814
|
-
|
|
832
|
+
messageFailureText(failure),
|
|
815
833
|
),
|
|
816
834
|
logger: this.#logger,
|
|
817
835
|
});
|
|
@@ -822,6 +840,7 @@ export class WecomHarnessBridge {
|
|
|
822
840
|
return {
|
|
823
841
|
receipt: delivery.receipt,
|
|
824
842
|
failureNoticeVisible: delivery.failureNoticeVisible,
|
|
843
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
825
844
|
};
|
|
826
845
|
}
|
|
827
846
|
|
|
@@ -849,6 +868,7 @@ export class WecomHarnessBridge {
|
|
|
849
868
|
let streamId = null;
|
|
850
869
|
let streamStarted = false;
|
|
851
870
|
let batchSettled = batchSubmission === null;
|
|
871
|
+
let promptRecorded = false;
|
|
852
872
|
try {
|
|
853
873
|
if (!text && !hasImages && !hasFiles) {
|
|
854
874
|
await this.#sendImmediate(frame, chatId, t('目前支持文字、图片、文件和语音转写消息。'));
|
|
@@ -909,6 +929,8 @@ export class WecomHarnessBridge {
|
|
|
909
929
|
const content = hasImages
|
|
910
930
|
? await promptContentForMessage(message, { signal: this.#signal })
|
|
911
931
|
: undefined;
|
|
932
|
+
await this.#state.markSeen(messageId);
|
|
933
|
+
promptRecorded = true;
|
|
912
934
|
const { answer, artifacts = [] } = await askInWorkspaceSession({
|
|
913
935
|
harness: this.#harness,
|
|
914
936
|
state: this.#state,
|
|
@@ -982,7 +1004,7 @@ export class WecomHarnessBridge {
|
|
|
982
1004
|
});
|
|
983
1005
|
}
|
|
984
1006
|
} catch (error) {
|
|
985
|
-
textSendError = error;
|
|
1007
|
+
textSendError = channelDeliveryFailure(error);
|
|
986
1008
|
this.#logger.warn?.(
|
|
987
1009
|
'[dsh-im:wecom] final text delivery failed; continuing with result files:',
|
|
988
1010
|
error,
|
|
@@ -995,10 +1017,16 @@ export class WecomHarnessBridge {
|
|
|
995
1017
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
996
1018
|
throw textSendError;
|
|
997
1019
|
}
|
|
998
|
-
|
|
1020
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
1021
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
1022
|
+
}
|
|
1023
|
+
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
999
1024
|
this.#status.messagesReplied += 1;
|
|
1000
1025
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
1001
1026
|
this.#status.lastError = null;
|
|
1027
|
+
if (!textSendError && delivery.artifactSendErrors === 0) {
|
|
1028
|
+
clearLastMessageFailure(this.#status);
|
|
1029
|
+
}
|
|
1002
1030
|
return delivery.receipt;
|
|
1003
1031
|
} catch (error) {
|
|
1004
1032
|
let batchFailureMessage = null;
|
|
@@ -1015,15 +1043,21 @@ export class WecomHarnessBridge {
|
|
|
1015
1043
|
await this.#client.replyStream(frame, streamId, t('已停止。'), true)
|
|
1016
1044
|
.catch(() => undefined);
|
|
1017
1045
|
}
|
|
1018
|
-
await this.#state.markSeen(messageId);
|
|
1046
|
+
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
1019
1047
|
return;
|
|
1020
1048
|
}
|
|
1021
1049
|
if (this.#signal?.aborted) return;
|
|
1022
1050
|
this.#status.lastError = error?.message ?? String(error);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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);
|
|
1027
1061
|
const visibleError = batchFailureMessage
|
|
1028
1062
|
? `${errorText}\n\n${batchFailureMessage}`
|
|
1029
1063
|
: errorText;
|
|
@@ -1033,7 +1067,7 @@ export class WecomHarnessBridge {
|
|
|
1033
1067
|
} else {
|
|
1034
1068
|
await this.#sendImmediate(frame, chatId, visibleError);
|
|
1035
1069
|
}
|
|
1036
|
-
await this.#state.markSeen(messageId);
|
|
1070
|
+
if (!promptRecorded) await this.#state.markSeen(messageId);
|
|
1037
1071
|
} catch {
|
|
1038
1072
|
this.#logger.error?.('[dsh-im:wecom] failed to send the safe error reply');
|
|
1039
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);
|