@xmanrui/dsh-im 4.36.0 → 4.37.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 +14 -2
- package/README.md +13 -1
- package/lib/client.js +348 -195
- package/lib/index.js +314 -313
- package/package.json +2 -2
- package/plugin-src/client/bot-general-settings.js +83 -0
- package/plugin-src/client/channels/dingtalk/api.js +3 -1
- package/plugin-src/client/channels/feishu/api.js +3 -1
- package/plugin-src/client/channels/qq/api.js +3 -1
- package/plugin-src/client/channels/shared/token-api.js +3 -1
- package/plugin-src/client/channels/wecom/api.js +3 -1
- package/plugin-src/client/channels/wecom-app/api.js +3 -1
- package/plugin-src/client/channels/weixin/api.js +3 -1
- package/plugin-src/client/channels/whatsapp/api.js +3 -1
- package/plugin-src/client/connection-error.js +2 -1
- package/plugin-src/client/delivery-settings.js +5 -1
- package/plugin-src/client/i18n.js +13 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/email/rpc.mjs +1 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +13 -0
- package/plugin-src/host/channels/imessage/rpc.mjs +1 -0
- package/plugin-src/host/channels/matrix/rpc.mjs +10 -0
- package/plugin-src/host/channels/qq/rpc.mjs +11 -0
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/slack/rpc.mjs +10 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +10 -0
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +31 -2
- package/src/channels/dingtalk/dingtalk-menu.mjs +1 -1
- package/src/channels/dingtalk/dingtalk-runtime.mjs +2 -1
- package/src/channels/discord/discord-runtime.mjs +3 -1
- package/src/channels/feishu/bridge.mjs +69 -5
- package/src/channels/feishu/feishu-cards.mjs +8 -4
- package/src/channels/feishu/slash-command-panel.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +40 -2
- package/src/channels/qq/qq-menu.mjs +2 -2
- package/src/channels/shared/bot-workspace-store.mjs +69 -1
- package/src/channels/shared/busy-message-dispatcher.mjs +94 -0
- package/src/channels/shared/clear-command.mjs +85 -0
- package/src/channels/shared/command-catalog.mjs +3 -0
- package/src/channels/shared/command-permission.mjs +2 -0
- package/src/channels/shared/connection-error.mjs +1 -0
- package/src/channels/shared/control-command.mjs +20 -11
- package/src/channels/shared/diagnostic-details.mjs +7 -1
- package/src/channels/shared/harness-client.mjs +15 -8
- package/src/channels/shared/i18n-en/diagnostics.mjs +3 -0
- package/src/channels/shared/i18n-en/feishu.mjs +6 -4
- package/src/channels/shared/i18n-en/qq.mjs +2 -1
- package/src/channels/shared/i18n-en/shared-b.mjs +27 -0
- package/src/channels/shared/message-failure.mjs +1 -1
- package/src/channels/shared/message-mode.mjs +19 -0
- package/src/channels/shared/text-harness-bridge.mjs +48 -5
- package/src/channels/wecom/wecom-bridge.mjs +26 -2
- package/src/channels/wecom/wecom-cards.mjs +1 -1
- package/src/channels/wecom-app/wecom-app-bridge.mjs +26 -2
- package/src/channels/weixin/weixin-bridge.mjs +22 -2
|
@@ -218,7 +218,7 @@ export function classifyMessageFailure(error, {
|
|
|
218
218
|
: businessCode;
|
|
219
219
|
if (code === 'REQUEST_TIMEOUT' || code === 'NETWORK_ERROR') details.hint = connectionHint(details);
|
|
220
220
|
return Object.freeze({
|
|
221
|
-
...(code === 'INTERNAL_UNKNOWN' || details.reason !== 'unknown' || details.httpStatus || details.providerCode ? { details } : {}),
|
|
221
|
+
...(code === 'INTERNAL_UNKNOWN' || details.reason !== 'unknown' || details.httpStatus || details.providerCode || details.method ? { details } : {}),
|
|
222
222
|
code,
|
|
223
223
|
reason: safeReason ?? safeFailureReason(error?.code) ?? code,
|
|
224
224
|
message: typeof userMessage === 'string' && userMessage.trim()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const SET_MESSAGE_MODE_ENDPOINT = 'bot.message-mode.set';
|
|
2
|
+
|
|
3
|
+
export function normalizeBusyMessageMode(value) {
|
|
4
|
+
return value === 'steer' ? 'steer' : 'queue';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function validateBusyMessageMode(value) {
|
|
8
|
+
if (value !== 'queue' && value !== 'steer') {
|
|
9
|
+
throw new TypeError('Message mode must be queue or steer');
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validMessageModePayload(payload) {
|
|
15
|
+
return payload !== null && typeof payload === 'object' && !Array.isArray(payload)
|
|
16
|
+
&& Object.keys(payload).length === 2
|
|
17
|
+
&& typeof payload.botId === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(payload.botId)
|
|
18
|
+
&& (payload.busyMessageMode === 'queue' || payload.busyMessageMode === 'steer');
|
|
19
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BusyMessageDispatcher, canAutomaticallySteer } from './busy-message-dispatcher.mjs';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
|
|
3
4
|
import { createDeferredDeliveryCoordinator, deferredOutcomeText } from './deferred-delivery-coordinator.mjs';
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from './context-enhancement.mjs';
|
|
15
16
|
import { runWorkspaceCommand } from './workspace-command.mjs';
|
|
16
17
|
import { runCompactCommand } from './compact-command.mjs';
|
|
18
|
+
import { isClearCommand, runClearCommand } from './clear-command.mjs';
|
|
17
19
|
import { isHistoryCommand, runHistoryCommand } from './history-command.mjs';
|
|
18
20
|
import {
|
|
19
21
|
isControlCommand,
|
|
@@ -178,6 +180,7 @@ export class TextHarnessBridge {
|
|
|
178
180
|
#signal;
|
|
179
181
|
#keepaliveIntervalMs;
|
|
180
182
|
#queues = new Map();
|
|
183
|
+
#busyMessages = new BusyMessageDispatcher();
|
|
181
184
|
#pendingInteractions = new Map();
|
|
182
185
|
#interactionKeys = new Map();
|
|
183
186
|
// Keep the accepted configuration through the existing queue/reply lifecycle.
|
|
@@ -236,7 +239,7 @@ export class TextHarnessBridge {
|
|
|
236
239
|
return structuredClone(this.#status);
|
|
237
240
|
}
|
|
238
241
|
|
|
239
|
-
accept(message, { contextSnapshot, accessDecision } = {}) {
|
|
242
|
+
accept(message, { contextSnapshot, accessDecision, busyMessageMode } = {}) {
|
|
240
243
|
if (this.#signal?.aborted) return Promise.resolve();
|
|
241
244
|
const conversationId = cleanText(message?.conversationId);
|
|
242
245
|
const kind = message?.kind === 'group' ? 'group' : 'direct';
|
|
@@ -300,7 +303,7 @@ export class TextHarnessBridge {
|
|
|
300
303
|
});
|
|
301
304
|
normalized.statusReaction = statusReaction;
|
|
302
305
|
|
|
303
|
-
const processing = this.#acceptAcceptedMessage(normalized, messageId, senderId);
|
|
306
|
+
const processing = this.#acceptAcceptedMessage(normalized, messageId, senderId, busyMessageMode);
|
|
304
307
|
void processing.then(
|
|
305
308
|
() => statusReaction.success(),
|
|
306
309
|
() => statusReaction.error(),
|
|
@@ -308,7 +311,7 @@ export class TextHarnessBridge {
|
|
|
308
311
|
return processing;
|
|
309
312
|
}
|
|
310
313
|
|
|
311
|
-
#acceptAcceptedMessage(normalized, messageId, senderId) {
|
|
314
|
+
#acceptAcceptedMessage(normalized, messageId, senderId, busyMessageMode) {
|
|
312
315
|
if (normalized.kind === 'direct') {
|
|
313
316
|
rememberConnectionTestTarget(
|
|
314
317
|
this.#state,
|
|
@@ -319,6 +322,18 @@ export class TextHarnessBridge {
|
|
|
319
322
|
const key = `${normalized.kind}:${normalized.conversationId}`;
|
|
320
323
|
const pending = this.#pendingInteractions.get(key);
|
|
321
324
|
const text = controlTextOf(normalized);
|
|
325
|
+
if (isClearCommand(text) && (
|
|
326
|
+
this.#queues.has(key)
|
|
327
|
+
|| pending
|
|
328
|
+
|| this.#approvals.hasPending(key)
|
|
329
|
+
|| this.#batches.status(key).phase !== 'idle'
|
|
330
|
+
)) {
|
|
331
|
+
return this.#finishLocalMessage(
|
|
332
|
+
normalized,
|
|
333
|
+
messageId,
|
|
334
|
+
t('当前会话正在生成回复或等待交互,请先完成交互或发送 /stop,再执行 /clear。'),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
322
337
|
const batchCommand = isBatchInputCommand(text);
|
|
323
338
|
if (batchCommand && normalized.kind === 'group' && normalized.addressed === true) {
|
|
324
339
|
return this.#finishLocalMessage(
|
|
@@ -373,7 +388,8 @@ export class TextHarnessBridge {
|
|
|
373
388
|
}
|
|
374
389
|
const collectingBatch = normalized.kind === 'direct'
|
|
375
390
|
&& this.#batches.status(key).phase === 'collecting';
|
|
376
|
-
const commandRunner = collectingBatch ? null :
|
|
391
|
+
const commandRunner = collectingBatch ? null : isClearCommand(text) ? runClearCommand
|
|
392
|
+
: isHistoryCommand(text) ? runHistoryCommand
|
|
377
393
|
: hasInboundFiles(normalized) ? null : isControlCommand(text)
|
|
378
394
|
? runControlCommand
|
|
379
395
|
: (isModelCommand(text)
|
|
@@ -458,7 +474,33 @@ export class TextHarnessBridge {
|
|
|
458
474
|
pending.queue = current;
|
|
459
475
|
return current;
|
|
460
476
|
}
|
|
461
|
-
return this.#
|
|
477
|
+
return this.#busyMessages.dispatch({
|
|
478
|
+
key, messageId, text: normalized.content,
|
|
479
|
+
commandText: text, mode: busyMessageMode,
|
|
480
|
+
onFailure: (error) => this.#signal?.aborted || error?.code === 'turn-stopped'
|
|
481
|
+
? normalized.statusReaction?.clear() : normalized.statusReaction?.error(),
|
|
482
|
+
eligible: () => normalized.plainText !== false
|
|
483
|
+
&& !hasInboundImages(normalized) && !hasInboundFiles(normalized)
|
|
484
|
+
&& !hasReplyReference(normalized)
|
|
485
|
+
&& canAutomaticallySteer(this.#accessPolicy, normalized.kind,
|
|
486
|
+
[senderId, normalized.senderAlternateId].filter(Boolean)),
|
|
487
|
+
harness: this.#harness, state: this.#state, status: this.#status,
|
|
488
|
+
signal: this.#signal, control: { owner: this, key }, logger: this.#logger,
|
|
489
|
+
acceptedMessageIds: this.#acceptedMessageIds,
|
|
490
|
+
pendingInteraction: () => this.#pendingInteractions.has(key) || this.#approvals.hasPending(key),
|
|
491
|
+
enhancement: {
|
|
492
|
+
snapshot: this.#acceptedMessageIds.get(messageId),
|
|
493
|
+
source: () => {
|
|
494
|
+
const source = normalized.contextSource?.();
|
|
495
|
+
return { channel: this.#descriptor.key, senderId, senderName: source?.senderName,
|
|
496
|
+
conversationTitle: source?.conversationTitle,
|
|
497
|
+
chatId: source?.chatId ?? normalized.conversationId, threadId: source?.threadId };
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
isQueued: () => this.#queues.has(key),
|
|
501
|
+
enqueue: (options) => this.#enqueueMessage(normalized, messageId, senderId, key, options),
|
|
502
|
+
send: (reply) => this.#bot.sendText(normalized.replyTarget, reply),
|
|
503
|
+
});
|
|
462
504
|
}
|
|
463
505
|
|
|
464
506
|
#finishLocalMessage(message, messageId, reply, { recordReceived = true } = {}) {
|
|
@@ -522,6 +564,7 @@ export class TextHarnessBridge {
|
|
|
522
564
|
}
|
|
523
565
|
|
|
524
566
|
async waitForIdle() {
|
|
567
|
+
await this.#busyMessages.whenIdle();
|
|
525
568
|
await Promise.allSettled([
|
|
526
569
|
...this.#queues.values(),
|
|
527
570
|
...[...this.#pendingInteractions.values()].flatMap((pending) => (
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BusyMessageDispatcher, canAutomaticallySteer } from '../shared/busy-message-dispatcher.mjs';
|
|
1
2
|
import { imageInputLimits } from '../shared/image-input-policy.mjs';
|
|
2
3
|
import { imageDownloadLimitMessage } from '../shared/image-prompt.mjs';
|
|
3
4
|
import { createDeferredDeliveryCoordinator, deferredOutcomeText } from '../shared/deferred-delivery-coordinator.mjs';
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
isBatchInputCommand,
|
|
21
22
|
} from '../shared/batch-input.mjs';
|
|
22
23
|
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
24
|
+
import { isClearCommand, runClearCommand } from '../shared/clear-command.mjs';
|
|
23
25
|
import { isHistoryCommand, runHistoryCommand } from '../shared/history-command.mjs';
|
|
24
26
|
import {
|
|
25
27
|
isControlCommand,
|
|
@@ -82,6 +84,7 @@ function helpText() {
|
|
|
82
84
|
t('/menu 或 /m 打开可点击的功能菜单'),
|
|
83
85
|
t('/new 开启一个全新会话'),
|
|
84
86
|
t('/compact 压缩当前会话的较早上下文'),
|
|
87
|
+
t('/clear 清空当前会话上下文(保留 Session 绑定和历史记录)'),
|
|
85
88
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
86
89
|
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
|
87
90
|
t('/workspacelist 列出工作区绝对路径'),
|
|
@@ -565,6 +568,7 @@ export class WecomHarnessBridge {
|
|
|
565
568
|
#signal;
|
|
566
569
|
#fileUploadTimeoutMs;
|
|
567
570
|
#queues = new Map();
|
|
571
|
+
#busyMessages = new BusyMessageDispatcher();
|
|
568
572
|
#pendingInteractions = new Map();
|
|
569
573
|
#interactionKeys = new Map();
|
|
570
574
|
// Keep the accepted configuration through the existing queue/reply lifecycle.
|
|
@@ -870,6 +874,7 @@ export class WecomHarnessBridge {
|
|
|
870
874
|
return { message: t('已开启新会话。请发送你的问题。') };
|
|
871
875
|
}
|
|
872
876
|
return await runWorkspaceCommand(text, this.#harness, key)
|
|
877
|
+
?? await runClearCommand(text, this.#harness, this.#state, key, options)
|
|
873
878
|
?? await runCompactCommand(text, this.#harness, this.#state, key, options);
|
|
874
879
|
}
|
|
875
880
|
|
|
@@ -951,7 +956,8 @@ export class WecomHarnessBridge {
|
|
|
951
956
|
const menuCommand = !hasInboundImages(commandMessage) && !hasInboundFiles(commandMessage)
|
|
952
957
|
&& (parseWecomMenu(commandText) || ['/help', '/status', '/new'].includes(commandText.toLowerCase())
|
|
953
958
|
|| this.#cardFrames.has(frame));
|
|
954
|
-
const commandRunner =
|
|
959
|
+
const commandRunner = isClearCommand(commandText) ? runClearCommand
|
|
960
|
+
: isHistoryCommand(commandText) ? runHistoryCommand
|
|
955
961
|
: hasInboundFiles(commandMessage) ? null : isControlCommand(commandText)
|
|
956
962
|
? runControlCommand
|
|
957
963
|
: (isModelCommand(commandText)
|
|
@@ -1048,7 +1054,23 @@ export class WecomHarnessBridge {
|
|
|
1048
1054
|
pending.queue = current;
|
|
1049
1055
|
return current;
|
|
1050
1056
|
}
|
|
1051
|
-
return this.#
|
|
1057
|
+
return this.#busyMessages.dispatch({
|
|
1058
|
+
key, messageId, text: commandText,
|
|
1059
|
+
eligible: () => isNativeWecomText(frame) && !hasReplyReference(commandMessage)
|
|
1060
|
+
&& !parseWecomMenu(commandText) && !this.#cardFrames.has(frame)
|
|
1061
|
+
&& canAutomaticallySteer(this.#accessPolicy, conversationType, senderId),
|
|
1062
|
+
harness: this.#harness, state: this.#state, status: this.#status,
|
|
1063
|
+
signal: this.#signal, control: { owner: this, key }, logger: this.#logger,
|
|
1064
|
+
acceptedMessageIds: this.#acceptedMessageIds,
|
|
1065
|
+
pendingInteraction: () => this.#pendingInteractions.has(key) || this.#approvals.hasPending(key),
|
|
1066
|
+
enhancement: {
|
|
1067
|
+
snapshot: this.#acceptedMessageIds.get(messageId),
|
|
1068
|
+
source: () => ({ channel: 'wecom', senderId, chatId }),
|
|
1069
|
+
},
|
|
1070
|
+
isQueued: () => this.#queues.has(key),
|
|
1071
|
+
enqueue: (options) => this.#enqueueMessage(frame, messageId, key, options),
|
|
1072
|
+
send: (reply) => this.#sendImmediate(frame, chatId, reply),
|
|
1073
|
+
});
|
|
1052
1074
|
}
|
|
1053
1075
|
|
|
1054
1076
|
#enqueueMessage(frame, messageId, key, {
|
|
@@ -1151,6 +1173,7 @@ export class WecomHarnessBridge {
|
|
|
1151
1173
|
}
|
|
1152
1174
|
|
|
1153
1175
|
async waitForIdle() {
|
|
1176
|
+
await this.#busyMessages.whenIdle();
|
|
1154
1177
|
await Promise.allSettled([
|
|
1155
1178
|
...this.#queues.values(),
|
|
1156
1179
|
...[...this.#pendingInteractions.values()].flatMap((pending) => (
|
|
@@ -1176,6 +1199,7 @@ export class WecomHarnessBridge {
|
|
|
1176
1199
|
hasFiles: hasInboundFiles(message),
|
|
1177
1200
|
pendingInteraction: this.#pendingInteractions.has(key)
|
|
1178
1201
|
|| this.#approvals.hasPending(key),
|
|
1202
|
+
busy: this.#queues.has(key) || this.#batchInputs.status(key).phase !== 'idle',
|
|
1179
1203
|
control: { owner: this, key },
|
|
1180
1204
|
deferredDelivery: this.#deferred,
|
|
1181
1205
|
enhancement: captureContextEnhancementSource(
|
|
@@ -48,7 +48,7 @@ export function wecomMenu({ workspace, workspaces = [] } = {}) {
|
|
|
48
48
|
entries: [
|
|
49
49
|
[t('切换工作区'), 'select:workspace'],
|
|
50
50
|
[t('🆕 新会话'), '/new'],
|
|
51
|
-
[t('⏹ 停止'), '/stop'], [t('📐 压缩'), '/compact'],
|
|
51
|
+
[t('⏹ 停止'), '/stop'], [t('📐 压缩'), '/compact'], [t('🧹 清空上下文'), '/clear'],
|
|
52
52
|
[t('📊 状态'), '/status'], [t('📖 帮助'), '/help'],
|
|
53
53
|
],
|
|
54
54
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BusyMessageDispatcher, canAutomaticallySteer } from '../shared/busy-message-dispatcher.mjs';
|
|
1
2
|
import { createDeferredDeliveryCoordinator, deferredOutcomeText } from '../shared/deferred-delivery-coordinator.mjs';
|
|
2
3
|
import {
|
|
3
4
|
harnessAnswerForQuestion,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
isBatchInputCommand,
|
|
12
13
|
} from '../shared/batch-input.mjs';
|
|
13
14
|
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
15
|
+
import { isClearCommand, runClearCommand } from '../shared/clear-command.mjs';
|
|
14
16
|
import { isHistoryCommand, runHistoryCommand } from '../shared/history-command.mjs';
|
|
15
17
|
import {
|
|
16
18
|
isControlCommand,
|
|
@@ -75,6 +77,7 @@ const HELP_TEXT = () => [
|
|
|
75
77
|
t('直接发送文字或图片即可继续当前会话。'),
|
|
76
78
|
t('/new 开启一个全新会话'),
|
|
77
79
|
t('/compact 压缩当前会话的较早上下文'),
|
|
80
|
+
t('/clear 清空当前会话上下文(保留 Session 绑定和历史记录)'),
|
|
78
81
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
79
82
|
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
|
80
83
|
t('/workspacelist 列出工作区绝对路径'),
|
|
@@ -202,6 +205,7 @@ export class WecomAppBridge {
|
|
|
202
205
|
#streamRegistry;
|
|
203
206
|
#maxChunkBytes;
|
|
204
207
|
#queues = new Map();
|
|
208
|
+
#busyMessages = new BusyMessageDispatcher();
|
|
205
209
|
#acceptedMessageIds = new Map();
|
|
206
210
|
#commandTasks = new Set();
|
|
207
211
|
#approvalTasks = new Set();
|
|
@@ -311,7 +315,8 @@ export class WecomAppBridge {
|
|
|
311
315
|
return this.#finishBatchResult(messageId, key, sender, result);
|
|
312
316
|
}
|
|
313
317
|
}
|
|
314
|
-
const commandRunner =
|
|
318
|
+
const commandRunner = isClearCommand(commandText) ? runClearCommand
|
|
319
|
+
: isHistoryCommand(commandText) ? runHistoryCommand
|
|
315
320
|
: isControlCommand(commandText)
|
|
316
321
|
? runControlCommand
|
|
317
322
|
: (isModelCommand(commandText)
|
|
@@ -385,7 +390,24 @@ export class WecomAppBridge {
|
|
|
385
390
|
pending.queue = current;
|
|
386
391
|
return current;
|
|
387
392
|
}
|
|
388
|
-
return this.#
|
|
393
|
+
return this.#busyMessages.dispatch({
|
|
394
|
+
key, messageId, text: commandText,
|
|
395
|
+
eligible: () => message.msgtype === 'text' && !hasImages
|
|
396
|
+
&& !hasReplyReference(wecomAppInboundMessage(message, this.#api))
|
|
397
|
+
&& canAutomaticallySteer(this.#accessPolicy, 'direct', sender),
|
|
398
|
+
harness: this.#harness, state: this.#state, status: this.#status,
|
|
399
|
+
signal: this.#signal, control: { owner: this, key }, logger: this.#logger,
|
|
400
|
+
acceptedMessageIds: this.#acceptedMessageIds,
|
|
401
|
+
pendingInteraction: () => this.#pendingInteractions.has(key) || this.#approvals.hasPending(key),
|
|
402
|
+
enhancement: {
|
|
403
|
+
snapshot: this.#acceptedMessageIds.get(messageId),
|
|
404
|
+
source: () => ({ channel: 'wecom-app', senderId: sender, chatId: sender }),
|
|
405
|
+
},
|
|
406
|
+
isQueued: () => this.#queues.has(key),
|
|
407
|
+
enqueue: (options) => this.#enqueueMessage(message, messageId, key, { ...options, sink }),
|
|
408
|
+
send: (reply) => this.#send(sender, reply),
|
|
409
|
+
onError: (error) => this.#handleInteractionFailure(message, messageId, error),
|
|
410
|
+
});
|
|
389
411
|
}
|
|
390
412
|
|
|
391
413
|
#enqueueMessage(message, messageId, key, {
|
|
@@ -472,6 +494,7 @@ export class WecomAppBridge {
|
|
|
472
494
|
}
|
|
473
495
|
|
|
474
496
|
async waitForIdle() {
|
|
497
|
+
await this.#busyMessages.whenIdle();
|
|
475
498
|
await Promise.allSettled([
|
|
476
499
|
...this.#queues.values(),
|
|
477
500
|
...[...this.#pendingInteractions.values()].flatMap((pending) => (
|
|
@@ -501,6 +524,7 @@ export class WecomAppBridge {
|
|
|
501
524
|
hasFiles: false,
|
|
502
525
|
pendingInteraction: this.#pendingInteractions.has(key)
|
|
503
526
|
|| this.#approvals.hasPending(key),
|
|
527
|
+
busy: this.#queues.has(key) || this.#batchInputs.status(key).phase !== 'idle',
|
|
504
528
|
control: { owner: this, key },
|
|
505
529
|
deferredDelivery: this.#deferred,
|
|
506
530
|
enhancement: captureContextEnhancementSource(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BusyMessageDispatcher, canAutomaticallySteer } from '../shared/busy-message-dispatcher.mjs';
|
|
1
2
|
import { createDeferredDeliveryCoordinator, deferredOutcomeText } from '../shared/deferred-delivery-coordinator.mjs';
|
|
2
3
|
import {
|
|
3
4
|
DEFAULT_WEIXIN_MAX_MESSAGE_CHARS,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
isBatchInputCommand,
|
|
22
23
|
} from '../shared/batch-input.mjs';
|
|
23
24
|
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
25
|
+
import { isClearCommand, runClearCommand } from '../shared/clear-command.mjs';
|
|
24
26
|
import { isHistoryCommand, runHistoryCommand } from '../shared/history-command.mjs';
|
|
25
27
|
import {
|
|
26
28
|
isControlCommand,
|
|
@@ -91,6 +93,7 @@ const HELP_TEXT = () => [
|
|
|
91
93
|
t('直接发送文字、图片、文件或带文字识别结果的语音即可继续当前会话。'),
|
|
92
94
|
t('/new 开启一个全新会话'),
|
|
93
95
|
t('/compact 压缩当前会话的较早上下文'),
|
|
96
|
+
t('/clear 清空当前会话上下文(保留 Session 绑定和历史记录)'),
|
|
94
97
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
95
98
|
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
|
96
99
|
t('/workspacelist 列出工作区绝对路径'),
|
|
@@ -313,6 +316,7 @@ export class WeixinHarnessBridge {
|
|
|
313
316
|
#typingKeepaliveMs;
|
|
314
317
|
#signal;
|
|
315
318
|
#queues = new Map();
|
|
319
|
+
#busyMessages = new BusyMessageDispatcher();
|
|
316
320
|
#pendingInteractions = new Map();
|
|
317
321
|
#interactionKeys = new Map();
|
|
318
322
|
// Keep the accepted configuration through the existing queue/reply lifecycle.
|
|
@@ -457,7 +461,8 @@ export class WeixinHarnessBridge {
|
|
|
457
461
|
);
|
|
458
462
|
}
|
|
459
463
|
}
|
|
460
|
-
const commandRunner =
|
|
464
|
+
const commandRunner = isClearCommand(commandText) ? runClearCommand
|
|
465
|
+
: isHistoryCommand(commandText) ? runHistoryCommand
|
|
461
466
|
: hasWeixinFileItems(message) ? null : isControlCommand(commandText)
|
|
462
467
|
? runControlCommand
|
|
463
468
|
: (isModelCommand(commandText)
|
|
@@ -542,7 +547,20 @@ export class WeixinHarnessBridge {
|
|
|
542
547
|
pending.queue = current;
|
|
543
548
|
return current;
|
|
544
549
|
}
|
|
545
|
-
return this.#
|
|
550
|
+
return this.#busyMessages.dispatch({
|
|
551
|
+
key, messageId, text: commandText,
|
|
552
|
+
eligible: () => isNativeWeixinText(message) && !extractWeixinReplyReference(message)
|
|
553
|
+
&& canAutomaticallySteer(this.#accessPolicy, 'direct', sender),
|
|
554
|
+
harness: this.#harness, state: this.#state, status: this.#status,
|
|
555
|
+
signal: this.#signal, control: { owner: this, key }, logger: this.#logger,
|
|
556
|
+
acceptedMessageIds: this.#acceptedMessageIds,
|
|
557
|
+
pendingInteraction: () => this.#pendingInteractions.has(key) || this.#approvals.hasPending(key),
|
|
558
|
+
enhancement: this.#acceptedMessageIds.get(messageId),
|
|
559
|
+
isQueued: () => this.#queues.has(key),
|
|
560
|
+
enqueue: (options) => this.#enqueueMessage(message, messageId, key, options),
|
|
561
|
+
send: (reply) => this.#sendOutOfBand(key, sender, reply, contextToken, runId),
|
|
562
|
+
onError: (error) => this.#handleInteractionFailure(message, messageId, error),
|
|
563
|
+
});
|
|
546
564
|
}
|
|
547
565
|
|
|
548
566
|
#enqueueMessage(message, messageId, key, {
|
|
@@ -684,6 +702,7 @@ export class WeixinHarnessBridge {
|
|
|
684
702
|
}
|
|
685
703
|
|
|
686
704
|
async waitForIdle() {
|
|
705
|
+
await this.#busyMessages.whenIdle();
|
|
687
706
|
await Promise.allSettled([
|
|
688
707
|
...this.#queues.values(),
|
|
689
708
|
...[...this.#pendingInteractions.values()].flatMap((pending) => (
|
|
@@ -724,6 +743,7 @@ export class WeixinHarnessBridge {
|
|
|
724
743
|
hasFiles: hasWeixinFileItems(message),
|
|
725
744
|
pendingInteraction: this.#pendingInteractions.has(key)
|
|
726
745
|
|| this.#approvals.hasPending(key),
|
|
746
|
+
busy: this.#queues.has(key) || this.#batchInputs.status(key).phase !== 'idle',
|
|
727
747
|
control: { owner: this, key },
|
|
728
748
|
deferredDelivery: this.#deferred,
|
|
729
749
|
enhancement: captureContextEnhancementSource(
|