@xmanrui/dsh-im 4.36.1 → 4.38.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.
Files changed (63) hide show
  1. package/README.en.md +14 -2
  2. package/README.md +13 -1
  3. package/assets/logo-plugin-message-link-gradient.webp +0 -0
  4. package/lib/client.js +417 -189
  5. package/lib/index.js +317 -316
  6. package/package.json +2 -2
  7. package/plugin-src/client/bot-general-settings.js +120 -0
  8. package/plugin-src/client/channels/dingtalk/api.js +3 -1
  9. package/plugin-src/client/channels/feishu/api.js +6 -2
  10. package/plugin-src/client/channels/qq/api.js +3 -1
  11. package/plugin-src/client/channels/shared/token-api.js +3 -1
  12. package/plugin-src/client/channels/wecom/api.js +3 -1
  13. package/plugin-src/client/channels/wecom-app/api.js +3 -1
  14. package/plugin-src/client/channels/weixin/api.js +3 -1
  15. package/plugin-src/client/channels/whatsapp/api.js +3 -1
  16. package/plugin-src/client/delivery-settings.js +5 -1
  17. package/plugin-src/client/i18n.js +14 -0
  18. package/plugin-src/client/styles.js +19 -0
  19. package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
  20. package/plugin-src/host/channels/email/rpc.mjs +1 -0
  21. package/plugin-src/host/channels/feishu/production.mjs +1 -0
  22. package/plugin-src/host/channels/feishu/rpc.mjs +31 -0
  23. package/plugin-src/host/channels/imessage/rpc.mjs +1 -0
  24. package/plugin-src/host/channels/matrix/rpc.mjs +10 -0
  25. package/plugin-src/host/channels/qq/rpc.mjs +11 -0
  26. package/plugin-src/host/channels/shared/rpc.mjs +9 -0
  27. package/plugin-src/host/channels/slack/rpc.mjs +10 -0
  28. package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
  29. package/plugin-src/host/channels/wecom-app/rpc.mjs +10 -0
  30. package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
  31. package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
  32. package/scripts/verify-package.mjs +11 -6
  33. package/src/channels/dingtalk/dingtalk-bridge.mjs +31 -2
  34. package/src/channels/dingtalk/dingtalk-menu.mjs +1 -1
  35. package/src/channels/dingtalk/dingtalk-runtime.mjs +2 -1
  36. package/src/channels/discord/discord-runtime.mjs +3 -1
  37. package/src/channels/feishu/bridge.mjs +91 -15
  38. package/src/channels/feishu/feishu-cards.mjs +16 -11
  39. package/src/channels/feishu/feishu-runtime.mjs +10 -1
  40. package/src/channels/feishu/multi-bot-controller.mjs +18 -0
  41. package/src/channels/feishu/plugin-config-store.mjs +2 -1
  42. package/src/channels/feishu/slash-command-panel.mjs +1 -0
  43. package/src/channels/feishu/step-push-mode.mjs +21 -0
  44. package/src/channels/matrix/matrix-crypto.mjs +19 -5
  45. package/src/channels/qq/qq-bridge.mjs +40 -2
  46. package/src/channels/qq/qq-menu.mjs +2 -2
  47. package/src/channels/shared/bot-workspace-store.mjs +69 -1
  48. package/src/channels/shared/busy-message-dispatcher.mjs +94 -0
  49. package/src/channels/shared/clear-command.mjs +85 -0
  50. package/src/channels/shared/command-catalog.mjs +3 -0
  51. package/src/channels/shared/command-permission.mjs +2 -0
  52. package/src/channels/shared/control-command.mjs +20 -11
  53. package/src/channels/shared/harness-approval.mjs +102 -2
  54. package/src/channels/shared/i18n-en/feishu.mjs +6 -4
  55. package/src/channels/shared/i18n-en/qq.mjs +2 -1
  56. package/src/channels/shared/i18n-en/shared-b.mjs +27 -0
  57. package/src/channels/shared/i18n-en/shared-c.mjs +4 -0
  58. package/src/channels/shared/message-mode.mjs +19 -0
  59. package/src/channels/shared/text-harness-bridge.mjs +48 -5
  60. package/src/channels/wecom/wecom-bridge.mjs +26 -2
  61. package/src/channels/wecom/wecom-cards.mjs +1 -1
  62. package/src/channels/wecom-app/wecom-app-bridge.mjs +26 -2
  63. package/src/channels/weixin/weixin-bridge.mjs +22 -2
@@ -1,3 +1,4 @@
1
+ import { SET_MESSAGE_MODE_ENDPOINT, validMessageModePayload } from '../../../../src/channels/shared/message-mode.mjs';
1
2
  import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
2
3
  import { SET_ALIAS_ENDPOINT, validAliasPayload } from './bot-alias-rpc.mjs';
3
4
  import { registerManagementRpc } from '../../../management-rpc.mjs';
@@ -26,6 +27,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
26
27
  setModel: SET_MODEL_ENDPOINT,
27
28
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
28
29
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
30
+ setBusyMessageMode: SET_MESSAGE_MODE_ENDPOINT,
29
31
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
30
32
  setAlias: SET_ALIAS_ENDPOINT,
31
33
  setThinkingTraces: SET_THINKING_TRACES_ENDPOINT,
@@ -87,6 +89,10 @@ function payloadFailure(endpoint, payload) {
87
89
  return validAgentPresetPayload(payload)
88
90
  ? null : '请选择 Agent Preset。';
89
91
  }
92
+ if (endpoint === TOKEN_BOT_ENDPOINTS.setBusyMessageMode) {
93
+ return validMessageModePayload(payload)
94
+ ? null : '请提交有效的消息处理方式。';
95
+ }
90
96
  if (endpoint === TOKEN_BOT_ENDPOINTS.setContextEnhancement) {
91
97
  return validContextEnhancementPayload(payload)
92
98
  ? null : '请提交有效的上下文增强设置。';
@@ -188,6 +194,9 @@ export function createTokenBotRpcHandler(controller, { channel }) {
188
194
  } else if (endpoint === TOKEN_BOT_ENDPOINTS.setModel) {
189
195
  if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
190
196
  value = await controller.updateModel(payload.botId, payload.model);
197
+ } else if (endpoint === TOKEN_BOT_ENDPOINTS.setBusyMessageMode) {
198
+ if (typeof controller.updateBusyMessageMode !== 'function') throw new Error('Message mode update is unavailable');
199
+ value = await controller.updateBusyMessageMode(payload.botId, payload.busyMessageMode);
191
200
  } else if (endpoint === TOKEN_BOT_ENDPOINTS.setContextEnhancement) {
192
201
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
193
202
  value = await controller.updateContextEnhancement(payload.botId, payload.config);
@@ -1,3 +1,4 @@
1
+ import { SET_MESSAGE_MODE_ENDPOINT, validMessageModePayload } from '../../../../src/channels/shared/message-mode.mjs';
1
2
  import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
2
3
  import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
3
4
  import { registerManagementRpc } from '../../../management-rpc.mjs';
@@ -26,6 +27,7 @@ export const SLACK_ENDPOINTS = Object.freeze({
26
27
  setModel: SET_MODEL_ENDPOINT,
27
28
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
28
29
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
30
+ setBusyMessageMode: SET_MESSAGE_MODE_ENDPOINT,
29
31
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
30
32
  setAlias: SET_ALIAS_ENDPOINT,
31
33
  });
@@ -88,6 +90,10 @@ function payloadFailure(endpoint, payload) {
88
90
  return validAgentPresetPayload(payload)
89
91
  ? null : '请选择 Agent Preset。';
90
92
  }
93
+ if (endpoint === SLACK_ENDPOINTS.setBusyMessageMode) {
94
+ return validMessageModePayload(payload)
95
+ ? null : '请提交有效的消息处理方式。';
96
+ }
91
97
  if (endpoint === SLACK_ENDPOINTS.setContextEnhancement) {
92
98
  return validContextEnhancementPayload(payload)
93
99
  ? null : '请提交有效的上下文增强设置。';
@@ -184,6 +190,10 @@ export function createSlackRpcHandler(controller) {
184
190
  if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
185
191
  value = await controller.updateModel(payload.botId, payload.model);
186
192
  }
193
+ else if (endpoint === SLACK_ENDPOINTS.setBusyMessageMode) {
194
+ if (typeof controller.updateBusyMessageMode !== 'function') throw new Error('Message mode update is unavailable');
195
+ value = await controller.updateBusyMessageMode(payload.botId, payload.busyMessageMode);
196
+ }
187
197
  else if (endpoint === SLACK_ENDPOINTS.setContextEnhancement) {
188
198
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
189
199
  value = await controller.updateContextEnhancement(payload.botId, payload.config);
@@ -1,3 +1,4 @@
1
+ import { SET_MESSAGE_MODE_ENDPOINT, validMessageModePayload } from '../../../../src/channels/shared/message-mode.mjs';
1
2
  import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
2
3
  import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
3
4
  import { registerManagementRpc } from '../../../management-rpc.mjs';
@@ -26,6 +27,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
26
27
  setModel: SET_MODEL_ENDPOINT,
27
28
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
28
29
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
30
+ setBusyMessageMode: SET_MESSAGE_MODE_ENDPOINT,
29
31
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
30
32
  setAlias: SET_ALIAS_ENDPOINT,
31
33
  });
@@ -89,6 +91,10 @@ function payloadFailure(endpoint, payload) {
89
91
  return validAgentPresetPayload(payload)
90
92
  ? null : '请选择 Agent Preset。';
91
93
  }
94
+ if (endpoint === WECOM_ENDPOINTS.setBusyMessageMode) {
95
+ return validMessageModePayload(payload)
96
+ ? null : '请提交有效的消息处理方式。';
97
+ }
92
98
  if (endpoint === WECOM_ENDPOINTS.setContextEnhancement) {
93
99
  return validContextEnhancementPayload(payload)
94
100
  ? null : '请提交有效的上下文增强设置。';
@@ -204,6 +210,11 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
204
210
  await controller.updateModel(payload.botId, payload.model),
205
211
  cachedEncode,
206
212
  );
213
+ } else if (endpoint === WECOM_ENDPOINTS.setBusyMessageMode) {
214
+ if (typeof controller.updateBusyMessageMode !== 'function') throw new Error('Message mode update is unavailable');
215
+ value = await controller.updateBusyMessageMode(
216
+ payload.botId, payload.busyMessageMode, (status) => publicStatus(status, cachedEncode),
217
+ );
207
218
  } else if (endpoint === WECOM_ENDPOINTS.setContextEnhancement) {
208
219
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
209
220
  value = await controller.updateContextEnhancement(
@@ -1,3 +1,4 @@
1
+ import { SET_MESSAGE_MODE_ENDPOINT, validMessageModePayload } from '../../../../src/channels/shared/message-mode.mjs';
1
2
  import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
2
3
  import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
3
4
  import { registerManagementRpc } from '../../../management-rpc.mjs';
@@ -24,6 +25,7 @@ export const WECOM_APP_ENDPOINTS = Object.freeze({
24
25
  setModel: SET_MODEL_ENDPOINT,
25
26
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
26
27
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
28
+ setBusyMessageMode: SET_MESSAGE_MODE_ENDPOINT,
27
29
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
28
30
  setAlias: SET_ALIAS_ENDPOINT,
29
31
  });
@@ -110,6 +112,9 @@ function payloadFailure(endpoint, payload) {
110
112
  if (endpoint === WECOM_APP_ENDPOINTS.setAgentPreset) {
111
113
  return validAgentPresetPayload(payload) ? null : '请选择 Agent Preset。';
112
114
  }
115
+ if (endpoint === WECOM_APP_ENDPOINTS.setBusyMessageMode) {
116
+ return validMessageModePayload(payload) ? null : '请提交有效的消息处理方式。';
117
+ }
113
118
  if (endpoint === WECOM_APP_ENDPOINTS.setContextEnhancement) {
114
119
  return validContextEnhancementPayload(payload) ? null : '请提交有效的上下文增强设置。';
115
120
  }
@@ -191,6 +196,11 @@ export function createWecomAppRpcHandler(controller) {
191
196
  } else if (endpoint === WECOM_APP_ENDPOINTS.setModel) {
192
197
  if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
193
198
  value = await publicStatus(await controller.updateModel(payload.botId, payload.model));
199
+ } else if (endpoint === WECOM_APP_ENDPOINTS.setBusyMessageMode) {
200
+ if (typeof controller.updateBusyMessageMode !== 'function') throw new Error('Message mode update is unavailable');
201
+ value = await controller.updateBusyMessageMode(
202
+ payload.botId, payload.busyMessageMode, (status) => publicStatus(status),
203
+ );
194
204
  } else if (endpoint === WECOM_APP_ENDPOINTS.setContextEnhancement) {
195
205
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
196
206
  value = await controller.updateContextEnhancement(
@@ -1,3 +1,4 @@
1
+ import { SET_MESSAGE_MODE_ENDPOINT, validMessageModePayload } from '../../../../src/channels/shared/message-mode.mjs';
1
2
  import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
2
3
  import { registerManagementRpc } from '../../../management-rpc.mjs';
3
4
  import QRCode from 'qrcode';
@@ -32,6 +33,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
32
33
  setModel: SET_MODEL_ENDPOINT,
33
34
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
34
35
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
36
+ setBusyMessageMode: SET_MESSAGE_MODE_ENDPOINT,
35
37
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
36
38
  setAlias: SET_ALIAS_ENDPOINT,
37
39
  });
@@ -95,6 +97,10 @@ function payloadFailure(endpoint, payload) {
95
97
  return validAgentPresetPayload(payload)
96
98
  ? null : '请选择 Agent Preset。';
97
99
  }
100
+ if (endpoint === WEIXIN_ENDPOINTS.setBusyMessageMode) {
101
+ return validMessageModePayload(payload)
102
+ ? null : '请提交有效的消息处理方式。';
103
+ }
98
104
  if (endpoint === WEIXIN_ENDPOINTS.setContextEnhancement) {
99
105
  return validContextEnhancementPayload(payload)
100
106
  ? null : '请提交有效的上下文增强设置。';
@@ -235,6 +241,11 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl, logge
235
241
  await controller.updateModel(payload.botId, payload.model),
236
242
  cachedEncode,
237
243
  );
244
+ } else if (endpoint === WEIXIN_ENDPOINTS.setBusyMessageMode) {
245
+ if (typeof controller.updateBusyMessageMode !== 'function') throw new Error('Message mode update is unavailable');
246
+ value = await controller.updateBusyMessageMode(
247
+ payload.botId, payload.busyMessageMode, (status) => publicStatus(status, cachedEncode),
248
+ );
238
249
  } else if (endpoint === WEIXIN_ENDPOINTS.setContextEnhancement) {
239
250
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
240
251
  value = await controller.updateContextEnhancement(
@@ -1,3 +1,4 @@
1
+ import { SET_MESSAGE_MODE_ENDPOINT, validMessageModePayload } from '../../../../src/channels/shared/message-mode.mjs';
1
2
  import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
2
3
  import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
3
4
  import { registerManagementRpc } from '../../../management-rpc.mjs';
@@ -25,6 +26,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
25
26
  setModel: SET_MODEL_ENDPOINT,
26
27
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
27
28
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
29
+ setBusyMessageMode: SET_MESSAGE_MODE_ENDPOINT,
28
30
  });
29
31
  export const WHATSAPP_RPC_ENDPOINTS = Object.freeze(Object.values(WHATSAPP_ENDPOINTS));
30
32
 
@@ -79,6 +81,10 @@ function payloadFailure(endpoint, payload) {
79
81
  return validAgentPresetPayload(payload)
80
82
  ? null : '请选择 Agent Preset。';
81
83
  }
84
+ if (endpoint === WHATSAPP_ENDPOINTS.setBusyMessageMode) {
85
+ return validMessageModePayload(payload)
86
+ ? null : '请提交有效的消息处理方式。';
87
+ }
82
88
  if (endpoint === WHATSAPP_ENDPOINTS.setContextEnhancement) {
83
89
  return validContextEnhancementPayload(payload)
84
90
  ? null : '请提交有效的上下文增强设置。';
@@ -192,6 +198,11 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
192
198
  await controller.updateModel(payload.botId, payload.model),
193
199
  cachedEncode,
194
200
  );
201
+ } else if (endpoint === WHATSAPP_ENDPOINTS.setBusyMessageMode) {
202
+ if (typeof controller.updateBusyMessageMode !== 'function') throw new Error('Message mode update is unavailable');
203
+ value = await controller.updateBusyMessageMode(
204
+ payload.botId, payload.busyMessageMode, (status) => publicStatus(status, cachedEncode),
205
+ );
195
206
  } else if (endpoint === WHATSAPP_ENDPOINTS.setContextEnhancement) {
196
207
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
197
208
  value = await controller.updateContextEnhancement(
@@ -148,27 +148,32 @@ if (client.includes('settings.plugins.tab') || clientSources.includes('settings.
148
148
  }
149
149
  // Connections still have no channel-enable toggle. Checkable inputs are owned
150
150
  // only by the shared context editor, the saved-target Session sync row, the
151
- // email settings, and the Telegram thinking-traces block.
151
+ // email settings, the Telegram thinking-traces block, and Feishu panel settings.
152
152
  // The context editor contains one switch template and one mapped field-input
153
153
  // template; the delivery target adds one ordinary checkbox template; the
154
- // Telegram thinking-traces block adds one ordinary checkbox template.
154
+ // Telegram thinking-traces block adds one ordinary checkbox template. General
155
+ // settings adds one mapped switch template for the two Feishu panel preferences.
155
156
  const contextEditorSource = await readFile(resolve(root, 'plugin-src/client/context-enhancement.js'), 'utf8');
156
157
  const deliverySettingsSource = await readFile(resolve(root, 'plugin-src/client/delivery-settings.js'), 'utf8');
157
158
  const emailSettingsSource = await readFile(resolve(root, 'plugin-src/client/channels/email/index.js'), 'utf8');
158
159
  const thinkingTracesSource = await readFile(resolve(root, 'plugin-src/client/channels/telegram/thinking-traces.js'), 'utf8');
160
+ const botGeneralSettingsSource = await readFile(resolve(root, 'plugin-src/client/bot-general-settings.js'), 'utf8');
159
161
  const otherClientSources = clientSources
160
162
  .replace(contextEditorSource, '')
161
163
  .replace(deliverySettingsSource, '')
162
164
  .replace(emailSettingsSource, '')
163
- .replace(thinkingTracesSource, '');
165
+ .replace(thinkingTracesSource, '')
166
+ .replace(botGeneralSettingsSource, '');
164
167
  if (/role:\s*["']switch|type:\s*["']checkbox/.test(otherClientSources)
165
168
  || (deliverySettingsSource.match(/type:\s*["']checkbox["']/g) ?? []).length !== 1
166
169
  || /role:\s*["']switch["']/u.test(deliverySettingsSource)
167
170
  || (thinkingTracesSource.match(/type:\s*["']checkbox["']/g) ?? []).length !== 1
168
171
  || /role:\s*["']switch["']/u.test(thinkingTracesSource)
169
- || (client.match(/role:\s*["']switch["']/g) ?? []).length !== 1
170
- || (client.match(/type:\s*["']checkbox["']/g) ?? []).length !== 4) {
171
- throw new Error('checkable inputs must be limited to context enhancement, Session sync, email settings, and the Telegram thinking-traces toggle');
172
+ || (botGeneralSettingsSource.match(/type:\s*["']checkbox["']/g) ?? []).length !== 1
173
+ || (botGeneralSettingsSource.match(/role:\s*["']switch["']/g) ?? []).length !== 1
174
+ || (client.match(/role:\s*["']switch["']/g) ?? []).length !== 2
175
+ || (client.match(/type:\s*["']checkbox["']/g) ?? []).length !== 5) {
176
+ throw new Error('checkable inputs must be limited to context enhancement, Session sync, email settings, Telegram thinking traces, and Feishu panel settings');
172
177
  }
173
178
  for (const marker of ['bot.context-enhancement.set', '<dsh_im_source>', '<dsh_im_source_guidance>']) {
174
179
  if (!host.includes(marker) || !client.includes(marker)) {
@@ -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
  DINGTALK_DONE_REACTION_NAME,
@@ -15,6 +16,7 @@ import {
15
16
  } from '../shared/harness-question.mjs';
16
17
  import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
17
18
  import { runCompactCommand } from '../shared/compact-command.mjs';
19
+ import { isClearCommand, runClearCommand } from '../shared/clear-command.mjs';
18
20
  import { isHistoryCommand, runHistoryCommand } from '../shared/history-command.mjs';
19
21
  import {
20
22
  isControlCommand,
@@ -87,6 +89,7 @@ const HELP_TEXT_LINES = [
87
89
  '/m 或 /menu 打开下拉操作菜单',
88
90
  '/new 开启一个全新会话',
89
91
  '/compact 压缩当前会话的较早上下文',
92
+ '/clear 清空当前会话上下文(保留 Session 绑定和历史记录)',
90
93
  '/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)',
91
94
  '/workspace 工作区序号或绝对路径 切换工作区',
92
95
  '/workspacelist 列出工作区绝对路径',
@@ -539,6 +542,7 @@ export class DingtalkHarnessBridge {
539
542
  #maxMessageChars;
540
543
  #signal;
541
544
  #queues = new Map();
545
+ #busyMessages = new BusyMessageDispatcher();
542
546
  #pendingInteractions = new Map();
543
547
  #interactionKeys = new Map();
544
548
  #interactionTasks = new Set();
@@ -597,7 +601,7 @@ export class DingtalkHarnessBridge {
597
601
  return structuredClone(this.#status);
598
602
  }
599
603
 
600
- accept(message, { contextSnapshot } = {}) {
604
+ accept(message, { contextSnapshot, busyMessageMode } = {}) {
601
605
  if (this.#signal?.aborted) return Promise.resolve();
602
606
  const messageId = nonEmptyString(message?.msgId);
603
607
  const sender = senderStaffId(message);
@@ -716,6 +720,7 @@ export class DingtalkHarnessBridge {
716
720
  const commandRunner = isDingtalkMenuCommand(commandText)
717
721
  && !hasInboundFiles(promptMessage) && !hasInboundImages(promptMessage)
718
722
  ? async () => { await this.#showMenu(message, key); }
723
+ : isClearCommand(commandText) ? runClearCommand
719
724
  : isHistoryCommand(commandText) ? runHistoryCommand
720
725
  : hasInboundFiles(promptMessage) ? null : isControlCommand(commandText)
721
726
  ? runControlCommand
@@ -834,7 +839,28 @@ export class DingtalkHarnessBridge {
834
839
  pending.queue = current;
835
840
  return finish(current);
836
841
  }
837
- return finish(this.#enqueueMessage(message, messageId, sender, key, { statusReaction }));
842
+ return finish(this.#busyMessages.dispatch({
843
+ key, messageId, text: commandText, mode: busyMessageMode,
844
+ onFailure: (error) => this.#finishStatusReaction(statusReaction,
845
+ this.#signal?.aborted || error?.code === 'turn-stopped' ? 'clear' : 'error'),
846
+ eligible: () => Boolean(sessionWebhook) && addressed && String(message.msgtype).toLowerCase() === 'text'
847
+ && !hasInboundImages(promptMessage) && !hasInboundFiles(promptMessage)
848
+ && !hasReplyReference(promptMessage) && !isDingtalkMenuCommand(commandText)
849
+ && canAutomaticallySteer(this.#accessPolicy, conversationType, sender),
850
+ harness: this.#harness, state: this.#state, status: this.#status,
851
+ signal: this.#signal, control: { owner: this, key }, logger: this.#logger,
852
+ acceptedMessageIds: this.#acceptedMessageIds,
853
+ pendingInteraction: () => this.#pendingInteractions.has(key) || this.#approvals.hasPending(key),
854
+ enhancement: {
855
+ snapshot: this.#acceptedMessageIds.get(messageId),
856
+ source: () => ({ channel: 'dingtalk', senderId: sender, senderName: message.senderNick,
857
+ conversationTitle: message.conversationTitle, chatId: message.conversationId }),
858
+ },
859
+ isQueued: () => this.#queues.has(key),
860
+ enqueue: (options) => this.#enqueueMessage(message, messageId, sender, key, { ...options, statusReaction }),
861
+ send: (reply) => sessionWebhook && addressed
862
+ ? this.#send(sessionWebhook, reply, this.#atUsersFor(message)) : Promise.resolve(),
863
+ }));
838
864
  }
839
865
 
840
866
  async #showMenu(message, key) {
@@ -933,6 +959,7 @@ export class DingtalkHarnessBridge {
933
959
  result = { message: helpText() };
934
960
  } else {
935
961
  result = await runWorkspaceCommand(command, this.#harness, key)
962
+ ?? await runClearCommand(command, this.#harness, this.#state, key, options)
936
963
  ?? await runCompactCommand(command, this.#harness, this.#state, key, options)
937
964
  ?? await runControlCommand(command, this.#harness, this.#state, key, options)
938
965
  ?? await runHistoryCommand(command, this.#harness, this.#state, key, options)
@@ -1133,6 +1160,7 @@ export class DingtalkHarnessBridge {
1133
1160
  }
1134
1161
 
1135
1162
  async waitForIdle() {
1163
+ await this.#busyMessages.whenIdle();
1136
1164
  await Promise.allSettled([
1137
1165
  ...this.#queues.values(),
1138
1166
  ...[...this.#pendingInteractions.values()].flatMap((pending) => (
@@ -1162,6 +1190,7 @@ export class DingtalkHarnessBridge {
1162
1190
  hasFiles: hasInboundFiles(prompt),
1163
1191
  pendingInteraction: this.#pendingInteractions.has(key)
1164
1192
  || this.#approvals.hasPending(key),
1193
+ busy: this.#queues.has(key) || this.#batchInputs.status(key).phase !== 'idle',
1165
1194
  control: { owner: this, key },
1166
1195
  deferredDelivery: this.#deferred,
1167
1196
  enhancement: captureContextEnhancementSource(
@@ -5,7 +5,7 @@ import { t } from '../shared/i18n.mjs';
5
5
  export const DINGTALK_MENU_TEMPLATE_ID = '9ca31362-e5b3-409c-a694-6993706a6004.schema';
6
6
  export const DINGTALK_CARD_TOPIC = '/v1.0/card/instances/callback';
7
7
  export const DINGTALK_MENU_BUTTONS = Object.freeze({
8
- new: '/new', history: '/history', stop: '/stop', compact: '/compact',
8
+ new: '/new', history: '/history', stop: '/stop', compact: '/compact', clear: '/clear',
9
9
  status: '/status', help: '/help',
10
10
  });
11
11
 
@@ -311,6 +311,7 @@ export class DingtalkRuntime {
311
311
  } catch {
312
312
  // Optional settings failures leave the original message path active.
313
313
  }
314
+ const busyMessageMode = this.#harness.currentBusyMessageMode?.() ?? 'queue';
314
315
  const task = Promise.resolve().then(async () => {
315
316
  if (this.#bridge !== bridge) return;
316
317
  let message;
@@ -330,7 +331,7 @@ export class DingtalkRuntime {
330
331
  get botId() { return contextEnhancement?.botId; },
331
332
  }, message.conversationType === '1' || message.conversationType === 1 ? 'direct'
332
333
  : message.conversationType === '2' || message.conversationType === 2 ? 'group' : null);
333
- await bridge.accept(message, { contextSnapshot });
334
+ await bridge.accept(message, { contextSnapshot, busyMessageMode });
334
335
  }).catch(() => {
335
336
  if (signal.aborted || this.#bridge !== bridge) return;
336
337
  this.#status.lastError = t('钉钉消息处理失败。');
@@ -876,13 +876,14 @@ export class DiscordRuntime {
876
876
  this.#contextEnhancement,
877
877
  message.guild_id ? 'group' : 'direct',
878
878
  );
879
+ const busyMessageMode = this.#harness.currentBusyMessageMode?.() ?? 'queue';
879
880
  const pendingRoute = resolveDiscordMessageRoute(message, this.#config.platformId, {
880
881
  api: this.#api,
881
882
  channel: this.#channels.get(String(message.channel_id)),
882
883
  signal: this.#abortController?.signal,
883
884
  onChannel: (resolved) => this.#rememberChannel(resolved),
884
885
  });
885
- route = { pendingRoute, contextSnapshot };
886
+ route = { pendingRoute, contextSnapshot, busyMessageMode };
886
887
  this.#routing.set(messageId, route);
887
888
  void pendingRoute.finally(() => {
888
889
  if (this.#routing.get(messageId) === route) this.#routing.delete(messageId);
@@ -893,6 +894,7 @@ export class DiscordRuntime {
893
894
  if (normalized) {
894
895
  await bridge.accept(normalized, {
895
896
  contextSnapshot: route.contextSnapshot,
897
+ busyMessageMode: route.busyMessageMode,
896
898
  ...(accessDecision ? { accessDecision } : {}),
897
899
  });
898
900
  }