@xmanrui/dsh-im 3.1.0 → 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.
Files changed (64) hide show
  1. package/README.en.md +15 -0
  2. package/README.md +15 -0
  3. package/lib/client.js +1653 -333
  4. package/lib/index.js +235 -223
  5. package/package.json +5 -1
  6. package/plugin-src/client/channels/dingtalk/api.js +3 -0
  7. package/plugin-src/client/channels/dingtalk/index.js +18 -5
  8. package/plugin-src/client/channels/feishu/api.js +3 -0
  9. package/plugin-src/client/channels/feishu/index.js +18 -5
  10. package/plugin-src/client/channels/qq/api.js +3 -0
  11. package/plugin-src/client/channels/qq/index.js +13 -0
  12. package/plugin-src/client/channels/shared/token-api.js +3 -0
  13. package/plugin-src/client/channels/shared/token-channel.js +13 -1
  14. package/plugin-src/client/channels/wecom/api.js +3 -0
  15. package/plugin-src/client/channels/wecom/index.js +13 -0
  16. package/plugin-src/client/channels/weixin/api.js +3 -0
  17. package/plugin-src/client/channels/weixin/index.js +19 -5
  18. package/plugin-src/client/channels/whatsapp/api.js +3 -0
  19. package/plugin-src/client/channels/whatsapp/index.js +13 -0
  20. package/plugin-src/client/context-enhancement.js +275 -0
  21. package/plugin-src/client/i18n.js +54 -3
  22. package/plugin-src/client/styles.js +78 -0
  23. package/plugin-src/client/update-panel.js +108 -7
  24. package/plugin-src/host/channels/dingtalk/production.mjs +1 -0
  25. package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
  26. package/plugin-src/host/channels/feishu/production.mjs +3 -0
  27. package/plugin-src/host/channels/feishu/rpc.mjs +13 -0
  28. package/plugin-src/host/channels/qq/production.mjs +1 -0
  29. package/plugin-src/host/channels/qq/rpc.mjs +11 -0
  30. package/plugin-src/host/channels/shared/context-enhancement-rpc.mjs +17 -0
  31. package/plugin-src/host/channels/shared/production.mjs +1 -0
  32. package/plugin-src/host/channels/shared/rpc.mjs +9 -0
  33. package/plugin-src/host/channels/shared/workspace-rpc.mjs +1 -0
  34. package/plugin-src/host/channels/slack/production.mjs +1 -0
  35. package/plugin-src/host/channels/slack/rpc.mjs +10 -0
  36. package/plugin-src/host/channels/wecom/production.mjs +1 -0
  37. package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
  38. package/plugin-src/host/channels/weixin/production.mjs +1 -0
  39. package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
  40. package/plugin-src/host/channels/whatsapp/production.mjs +1 -0
  41. package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
  42. package/plugin-src/host/update-runtime.mjs +3 -2
  43. package/plugin-src/host/update-service.mjs +2 -0
  44. package/scripts/verify-package.mjs +15 -2
  45. package/src/channels/dingtalk/dingtalk-api.mjs +39 -16
  46. package/src/channels/dingtalk/dingtalk-bridge.mjs +52 -23
  47. package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -1
  48. package/src/channels/discord/discord-runtime.mjs +18 -4
  49. package/src/channels/feishu/bridge.mjs +20 -4
  50. package/src/channels/feishu/feishu-channel.mjs +100 -44
  51. package/src/channels/feishu/feishu-runtime.mjs +4 -0
  52. package/src/channels/qq/qq-bridge.mjs +20 -4
  53. package/src/channels/qq/qq-runtime.mjs +4 -0
  54. package/src/channels/shared/bot-workspace-store.mjs +103 -6
  55. package/src/channels/shared/context-enhancement.mjs +135 -0
  56. package/src/channels/shared/i18n-en/feishu.mjs +2 -0
  57. package/src/channels/shared/text-harness-bridge.mjs +19 -4
  58. package/src/channels/slack/slack-runtime.mjs +4 -0
  59. package/src/channels/telegram/telegram-runtime.mjs +24 -2
  60. package/src/channels/wecom/wecom-bridge.mjs +18 -3
  61. package/src/channels/wecom/wecom-runtime.mjs +4 -0
  62. package/src/channels/weixin/weixin-bridge.mjs +19 -4
  63. package/src/channels/weixin/weixin-runtime.mjs +4 -0
  64. package/src/channels/whatsapp/whatsapp-runtime.mjs +5 -0
@@ -3,6 +3,7 @@ import { randomInt } from 'node:crypto';
3
3
  import { createEditableMessageStream, splitMessageText } from '../shared/editable-message-stream.mjs';
4
4
  import { createTextDeliveryBlock } from '../shared/semantic/delivery.mjs';
5
5
  import { t } from '../shared/i18n.mjs';
6
+ import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
6
7
  import { COMMANDS_MENU_BUTTON, TelegramApi } from './telegram-api.mjs';
7
8
  import { createTelegramBridgeStatus, TelegramHarnessBridge } from './telegram-bridge.mjs';
8
9
  import {
@@ -159,6 +160,11 @@ export function normalizeTelegramUpdate(update, {
159
160
  return {
160
161
  messageId: String(update.update_id),
161
162
  senderId: String(senderId),
163
+ contextSource: () => ({
164
+ senderName: [message.from?.first_name, message.from?.last_name]
165
+ .filter((value) => typeof value === 'string' && value.trim())
166
+ .map((value) => value.trim()).join(' ') || message.from?.username,
167
+ }),
162
168
  senderIsBot: message.from?.is_bot === true,
163
169
  kind: direct ? 'direct' : 'group',
164
170
  conversationId: messageThreadId === undefined
@@ -659,6 +665,7 @@ export class TelegramRuntime {
659
665
  #token;
660
666
  #harness;
661
667
  #state;
668
+ #contextEnhancement;
662
669
  #logger;
663
670
  #replyTimeoutMs;
664
671
  #createApi;
@@ -676,6 +683,7 @@ export class TelegramRuntime {
676
683
  token,
677
684
  harness,
678
685
  state,
686
+ contextEnhancement,
679
687
  logger = console,
680
688
  replyTimeoutMs = 600_000,
681
689
  createApi = (options) => new TelegramApi(options),
@@ -687,6 +695,7 @@ export class TelegramRuntime {
687
695
  this.#token = token;
688
696
  this.#harness = harness;
689
697
  this.#state = state;
698
+ this.#contextEnhancement = contextEnhancement;
690
699
  this.#logger = logger;
691
700
  this.#replyTimeoutMs = replyTimeoutMs;
692
701
  this.#createApi = createApi;
@@ -758,6 +767,7 @@ export class TelegramRuntime {
758
767
  bot: client,
759
768
  harness: this.#harness,
760
769
  state: this.#state,
770
+ contextEnhancement: this.#contextEnhancement,
761
771
  status: this.#status,
762
772
  logger: this.#logger,
763
773
  replyTimeoutMs: this.#replyTimeoutMs,
@@ -798,7 +808,19 @@ export class TelegramRuntime {
798
808
  while (!signal.aborted) {
799
809
  const updates = await this.#api.getUpdates({ offset: cursor, timeout: 25, signal });
800
810
  this.#status.lastCheckedAt = Date.now();
801
- for (const update of updates) {
811
+ if (signal.aborted) return;
812
+ // All updates have arrived together; cursor persistence must not move the
813
+ // settings boundary for the later messages in this received batch.
814
+ const received = updates.map((update) => {
815
+ const chatType = update?.message?.chat?.type;
816
+ return {
817
+ update,
818
+ contextSnapshot: captureContextEnhancement(this.#contextEnhancement,
819
+ chatType === 'private' ? 'direct'
820
+ : chatType === 'group' || chatType === 'supergroup' ? 'group' : null),
821
+ };
822
+ });
823
+ for (const { update, contextSnapshot } of received) {
802
824
  if (signal.aborted) return;
803
825
  const message = normalizeTelegramUpdate(update, {
804
826
  botId: this.#config.platformId,
@@ -810,7 +832,7 @@ export class TelegramRuntime {
810
832
  accessMode: this.#accessMode,
811
833
  allowedPrivateUserIds: this.#allowedPrivateUserIds,
812
834
  })) {
813
- void this.#bridge.accept(message).catch((error) => {
835
+ void this.#bridge.accept(message, { contextSnapshot }).catch((error) => {
814
836
  if (signal.aborted) return;
815
837
  this.#logger.error?.(
816
838
  `[dsh-im:telegram] bot ${this.#config.botId} message handling failed:`,
@@ -27,6 +27,7 @@ import {
27
27
  } from '../shared/preset-command.mjs';
28
28
  import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
29
29
  import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
30
+ import { captureContextEnhancement, enhanceContextContent } from '../shared/context-enhancement.mjs';
30
31
  import {
31
32
  hasInboundImages,
32
33
  ImagePromptError,
@@ -488,6 +489,7 @@ export class WecomHarnessBridge {
488
489
  #client;
489
490
  #harness;
490
491
  #state;
492
+ #contextEnhancement;
491
493
  #status;
492
494
  #logger;
493
495
  #replyTimeoutMs;
@@ -497,7 +499,8 @@ export class WecomHarnessBridge {
497
499
  #queues = new Map();
498
500
  #pendingInteractions = new Map();
499
501
  #interactionKeys = new Map();
500
- #acceptedMessageIds = new Set();
502
+ // Keep the accepted configuration through the existing queue/reply lifecycle.
503
+ #acceptedMessageIds = new Map();
501
504
  #approvalTasks = new Set();
502
505
  #commandTasks = new Set();
503
506
  #approvals;
@@ -508,6 +511,7 @@ export class WecomHarnessBridge {
508
511
  client,
509
512
  harness,
510
513
  state,
514
+ contextEnhancement,
511
515
  status = createWecomBridgeStatus(),
512
516
  logger = console,
513
517
  replyTimeoutMs = 600_000,
@@ -525,6 +529,7 @@ export class WecomHarnessBridge {
525
529
  this.#client = client;
526
530
  this.#harness = harness;
527
531
  this.#state = state;
532
+ this.#contextEnhancement = contextEnhancement;
528
533
  this.#status = status;
529
534
  this.#logger = logger;
530
535
  this.#replyTimeoutMs = replyTimeoutMs;
@@ -552,7 +557,10 @@ export class WecomHarnessBridge {
552
557
  || this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
553
558
 
554
559
  const key = conversationKey(frame);
555
- this.#acceptedMessageIds.add(messageId);
560
+ this.#acceptedMessageIds.set(messageId, captureContextEnhancement(
561
+ this.#contextEnhancement,
562
+ body.chattype === 'single' ? 'direct' : 'group',
563
+ ));
556
564
  if (body.chattype === 'single') {
557
565
  rememberConnectionTestTarget(this.#state, { chatId });
558
566
  }
@@ -948,9 +956,16 @@ export class WecomHarnessBridge {
948
956
  this.#logger.warn?.('[dsh-im:wecom] unable to start a stream; using an active reply:', error);
949
957
  }
950
958
 
951
- const content = hasImages
959
+ let content = hasImages
952
960
  ? await promptContentForMessage(message, { signal: this.#signal })
953
961
  : undefined;
962
+ const snapshot = this.#acceptedMessageIds.get(messageId);
963
+ if (snapshot) {
964
+ content = enhanceContextContent(content ?? text, snapshot, () => ({
965
+ channel: 'wecom',
966
+ senderId,
967
+ }));
968
+ }
954
969
  await this.#state.markSeen(messageId);
955
970
  promptRecorded = true;
956
971
  const { answer, artifacts = [] } = await askInWorkspaceSession({
@@ -28,6 +28,7 @@ export class WecomRuntime {
28
28
  #secret;
29
29
  #harness;
30
30
  #state;
31
+ #contextEnhancement;
31
32
  #logger;
32
33
  #replyTimeoutMs;
33
34
  #connectTimeoutMs;
@@ -45,6 +46,7 @@ export class WecomRuntime {
45
46
  secret,
46
47
  harness,
47
48
  state,
49
+ contextEnhancement,
48
50
  logger = console,
49
51
  replyTimeoutMs = 600_000,
50
52
  connectTimeoutMs = 20_000,
@@ -58,6 +60,7 @@ export class WecomRuntime {
58
60
  this.#secret = secret;
59
61
  this.#harness = harness;
60
62
  this.#state = state;
63
+ this.#contextEnhancement = contextEnhancement;
61
64
  this.#logger = logger;
62
65
  this.#replyTimeoutMs = replyTimeoutMs;
63
66
  this.#connectTimeoutMs = connectTimeoutMs;
@@ -107,6 +110,7 @@ export class WecomRuntime {
107
110
  client,
108
111
  harness: this.#harness,
109
112
  state: this.#state,
113
+ contextEnhancement: this.#contextEnhancement,
110
114
  status: this.#status,
111
115
  logger: this.#logger,
112
116
  replyTimeoutMs: this.#replyTimeoutMs,
@@ -33,6 +33,7 @@ import {
33
33
  } from '../shared/preset-command.mjs';
34
34
  import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
35
35
  import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
36
+ import { captureContextEnhancement, enhanceContextContent } from '../shared/context-enhancement.mjs';
36
37
  import {
37
38
  hasInboundImages,
38
39
  imagePromptDiagnostic,
@@ -258,6 +259,7 @@ export class WeixinHarnessBridge {
258
259
  #ownerUserId;
259
260
  #harness;
260
261
  #state;
262
+ #contextEnhancement;
261
263
  #status;
262
264
  #logger;
263
265
  #replyTimeoutMs;
@@ -267,7 +269,8 @@ export class WeixinHarnessBridge {
267
269
  #queues = new Map();
268
270
  #pendingInteractions = new Map();
269
271
  #interactionKeys = new Map();
270
- #acceptedMessageIds = new Set();
272
+ // Keep the accepted configuration through the existing queue/reply lifecycle.
273
+ #acceptedMessageIds = new Map();
271
274
  #approvalTasks = new Set();
272
275
  #commandTasks = new Set();
273
276
  #approvals;
@@ -288,6 +291,7 @@ export class WeixinHarnessBridge {
288
291
  ownerUserId,
289
292
  harness,
290
293
  state,
294
+ contextEnhancement,
291
295
  status = createWeixinBridgeStatus(),
292
296
  logger = console,
293
297
  replyTimeoutMs = 600_000,
@@ -307,6 +311,7 @@ export class WeixinHarnessBridge {
307
311
  this.#ownerUserId = ownerUserId;
308
312
  this.#harness = harness;
309
313
  this.#state = state;
314
+ this.#contextEnhancement = contextEnhancement;
310
315
  this.#status = status;
311
316
  this.#logger = logger;
312
317
  this.#replyTimeoutMs = replyTimeoutMs;
@@ -327,7 +332,10 @@ export class WeixinHarnessBridge {
327
332
  const sender = nonEmptyString(message?.from_user_id);
328
333
  if (!messageId || !sender || this.#state.hasSeen(messageId)
329
334
  || this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
330
- this.#acceptedMessageIds.add(messageId);
335
+ this.#acceptedMessageIds.set(messageId, captureContextEnhancement(
336
+ this.#contextEnhancement,
337
+ 'direct',
338
+ ));
331
339
  if (sender === this.#ownerUserId) {
332
340
  rememberConnectionTestTarget(this.#state, { toUserId: sender });
333
341
  }
@@ -650,16 +658,23 @@ export class WeixinHarnessBridge {
650
658
  let artifacts = [];
651
659
  await this.#startTyping(sender, contextToken);
652
660
  try {
653
- const content = hasImages
661
+ let content = hasImages
654
662
  ? await promptContentForMessage(promptMessage, { signal: this.#signal })
655
663
  : undefined;
664
+ const snapshot = this.#acceptedMessageIds.get(messageId);
665
+ if (snapshot) {
666
+ content = enhanceContextContent(content ?? text, snapshot, () => ({
667
+ channel: 'weixin',
668
+ senderId: sender,
669
+ }));
670
+ }
656
671
  await this.#state.markSeen(messageId);
657
672
  promptRecorded = true;
658
673
  ({ answer, artifacts = [] } = await askInWorkspaceSession({
659
674
  harness: this.#harness,
660
675
  state: this.#state,
661
676
  key,
662
- ...(hasImages ? { content } : { text }),
677
+ ...(content !== undefined ? { content } : { text }),
663
678
  createOptions: { signal: this.#signal },
664
679
  existsOptions: { signal: this.#signal },
665
680
  askOptions: {
@@ -108,6 +108,7 @@ export class WeixinRuntime {
108
108
  #token;
109
109
  #harness;
110
110
  #state;
111
+ #contextEnhancement;
111
112
  #logger;
112
113
  #replyTimeoutMs;
113
114
  #maxMessageChars;
@@ -124,6 +125,7 @@ export class WeixinRuntime {
124
125
  token,
125
126
  harness,
126
127
  state,
128
+ contextEnhancement,
127
129
  logger = console,
128
130
  replyTimeoutMs = 600_000,
129
131
  maxMessageChars = DEFAULT_WEIXIN_MAX_MESSAGE_CHARS,
@@ -137,6 +139,7 @@ export class WeixinRuntime {
137
139
  this.#token = token;
138
140
  this.#harness = harness;
139
141
  this.#state = state;
142
+ this.#contextEnhancement = contextEnhancement;
140
143
  this.#logger = logger;
141
144
  this.#replyTimeoutMs = replyTimeoutMs;
142
145
  this.#maxMessageChars = maxMessageChars;
@@ -178,6 +181,7 @@ export class WeixinRuntime {
178
181
  ownerUserId: this.#config.ownerUserId,
179
182
  harness: this.#harness,
180
183
  state: this.#state,
184
+ contextEnhancement: this.#contextEnhancement,
181
185
  status: this.#status,
182
186
  logger: this.#logger,
183
187
  replyTimeoutMs: this.#replyTimeoutMs,
@@ -234,6 +234,7 @@ export function normalizeWhatsappMessage(message, accountJid, {
234
234
  messageId: `${remoteJid}:${messageId}`,
235
235
  providerMessageId: messageId,
236
236
  senderId: senderJid,
237
+ contextSource: () => ({ senderName: message.pushName }),
237
238
  senderAlternateId: typeof senderAlternateJid === 'string' ? senderAlternateJid : '',
238
239
  senderIsBot: false,
239
240
  kind: group ? 'group' : 'direct',
@@ -536,6 +537,7 @@ export class WhatsappRuntime {
536
537
  #authDir;
537
538
  #harness;
538
539
  #state;
540
+ #contextEnhancement;
539
541
  #logger;
540
542
  #replyTimeoutMs;
541
543
  #connectTimeoutMs;
@@ -555,6 +557,7 @@ export class WhatsappRuntime {
555
557
  authDir,
556
558
  harness,
557
559
  state,
560
+ contextEnhancement,
558
561
  logger = console,
559
562
  replyTimeoutMs = 600_000,
560
563
  connectTimeoutMs = 30_000,
@@ -568,6 +571,7 @@ export class WhatsappRuntime {
568
571
  this.#authDir = authDir;
569
572
  this.#harness = harness;
570
573
  this.#state = state;
574
+ this.#contextEnhancement = contextEnhancement;
571
575
  this.#logger = logger;
572
576
  this.#replyTimeoutMs = replyTimeoutMs;
573
577
  this.#connectTimeoutMs = connectTimeoutMs;
@@ -673,6 +677,7 @@ export class WhatsappRuntime {
673
677
  bot: client,
674
678
  harness: this.#harness,
675
679
  state: this.#state,
680
+ contextEnhancement: this.#contextEnhancement,
676
681
  status: this.#status,
677
682
  logger: this.#logger,
678
683
  replyTimeoutMs: this.#replyTimeoutMs,