@xmanrui/dsh-im 2.4.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 +1 -1
- package/README.md +1 -1
- package/lib/client.js +321 -109
- package/lib/index.js +214 -207
- 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 +1 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +49 -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 +99 -43
- 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/text-harness-bridge.mjs +65 -60
- package/src/channels/shared/token-bot-controller.mjs +2 -0
- package/src/channels/slack/slack-controller.mjs +2 -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-controller.mjs +2 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
connectionTestTargetUnavailable,
|
|
12
12
|
} from '../shared/connection-test.mjs';
|
|
13
13
|
import { t } from '../shared/i18n.mjs';
|
|
14
|
+
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
14
15
|
|
|
15
16
|
const ACTIVE_ATTEMPT_STATES = new Set(['starting', 'pending', 'connecting']);
|
|
16
17
|
const TERMINAL_ATTEMPT_STATES = new Set(['connected', 'expired', 'failed', 'cancelled']);
|
|
@@ -516,6 +517,7 @@ export class DingtalkController {
|
|
|
516
517
|
messagesReceived: Number(currentStatus.messagesReceived) || 0,
|
|
517
518
|
messagesReplied: Number(currentStatus.messagesReplied) || 0,
|
|
518
519
|
},
|
|
520
|
+
lastMessageError: publicMessageFailure(currentStatus.lastMessageError),
|
|
519
521
|
senders: {
|
|
520
522
|
pending,
|
|
521
523
|
approved: config.approvedSenders.map(publicApprovedSender),
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from './message-utils.mjs';
|
|
10
10
|
import {
|
|
11
11
|
hasInboundImages,
|
|
12
|
+
imagePromptDiagnostic,
|
|
12
13
|
imagePromptUserMessage,
|
|
13
14
|
promptContentForMessage,
|
|
14
15
|
} from '../shared/image-prompt.mjs';
|
|
@@ -48,6 +49,12 @@ import { deliverOutboundArtifacts } from '../shared/semantic/artifact-delivery.m
|
|
|
48
49
|
import {
|
|
49
50
|
createDeliveryReceipt,
|
|
50
51
|
} from '../shared/semantic/delivery.mjs';
|
|
52
|
+
import {
|
|
53
|
+
channelDeliveryFailure,
|
|
54
|
+
clearLastMessageFailure,
|
|
55
|
+
messageFailureText,
|
|
56
|
+
setLastMessageFailure,
|
|
57
|
+
} from '../shared/message-failure.mjs';
|
|
51
58
|
import {
|
|
52
59
|
MENU_PAGE_SIZE,
|
|
53
60
|
PRESET_FOLLOW_DEFAULT_SENTINEL,
|
|
@@ -365,6 +372,7 @@ function ensureStatus(status) {
|
|
|
365
372
|
status.lastReplyAt ??= null;
|
|
366
373
|
status.lastRejectedAt ??= null;
|
|
367
374
|
status.lastError ??= null;
|
|
375
|
+
status.lastMessageError ??= null;
|
|
368
376
|
}
|
|
369
377
|
|
|
370
378
|
export class FeishuHarnessBridge {
|
|
@@ -801,6 +809,30 @@ export class FeishuHarnessBridge {
|
|
|
801
809
|
return current;
|
|
802
810
|
}
|
|
803
811
|
|
|
812
|
+
#recordFailure(error, {
|
|
813
|
+
logLabel = 'operation',
|
|
814
|
+
logLevel = 'warn',
|
|
815
|
+
userMessage,
|
|
816
|
+
reason,
|
|
817
|
+
} = {}) {
|
|
818
|
+
this.#status.lastError = error?.message ?? String(error);
|
|
819
|
+
const failure = setLastMessageFailure(this.#status, error, { userMessage, reason });
|
|
820
|
+
this.#logger?.[logLevel]?.(
|
|
821
|
+
`[dsh-feishu] ${logLabel} failed [${failure.referenceId}]:`,
|
|
822
|
+
error?.message ?? String(error),
|
|
823
|
+
);
|
|
824
|
+
return failure;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async #sendFailure(chatId, error, options = {}) {
|
|
828
|
+
const failure = this.#recordFailure(error, options);
|
|
829
|
+
const text = options.appendMessage
|
|
830
|
+
? `${messageFailureText(failure)}\n\n${options.appendMessage}`
|
|
831
|
+
: messageFailureText(failure);
|
|
832
|
+
await this.#send(chatId, text).catch(() => undefined);
|
|
833
|
+
return failure;
|
|
834
|
+
}
|
|
835
|
+
|
|
804
836
|
async #handleMessageFailure(event, messageId, processingReaction, error) {
|
|
805
837
|
if (error?.code === 'turn-stopped') {
|
|
806
838
|
await this.#removeProcessingReaction(messageId, processingReaction);
|
|
@@ -813,15 +845,21 @@ export class FeishuHarnessBridge {
|
|
|
813
845
|
await this.#removeProcessingReaction(messageId, processingReaction);
|
|
814
846
|
return;
|
|
815
847
|
}
|
|
816
|
-
|
|
817
|
-
|
|
848
|
+
const userMessage = inboundFileUserMessage(error)
|
|
849
|
+
?? imagePromptUserMessage(error);
|
|
850
|
+
const failure = this.#recordFailure(error, {
|
|
851
|
+
logLabel: 'message handling',
|
|
852
|
+
logLevel: 'error',
|
|
853
|
+
userMessage,
|
|
854
|
+
reason: imagePromptDiagnostic(error)?.reason,
|
|
855
|
+
});
|
|
818
856
|
await this.#finishReaction(messageId, processingReaction, 'ERROR');
|
|
857
|
+
const failureText = error?.batchInputMessage
|
|
858
|
+
? `${messageFailureText(failure)}\n\n${error.batchInputMessage}`
|
|
859
|
+
: messageFailureText(failure);
|
|
819
860
|
await this.#send(
|
|
820
861
|
event.message.chat_id,
|
|
821
|
-
|
|
822
|
-
?? inboundFileUserMessage(error)
|
|
823
|
-
?? imagePromptUserMessage(error)
|
|
824
|
-
?? t('处理失败,请稍后重试。如果问题持续,请在 DeepSeek Harness 的飞书插件页面检查连接状态。'),
|
|
862
|
+
failureText,
|
|
825
863
|
).catch(() => undefined);
|
|
826
864
|
}
|
|
827
865
|
|
|
@@ -1025,7 +1063,11 @@ export class FeishuHarnessBridge {
|
|
|
1025
1063
|
const batchSubmission = event.batchSubmission ?? null;
|
|
1026
1064
|
let batchAskCompleted = false;
|
|
1027
1065
|
try {
|
|
1028
|
-
const
|
|
1066
|
+
const {
|
|
1067
|
+
receipt,
|
|
1068
|
+
artifactSendErrors,
|
|
1069
|
+
textDeliveryErrors = 0,
|
|
1070
|
+
} = await this.#answerWithStream(event, key, message, {
|
|
1029
1071
|
onAskComplete: batchSubmission
|
|
1030
1072
|
? () => {
|
|
1031
1073
|
batchAskCompleted = this.#batchInputs.complete(
|
|
@@ -1038,6 +1080,9 @@ export class FeishuHarnessBridge {
|
|
|
1038
1080
|
this.#status.messagesReplied += 1;
|
|
1039
1081
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
1040
1082
|
this.#status.lastError = null;
|
|
1083
|
+
if (textDeliveryErrors === 0 && artifactSendErrors === 0) {
|
|
1084
|
+
clearLastMessageFailure(this.#status);
|
|
1085
|
+
}
|
|
1041
1086
|
return receipt;
|
|
1042
1087
|
} catch (error) {
|
|
1043
1088
|
if (batchSubmission && !batchAskCompleted) {
|
|
@@ -1049,6 +1094,9 @@ export class FeishuHarnessBridge {
|
|
|
1049
1094
|
if (failed.retained) {
|
|
1050
1095
|
const batchError = new Error(error?.message ?? String(error), { cause: error });
|
|
1051
1096
|
batchError.code = error?.code;
|
|
1097
|
+
batchError.providerCode = error?.providerCode;
|
|
1098
|
+
batchError.method = error?.method;
|
|
1099
|
+
if (Number.isInteger(error?.status)) batchError.status = error.status;
|
|
1052
1100
|
batchError.batchInputMessage = failed.message;
|
|
1053
1101
|
throw batchError;
|
|
1054
1102
|
}
|
|
@@ -1643,9 +1691,7 @@ export class FeishuHarnessBridge {
|
|
|
1643
1691
|
})
|
|
1644
1692
|
.catch(async (error) => {
|
|
1645
1693
|
if (this.#signal?.aborted) return;
|
|
1646
|
-
this.#
|
|
1647
|
-
this.#status.lastError = error?.message ?? String(error);
|
|
1648
|
-
await this.#send(entry.chatId, t('卡片操作失败,请稍后重试。')).catch(() => undefined);
|
|
1694
|
+
await this.#sendFailure(entry.chatId, error, { logLabel: 'card action' });
|
|
1649
1695
|
})
|
|
1650
1696
|
.finally(() => {
|
|
1651
1697
|
if (this.#cardActionInFlight.get(dedupeKey) === tracked) {
|
|
@@ -2008,8 +2054,7 @@ export class FeishuHarnessBridge {
|
|
|
2008
2054
|
},
|
|
2009
2055
|
);
|
|
2010
2056
|
} catch (error) {
|
|
2011
|
-
this.#
|
|
2012
|
-
await this.#send(chatId, t('暂时无法获取会话列表,请稍后重试。'));
|
|
2057
|
+
await this.#sendFailure(chatId, error, { logLabel: 'session list' });
|
|
2013
2058
|
}
|
|
2014
2059
|
}
|
|
2015
2060
|
|
|
@@ -2026,8 +2071,7 @@ export class FeishuHarnessBridge {
|
|
|
2026
2071
|
{ key, updateMessageId },
|
|
2027
2072
|
);
|
|
2028
2073
|
} catch (error) {
|
|
2029
|
-
this.#
|
|
2030
|
-
await this.#send(chatId, t('暂时无法获取工作区列表,请稍后重试。'));
|
|
2074
|
+
await this.#sendFailure(chatId, error, { logLabel: 'workspace list' });
|
|
2031
2075
|
}
|
|
2032
2076
|
}
|
|
2033
2077
|
|
|
@@ -2038,7 +2082,10 @@ export class FeishuHarnessBridge {
|
|
|
2038
2082
|
await this.#send(chatId, t('已绑定会话「{title}」\nID:{id}', { title, id: bound?.sessionId ?? sessionId }));
|
|
2039
2083
|
await this.#sendMenuCard(key, chatId, { updateMessageId });
|
|
2040
2084
|
} catch (error) {
|
|
2041
|
-
await this.#
|
|
2085
|
+
await this.#sendFailure(chatId, error, {
|
|
2086
|
+
logLabel: 'session binding',
|
|
2087
|
+
userMessage: t('绑定失败:{message}', { message: safeErrorText(error) }),
|
|
2088
|
+
});
|
|
2042
2089
|
}
|
|
2043
2090
|
}
|
|
2044
2091
|
|
|
@@ -2048,7 +2095,10 @@ export class FeishuHarnessBridge {
|
|
|
2048
2095
|
await this.#send(chatId, t('工作区已切换为:{workspace}', { workspace: current }));
|
|
2049
2096
|
await this.#sendMenuCard(key, chatId, { updateMessageId });
|
|
2050
2097
|
} catch (error) {
|
|
2051
|
-
await this.#
|
|
2098
|
+
await this.#sendFailure(chatId, error, {
|
|
2099
|
+
logLabel: 'workspace switch',
|
|
2100
|
+
userMessage: t('切换失败:{message}', { message: safeErrorText(error) }),
|
|
2101
|
+
});
|
|
2052
2102
|
}
|
|
2053
2103
|
}
|
|
2054
2104
|
|
|
@@ -2247,8 +2297,7 @@ export class FeishuHarnessBridge {
|
|
|
2247
2297
|
catalog._currentId = settings.agentPreset;
|
|
2248
2298
|
await this.#sendCard(chatId, presetCard(catalog), { key, updateMessageId });
|
|
2249
2299
|
} catch (error) {
|
|
2250
|
-
this.#
|
|
2251
|
-
await this.#send(chatId, t('暂时无法获取预设列表,请稍后重试。'));
|
|
2300
|
+
await this.#sendFailure(chatId, error, { logLabel: 'preset card' });
|
|
2252
2301
|
}
|
|
2253
2302
|
}
|
|
2254
2303
|
|
|
@@ -2273,8 +2322,7 @@ export class FeishuHarnessBridge {
|
|
|
2273
2322
|
}
|
|
2274
2323
|
await this.#sendCard(chatId, modelCard(catalog), { key, updateMessageId });
|
|
2275
2324
|
} catch (error) {
|
|
2276
|
-
this.#
|
|
2277
|
-
await this.#send(chatId, t('暂时无法获取模型列表,请稍后重试。'));
|
|
2325
|
+
await this.#sendFailure(chatId, error, { logLabel: 'model card' });
|
|
2278
2326
|
}
|
|
2279
2327
|
}
|
|
2280
2328
|
|
|
@@ -2302,8 +2350,7 @@ export class FeishuHarnessBridge {
|
|
|
2302
2350
|
}
|
|
2303
2351
|
await this.#send(chatId, lines.join('\n'));
|
|
2304
2352
|
} catch (error) {
|
|
2305
|
-
this.#
|
|
2306
|
-
await this.#send(chatId, t('暂时无法获取系统状态,请稍后重试。'));
|
|
2353
|
+
await this.#sendFailure(chatId, error, { logLabel: 'status text' });
|
|
2307
2354
|
}
|
|
2308
2355
|
}
|
|
2309
2356
|
|
|
@@ -2355,8 +2402,7 @@ export class FeishuHarnessBridge {
|
|
|
2355
2402
|
|
|
2356
2403
|
await this.#sendCard(chatId, statusCard(info), { key, updateMessageId });
|
|
2357
2404
|
} catch (error) {
|
|
2358
|
-
this.#
|
|
2359
|
-
await this.#send(chatId, t('暂时无法获取系统状态,请稍后重试。'));
|
|
2405
|
+
await this.#sendFailure(chatId, error, { logLabel: 'status card' });
|
|
2360
2406
|
}
|
|
2361
2407
|
}
|
|
2362
2408
|
|
|
@@ -2381,8 +2427,7 @@ export class FeishuHarnessBridge {
|
|
|
2381
2427
|
);
|
|
2382
2428
|
await this.#send(chatId, result?.message || t('上下文压缩失败。'));
|
|
2383
2429
|
} catch (error) {
|
|
2384
|
-
this.#
|
|
2385
|
-
await this.#send(chatId, t('上下文压缩失败,请稍后重试。'));
|
|
2430
|
+
await this.#sendFailure(chatId, error, { logLabel: 'compact' });
|
|
2386
2431
|
}
|
|
2387
2432
|
}
|
|
2388
2433
|
|
|
@@ -2405,8 +2450,7 @@ export class FeishuHarnessBridge {
|
|
|
2405
2450
|
}
|
|
2406
2451
|
await this.#send(chatId, result?.message || t('/stop 执行完成。'));
|
|
2407
2452
|
} catch (error) {
|
|
2408
|
-
this.#
|
|
2409
|
-
await this.#send(chatId, t('停止任务失败,请稍后重试。'));
|
|
2453
|
+
await this.#sendFailure(chatId, error, { logLabel: 'stop' });
|
|
2410
2454
|
}
|
|
2411
2455
|
}
|
|
2412
2456
|
|
|
@@ -2446,8 +2490,7 @@ export class FeishuHarnessBridge {
|
|
|
2446
2490
|
if (reply) await this.#send(chatId, reply);
|
|
2447
2491
|
}
|
|
2448
2492
|
} catch (error) {
|
|
2449
|
-
this.#
|
|
2450
|
-
await this.#send(chatId, t('预设重置失败,请稍后重试。'));
|
|
2493
|
+
await this.#sendFailure(chatId, error, { logLabel: 'preset reset' });
|
|
2451
2494
|
return;
|
|
2452
2495
|
}
|
|
2453
2496
|
try {
|
|
@@ -2470,8 +2513,7 @@ export class FeishuHarnessBridge {
|
|
|
2470
2513
|
if (reply) await this.#send(chatId, reply);
|
|
2471
2514
|
}
|
|
2472
2515
|
} catch (error) {
|
|
2473
|
-
this.#
|
|
2474
|
-
await this.#send(chatId, t('预设切换失败,请稍后重试。'));
|
|
2516
|
+
await this.#sendFailure(chatId, error, { logLabel: 'preset selection' });
|
|
2475
2517
|
return;
|
|
2476
2518
|
}
|
|
2477
2519
|
try {
|
|
@@ -2503,8 +2545,7 @@ export class FeishuHarnessBridge {
|
|
|
2503
2545
|
if (reply) await this.#send(chatId, reply);
|
|
2504
2546
|
}
|
|
2505
2547
|
} catch (error) {
|
|
2506
|
-
this.#
|
|
2507
|
-
await this.#send(chatId, t('模型切换失败,请稍后重试。'));
|
|
2548
|
+
await this.#sendFailure(chatId, error, { logLabel: 'model selection' });
|
|
2508
2549
|
return;
|
|
2509
2550
|
}
|
|
2510
2551
|
try {
|
|
@@ -3046,10 +3087,14 @@ export class FeishuHarnessBridge {
|
|
|
3046
3087
|
signal: this.#signal,
|
|
3047
3088
|
})
|
|
3048
3089
|
: undefined,
|
|
3049
|
-
|
|
3090
|
+
onFailure: (artifact, error) => setLastMessageFailure(this.#status, error, {
|
|
3091
|
+
userMessage: artifactFailureText(artifact?.fileName, error),
|
|
3092
|
+
reason: error?.code,
|
|
3093
|
+
}),
|
|
3094
|
+
sendFailureNotice: async (_artifact, _error, failure) => ({
|
|
3050
3095
|
messageId: await this.#send(
|
|
3051
3096
|
chatId,
|
|
3052
|
-
|
|
3097
|
+
messageFailureText(failure),
|
|
3053
3098
|
),
|
|
3054
3099
|
}),
|
|
3055
3100
|
logger: this.#logger,
|
|
@@ -3065,11 +3110,13 @@ export class FeishuHarnessBridge {
|
|
|
3065
3110
|
presentation: 'feishu-files',
|
|
3066
3111
|
}),
|
|
3067
3112
|
failureNoticeVisible: delivery.failureNoticeVisible,
|
|
3113
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
3068
3114
|
};
|
|
3069
3115
|
}
|
|
3070
3116
|
return {
|
|
3071
3117
|
receipt: delivery.receipt,
|
|
3072
3118
|
failureNoticeVisible: delivery.failureNoticeVisible,
|
|
3119
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
3073
3120
|
};
|
|
3074
3121
|
}
|
|
3075
3122
|
|
|
@@ -3110,7 +3157,7 @@ export class FeishuHarnessBridge {
|
|
|
3110
3157
|
},
|
|
3111
3158
|
);
|
|
3112
3159
|
} catch (error) {
|
|
3113
|
-
textSendError = error;
|
|
3160
|
+
textSendError = channelDeliveryFailure(error);
|
|
3114
3161
|
this.#logger.warn?.(
|
|
3115
3162
|
'[dsh-feishu] final text delivery failed; continuing with result files:',
|
|
3116
3163
|
error,
|
|
@@ -3123,8 +3170,11 @@ export class FeishuHarnessBridge {
|
|
|
3123
3170
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
3124
3171
|
throw textSendError;
|
|
3125
3172
|
}
|
|
3173
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
3174
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
3175
|
+
}
|
|
3126
3176
|
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
|
|
3127
|
-
return delivery
|
|
3177
|
+
return { ...delivery, textDeliveryErrors: textSendError ? 1 : 0 };
|
|
3128
3178
|
}
|
|
3129
3179
|
|
|
3130
3180
|
let promptStarted = false;
|
|
@@ -3177,7 +3227,7 @@ export class FeishuHarnessBridge {
|
|
|
3177
3227
|
},
|
|
3178
3228
|
);
|
|
3179
3229
|
} catch (fallbackError) {
|
|
3180
|
-
textSendError = fallbackError;
|
|
3230
|
+
textSendError = channelDeliveryFailure(fallbackError);
|
|
3181
3231
|
this.#logger.warn?.(
|
|
3182
3232
|
'[dsh-feishu] fallback text delivery failed; continuing with result files:',
|
|
3183
3233
|
fallbackError,
|
|
@@ -3195,8 +3245,11 @@ export class FeishuHarnessBridge {
|
|
|
3195
3245
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
3196
3246
|
throw textSendError;
|
|
3197
3247
|
}
|
|
3248
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
3249
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
3250
|
+
}
|
|
3198
3251
|
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
|
|
3199
|
-
return delivery
|
|
3252
|
+
return { ...delivery, textDeliveryErrors: textSendError ? 1 : 0 };
|
|
3200
3253
|
}
|
|
3201
3254
|
if (promptStarted) throw error;
|
|
3202
3255
|
|
|
@@ -3224,7 +3277,7 @@ export class FeishuHarnessBridge {
|
|
|
3224
3277
|
},
|
|
3225
3278
|
);
|
|
3226
3279
|
} catch (fallbackError) {
|
|
3227
|
-
textSendError = fallbackError;
|
|
3280
|
+
textSendError = channelDeliveryFailure(fallbackError);
|
|
3228
3281
|
this.#logger.warn?.(
|
|
3229
3282
|
'[dsh-feishu] fallback text delivery failed; continuing with result files:',
|
|
3230
3283
|
fallbackError,
|
|
@@ -3237,8 +3290,11 @@ export class FeishuHarnessBridge {
|
|
|
3237
3290
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
3238
3291
|
throw textSendError;
|
|
3239
3292
|
}
|
|
3293
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
3294
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
3295
|
+
}
|
|
3240
3296
|
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
|
|
3241
|
-
return delivery
|
|
3297
|
+
return { ...delivery, textDeliveryErrors: textSendError ? 1 : 0 };
|
|
3242
3298
|
}
|
|
3243
3299
|
const delivery = await this.#deliverArtifacts(
|
|
3244
3300
|
chatId,
|
|
@@ -3251,7 +3307,7 @@ export class FeishuHarnessBridge {
|
|
|
3251
3307
|
}),
|
|
3252
3308
|
);
|
|
3253
3309
|
this.#status.streamResponses = (this.#status.streamResponses ?? 0) + 1;
|
|
3254
|
-
return delivery
|
|
3310
|
+
return delivery;
|
|
3255
3311
|
}
|
|
3256
3312
|
|
|
3257
3313
|
async #processInteractionReply(event, messageId, key, expected, processingReaction) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { connectionTestMessage } from '../shared/connection-test.mjs';
|
|
3
|
+
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
3
4
|
import { RegistrationManager } from './registration-manager.mjs';
|
|
4
5
|
import {
|
|
5
6
|
CALLBACK_REPAIR_OPERATION,
|
|
@@ -633,6 +634,7 @@ export class MultiBotDshFeishuController {
|
|
|
633
634
|
groupMessagePermissionGranted: config.groupMessagePermissionGranted === true,
|
|
634
635
|
bot: publicBot(config),
|
|
635
636
|
connection,
|
|
637
|
+
lastMessageError: publicMessageFailure(connection.lastMessageError),
|
|
636
638
|
error,
|
|
637
639
|
};
|
|
638
640
|
});
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
fetchImageBuffer,
|
|
31
31
|
hasInboundImages,
|
|
32
|
+
imagePromptDiagnostic,
|
|
32
33
|
imagePromptUserMessage,
|
|
33
34
|
promptContentForMessage,
|
|
34
35
|
} from '../shared/image-prompt.mjs';
|
|
@@ -45,6 +46,12 @@ import {
|
|
|
45
46
|
createDeliveryReceipt,
|
|
46
47
|
providerMessageIdsFor,
|
|
47
48
|
} from '../shared/semantic/delivery.mjs';
|
|
49
|
+
import {
|
|
50
|
+
channelDeliveryFailure,
|
|
51
|
+
clearLastMessageFailure,
|
|
52
|
+
messageFailureText,
|
|
53
|
+
setLastMessageFailure,
|
|
54
|
+
} from '../shared/message-failure.mjs';
|
|
48
55
|
import { sendMarkdownReply } from './markdown-reply.mjs';
|
|
49
56
|
import { t } from '../shared/i18n.mjs';
|
|
50
57
|
|
|
@@ -216,9 +223,15 @@ function answerTextForDelivery(answer, artifacts) {
|
|
|
216
223
|
}
|
|
217
224
|
|
|
218
225
|
function qqArtifactError(error, { dispatched = false } = {}) {
|
|
219
|
-
if (error?.code?.startsWith?.('artifact-')
|
|
226
|
+
if (error?.code?.startsWith?.('artifact-')) {
|
|
220
227
|
return error;
|
|
221
228
|
}
|
|
229
|
+
if (error?.name === 'UploadDailyLimitExceededError') {
|
|
230
|
+
const wrapped = new Error('QQ daily file upload limit exceeded', { cause: error });
|
|
231
|
+
wrapped.name = error.name;
|
|
232
|
+
wrapped.code = 'artifact-rate-limited';
|
|
233
|
+
return wrapped;
|
|
234
|
+
}
|
|
222
235
|
const status = Number(error?.httpStatus);
|
|
223
236
|
const wrapped = new Error('QQ file delivery failed', { cause: error });
|
|
224
237
|
if (status === 401 || status === 403) wrapped.code = 'artifact-permission-required';
|
|
@@ -341,6 +354,7 @@ export function createQqBridgeStatus() {
|
|
|
341
354
|
lastReplyAt: null,
|
|
342
355
|
lastRejectedAt: null,
|
|
343
356
|
lastError: null,
|
|
357
|
+
lastMessageError: null,
|
|
344
358
|
};
|
|
345
359
|
}
|
|
346
360
|
|
|
@@ -470,8 +484,12 @@ export class QqHarnessBridge {
|
|
|
470
484
|
).catch((error) => {
|
|
471
485
|
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
|
472
486
|
this.#status.lastError = error?.message ?? String(error);
|
|
473
|
-
|
|
474
|
-
|
|
487
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
488
|
+
this.#logger.error?.(
|
|
489
|
+
`[dsh-im:qq] failed to process a command [${failure.referenceId}]:`,
|
|
490
|
+
error,
|
|
491
|
+
);
|
|
492
|
+
return this.#bot.sendText(message.replyTarget, messageFailureText(failure))
|
|
475
493
|
.catch(() => undefined);
|
|
476
494
|
}).finally(() => {
|
|
477
495
|
this.#acceptedMessageIds.delete(messageId);
|
|
@@ -614,8 +632,12 @@ export class QqHarnessBridge {
|
|
|
614
632
|
}).catch(async (error) => {
|
|
615
633
|
if (this.#signal?.aborted) return;
|
|
616
634
|
this.#status.lastError = error?.message ?? String(error);
|
|
617
|
-
|
|
618
|
-
|
|
635
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
636
|
+
this.#logger.error?.(
|
|
637
|
+
`[dsh-im:qq] failed to process a batch input message [${failure.referenceId}]:`,
|
|
638
|
+
error,
|
|
639
|
+
);
|
|
640
|
+
await this.#bot.sendText(message.replyTarget, messageFailureText(failure))
|
|
619
641
|
.catch(() => undefined);
|
|
620
642
|
}).finally(() => {
|
|
621
643
|
this.#acceptedMessageIds.delete(messageId);
|
|
@@ -627,7 +649,7 @@ export class QqHarnessBridge {
|
|
|
627
649
|
|
|
628
650
|
async #deliverArtifacts(target, replyTo, artifacts = [], baseReceipt = null) {
|
|
629
651
|
if (artifacts.length === 0) {
|
|
630
|
-
return { receipt: baseReceipt, failureNoticeVisible: false };
|
|
652
|
+
return { receipt: baseReceipt, failureNoticeVisible: false, artifactSendErrors: 0 };
|
|
631
653
|
}
|
|
632
654
|
const delivery = await deliverOutboundArtifacts({
|
|
633
655
|
artifacts,
|
|
@@ -645,9 +667,13 @@ export class QqHarnessBridge {
|
|
|
645
667
|
signal: this.#signal,
|
|
646
668
|
timeoutMs: this.#fileUploadTimeoutMs,
|
|
647
669
|
}),
|
|
648
|
-
|
|
670
|
+
onFailure: (artifact, error) => setLastMessageFailure(this.#status, error, {
|
|
671
|
+
userMessage: artifactFailureText(artifact?.fileName, error),
|
|
672
|
+
reason: error?.code,
|
|
673
|
+
}),
|
|
674
|
+
sendFailureNotice: (_artifact, _error, failure) => this.#bot.sendText(
|
|
649
675
|
target,
|
|
650
|
-
|
|
676
|
+
messageFailureText(failure),
|
|
651
677
|
),
|
|
652
678
|
logger: this.#logger,
|
|
653
679
|
});
|
|
@@ -658,6 +684,7 @@ export class QqHarnessBridge {
|
|
|
658
684
|
return {
|
|
659
685
|
receipt: delivery.receipt,
|
|
660
686
|
failureNoticeVisible: delivery.failureNoticeVisible,
|
|
687
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
661
688
|
};
|
|
662
689
|
}
|
|
663
690
|
|
|
@@ -676,6 +703,12 @@ export class QqHarnessBridge {
|
|
|
676
703
|
this.#status.messagesReceived += 1;
|
|
677
704
|
this.#status.lastMessageAt = new Date().toISOString();
|
|
678
705
|
}
|
|
706
|
+
let messageRecorded = alreadyRecorded;
|
|
707
|
+
const markMessageSeen = async () => {
|
|
708
|
+
if (messageRecorded) return;
|
|
709
|
+
await this.#state.markSeen(messageId);
|
|
710
|
+
messageRecorded = true;
|
|
711
|
+
};
|
|
679
712
|
if (this.#ownerUserOpenid !== '*' && sender !== this.#ownerUserOpenid) {
|
|
680
713
|
this.#status.messagesRejected += 1;
|
|
681
714
|
this.#status.lastRejectedAt = new Date().toISOString();
|
|
@@ -694,25 +727,25 @@ export class QqHarnessBridge {
|
|
|
694
727
|
try {
|
|
695
728
|
if (!text && !hasImages && !hasFiles) {
|
|
696
729
|
await this.#bot.sendText(target, t('目前支持文字、图片和文件消息。'));
|
|
697
|
-
await
|
|
730
|
+
await markMessageSeen();
|
|
698
731
|
return;
|
|
699
732
|
}
|
|
700
733
|
const command = text.toLowerCase();
|
|
701
734
|
if (!hasImages && !hasFiles && command === '/help') {
|
|
702
735
|
await this.#bot.sendText(target, helpText());
|
|
703
|
-
await
|
|
736
|
+
await markMessageSeen();
|
|
704
737
|
return;
|
|
705
738
|
}
|
|
706
739
|
if (!hasImages && !hasFiles && command === '/status') {
|
|
707
740
|
await this.#harness.ensureRunning({ signal: this.#signal });
|
|
708
741
|
await this.#bot.sendText(target, t('QQ 机器人与 DeepSeek Harness 连接正常。'));
|
|
709
|
-
await
|
|
742
|
+
await markMessageSeen();
|
|
710
743
|
return;
|
|
711
744
|
}
|
|
712
745
|
if (!hasImages && !hasFiles && command === '/new') {
|
|
713
746
|
await this.#state.clearSession(key);
|
|
714
747
|
await this.#bot.sendText(target, t('已开启新会话。请发送你的问题。'));
|
|
715
|
-
await
|
|
748
|
+
await markMessageSeen();
|
|
716
749
|
return;
|
|
717
750
|
}
|
|
718
751
|
const workspaceCommand = hasImages || hasFiles
|
|
@@ -722,7 +755,7 @@ export class QqHarnessBridge {
|
|
|
722
755
|
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
|
|
723
756
|
await this.#bot.sendText(target, reply);
|
|
724
757
|
}
|
|
725
|
-
await
|
|
758
|
+
await markMessageSeen();
|
|
726
759
|
return;
|
|
727
760
|
}
|
|
728
761
|
const compactCommand = hasImages || hasFiles
|
|
@@ -736,7 +769,7 @@ export class QqHarnessBridge {
|
|
|
736
769
|
);
|
|
737
770
|
if (compactCommand) {
|
|
738
771
|
await this.#bot.sendText(target, compactCommand.message);
|
|
739
|
-
await
|
|
772
|
+
await markMessageSeen();
|
|
740
773
|
return;
|
|
741
774
|
}
|
|
742
775
|
|
|
@@ -757,6 +790,9 @@ export class QqHarnessBridge {
|
|
|
757
790
|
let answer;
|
|
758
791
|
let artifacts = [];
|
|
759
792
|
try {
|
|
793
|
+
// Persist consumption before handing the prompt to Harness. Provider
|
|
794
|
+
// redelivery after a failed error notice must never execute it twice.
|
|
795
|
+
await markMessageSeen();
|
|
760
796
|
({ answer, artifacts = [] } = await askInWorkspaceSession({
|
|
761
797
|
harness: this.#harness,
|
|
762
798
|
state: this.#state,
|
|
@@ -771,10 +807,13 @@ export class QqHarnessBridge {
|
|
|
771
807
|
progressMode: 'all',
|
|
772
808
|
onUpdate: (update) => {
|
|
773
809
|
if (update.error) {
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
toolErrors.push(
|
|
810
|
+
const name = (nonEmptyString(update.toolName) ?? t('工具'))
|
|
811
|
+
.replace(/[\r\n]+/gu, ' ')
|
|
812
|
+
.slice(0, 80);
|
|
813
|
+
toolErrors.push(t(
|
|
814
|
+
'工具调用「{name}」未成功,请检查工具配置或稍后重试。',
|
|
815
|
+
{ name },
|
|
816
|
+
));
|
|
778
817
|
}
|
|
779
818
|
},
|
|
780
819
|
onInteraction: (interaction) => this.#handleInteraction(interaction, {
|
|
@@ -836,7 +875,7 @@ export class QqHarnessBridge {
|
|
|
836
875
|
});
|
|
837
876
|
}
|
|
838
877
|
} catch (error) {
|
|
839
|
-
textSendError = error;
|
|
878
|
+
textSendError = channelDeliveryFailure(error);
|
|
840
879
|
this.#logger.warn?.('[dsh-im:qq] final text delivery failed; continuing with result files:', error);
|
|
841
880
|
}
|
|
842
881
|
const delivery = await this.#deliverArtifacts(target, messageId, artifacts, textReceipt);
|
|
@@ -846,10 +885,15 @@ export class QqHarnessBridge {
|
|
|
846
885
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
847
886
|
throw textSendError;
|
|
848
887
|
}
|
|
849
|
-
|
|
888
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
889
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
890
|
+
}
|
|
850
891
|
this.#status.messagesReplied += 1;
|
|
851
892
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
852
893
|
this.#status.lastError = null;
|
|
894
|
+
if (!textSendError && delivery.artifactSendErrors === 0) {
|
|
895
|
+
clearLastMessageFailure(this.#status);
|
|
896
|
+
}
|
|
853
897
|
return delivery.receipt;
|
|
854
898
|
} catch (error) {
|
|
855
899
|
let batchFailureMessage = null;
|
|
@@ -872,7 +916,7 @@ export class QqHarnessBridge {
|
|
|
872
916
|
} catch (sendError) {
|
|
873
917
|
this.#logger.warn?.('[dsh-im:qq] unable to announce a stopped QQ turn:', sendError);
|
|
874
918
|
}
|
|
875
|
-
await
|
|
919
|
+
await markMessageSeen();
|
|
876
920
|
return;
|
|
877
921
|
}
|
|
878
922
|
try {
|
|
@@ -882,16 +926,23 @@ export class QqHarnessBridge {
|
|
|
882
926
|
}
|
|
883
927
|
if (this.#signal?.aborted) return;
|
|
884
928
|
this.#status.lastError = error?.message ?? String(error);
|
|
885
|
-
|
|
929
|
+
const userMessage = inboundFileUserMessage(error)
|
|
930
|
+
?? imagePromptUserMessage(error);
|
|
931
|
+
const failure = setLastMessageFailure(this.#status, error, {
|
|
932
|
+
userMessage,
|
|
933
|
+
reason: imagePromptDiagnostic(error)?.reason,
|
|
934
|
+
});
|
|
935
|
+
this.#logger.error?.(
|
|
936
|
+
`[dsh-im:qq] failed to process an inbound message [${failure.referenceId}]:`,
|
|
937
|
+
error,
|
|
938
|
+
);
|
|
886
939
|
try {
|
|
887
|
-
const errorMessage =
|
|
888
|
-
?? imagePromptUserMessage(error)
|
|
889
|
-
?? t('消息处理失败,请稍后重试。');
|
|
940
|
+
const errorMessage = messageFailureText(failure);
|
|
890
941
|
await this.#bot.sendText(
|
|
891
942
|
target,
|
|
892
943
|
batchFailureMessage ? `${errorMessage}\n\n${batchFailureMessage}` : errorMessage,
|
|
893
944
|
);
|
|
894
|
-
await
|
|
945
|
+
await markMessageSeen();
|
|
895
946
|
} catch (sendError) {
|
|
896
947
|
this.#logger.error?.('[dsh-im:qq] failed to send the safe error reply:', sendError);
|
|
897
948
|
}
|
|
@@ -1166,11 +1217,15 @@ export class QqHarnessBridge {
|
|
|
1166
1217
|
async #handleInteractionFailure(message, messageId, error) {
|
|
1167
1218
|
if (this.#signal?.aborted) return;
|
|
1168
1219
|
this.#status.lastError = error?.message ?? String(error);
|
|
1169
|
-
|
|
1220
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
1221
|
+
this.#logger.error?.(
|
|
1222
|
+
`[dsh-im:qq] failed to process an interaction reply [${failure.referenceId}]:`,
|
|
1223
|
+
error,
|
|
1224
|
+
);
|
|
1170
1225
|
if (!this.#state.hasSeen(messageId)) {
|
|
1171
1226
|
await this.#state.markSeen(messageId).catch(() => undefined);
|
|
1172
1227
|
}
|
|
1173
|
-
await this.#bot.sendText(message.replyTarget,
|
|
1228
|
+
await this.#bot.sendText(message.replyTarget, messageFailureText(failure))
|
|
1174
1229
|
.catch(() => undefined);
|
|
1175
1230
|
}
|
|
1176
1231
|
}
|