@xmanrui/dsh-im 3.1.1 → 3.2.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 +15 -0
- package/README.md +15 -0
- package/lib/client.js +1653 -333
- package/lib/index.js +235 -226
- package/package.json +5 -1
- package/plugin-src/client/channels/dingtalk/api.js +3 -0
- package/plugin-src/client/channels/dingtalk/index.js +18 -5
- package/plugin-src/client/channels/feishu/api.js +3 -0
- package/plugin-src/client/channels/feishu/index.js +18 -5
- package/plugin-src/client/channels/qq/api.js +3 -0
- package/plugin-src/client/channels/qq/index.js +13 -0
- package/plugin-src/client/channels/shared/token-api.js +3 -0
- package/plugin-src/client/channels/shared/token-channel.js +13 -1
- package/plugin-src/client/channels/wecom/api.js +3 -0
- package/plugin-src/client/channels/wecom/index.js +13 -0
- package/plugin-src/client/channels/weixin/api.js +3 -0
- package/plugin-src/client/channels/weixin/index.js +19 -5
- package/plugin-src/client/channels/whatsapp/api.js +3 -0
- package/plugin-src/client/channels/whatsapp/index.js +13 -0
- package/plugin-src/client/context-enhancement.js +275 -0
- package/plugin-src/client/i18n.js +54 -3
- package/plugin-src/client/styles.js +78 -0
- package/plugin-src/client/update-panel.js +108 -7
- package/plugin-src/host/channels/dingtalk/production.mjs +1 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +13 -0
- package/plugin-src/host/channels/qq/production.mjs +1 -0
- package/plugin-src/host/channels/qq/rpc.mjs +11 -0
- package/plugin-src/host/channels/shared/context-enhancement-rpc.mjs +17 -0
- package/plugin-src/host/channels/shared/production.mjs +1 -0
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +1 -0
- package/plugin-src/host/channels/slack/production.mjs +1 -0
- package/plugin-src/host/channels/slack/rpc.mjs +10 -0
- package/plugin-src/host/channels/wecom/production.mjs +1 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/weixin/production.mjs +1 -0
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +1 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
- package/plugin-src/host/update-runtime.mjs +3 -2
- package/plugin-src/host/update-service.mjs +2 -0
- package/scripts/verify-package.mjs +15 -2
- package/src/channels/dingtalk/dingtalk-api.mjs +39 -16
- package/src/channels/dingtalk/dingtalk-bridge.mjs +52 -23
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -1
- package/src/channels/discord/discord-runtime.mjs +18 -4
- package/src/channels/feishu/bridge.mjs +18 -3
- package/src/channels/feishu/feishu-runtime.mjs +4 -0
- package/src/channels/qq/qq-bridge.mjs +20 -4
- package/src/channels/qq/qq-runtime.mjs +4 -0
- package/src/channels/shared/bot-workspace-store.mjs +103 -6
- package/src/channels/shared/context-enhancement.mjs +135 -0
- package/src/channels/shared/text-harness-bridge.mjs +19 -4
- package/src/channels/slack/slack-runtime.mjs +4 -0
- package/src/channels/telegram/telegram-runtime.mjs +24 -2
- package/src/channels/wecom/wecom-bridge.mjs +18 -3
- package/src/channels/wecom/wecom-runtime.mjs +4 -0
- package/src/channels/weixin/weixin-bridge.mjs +19 -4
- package/src/channels/weixin/weixin-runtime.mjs +4 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +5 -0
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../shared/preset-command.mjs';
|
|
29
29
|
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
30
30
|
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
31
|
+
import { captureContextEnhancement, enhanceContextContent } from '../shared/context-enhancement.mjs';
|
|
31
32
|
import {
|
|
32
33
|
BatchInputManager,
|
|
33
34
|
batchInputBusyMessage,
|
|
@@ -251,7 +252,11 @@ function conversationKey(message, sender) {
|
|
|
251
252
|
|
|
252
253
|
function cardTarget(message, sender) {
|
|
253
254
|
if (String(message?.conversationType) === '2') {
|
|
254
|
-
return {
|
|
255
|
+
return {
|
|
256
|
+
type: 'group',
|
|
257
|
+
openConversationId: nonEmptyString(message?.conversationId),
|
|
258
|
+
atUserIds: { [sender]: nonEmptyString(message?.senderNick) ?? sender },
|
|
259
|
+
};
|
|
255
260
|
}
|
|
256
261
|
return { type: 'user', userId: sender };
|
|
257
262
|
}
|
|
@@ -368,6 +373,7 @@ export class DingtalkHarnessBridge {
|
|
|
368
373
|
#clientSecret;
|
|
369
374
|
#harness;
|
|
370
375
|
#state;
|
|
376
|
+
#contextEnhancement;
|
|
371
377
|
#status;
|
|
372
378
|
#logger;
|
|
373
379
|
#replyTimeoutMs;
|
|
@@ -379,7 +385,8 @@ export class DingtalkHarnessBridge {
|
|
|
379
385
|
#interactionKeys = new Map();
|
|
380
386
|
#interactionTasks = new Set();
|
|
381
387
|
#commandTasks = new Set();
|
|
382
|
-
|
|
388
|
+
// Keep the accepted configuration through the existing queue/reply lifecycle.
|
|
389
|
+
#acceptedMessageIds = new Map();
|
|
383
390
|
#approvals;
|
|
384
391
|
#batchInputs = new BatchInputManager();
|
|
385
392
|
|
|
@@ -389,6 +396,7 @@ export class DingtalkHarnessBridge {
|
|
|
389
396
|
clientSecret,
|
|
390
397
|
harness,
|
|
391
398
|
state,
|
|
399
|
+
contextEnhancement,
|
|
392
400
|
status = createDingtalkBridgeStatus(),
|
|
393
401
|
logger = console,
|
|
394
402
|
replyTimeoutMs = 600_000,
|
|
@@ -406,6 +414,7 @@ export class DingtalkHarnessBridge {
|
|
|
406
414
|
this.#clientSecret = clientSecret.trim();
|
|
407
415
|
this.#harness = harness;
|
|
408
416
|
this.#state = state;
|
|
417
|
+
this.#contextEnhancement = contextEnhancement;
|
|
409
418
|
this.#status = status;
|
|
410
419
|
this.#logger = logger;
|
|
411
420
|
this.#approvals = new HarnessApprovalQueue({ label: 'DingTalk', logger });
|
|
@@ -424,13 +433,17 @@ export class DingtalkHarnessBridge {
|
|
|
424
433
|
return structuredClone(this.#status);
|
|
425
434
|
}
|
|
426
435
|
|
|
427
|
-
accept(message) {
|
|
436
|
+
accept(message, { contextSnapshot } = {}) {
|
|
428
437
|
if (this.#signal?.aborted) return Promise.resolve();
|
|
429
438
|
const messageId = nonEmptyString(message?.msgId);
|
|
430
439
|
const sender = senderStaffId(message);
|
|
431
440
|
if (!messageId || !sender || this.#state.hasSeen(messageId)
|
|
432
441
|
|| this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
|
|
433
|
-
this.#acceptedMessageIds.
|
|
442
|
+
this.#acceptedMessageIds.set(messageId, contextSnapshot === undefined ? captureContextEnhancement(
|
|
443
|
+
this.#contextEnhancement,
|
|
444
|
+
message.conversationType === '1' || message.conversationType === 1 ? 'direct'
|
|
445
|
+
: message.conversationType === '2' || message.conversationType === 2 ? 'group' : null,
|
|
446
|
+
) : contextSnapshot);
|
|
434
447
|
|
|
435
448
|
let key;
|
|
436
449
|
try {
|
|
@@ -551,7 +564,7 @@ export class DingtalkHarnessBridge {
|
|
|
551
564
|
`[dsh-dingtalk] failed to process a command [${failure.referenceId}]`,
|
|
552
565
|
safeErrorDiagnostic(error),
|
|
553
566
|
);
|
|
554
|
-
return this.#send(sessionWebhook, messageFailureText(failure)).catch(() => undefined);
|
|
567
|
+
return this.#send(sessionWebhook, messageFailureText(failure), this.#atUsersFor(message)).catch(() => undefined);
|
|
555
568
|
}).finally(() => {
|
|
556
569
|
this.#acceptedMessageIds.delete(messageId);
|
|
557
570
|
this.#commandTasks.delete(task);
|
|
@@ -573,7 +586,7 @@ export class DingtalkHarnessBridge {
|
|
|
573
586
|
: null,
|
|
574
587
|
isQuestionPending: () => this.#pendingInteractions.has(key),
|
|
575
588
|
send: sessionWebhook
|
|
576
|
-
? (reply) => this.#send(sessionWebhook, reply)
|
|
589
|
+
? (reply) => this.#send(sessionWebhook, reply, this.#atUsersFor(message))
|
|
577
590
|
: async () => undefined,
|
|
578
591
|
});
|
|
579
592
|
if (approvalReply) {
|
|
@@ -807,7 +820,7 @@ export class DingtalkHarnessBridge {
|
|
|
807
820
|
]);
|
|
808
821
|
}
|
|
809
822
|
for (const reply of result?.messages ?? [result?.message]) {
|
|
810
|
-
if (reply) await this.#send(sessionWebhook, reply);
|
|
823
|
+
if (reply) await this.#send(sessionWebhook, reply, this.#atUsersFor(message));
|
|
811
824
|
}
|
|
812
825
|
this.#status.lastError = null;
|
|
813
826
|
}
|
|
@@ -885,23 +898,23 @@ export class DingtalkHarnessBridge {
|
|
|
885
898
|
let batchSettled = batchSubmission === null;
|
|
886
899
|
try {
|
|
887
900
|
if (!text && !hasImages && !hasFiles) {
|
|
888
|
-
await this.#send(sessionWebhook, t('目前支持文字、图片和文件消息。'));
|
|
901
|
+
await this.#send(sessionWebhook, t('目前支持文字、图片和文件消息。'), this.#atUsersFor(message));
|
|
889
902
|
return;
|
|
890
903
|
}
|
|
891
904
|
|
|
892
905
|
const command = text.toLowerCase();
|
|
893
906
|
if (isPlainText && !hasImages && !hasFiles && command === '/help') {
|
|
894
|
-
await this.#send(sessionWebhook, helpText());
|
|
907
|
+
await this.#send(sessionWebhook, helpText(), this.#atUsersFor(message));
|
|
895
908
|
return;
|
|
896
909
|
}
|
|
897
910
|
if (isPlainText && !hasImages && !hasFiles && command === '/status') {
|
|
898
911
|
await this.#harness.ensureRunning({ signal: this.#signal });
|
|
899
|
-
await this.#send(sessionWebhook, t('钉钉机器人与 DeepSeek Harness 连接正常。'));
|
|
912
|
+
await this.#send(sessionWebhook, t('钉钉机器人与 DeepSeek Harness 连接正常。'), this.#atUsersFor(message));
|
|
900
913
|
return;
|
|
901
914
|
}
|
|
902
915
|
if (isPlainText && !hasImages && !hasFiles && command === '/new') {
|
|
903
916
|
await this.#state.clearSession(key);
|
|
904
|
-
await this.#send(sessionWebhook, t('已开启新会话。请发送你的问题。'));
|
|
917
|
+
await this.#send(sessionWebhook, t('已开启新会话。请发送你的问题。'), this.#atUsersFor(message));
|
|
905
918
|
return;
|
|
906
919
|
}
|
|
907
920
|
const workspaceCommand = isPlainText && !hasImages && !hasFiles
|
|
@@ -909,7 +922,7 @@ export class DingtalkHarnessBridge {
|
|
|
909
922
|
: null;
|
|
910
923
|
if (workspaceCommand) {
|
|
911
924
|
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
|
|
912
|
-
await this.#send(sessionWebhook, reply);
|
|
925
|
+
await this.#send(sessionWebhook, reply, this.#atUsersFor(message));
|
|
913
926
|
}
|
|
914
927
|
return;
|
|
915
928
|
}
|
|
@@ -923,13 +936,21 @@ export class DingtalkHarnessBridge {
|
|
|
923
936
|
)
|
|
924
937
|
: null;
|
|
925
938
|
if (compactCommand) {
|
|
926
|
-
await this.#send(sessionWebhook, compactCommand.message);
|
|
939
|
+
await this.#send(sessionWebhook, compactCommand.message, this.#atUsersFor(message));
|
|
927
940
|
return;
|
|
928
941
|
}
|
|
929
942
|
|
|
930
|
-
|
|
943
|
+
let content = hasImages
|
|
931
944
|
? await promptContentForMessage(promptMessage, { signal: this.#signal })
|
|
932
945
|
: undefined;
|
|
946
|
+
const snapshot = this.#acceptedMessageIds.get(messageId);
|
|
947
|
+
if (snapshot) {
|
|
948
|
+
content = enhanceContextContent(content ?? text, snapshot, () => ({
|
|
949
|
+
channel: 'dingtalk',
|
|
950
|
+
senderId: sender,
|
|
951
|
+
senderName: message.senderNick,
|
|
952
|
+
}));
|
|
953
|
+
}
|
|
933
954
|
if (typeof this.#api.createAiCard === 'function'
|
|
934
955
|
&& typeof this.#api.updateAiCard === 'function'
|
|
935
956
|
&& typeof this.#api.finishAiCard === 'function') {
|
|
@@ -947,7 +968,7 @@ export class DingtalkHarnessBridge {
|
|
|
947
968
|
harness: this.#harness,
|
|
948
969
|
state: this.#state,
|
|
949
970
|
key,
|
|
950
|
-
...(
|
|
971
|
+
...(content !== undefined ? { content } : { text }),
|
|
951
972
|
createOptions: { signal: this.#signal },
|
|
952
973
|
existsOptions: { signal: this.#signal },
|
|
953
974
|
askOptions: {
|
|
@@ -988,7 +1009,7 @@ export class DingtalkHarnessBridge {
|
|
|
988
1009
|
textReceipt = createDeliveryReceipt({
|
|
989
1010
|
deliveryId: messageId,
|
|
990
1011
|
presentation: 'dingtalk-text',
|
|
991
|
-
providerMessageIds: await this.#send(sessionWebhook, answerText),
|
|
1012
|
+
providerMessageIds: await this.#send(sessionWebhook, answerText, this.#atUsersFor(message)),
|
|
992
1013
|
});
|
|
993
1014
|
}
|
|
994
1015
|
} catch (error) {
|
|
@@ -1049,7 +1070,7 @@ export class DingtalkHarnessBridge {
|
|
|
1049
1070
|
? `${errorText}\n\n${batchFailureMessage}`
|
|
1050
1071
|
: errorText;
|
|
1051
1072
|
const streamed = cardStarted && await cardStream.finish(visibleError);
|
|
1052
|
-
if (!streamed) await this.#send(sessionWebhook, visibleError);
|
|
1073
|
+
if (!streamed) await this.#send(sessionWebhook, visibleError, this.#atUsersFor(message));
|
|
1053
1074
|
} catch {
|
|
1054
1075
|
this.#logger.error?.('[dsh-dingtalk] failed to send the safe error reply');
|
|
1055
1076
|
}
|
|
@@ -1102,7 +1123,7 @@ export class DingtalkHarnessBridge {
|
|
|
1102
1123
|
const text = message?.msgtype === 'text' ? nonEmptyString(message?.text?.content) : null;
|
|
1103
1124
|
if (!text) {
|
|
1104
1125
|
try {
|
|
1105
|
-
await this.#send(sessionWebhook, t('请用文字回答当前问题。'));
|
|
1126
|
+
await this.#send(sessionWebhook, t('请用文字回答当前问题。'), this.#atUsersFor(message));
|
|
1106
1127
|
} catch {
|
|
1107
1128
|
this.#logger.error?.('[dsh-dingtalk] failed to reject a non-text interaction reply');
|
|
1108
1129
|
}
|
|
@@ -1113,7 +1134,7 @@ export class DingtalkHarnessBridge {
|
|
|
1113
1134
|
if (!pending || pending !== expected || pending.submitting) {
|
|
1114
1135
|
if (claimed && (!pending || pending !== expected)) {
|
|
1115
1136
|
try {
|
|
1116
|
-
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT));
|
|
1137
|
+
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT), this.#atUsersFor(message));
|
|
1117
1138
|
} catch {
|
|
1118
1139
|
this.#logger.error?.('[dsh-dingtalk] failed to send an expired interaction notice');
|
|
1119
1140
|
}
|
|
@@ -1178,7 +1199,7 @@ export class DingtalkHarnessBridge {
|
|
|
1178
1199
|
if (error?.code === 'interaction-not-pending') {
|
|
1179
1200
|
this.#clearPendingInteraction(key, pending.interactionId);
|
|
1180
1201
|
try {
|
|
1181
|
-
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT));
|
|
1202
|
+
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT), this.#atUsersFor(message));
|
|
1182
1203
|
} catch {
|
|
1183
1204
|
this.#logger.error?.('[dsh-dingtalk] failed to send an expired interaction notice');
|
|
1184
1205
|
}
|
|
@@ -1191,7 +1212,7 @@ export class DingtalkHarnessBridge {
|
|
|
1191
1212
|
this.#status.lastError = t('回答提交失败。');
|
|
1192
1213
|
this.#logger.error?.('[dsh-dingtalk] failed to answer a Harness interaction');
|
|
1193
1214
|
try {
|
|
1194
|
-
await this.#send(sessionWebhook, t('回答提交失败,请重新发送当前问题的答案。'));
|
|
1215
|
+
await this.#send(sessionWebhook, t('回答提交失败,请重新发送当前问题的答案。'), this.#atUsersFor(message));
|
|
1195
1216
|
} catch {
|
|
1196
1217
|
this.#logger.error?.('[dsh-dingtalk] failed to send an interaction retry notice');
|
|
1197
1218
|
}
|
|
@@ -1323,7 +1344,7 @@ export class DingtalkHarnessBridge {
|
|
|
1323
1344
|
return;
|
|
1324
1345
|
}
|
|
1325
1346
|
try {
|
|
1326
|
-
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT));
|
|
1347
|
+
await this.#send(sessionWebhook, t(INTERACTION_RESOLVED_TEXT), this.#atUsersFor(message));
|
|
1327
1348
|
} catch {
|
|
1328
1349
|
this.#logger.error?.('[dsh-dingtalk] failed to send an expired interaction notice');
|
|
1329
1350
|
}
|
|
@@ -1367,7 +1388,14 @@ export class DingtalkHarnessBridge {
|
|
|
1367
1388
|
}
|
|
1368
1389
|
}
|
|
1369
1390
|
|
|
1370
|
-
|
|
1391
|
+
#atUsersFor(message) {
|
|
1392
|
+
const sender = senderStaffId(message);
|
|
1393
|
+
return String(message?.conversationType) === '2' && sender
|
|
1394
|
+
? { atUserIds: [sender] }
|
|
1395
|
+
: undefined;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
async #send(sessionWebhook, text, at) {
|
|
1371
1399
|
const providerMessageIds = [];
|
|
1372
1400
|
for (const chunk of splitDingtalkText(text, this.#maxMessageChars)) {
|
|
1373
1401
|
this.#signal?.throwIfAborted();
|
|
@@ -1376,6 +1404,7 @@ export class DingtalkHarnessBridge {
|
|
|
1376
1404
|
clientSecret: this.#clientSecret,
|
|
1377
1405
|
sessionWebhook,
|
|
1378
1406
|
text: chunk,
|
|
1407
|
+
at,
|
|
1379
1408
|
signal: this.#signal,
|
|
1380
1409
|
});
|
|
1381
1410
|
providerMessageIds.push(...providerMessageIdsFor(result));
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from './dingtalk-bridge.mjs';
|
|
6
6
|
import { sendRememberedConnectionTest } from '../shared/connection-test.mjs';
|
|
7
7
|
import { t } from '../shared/i18n.mjs';
|
|
8
|
+
import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
|
|
8
9
|
|
|
9
10
|
function nonEmptyString(value) {
|
|
10
11
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
@@ -128,6 +129,7 @@ export class DingtalkRuntime {
|
|
|
128
129
|
#clientSecret;
|
|
129
130
|
#harness;
|
|
130
131
|
#state;
|
|
132
|
+
#contextEnhancement;
|
|
131
133
|
#logger;
|
|
132
134
|
#replyTimeoutMs;
|
|
133
135
|
#maxMessageChars;
|
|
@@ -149,6 +151,7 @@ export class DingtalkRuntime {
|
|
|
149
151
|
clientSecret,
|
|
150
152
|
harness,
|
|
151
153
|
state,
|
|
154
|
+
contextEnhancement,
|
|
152
155
|
logger = console,
|
|
153
156
|
replyTimeoutMs = 600_000,
|
|
154
157
|
maxMessageChars = 4_000,
|
|
@@ -166,6 +169,7 @@ export class DingtalkRuntime {
|
|
|
166
169
|
this.#clientSecret = clientSecret.trim();
|
|
167
170
|
this.#harness = harness;
|
|
168
171
|
this.#state = state;
|
|
172
|
+
this.#contextEnhancement = contextEnhancement;
|
|
169
173
|
this.#logger = logger;
|
|
170
174
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
171
175
|
this.#maxMessageChars = maxMessageChars;
|
|
@@ -233,6 +237,7 @@ export class DingtalkRuntime {
|
|
|
233
237
|
approvedSenders: this.#config.approvedSenders,
|
|
234
238
|
harness: this.#harness,
|
|
235
239
|
state: this.#state,
|
|
240
|
+
contextEnhancement: this.#contextEnhancement,
|
|
236
241
|
status: this.#status,
|
|
237
242
|
logger: this.#logger,
|
|
238
243
|
replyTimeoutMs: this.#replyTimeoutMs,
|
|
@@ -268,6 +273,15 @@ export class DingtalkRuntime {
|
|
|
268
273
|
}
|
|
269
274
|
}
|
|
270
275
|
|
|
276
|
+
// Retain the committed, read-only settings at receipt without moving
|
|
277
|
+
// JSON parsing out of the existing asynchronous callback path.
|
|
278
|
+
const contextEnhancement = this.#contextEnhancement;
|
|
279
|
+
let receivedSettings;
|
|
280
|
+
try {
|
|
281
|
+
receivedSettings = contextEnhancement?.getSettings?.();
|
|
282
|
+
} catch {
|
|
283
|
+
// Optional settings failures leave the original message path active.
|
|
284
|
+
}
|
|
271
285
|
const task = Promise.resolve().then(async () => {
|
|
272
286
|
if (this.#bridge !== bridge) return;
|
|
273
287
|
let message;
|
|
@@ -282,7 +296,12 @@ export class DingtalkRuntime {
|
|
|
282
296
|
}
|
|
283
297
|
if (!message || typeof message !== 'object') return;
|
|
284
298
|
this.#status.lastCallbackAt = Date.now();
|
|
285
|
-
|
|
299
|
+
const contextSnapshot = captureContextEnhancement({
|
|
300
|
+
getSettings: () => receivedSettings,
|
|
301
|
+
get botId() { return contextEnhancement?.botId; },
|
|
302
|
+
}, message.conversationType === '1' || message.conversationType === 1 ? 'direct'
|
|
303
|
+
: message.conversationType === '2' || message.conversationType === 2 ? 'group' : null);
|
|
304
|
+
await bridge.accept(message, { contextSnapshot });
|
|
286
305
|
}).catch(() => {
|
|
287
306
|
if (signal.aborted || this.#bridge !== bridge) return;
|
|
288
307
|
this.#status.lastError = t('钉钉消息处理失败。');
|
|
@@ -2,6 +2,7 @@ import { createEditableMessageStream, splitMessageText } from '../shared/editabl
|
|
|
2
2
|
import { fetchFileStream } from '../shared/file-download.mjs';
|
|
3
3
|
import { fetchImageBuffer } from '../shared/image-prompt.mjs';
|
|
4
4
|
import { t } from '../shared/i18n.mjs';
|
|
5
|
+
import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
|
|
5
6
|
import { DiscordApi } from './discord-api.mjs';
|
|
6
7
|
import { createDiscordBridgeStatus, DiscordHarnessBridge } from './discord-bridge.mjs';
|
|
7
8
|
|
|
@@ -213,6 +214,10 @@ export function normalizeDiscordMessage(message, botId, { fetchImpl = fetch } =
|
|
|
213
214
|
return {
|
|
214
215
|
messageId: String(message.id),
|
|
215
216
|
senderId: String(message.author.id),
|
|
217
|
+
contextSource: () => ({
|
|
218
|
+
senderName: [message.member?.nick, message.author.global_name, message.author.username]
|
|
219
|
+
.find((value) => typeof value === 'string' && value.trim()),
|
|
220
|
+
}),
|
|
216
221
|
senderIsBot: message.author.bot === true,
|
|
217
222
|
kind: direct ? 'direct' : 'group',
|
|
218
223
|
conversationId: String(message.channel_id),
|
|
@@ -427,6 +432,7 @@ export class DiscordRuntime {
|
|
|
427
432
|
#token;
|
|
428
433
|
#harness;
|
|
429
434
|
#state;
|
|
435
|
+
#contextEnhancement;
|
|
430
436
|
#logger;
|
|
431
437
|
#replyTimeoutMs;
|
|
432
438
|
#connectTimeoutMs;
|
|
@@ -457,6 +463,7 @@ export class DiscordRuntime {
|
|
|
457
463
|
token,
|
|
458
464
|
harness,
|
|
459
465
|
state,
|
|
466
|
+
contextEnhancement,
|
|
460
467
|
logger = console,
|
|
461
468
|
replyTimeoutMs = 600_000,
|
|
462
469
|
connectTimeoutMs = 20_000,
|
|
@@ -472,6 +479,7 @@ export class DiscordRuntime {
|
|
|
472
479
|
this.#token = token;
|
|
473
480
|
this.#harness = harness;
|
|
474
481
|
this.#state = state;
|
|
482
|
+
this.#contextEnhancement = contextEnhancement;
|
|
475
483
|
this.#logger = logger;
|
|
476
484
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
477
485
|
this.#connectTimeoutMs = connectTimeoutMs;
|
|
@@ -534,6 +542,7 @@ export class DiscordRuntime {
|
|
|
534
542
|
bot: client,
|
|
535
543
|
harness: this.#harness,
|
|
536
544
|
state: this.#state,
|
|
545
|
+
contextEnhancement: this.#contextEnhancement,
|
|
537
546
|
status: this.#status,
|
|
538
547
|
logger: this.#logger,
|
|
539
548
|
replyTimeoutMs: this.#replyTimeoutMs,
|
|
@@ -707,20 +716,25 @@ export class DiscordRuntime {
|
|
|
707
716
|
if (!messageId || this.#state.hasSeen(messageId)) return;
|
|
708
717
|
let route = this.#routing.get(messageId);
|
|
709
718
|
if (!route) {
|
|
710
|
-
|
|
719
|
+
const contextSnapshot = captureContextEnhancement(
|
|
720
|
+
this.#contextEnhancement,
|
|
721
|
+
message.guild_id ? 'group' : 'direct',
|
|
722
|
+
);
|
|
723
|
+
const pendingRoute = resolveDiscordMessageRoute(message, this.#config.platformId, {
|
|
711
724
|
api: this.#api,
|
|
712
725
|
channel: this.#channels.get(String(message.channel_id)),
|
|
713
726
|
signal: this.#abortController?.signal,
|
|
714
727
|
onChannel: (resolved) => this.#rememberChannel(resolved),
|
|
715
728
|
});
|
|
729
|
+
route = { pendingRoute, contextSnapshot };
|
|
716
730
|
this.#routing.set(messageId, route);
|
|
717
|
-
void
|
|
731
|
+
void pendingRoute.finally(() => {
|
|
718
732
|
if (this.#routing.get(messageId) === route) this.#routing.delete(messageId);
|
|
719
733
|
}).catch(() => undefined);
|
|
720
734
|
}
|
|
721
735
|
try {
|
|
722
|
-
const normalized = await route;
|
|
723
|
-
if (normalized) await bridge.accept(normalized);
|
|
736
|
+
const normalized = await route.pendingRoute;
|
|
737
|
+
if (normalized) await bridge.accept(normalized, { contextSnapshot: route.contextSnapshot });
|
|
724
738
|
} catch (error) {
|
|
725
739
|
if (error?.code === 'discord-thread-create-uncertain') {
|
|
726
740
|
await this.#state.markSeen(messageId);
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
} from '../shared/preset-command.mjs';
|
|
47
47
|
import { runWorkspaceCommand, resolveSessionListWorkspace, workspacePathSnapshot } from '../shared/workspace-command.mjs';
|
|
48
48
|
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
49
|
+
import { captureContextEnhancement, enhanceContextContent } from '../shared/context-enhancement.mjs';
|
|
49
50
|
import { deliverOutboundArtifacts } from '../shared/semantic/artifact-delivery.mjs';
|
|
50
51
|
import {
|
|
51
52
|
createDeliveryReceipt,
|
|
@@ -383,12 +384,14 @@ export class FeishuHarnessBridge {
|
|
|
383
384
|
#channel;
|
|
384
385
|
#harness;
|
|
385
386
|
#state;
|
|
387
|
+
#contextEnhancement;
|
|
386
388
|
#queues = new Map();
|
|
387
389
|
#batchInputs = new BatchInputManager();
|
|
388
390
|
#pendingInteractions = new Map();
|
|
389
391
|
#interactionKeys = new Map();
|
|
390
392
|
#resolvedQuestionReplies = new Map();
|
|
391
|
-
|
|
393
|
+
// Keep the accepted configuration through the existing queue/reply lifecycle.
|
|
394
|
+
#acceptedMessageIds = new Map();
|
|
392
395
|
#interactionTasks = new Set();
|
|
393
396
|
#commandTasks = new Set();
|
|
394
397
|
/** All accepted card work, including tasks waiting behind an earlier click. */
|
|
@@ -447,6 +450,7 @@ export class FeishuHarnessBridge {
|
|
|
447
450
|
channel,
|
|
448
451
|
harness,
|
|
449
452
|
state,
|
|
453
|
+
contextEnhancement,
|
|
450
454
|
status,
|
|
451
455
|
allowedSenderOpenIds = new Set(),
|
|
452
456
|
botId,
|
|
@@ -483,6 +487,7 @@ export class FeishuHarnessBridge {
|
|
|
483
487
|
this.#channel = channel;
|
|
484
488
|
this.#harness = harness;
|
|
485
489
|
this.#state = state;
|
|
490
|
+
this.#contextEnhancement = contextEnhancement;
|
|
486
491
|
this.#status = status;
|
|
487
492
|
this.#allowedSenderOpenIds = allowedSenderOpenIds;
|
|
488
493
|
this.#botId = nonEmptyString(botId);
|
|
@@ -551,7 +556,10 @@ export class FeishuHarnessBridge {
|
|
|
551
556
|
if (chatId) rememberConnectionTestTarget(this.#state, { chatId });
|
|
552
557
|
}
|
|
553
558
|
|
|
554
|
-
this.#acceptedMessageIds.
|
|
559
|
+
this.#acceptedMessageIds.set(messageId, captureContextEnhancement(
|
|
560
|
+
this.#contextEnhancement,
|
|
561
|
+
event.message.chat_type === 'p2p' ? 'direct' : event.message.chat_type === 'group' ? 'group' : null,
|
|
562
|
+
));
|
|
555
563
|
const processingReaction = this.#beginReaction(messageId);
|
|
556
564
|
const commandMessage = extractInboundMessage(event, this.#client);
|
|
557
565
|
const commandText = nonEmptyString(commandMessage.content) ?? '';
|
|
@@ -3143,9 +3151,16 @@ export class FeishuHarnessBridge {
|
|
|
3143
3151
|
askCompleted = true;
|
|
3144
3152
|
onAskComplete?.();
|
|
3145
3153
|
};
|
|
3146
|
-
|
|
3154
|
+
let content = hasInboundImages(message)
|
|
3147
3155
|
? await promptContentForMessage(message, { signal: this.#signal })
|
|
3148
3156
|
: undefined;
|
|
3157
|
+
const snapshot = this.#acceptedMessageIds.get(messageId);
|
|
3158
|
+
if (snapshot) {
|
|
3159
|
+
content = enhanceContextContent(content ?? text, snapshot, () => ({
|
|
3160
|
+
channel: 'feishu',
|
|
3161
|
+
senderId: senderOpenId(event),
|
|
3162
|
+
}));
|
|
3163
|
+
}
|
|
3149
3164
|
if (!this.#channel?.stream) {
|
|
3150
3165
|
const { answer, artifacts = [] } = await askInWorkspaceSession({
|
|
3151
3166
|
harness: this.#harness,
|
|
@@ -104,6 +104,7 @@ export class FeishuRuntime {
|
|
|
104
104
|
#ownerOpenIds;
|
|
105
105
|
#harness;
|
|
106
106
|
#state;
|
|
107
|
+
#contextEnhancement;
|
|
107
108
|
#replyTimeoutMs;
|
|
108
109
|
#connectTimeoutMs;
|
|
109
110
|
#requestTimeoutMs;
|
|
@@ -131,6 +132,7 @@ export class FeishuRuntime {
|
|
|
131
132
|
ownerOpenIds,
|
|
132
133
|
harness,
|
|
133
134
|
state,
|
|
135
|
+
contextEnhancement,
|
|
134
136
|
repair,
|
|
135
137
|
replyTimeoutMs = 600000,
|
|
136
138
|
connectTimeoutMs = 15000,
|
|
@@ -162,6 +164,7 @@ export class FeishuRuntime {
|
|
|
162
164
|
this.#ownerOpenIds = normalizedOwners;
|
|
163
165
|
this.#harness = harness;
|
|
164
166
|
this.#state = state;
|
|
167
|
+
this.#contextEnhancement = contextEnhancement;
|
|
165
168
|
this.#repair = repair ?? null;
|
|
166
169
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
167
170
|
this.#connectTimeoutMs = connectTimeoutMs;
|
|
@@ -260,6 +263,7 @@ export class FeishuRuntime {
|
|
|
260
263
|
channel,
|
|
261
264
|
harness: this.#harness,
|
|
262
265
|
state: this.#state,
|
|
266
|
+
contextEnhancement: this.#contextEnhancement,
|
|
263
267
|
status: this.#status,
|
|
264
268
|
allowedSenderOpenIds: new Set(this.#ownerOpenIds),
|
|
265
269
|
botId: this.#botId,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
runPresetCommand,
|
|
22
22
|
} from '../shared/preset-command.mjs';
|
|
23
23
|
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
24
|
+
import { captureContextEnhancement, enhanceContextContent } from '../shared/context-enhancement.mjs';
|
|
24
25
|
import {
|
|
25
26
|
BatchInputManager,
|
|
26
27
|
batchInputBusyMessage,
|
|
@@ -365,6 +366,7 @@ export class QqHarnessBridge {
|
|
|
365
366
|
#ownerUserOpenid;
|
|
366
367
|
#harness;
|
|
367
368
|
#state;
|
|
369
|
+
#contextEnhancement;
|
|
368
370
|
#status;
|
|
369
371
|
#logger;
|
|
370
372
|
#replyTimeoutMs;
|
|
@@ -374,7 +376,8 @@ export class QqHarnessBridge {
|
|
|
374
376
|
#queues = new Map();
|
|
375
377
|
#pendingInteractions = new Map();
|
|
376
378
|
#interactionKeys = new Map();
|
|
377
|
-
|
|
379
|
+
// Keep the accepted configuration through the existing queue/reply lifecycle.
|
|
380
|
+
#acceptedMessageIds = new Map();
|
|
378
381
|
#approvalTasks = new Set();
|
|
379
382
|
#commandTasks = new Set();
|
|
380
383
|
#approvals;
|
|
@@ -385,6 +388,7 @@ export class QqHarnessBridge {
|
|
|
385
388
|
ownerUserOpenid,
|
|
386
389
|
harness,
|
|
387
390
|
state,
|
|
391
|
+
contextEnhancement,
|
|
388
392
|
status = createQqBridgeStatus(),
|
|
389
393
|
logger = console,
|
|
390
394
|
replyTimeoutMs = 600_000,
|
|
@@ -403,6 +407,7 @@ export class QqHarnessBridge {
|
|
|
403
407
|
this.#ownerUserOpenid = ownerUserOpenid;
|
|
404
408
|
this.#harness = harness;
|
|
405
409
|
this.#state = state;
|
|
410
|
+
this.#contextEnhancement = contextEnhancement;
|
|
406
411
|
this.#status = status;
|
|
407
412
|
this.#logger = logger;
|
|
408
413
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
@@ -425,7 +430,10 @@ export class QqHarnessBridge {
|
|
|
425
430
|
|| this.#state.hasSeen(messageId)
|
|
426
431
|
|| this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
|
|
427
432
|
const key = conversationKey(message);
|
|
428
|
-
this.#acceptedMessageIds.
|
|
433
|
+
this.#acceptedMessageIds.set(messageId, captureContextEnhancement(
|
|
434
|
+
this.#contextEnhancement,
|
|
435
|
+
message.kind === 'c2c' ? 'direct' : 'group',
|
|
436
|
+
));
|
|
429
437
|
if (message.kind === 'c2c'
|
|
430
438
|
&& (this.#ownerUserOpenid === '*' || sender === this.#ownerUserOpenid)
|
|
431
439
|
&& message.replyTarget?.scope === 'c2c'
|
|
@@ -777,9 +785,17 @@ export class QqHarnessBridge {
|
|
|
777
785
|
return;
|
|
778
786
|
}
|
|
779
787
|
|
|
780
|
-
|
|
788
|
+
let content = hasImages
|
|
781
789
|
? await promptContentForMessage(promptMessage, { signal: this.#signal })
|
|
782
790
|
: undefined;
|
|
791
|
+
const snapshot = this.#acceptedMessageIds.get(messageId);
|
|
792
|
+
if (snapshot) {
|
|
793
|
+
content = enhanceContextContent(content ?? text, snapshot, () => ({
|
|
794
|
+
channel: 'qq',
|
|
795
|
+
senderId: sender,
|
|
796
|
+
senderName: message.kind === 'group' ? message.senderName : undefined,
|
|
797
|
+
}));
|
|
798
|
+
}
|
|
783
799
|
// QQ stream_messages can acknowledge a final frame without rendering it in
|
|
784
800
|
// some C2C clients. Standard Markdown delivery is the reliable reply path.
|
|
785
801
|
const toolErrors = [];
|
|
@@ -793,7 +809,7 @@ export class QqHarnessBridge {
|
|
|
793
809
|
harness: this.#harness,
|
|
794
810
|
state: this.#state,
|
|
795
811
|
key,
|
|
796
|
-
...(
|
|
812
|
+
...(content !== undefined ? { content } : { text }),
|
|
797
813
|
createOptions: { signal: this.#signal },
|
|
798
814
|
existsOptions: { signal: this.#signal },
|
|
799
815
|
askOptions: {
|
|
@@ -31,6 +31,7 @@ export class QqRuntime {
|
|
|
31
31
|
#appSecret;
|
|
32
32
|
#harness;
|
|
33
33
|
#state;
|
|
34
|
+
#contextEnhancement;
|
|
34
35
|
#logger;
|
|
35
36
|
#replyTimeoutMs;
|
|
36
37
|
#connectTimeoutMs;
|
|
@@ -48,6 +49,7 @@ export class QqRuntime {
|
|
|
48
49
|
appSecret,
|
|
49
50
|
harness,
|
|
50
51
|
state,
|
|
52
|
+
contextEnhancement,
|
|
51
53
|
logger = console,
|
|
52
54
|
replyTimeoutMs = 600_000,
|
|
53
55
|
connectTimeoutMs = 20_000,
|
|
@@ -61,6 +63,7 @@ export class QqRuntime {
|
|
|
61
63
|
this.#appSecret = appSecret;
|
|
62
64
|
this.#harness = harness;
|
|
63
65
|
this.#state = state;
|
|
66
|
+
this.#contextEnhancement = contextEnhancement;
|
|
64
67
|
this.#logger = logger;
|
|
65
68
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
66
69
|
this.#connectTimeoutMs = connectTimeoutMs;
|
|
@@ -139,6 +142,7 @@ export class QqRuntime {
|
|
|
139
142
|
ownerUserOpenid: this.#config.ownerUserOpenid,
|
|
140
143
|
harness: this.#harness,
|
|
141
144
|
state: this.#state,
|
|
145
|
+
contextEnhancement: this.#contextEnhancement,
|
|
142
146
|
status: this.#status,
|
|
143
147
|
logger: this.#logger,
|
|
144
148
|
replyTimeoutMs: this.#replyTimeoutMs,
|