@xmanrui/dsh-im 4.18.1 → 4.19.1

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 (47) hide show
  1. package/README.en.md +1 -0
  2. package/README.md +1 -0
  3. package/lib/client.js +917 -521
  4. package/lib/index.js +269 -267
  5. package/package.json +1 -1
  6. package/plugin-src/client/bot-alias.js +169 -0
  7. package/plugin-src/client/channels/dingtalk/api.js +3 -0
  8. package/plugin-src/client/channels/dingtalk/index.js +7 -1
  9. package/plugin-src/client/channels/feishu/api.js +3 -0
  10. package/plugin-src/client/channels/feishu/index.js +7 -1
  11. package/plugin-src/client/channels/qq/api.js +3 -0
  12. package/plugin-src/client/channels/qq/index.js +9 -1
  13. package/plugin-src/client/channels/shared/token-api.js +3 -0
  14. package/plugin-src/client/channels/shared/token-channel.js +9 -2
  15. package/plugin-src/client/channels/wecom/api.js +3 -0
  16. package/plugin-src/client/channels/wecom/index.js +9 -1
  17. package/plugin-src/client/channels/wecom-app/api.js +3 -0
  18. package/plugin-src/client/channels/wecom-app/index.js +9 -1
  19. package/plugin-src/client/channels/weixin/api.js +3 -0
  20. package/plugin-src/client/channels/weixin/index.js +7 -1
  21. package/plugin-src/client/channels/whatsapp/api.js +3 -0
  22. package/plugin-src/client/channels/whatsapp/index.js +9 -1
  23. package/plugin-src/client/i18n.js +10 -0
  24. package/plugin-src/client/styles.js +41 -6
  25. package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
  26. package/plugin-src/host/channels/feishu/production.mjs +22 -0
  27. package/plugin-src/host/channels/feishu/rpc.mjs +14 -0
  28. package/plugin-src/host/channels/imessage/rpc.mjs +1 -0
  29. package/plugin-src/host/channels/qq/rpc.mjs +11 -0
  30. package/plugin-src/host/channels/shared/bot-alias-rpc.mjs +15 -0
  31. package/plugin-src/host/channels/shared/rpc.mjs +9 -0
  32. package/plugin-src/host/channels/slack/rpc.mjs +10 -0
  33. package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
  34. package/plugin-src/host/channels/wecom-app/rpc.mjs +10 -0
  35. package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
  36. package/plugin-src/host/channels/whatsapp/rpc.mjs +13 -0
  37. package/plugin-src/host/session-sync-coordinator.mjs +20 -1
  38. package/src/channels/feishu/bridge.mjs +330 -10
  39. package/src/channels/feishu/feishu-cards.mjs +1 -1
  40. package/src/channels/feishu/feishu-runtime.mjs +6 -0
  41. package/src/channels/feishu/state-store.mjs +17 -0
  42. package/src/channels/shared/bot-alias.mjs +29 -0
  43. package/src/channels/shared/bot-workspace-store.mjs +71 -3
  44. package/src/channels/shared/i18n-en/shared-a.mjs +2 -2
  45. package/src/channels/shared/message-failure.mjs +2 -1
  46. package/src/channels/shared/model-command.mjs +3 -2
  47. package/src/channels/shared/session-sync-registry.mjs +22 -0
@@ -1,3 +1,4 @@
1
+ import { validateBotAlias, withBotAlias } from './bot-alias.mjs';
1
2
  import {
2
3
  mkdir,
3
4
  readFile,
@@ -264,6 +265,16 @@ function normalizeDocument(value) {
264
265
  if (value.version === 1 && value.deliveryTargets !== undefined) return null;
265
266
  const deliveryTargets = normalizeDeliveryTargets(value.deliveryTargets, { version: value.version });
266
267
  if (!deliveryTargets) return null;
268
+ const aliases = Object.create(null);
269
+ if (value.aliases && typeof value.aliases === 'object' && !Array.isArray(value.aliases)) {
270
+ for (const [botId, alias] of Object.entries(value.aliases)) {
271
+ try {
272
+ botIdOf(botId);
273
+ const normalized = validateBotAlias(alias);
274
+ if (normalized) aliases[botId] = normalized;
275
+ } catch { /* A damaged display name must not disable the bot. */ }
276
+ }
277
+ }
267
278
  const accessPolicies = normalizeAccessPolicies(value.accessPolicies, workspaces);
268
279
  const version = Math.max(
269
280
  value.version,
@@ -279,6 +290,7 @@ function normalizeDocument(value) {
279
290
  contextEnhancement,
280
291
  deliveryTargets,
281
292
  accessPolicies,
293
+ aliases,
282
294
  };
283
295
  }
284
296
 
@@ -290,8 +302,10 @@ function storedDocument({
290
302
  contextEnhancement,
291
303
  deliveryTargets,
292
304
  accessPolicies,
305
+ aliases,
293
306
  }) {
294
307
  const document = { version, workspaces };
308
+ if (Object.keys(aliases).length > 0) document.aliases = aliases;
295
309
  if (Object.keys(agentPresets).length > 0) document.agentPresets = agentPresets;
296
310
  if (Object.keys(models).length > 0) document.models = models;
297
311
  if (Object.keys(contextEnhancement).length > 0) {
@@ -346,6 +360,7 @@ export class BotWorkspaceStore {
346
360
  #workspaces = {};
347
361
  #agentPresets = {};
348
362
  #models = {};
363
+ #aliases = Object.create(null);
349
364
  #contextEnhancement = {};
350
365
  #deliveryTargets = Object.create(null);
351
366
  #accessPolicies = Object.create(null);
@@ -373,6 +388,7 @@ export class BotWorkspaceStore {
373
388
  this.#workspaces = normalized.workspaces;
374
389
  this.#agentPresets = normalized.agentPresets;
375
390
  this.#models = normalized.models;
391
+ this.#aliases = normalized.aliases;
376
392
  this.#contextEnhancement = normalized.contextEnhancement;
377
393
  this.#deliveryTargets = normalized.deliveryTargets;
378
394
  this.#accessPolicies = normalized.accessPolicies;
@@ -382,6 +398,7 @@ export class BotWorkspaceStore {
382
398
  this.#workspaces = {};
383
399
  this.#agentPresets = {};
384
400
  this.#models = {};
401
+ this.#aliases = Object.create(null);
385
402
  this.#contextEnhancement = {};
386
403
  this.#deliveryTargets = Object.create(null);
387
404
  this.#accessPolicies = Object.create(null);
@@ -427,6 +444,11 @@ export class BotWorkspaceStore {
427
444
  return selection ? { ...selection } : null;
428
445
  }
429
446
 
447
+ aliasFor(botId) {
448
+ const id = botIdOf(botId);
449
+ return this.has(id) && Object.hasOwn(this.#aliases, id) ? this.#aliases[id] : '';
450
+ }
451
+
430
452
  contextEnhancementFor(botId) {
431
453
  const id = botIdOf(botId);
432
454
  return this.has(id) && Object.hasOwn(this.#contextEnhancement, id)
@@ -745,6 +767,26 @@ export class BotWorkspaceStore {
745
767
  });
746
768
  }
747
769
 
770
+ async setAlias(botId, value, { incarnation } = {}) {
771
+ const id = botIdOf(botId);
772
+ const expectedIncarnation = incarnation === undefined ? this.incarnationFor(id) : incarnation;
773
+ const alias = validateBotAlias(value);
774
+ return this.#enqueue(id, async () => {
775
+ if (!this.has(id) || expectedIncarnation !== this.incarnationFor(id)) {
776
+ const error = new Error('找不到要修改的机器人。');
777
+ error.code = 'workspace-bot-not-found';
778
+ throw error;
779
+ }
780
+ const next = { ...this.#aliases };
781
+ if (alias) next[id] = alias;
782
+ else delete next[id];
783
+ await this.#persist(this.#contextEnhancement, this.#deliveryTargets,
784
+ this.#version, this.#accessPolicies, next);
785
+ this.#aliases = next;
786
+ return alias;
787
+ });
788
+ }
789
+
748
790
  async setContextEnhancement(botId, value, { incarnation } = {}) {
749
791
  const id = botIdOf(botId);
750
792
  const expectedIncarnation = incarnation === undefined ? this.incarnationFor(id) : incarnation;
@@ -945,6 +987,7 @@ export class BotWorkspaceStore {
945
987
  ...Object.keys(this.#workspaces),
946
988
  ...Object.keys(this.#agentPresets),
947
989
  ...Object.keys(this.#models),
990
+ ...Object.keys(this.#aliases),
948
991
  ...Object.keys(this.#contextEnhancement),
949
992
  ...Object.keys(this.#deliveryTargets),
950
993
  ...Object.keys(this.#accessPolicies),
@@ -962,6 +1005,7 @@ export class BotWorkspaceStore {
962
1005
  bots: status.bots.map((bot) => bot?.botId
963
1006
  ? {
964
1007
  ...bot,
1008
+ ...(this.aliasFor(bot.botId) ? { bot: withBotAlias(bot.bot, this.aliasFor(bot.botId)) } : {}),
965
1009
  workspace: this.workspaceFor(bot.botId),
966
1010
  agentPreset: this.agentPresetFor(bot.botId),
967
1011
  model: this.modelFor(bot.botId),
@@ -997,14 +1041,16 @@ export class BotWorkspaceStore {
997
1041
  const hadWorkspace = Object.hasOwn(this.#workspaces, id);
998
1042
  const hadPreset = Object.hasOwn(this.#agentPresets, id);
999
1043
  const hadModel = Object.hasOwn(this.#models, id);
1044
+ const hadAlias = Object.hasOwn(this.#aliases, id);
1000
1045
  const hadContextEnhancement = Object.hasOwn(this.#contextEnhancement, id);
1001
1046
  const hadDeliveryTargets = Object.hasOwn(this.#deliveryTargets, id);
1002
1047
  const hadAccessPolicy = Object.hasOwn(this.#accessPolicies, id);
1003
- const needsCleanup = hadWorkspace || hadPreset || hadModel || hadContextEnhancement
1048
+ const needsCleanup = hadWorkspace || hadPreset || hadModel || hadAlias || hadContextEnhancement
1004
1049
  || hadDeliveryTargets || hadAccessPolicy || this.#dirtyRemovals.has(id);
1005
1050
  delete this.#workspaces[id];
1006
1051
  delete this.#agentPresets[id];
1007
1052
  delete this.#models[id];
1053
+ delete this.#aliases[id];
1008
1054
  delete this.#contextEnhancement[id];
1009
1055
  delete this.#deliveryTargets[id];
1010
1056
  delete this.#accessPolicies[id];
@@ -1042,6 +1088,7 @@ export class BotWorkspaceStore {
1042
1088
  deliveryTargets = this.#deliveryTargets,
1043
1089
  version = this.#version,
1044
1090
  accessPolicies = this.#accessPolicies,
1091
+ aliases = this.#aliases,
1045
1092
  ) {
1046
1093
  await writeStoredDocument(this.#path, storedDocument({
1047
1094
  version,
@@ -1051,6 +1098,7 @@ export class BotWorkspaceStore {
1051
1098
  contextEnhancement,
1052
1099
  deliveryTargets,
1053
1100
  accessPolicies,
1101
+ aliases,
1054
1102
  }));
1055
1103
  this.#dirtyRemovals.clear();
1056
1104
  }
@@ -1059,6 +1107,7 @@ export class BotWorkspaceStore {
1059
1107
  if (Object.keys(this.#workspaces).length > 0
1060
1108
  || Object.keys(this.#agentPresets).length > 0
1061
1109
  || Object.keys(this.#models).length > 0
1110
+ || Object.keys(this.#aliases).length > 0
1062
1111
  || Object.keys(this.#contextEnhancement).length > 0
1063
1112
  || Object.keys(this.#deliveryTargets).length > 0
1064
1113
  || Object.keys(this.#accessPolicies).length > 0) {
@@ -1350,6 +1399,7 @@ export function createBotWorkspaceScope(
1350
1399
  }
1351
1400
  if (property === 'createSession') {
1352
1401
  return async (options = {}) => {
1402
+ const { inheritBotModel = true, ...createOptions } = options;
1353
1403
  await workspaces.whenBotIdle(botId);
1354
1404
  if (!isCurrentScope()) {
1355
1405
  const error = new Error('找不到要修改的机器人。');
@@ -1358,9 +1408,9 @@ export function createBotWorkspaceScope(
1358
1408
  }
1359
1409
  const generation = workspaces.generationFor(botId);
1360
1410
  const agentPreset = workspaces.agentPresetFor(botId);
1361
- const model = workspaces.modelFor(botId);
1411
+ const model = inheritBotModel === false ? null : workspaces.modelFor(botId);
1362
1412
  const sessionId = await target.createSession({
1363
- ...options,
1413
+ ...createOptions,
1364
1414
  workspace: workspaces.workspaceFor(botId),
1365
1415
  ...(agentPreset == null ? {} : { agentPreset }),
1366
1416
  });
@@ -1645,6 +1695,23 @@ export function createWorkspaceAwareController(controller, {
1645
1695
  );
1646
1696
  });
1647
1697
  };
1698
+ const updateAlias = (botId, value, projectStatus) => {
1699
+ const incarnation = workspaces.incarnationFor(botId);
1700
+ const alias = validateBotAlias(value);
1701
+ return withBotTransition(botId, async () => {
1702
+ const snapshot = await decorate(await controller.status());
1703
+ if (!snapshot?.bots?.some((bot) => bot?.botId === botId)) {
1704
+ const error = new Error('找不到要修改的机器人。');
1705
+ error.code = 'workspace-bot-not-found';
1706
+ throw error;
1707
+ }
1708
+ const updated = { ...snapshot, bots: snapshot.bots.map((bot) => bot.botId === botId
1709
+ ? { ...bot, bot: withBotAlias(bot.bot, alias) } : bot) };
1710
+ const result = projectStatus ? await projectStatus(updated) : updated;
1711
+ await workspaces.setAlias(botId, alias, { incarnation });
1712
+ return result;
1713
+ });
1714
+ };
1648
1715
  const updateContextEnhancement = (botId, value, projectStatus) => {
1649
1716
  const incarnation = workspaces.incarnationFor(botId);
1650
1717
  const config = validateContextEnhancementConfig(value);
@@ -1743,6 +1810,7 @@ export function createWorkspaceAwareController(controller, {
1743
1810
  if (property === 'updateWorkspace') return updateWorkspace;
1744
1811
  if (property === 'updateAgentPreset') return updateAgentPreset;
1745
1812
  if (property === 'updateModel') return updateModel;
1813
+ if (property === 'updateAlias') return updateAlias;
1746
1814
  if (property === 'updateContextEnhancement') return updateContextEnhancement;
1747
1815
  if (property === 'updateAccessPolicy') return updateAccessPolicy;
1748
1816
  const value = Reflect.get(target, property, target);
@@ -57,8 +57,8 @@ export default {
57
57
  'This conversation exceeds the model context limit. Send /compact or /new, then try again.',
58
58
  '当前模型不存在或暂不可用。请发送 /models 查看并使用 /model 切换模型。':
59
59
  'The current model does not exist or is unavailable. Send /models and use /model to switch models.',
60
- '当前模型不存在或不支持所选配置。请发送 /models,并使用 /model 重新选择。':
61
- 'The current model does not exist or does not support the selected settings. Send /models and use /model to choose again.',
60
+ '当前模型不存在或不支持所选配置。请发送 /models,再用 /model <序号> 为当前聊天重新选择。若要修改后续新会话的默认模型,请到 DSH 设置 → IM机器人 → 对应机器人卡片修改。':
61
+ 'The current model does not exist or does not support the selected settings. Send /models, then use /model <index> to choose a model for the current conversation. To change the default model for new conversations, open DSH Settings → IM Bots → the corresponding bot card.',
62
62
  '当前模型不支持这类内容或所选配置。请调整内容、模型或推理等级后重试。':
63
63
  'The current model does not support this content or the selected settings. Adjust the content, model, or reasoning effort and try again.',
64
64
  '当前模型不支持所选配置。请切换模型或推理等级后重试。':
@@ -47,7 +47,7 @@ const FAILURE_MESSAGES = Object.freeze({
47
47
  MODEL_CONTEXT_LIMIT:
48
48
  '当前会话内容超过模型上下文上限。请发送 /compact 或 /new 后重试。',
49
49
  MODEL_UNAVAILABLE:
50
- '当前模型不存在或不支持所选配置。请发送 /models,并使用 /model 重新选择。',
50
+ '当前模型不存在或不支持所选配置。请发送 /models,再用 /model <序号> 为当前聊天重新选择。若要修改后续新会话的默认模型,请到 DSH 设置 → IM机器人 → 对应机器人卡片修改。',
51
51
  MODEL_CONFIG:
52
52
  '当前模型不支持这类内容或所选配置。请调整内容、模型或推理等级后重试。',
53
53
  MODEL_TIMEOUT:
@@ -125,6 +125,7 @@ function failureCode(error) {
125
125
  }
126
126
  if (code === 'harness-turn-failed') return 'INTERNAL_UNKNOWN';
127
127
  if (['harness-http-failed', 'harness-rpc-rejected'].includes(code)) return 'HARNESS_SERVICE';
128
+ if (code === 'model-unavailable' || code === 'session/model-unavailable') return 'MODEL_UNAVAILABLE';
128
129
  if (code === 'model-empty-response') return 'MODEL_EMPTY_REPLY';
129
130
  if (code === 'model-max-tokens') return 'MODEL_OUTPUT_LIMIT';
130
131
  if (code === 'turn-blocked') return 'TURN_BLOCKED';
@@ -393,7 +393,7 @@ function modelErrorMessage(error, action) {
393
393
  if (code === 'session-not-found') {
394
394
  return t('当前聊天绑定的会话已不存在,请重试。');
395
395
  }
396
- if (code === 'model-unavailable') {
396
+ if (code === 'model-unavailable' || code === 'session/model-unavailable') {
397
397
  if (action === 'reasoning-select') {
398
398
  return t('无法切换推理等级。当前模型或推理等级不可用。');
399
399
  }
@@ -704,7 +704,8 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
704
704
  || typeof state?.setSession !== 'function') {
705
705
  throw new TypeError('Harness cannot create a conversation session');
706
706
  }
707
- const sessionId = await harness.createSession(requestOptions);
707
+ // An explicit choice must remain usable even when the saved bot model expires.
708
+ const sessionId = await harness.createSession({ ...requestOptions, inheritBotModel: false });
708
709
  if (typeof sessionId !== 'string' || !sessionId) {
709
710
  throw new TypeError('Harness returned an invalid session id');
710
711
  }
@@ -0,0 +1,22 @@
1
+ /** Optional per-bot card delivery. A registration is not a delivery receipt:
2
+ * the coordinator must await the renderer before suppressing final text. */
3
+ const renderers = new Map();
4
+
5
+ function botKey({ channel, botId }) {
6
+ return JSON.stringify([channel, botId]);
7
+ }
8
+
9
+ export function registerSessionSyncMirror(target, render) {
10
+ const key = botKey(target);
11
+ const entry = { render };
12
+ renderers.set(key, entry);
13
+ return () => {
14
+ if (renderers.get(key) === entry) renderers.delete(key);
15
+ };
16
+ }
17
+
18
+ export async function deliverSessionSyncMirror(target, sessionId, turn, text) {
19
+ const entry = renderers.get(botKey(target));
20
+ if (!entry) return false;
21
+ return await entry.render({ target, sessionId, turn, text }) === true;
22
+ }