@xmanrui/dsh-im 4.9.1 → 4.11.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 (72) hide show
  1. package/PROACTIVE_DELIVERY.en.md +23 -1
  2. package/PROACTIVE_DELIVERY.md +23 -1
  3. package/README.en.md +5 -1
  4. package/README.md +6 -2
  5. package/lib/client.js +2077 -673
  6. package/lib/index.js +277 -272
  7. package/package.json +13 -1
  8. package/plugin-src/client/channel-card-meta.js +9 -1
  9. package/plugin-src/client/channels/dingtalk/api.js +4 -0
  10. package/plugin-src/client/channels/dingtalk/index.js +25 -2
  11. package/plugin-src/client/channels/feishu/api.js +6 -0
  12. package/plugin-src/client/channels/feishu/group-settings.js +581 -0
  13. package/plugin-src/client/channels/feishu/index.js +26 -150
  14. package/plugin-src/client/channels/qq/api.js +4 -0
  15. package/plugin-src/client/channels/qq/index.js +25 -2
  16. package/plugin-src/client/channels/shared/token-api.js +4 -0
  17. package/plugin-src/client/channels/shared/token-channel.js +25 -3
  18. package/plugin-src/client/channels/wecom/api.js +4 -0
  19. package/plugin-src/client/channels/wecom/index.js +25 -2
  20. package/plugin-src/client/channels/weixin/api.js +4 -0
  21. package/plugin-src/client/channels/weixin/index.js +25 -2
  22. package/plugin-src/client/channels/whatsapp/api.js +4 -0
  23. package/plugin-src/client/channels/whatsapp/index.js +24 -2
  24. package/plugin-src/client/delivery-settings.js +73 -5
  25. package/plugin-src/client/global-settings.js +341 -0
  26. package/plugin-src/client/i18n.js +49 -0
  27. package/plugin-src/client/index.js +47 -6
  28. package/plugin-src/client/model-setting.js +104 -0
  29. package/plugin-src/client/styles.js +99 -0
  30. package/plugin-src/host/channels/dingtalk/production.mjs +15 -0
  31. package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
  32. package/plugin-src/host/channels/feishu/production.mjs +17 -0
  33. package/plugin-src/host/channels/feishu/rpc.mjs +32 -0
  34. package/plugin-src/host/channels/qq/production.mjs +15 -0
  35. package/plugin-src/host/channels/qq/rpc.mjs +11 -0
  36. package/plugin-src/host/channels/shared/model-setting-rpc.mjs +19 -0
  37. package/plugin-src/host/channels/shared/production.mjs +15 -0
  38. package/plugin-src/host/channels/shared/rpc.mjs +9 -0
  39. package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
  40. package/plugin-src/host/channels/slack/production.mjs +15 -0
  41. package/plugin-src/host/channels/slack/rpc.mjs +9 -0
  42. package/plugin-src/host/channels/wecom/production.mjs +15 -0
  43. package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
  44. package/plugin-src/host/channels/weixin/production.mjs +15 -0
  45. package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
  46. package/plugin-src/host/channels/whatsapp/production.mjs +15 -0
  47. package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
  48. package/plugin-src/host/delivery-adapter.mjs +142 -3
  49. package/plugin-src/host/delivery-rpc.mjs +12 -0
  50. package/plugin-src/host/delivery-service.mjs +125 -3
  51. package/plugin-src/host/delivery-suggestions.mjs +46 -0
  52. package/plugin-src/host/harness-session-coordinator.mjs +32 -10
  53. package/plugin-src/host/inbound-ttl-rpc.mjs +83 -0
  54. package/plugin-src/host/inbound-ttl-runtime.mjs +85 -0
  55. package/plugin-src/host/inbound-ttl-service.mjs +179 -0
  56. package/plugin-src/host/index.mjs +26 -1
  57. package/plugin-src/host/modern-harness-api.mjs +58 -2
  58. package/plugin-src/host/session-sync-coordinator.mjs +217 -0
  59. package/scripts/verify-package.mjs +12 -6
  60. package/src/channels/feishu/bridge.mjs +164 -4
  61. package/src/channels/feishu/feishu-channel.mjs +45 -10
  62. package/src/channels/feishu/feishu-runtime.mjs +9 -0
  63. package/src/channels/feishu/message-utils.mjs +13 -0
  64. package/src/channels/feishu/multi-bot-controller.mjs +16 -0
  65. package/src/channels/feishu/plugin-config-store.mjs +1 -0
  66. package/src/channels/feishu/state-store.mjs +25 -0
  67. package/src/channels/shared/bot-workspace-store.mjs +291 -38
  68. package/src/channels/shared/harness-client.mjs +124 -43
  69. package/src/channels/shared/inbound-file.mjs +117 -2
  70. package/src/channels/shared/inbound-ttl-store.mjs +110 -0
  71. package/src/channels/shared/inbound-ttl.mjs +22 -0
  72. package/src/channels/shared/model-setting.mjs +110 -0
@@ -8,7 +8,6 @@ import {
8
8
  FEISHU_REGISTRATION_OPERATIONS,
9
9
  formatRemaining,
10
10
  normalizeBotsSnapshot,
11
- normalizeGroupResponseMode,
12
11
  normalizePollResult,
13
12
  normalizeProvisioning,
14
13
  presentError,
@@ -22,6 +21,11 @@ import {
22
21
  AgentPresetEditor,
23
22
  EMPTY_AGENT_PRESET_CATALOG,
24
23
  } from "../../agent-preset.js";
24
+ import {
25
+ EMPTY_MODEL_CATALOG,
26
+ ModelCatalogContext,
27
+ ModelEditor,
28
+ } from "../../model-setting.js";
25
29
  import { useWorkspaceSnapshotFence } from "../../workspace-snapshot-fence.js";
26
30
  import {
27
31
  BotSettingsButton,
@@ -459,89 +463,6 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
459
463
  );
460
464
  }
461
465
 
462
- function GroupResponseModeEditor({
463
- value,
464
- permissionGranted = false,
465
- disabled = false,
466
- authorizationDisabled = false,
467
- onSave,
468
- onAuthorize,
469
- }) {
470
- const current = normalizeGroupResponseMode(value);
471
- const [saving, setSaving] = React.useState(false);
472
- const [authorizing, setAuthorizing] = React.useState(false);
473
- const [error, setError] = React.useState(null);
474
-
475
- const change = async (event) => {
476
- const next = normalizeGroupResponseMode(event.target.value);
477
- if (next === current || saving || disabled) return;
478
- setSaving(true);
479
- setError(null);
480
- try {
481
- await onSave?.(next);
482
- } catch (cause) {
483
- setError(cause?.message ?? "群聊响应方式修改失败,请重试。");
484
- } finally {
485
- setSaving(false);
486
- }
487
- };
488
-
489
- const authorize = async () => {
490
- if (current !== "all" || saving || authorizing || disabled || authorizationDisabled) return;
491
- setAuthorizing(true);
492
- setError(null);
493
- try {
494
- await onAuthorize?.();
495
- } catch (cause) {
496
- setError(cause?.message ?? "群消息权限授权失败,请重试。");
497
- } finally {
498
- setAuthorizing(false);
499
- }
500
- };
501
-
502
- return h("div", { className: "bxf-responseMode dim-responseMode" },
503
- h("div", { className: "bxf-responseModeHeader dim-responseModeHeader" },
504
- h("span", null, "群聊响应方式"),
505
- saving || authorizing
506
- ? h("span", { className: "bxf-responseModeStatus dim-responseModeStatus" },
507
- saving ? "保存中…" : "正在准备授权…")
508
- : null),
509
- h("select", {
510
- className: "bxf-responseModeSelect dim-responseModeSelect",
511
- value: current,
512
- disabled: disabled || saving,
513
- "aria-label": "群聊响应方式",
514
- onChange: (event) => { void change(event); },
515
- },
516
- h("option", { value: "mention" }, "仅在 @机器人时响应(推荐)"),
517
- h("option", { value: "all" }, "响应所有群消息"),
518
- ),
519
- h("small", { className: "bxf-responseModeHelp dim-responseModeHelp" },
520
- current === "mention"
521
- ? permissionGranted
522
- ? "私聊始终响应;群聊仅处理明确 @当前机器人的消息。群消息权限已开通,再次切换无需授权。"
523
- : "私聊始终响应;群聊仅处理明确 @当前机器人的消息。选择全部消息后会打开飞书官方授权流程。"
524
- : permissionGranted
525
- ? "已开通“获取群组中所有消息”权限(im:message.group_msg);机器人会处理群聊中的所有可见消息。"
526
- : "尚未确认“获取群组中所有消息”权限,请完成飞书授权。"),
527
- current === "all"
528
- ? h("div", { className: "bxf-responseModePermissionAction dim-responseModePermissionAction" },
529
- h(Button, {
530
- className: "bxf-responseModePermissionButton",
531
- size: "small",
532
- disabled: disabled || authorizationDisabled || saving || authorizing,
533
- "aria-busy": authorizing ? "true" : undefined,
534
- "aria-label": permissionGranted ? "重新授权群消息权限" : "授权群消息权限",
535
- onClick: () => { void authorize(); },
536
- }, authorizing ? "正在准备…" : permissionGranted ? "重新授权" : "去授权"))
537
- : null,
538
- error ? h("p", {
539
- className: "bxf-responseModeError dim-responseModeError",
540
- role: "alert",
541
- }, error) : null,
542
- );
543
- }
544
-
545
466
  export function BotCard({
546
467
  connection,
547
468
  busy,
@@ -554,10 +475,9 @@ export function BotCard({
554
475
  onReconnect,
555
476
  onRepairCallback,
556
477
  onWorkspaceSave,
478
+ onModelSave,
557
479
  onAgentPresetSave,
558
480
  onContextEnhancementSave,
559
- onGroupResponseModeSave,
560
- onGroupMessagePermissionAuthorize,
561
481
  onRequestRemove,
562
482
  onConfirmRemove,
563
483
  onCancelRemove,
@@ -610,6 +530,11 @@ export function BotCard({
610
530
  botName: bot.name,
611
531
  connected,
612
532
  accessPolicy: connection.accessPolicy,
533
+ channelSettings: {
534
+ groupResponseMode: connection.groupResponseMode,
535
+ groupTopicReply: connection.groupTopicReply,
536
+ groupMessagePermissionGranted: connection.groupMessagePermissionGranted,
537
+ },
613
538
  })),
614
539
  ),
615
540
  h(WorkspaceEditor, {
@@ -617,6 +542,11 @@ export function BotCard({
617
542
  disabled: Boolean(busy),
618
543
  onSave: onWorkspaceSave,
619
544
  }),
545
+ h(ModelEditor, {
546
+ model: connection.model,
547
+ disabled: Boolean(busy),
548
+ onSave: onModelSave,
549
+ }),
620
550
  h(AgentPresetEditor, {
621
551
  agentPreset: connection.agentPreset,
622
552
  disabled: Boolean(busy),
@@ -627,14 +557,6 @@ export function BotCard({
627
557
  disabled: Boolean(busy),
628
558
  onSave: onContextEnhancementSave,
629
559
  }),
630
- h(GroupResponseModeEditor, {
631
- value: connection.groupResponseMode,
632
- permissionGranted: connection.groupMessagePermissionGranted,
633
- disabled: Boolean(busy),
634
- authorizationDisabled: repairDisabled,
635
- onSave: onGroupResponseModeSave,
636
- onAuthorize: onGroupMessagePermissionAuthorize,
637
- }),
638
560
  provisionContent
639
561
  ? h("section", {
640
562
  className: "bxf-botProvision dim-botProvision",
@@ -723,10 +645,9 @@ function BotList(props) {
723
645
  onReconnect: () => props.onReconnect(bot),
724
646
  onRepairCallback: () => props.onRepairCallback(bot),
725
647
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
648
+ onModelSave: (model) => props.onModelSave(bot, model),
726
649
  onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
727
650
  onContextEnhancementSave: (config) => props.onContextEnhancementSave(bot, config),
728
- onGroupResponseModeSave: (groupResponseMode) => props.onGroupResponseModeSave(bot, groupResponseMode),
729
- onGroupMessagePermissionAuthorize: () => props.onGroupMessagePermissionAuthorize(bot),
730
651
  onRequestRemove: () => props.onRequestRemove(bot),
731
652
  onConfirmRemove: () => props.onConfirmRemove(bot),
732
653
  onCancelRemove: props.onCancelRemove,
@@ -779,6 +700,7 @@ export function mergeFeishuSnapshotState(
779
700
  pageError: null,
780
701
  statusError: null,
781
702
  agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog,
703
+ modelCatalog: snapshot.modelCatalog ?? current.modelCatalog,
782
704
  };
783
705
  }
784
706
 
@@ -792,6 +714,7 @@ export function FeishuSettingsTab({ rpcCall }) {
792
714
  pageError: null,
793
715
  statusError: null,
794
716
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
717
+ modelCatalog: EMPTY_MODEL_CATALOG,
795
718
  });
796
719
  const [pageBusy, setPageBusy] = React.useState(false);
797
720
  const [provisionBusy, setProvisionBusy] = React.useState(false);
@@ -1333,56 +1256,6 @@ export function FeishuSettingsTab({ rpcCall }) {
1333
1256
  }
1334
1257
  }, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
1335
1258
 
1336
- const authorizeGroupMessages = React.useCallback(async (connection) => {
1337
- const { botId } = connection;
1338
- if (model.provisioning) {
1339
- throw new Error("请先完成当前飞书授权操作,再开通群消息权限。");
1340
- }
1341
- setRemoveTargetId(null);
1342
- setBotError(botId, null);
1343
- setTestNoticesByBot((current) => {
1344
- const next = { ...current };
1345
- delete next[botId];
1346
- return next;
1347
- });
1348
- await startProvisioning({
1349
- operation: GROUP_MESSAGE_PERMISSION_OPERATION,
1350
- bot: connection,
1351
- });
1352
- }, [model.provisioning, setBotError, startProvisioning]);
1353
-
1354
- const saveGroupResponseMode = React.useCallback(async (connection, groupResponseMode) => {
1355
- const { botId } = connection;
1356
- if (groupResponseMode === "all" && connection.groupMessagePermissionGranted !== true) {
1357
- await authorizeGroupMessages(connection);
1358
- return;
1359
- }
1360
- const snapshotVersion = workspaceFence.beginMutation();
1361
- setBotBusy(botId, "group-response-mode");
1362
- setBotError(botId, null);
1363
- try {
1364
- const snapshot = normalizeBotsSnapshot(await invoke(
1365
- FEISHU_ENDPOINTS.setGroupResponseMode,
1366
- { botId, groupResponseMode },
1367
- ));
1368
- if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
1369
- mergeSnapshot(snapshot);
1370
- }
1371
- } finally {
1372
- const shouldRefresh = workspaceFence.endMutation();
1373
- if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
1374
- if (mountedRef.current) setBotBusy(botId, null);
1375
- }
1376
- }, [
1377
- invoke,
1378
- authorizeGroupMessages,
1379
- loadStatus,
1380
- mergeSnapshot,
1381
- setBotBusy,
1382
- setBotError,
1383
- workspaceFence,
1384
- ]);
1385
-
1386
1259
  const requestRemove = React.useCallback((connection) => {
1387
1260
  setRemoveTargetId(connection.botId);
1388
1261
  }, []);
@@ -1490,7 +1363,9 @@ export function FeishuSettingsTab({ rpcCall }) {
1490
1363
  else removeButtonRefs.current.delete(botId);
1491
1364
  }, []);
1492
1365
 
1493
- return h(AgentPresetCatalogContext.Provider, {
1366
+ return h(ModelCatalogContext.Provider, {
1367
+ value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
1368
+ }, h(AgentPresetCatalogContext.Provider, {
1494
1369
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
1495
1370
  }, h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" },
1496
1371
  h(Heading, {
@@ -1538,14 +1413,15 @@ export function FeishuSettingsTab({ rpcCall }) {
1538
1413
  onReconnect: (bot) => void reconnectOneBot(bot),
1539
1414
  onRepairCallback: repairCallback,
1540
1415
  onWorkspaceSave: saveWorkspace,
1416
+ onModelSave: (connection, selectedModel) => saveBotSetting(
1417
+ connection, "model", FEISHU_ENDPOINTS.setModel, { model: selectedModel },
1418
+ ),
1541
1419
  onAgentPresetSave: (connection, agentPreset) => saveBotSetting(
1542
1420
  connection, "preset", FEISHU_ENDPOINTS.setAgentPreset, { agentPreset },
1543
1421
  ),
1544
1422
  onContextEnhancementSave: (connection, config) => saveBotSetting(
1545
1423
  connection, "context-enhancement", FEISHU_ENDPOINTS.setContextEnhancement, { config },
1546
1424
  ),
1547
- onGroupResponseModeSave: saveGroupResponseMode,
1548
- onGroupMessagePermissionAuthorize: authorizeGroupMessages,
1549
1425
  onRequestRemove: requestRemove,
1550
1426
  onConfirmRemove: (bot) => void confirmRemove(bot),
1551
1427
  onCancelRemove: cancelRemove,
@@ -1554,5 +1430,5 @@ export function FeishuSettingsTab({ rpcCall }) {
1554
1430
  })
1555
1431
  : null,
1556
1432
  ),
1557
- ));
1433
+ )));
1558
1434
  }
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
2
3
  import { normalizeLastMessageError } from '../../last-message-error.js';
3
4
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
4
5
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -14,6 +15,7 @@ export const QQ_ENDPOINTS = Object.freeze({
14
15
  reconnectBot: 'bot.reconnect',
15
16
  deleteBot: 'bot.delete',
16
17
  setWorkspace: 'bot.workspace.set',
18
+ setModel: SET_MODEL_ENDPOINT,
17
19
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
18
20
  setContextEnhancement: 'bot.context-enhancement.set',
19
21
  setAccessPolicy: 'bot.access-policy.set',
@@ -90,6 +92,7 @@ function normalizeBot(value) {
90
92
  connected,
91
93
  state: connected ? 'connected' : state,
92
94
  workspace: text(value.workspace, '', 4_096),
95
+ model: normalizeModelSelection(value.model),
93
96
  agentPreset: normalizeAgentPresetId(value.agentPreset),
94
97
  contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
95
98
  ...(Object.hasOwn(value, 'accessPolicy')
@@ -132,6 +135,7 @@ export function normalizeSnapshot(value) {
132
135
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
133
136
  provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
134
137
  agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
138
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
135
139
  ...(testMessage ? { testMessage } : {}),
136
140
  };
137
141
  }
@@ -10,6 +10,11 @@ import {
10
10
  AgentPresetEditor,
11
11
  EMPTY_AGENT_PRESET_CATALOG,
12
12
  } from '../../agent-preset.js';
13
+ import {
14
+ EMPTY_MODEL_CATALOG,
15
+ ModelCatalogContext,
16
+ ModelEditor,
17
+ } from '../../model-setting.js';
13
18
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
14
19
  import {
15
20
  BotSettingsButton,
@@ -167,6 +172,7 @@ export function AccountCard({
167
172
  removing,
168
173
  onReconnect,
169
174
  onWorkspaceSave,
175
+ onModelSave,
170
176
  onAgentPresetSave,
171
177
  onContextEnhancementSave,
172
178
  onRequestRemove,
@@ -204,6 +210,11 @@ export function AccountCard({
204
210
  disabled: Boolean(busy),
205
211
  onSave: onWorkspaceSave,
206
212
  }),
213
+ h(ModelEditor, {
214
+ model: account.model,
215
+ disabled: Boolean(busy),
216
+ onSave: onModelSave,
217
+ }),
207
218
  h(AgentPresetEditor, {
208
219
  agentPreset: account.agentPreset,
209
220
  disabled: Boolean(busy),
@@ -238,6 +249,7 @@ export function QqSettingsTab({ rpcCall }) {
238
249
  const [model, setModel] = React.useState({
239
250
  phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
240
251
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
252
+ modelCatalog: EMPTY_MODEL_CATALOG,
241
253
  });
242
254
  const [provision, setProvision] = React.useState(null);
243
255
  const [busy, setBusy] = React.useState(false);
@@ -278,6 +290,7 @@ export function QqSettingsTab({ rpcCall }) {
278
290
  setModel({
279
291
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
280
292
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
293
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
281
294
  });
282
295
  if (restore && snapshot.provisioning) setProvision({
283
296
  ...snapshot.provisioning,
@@ -345,6 +358,7 @@ export function QqSettingsTab({ rpcCall }) {
345
358
  setModel({
346
359
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
347
360
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
361
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
348
362
  });
349
363
  }
350
364
  setCredentialOpen(false);
@@ -408,6 +422,7 @@ export function QqSettingsTab({ rpcCall }) {
408
422
  setModel({
409
423
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
410
424
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
425
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
411
426
  });
412
427
  }
413
428
  return snapshot;
@@ -478,6 +493,12 @@ export function QqSettingsTab({ rpcCall }) {
478
493
  QQ_ENDPOINTS.setWorkspace,
479
494
  { botId: account.botId, workspace },
480
495
  ),
496
+ onModelSave: (selectedModel) => botAction(
497
+ account,
498
+ 'model',
499
+ QQ_ENDPOINTS.setModel,
500
+ { botId: account.botId, model: selectedModel },
501
+ ),
481
502
  onAgentPresetSave: (agentPreset) => botAction(
482
503
  account,
483
504
  'preset',
@@ -513,7 +534,9 @@ export function QqSettingsTab({ rpcCall }) {
513
534
  })
514
535
  : null;
515
536
 
516
- return h(AgentPresetCatalogContext.Provider, {
537
+ return h(ModelCatalogContext.Provider, {
538
+ value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
539
+ }, h(AgentPresetCatalogContext.Provider, {
517
540
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
518
541
  }, h('section', { className: 'ddt-page dqq-page dim-channelPage', 'aria-label': 'QQ 设置' },
519
542
  h(Heading, {
@@ -533,5 +556,5 @@ export function QqSettingsTab({ rpcCall }) {
533
556
  provisionView,
534
557
  model.bots.length === 0 && !provision && !credentialOpen
535
558
  ? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
536
- botList)));
559
+ botList))));
537
560
  }
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
2
3
  import { normalizeLastMessageError } from '../../last-message-error.js';
3
4
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
4
5
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -30,6 +31,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
30
31
  reconnectBot: 'bot.reconnect',
31
32
  deleteBot: 'bot.delete',
32
33
  setWorkspace: 'bot.workspace.set',
34
+ setModel: SET_MODEL_ENDPOINT,
33
35
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
34
36
  setContextEnhancement: 'bot.context-enhancement.set',
35
37
  setAccessPolicy: 'bot.access-policy.set',
@@ -60,6 +62,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
60
62
  connected,
61
63
  state: connected ? 'connected' : state,
62
64
  workspace: text(value.workspace, '', 4_096),
65
+ model: normalizeModelSelection(value.model),
63
66
  agentPreset: normalizeAgentPresetId(value.agentPreset),
64
67
  contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
65
68
  ...(Object.hasOwn(value, 'accessPolicy')
@@ -97,6 +100,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
97
100
  bots,
98
101
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
99
102
  agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
103
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
100
104
  };
101
105
  };
102
106
 
@@ -10,6 +10,11 @@ import {
10
10
  AgentPresetEditor,
11
11
  EMPTY_AGENT_PRESET_CATALOG,
12
12
  } from '../../agent-preset.js';
13
+ import {
14
+ EMPTY_MODEL_CATALOG,
15
+ ModelCatalogContext,
16
+ ModelEditor,
17
+ } from '../../model-setting.js';
13
18
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
14
19
  import {
15
20
  BotSettingsButton,
@@ -75,7 +80,7 @@ export function createTokenChannelSettings(definition) {
75
80
  accountSettingsEndpoint = null,
76
81
  } = definition;
77
82
 
78
- function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
83
+ function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
79
84
  const state = busy === 'reconnect' ? 'connecting' : account.state;
80
85
  const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning';
81
86
  const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪';
@@ -110,6 +115,11 @@ export function createTokenChannelSettings(definition) {
110
115
  disabled: Boolean(busy),
111
116
  onSave: onWorkspaceSave,
112
117
  }),
118
+ h(ModelEditor, {
119
+ model: account.model,
120
+ disabled: Boolean(busy),
121
+ onSave: onModelSave,
122
+ }),
113
123
  h(AgentPresetEditor, {
114
124
  agentPreset: account.agentPreset,
115
125
  disabled: Boolean(busy),
@@ -161,6 +171,7 @@ export function createTokenChannelSettings(definition) {
161
171
  const [model, setModel] = React.useState({
162
172
  phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
163
173
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
174
+ modelCatalog: EMPTY_MODEL_CATALOG,
164
175
  });
165
176
  const [credentialOpen, setCredentialOpen] = React.useState(false);
166
177
  const [credentialError, setCredentialError] = React.useState(null);
@@ -198,6 +209,7 @@ export function createTokenChannelSettings(definition) {
198
209
  setModel({
199
210
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
200
211
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
212
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
201
213
  });
202
214
  } catch (error) {
203
215
  if (error?.name !== 'AbortError' && mounted.current && !signal?.aborted
@@ -244,6 +256,7 @@ export function createTokenChannelSettings(definition) {
244
256
  setModel({
245
257
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
246
258
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
259
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
247
260
  });
248
261
  }
249
262
  setCredentialOpen(false);
@@ -266,6 +279,7 @@ export function createTokenChannelSettings(definition) {
266
279
  setModel({
267
280
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
268
281
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
282
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
269
283
  });
270
284
  }
271
285
  if (mounted.current && operation === 'reconnect') {
@@ -318,6 +332,12 @@ export function createTokenChannelSettings(definition) {
318
332
  endpoints.setWorkspace,
319
333
  { botId: account.botId, workspace },
320
334
  ),
335
+ onModelSave: (selectedModel) => botAction(
336
+ account,
337
+ 'model',
338
+ endpoints.setModel,
339
+ { botId: account.botId, model: selectedModel },
340
+ ),
321
341
  onAgentPresetSave: (agentPreset) => botAction(
322
342
  account,
323
343
  'preset',
@@ -350,7 +370,9 @@ export function createTokenChannelSettings(definition) {
350
370
  })))))
351
371
  : null;
352
372
 
353
- return h(AgentPresetCatalogContext.Provider, {
373
+ return h(ModelCatalogContext.Provider, {
374
+ value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
375
+ }, h(AgentPresetCatalogContext.Provider, {
354
376
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
355
377
  }, h('section', {
356
378
  className: `ddt-page ${pageClass} dim-channelPage`,
@@ -418,7 +440,7 @@ export function createTokenChannelSettings(definition) {
418
440
  'aria-hidden': 'true',
419
441
  }, h(LogoGlyph, { size: 64 }))))
420
442
  : null,
421
- botList)));
443
+ botList))));
422
444
  }
423
445
 
424
446
  return { SettingsTab, AccountCard };
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
2
3
  import { normalizeLastMessageError } from '../../last-message-error.js';
3
4
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
4
5
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -14,6 +15,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
14
15
  reconnectBot: 'bot.reconnect',
15
16
  deleteBot: 'bot.delete',
16
17
  setWorkspace: 'bot.workspace.set',
18
+ setModel: SET_MODEL_ENDPOINT,
17
19
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
18
20
  setContextEnhancement: 'bot.context-enhancement.set',
19
21
  setAccessPolicy: 'bot.access-policy.set',
@@ -99,6 +101,7 @@ function normalizeBot(value) {
99
101
  connected,
100
102
  state: connected ? 'connected' : state,
101
103
  workspace: text(value.workspace, '', 4_096),
104
+ model: normalizeModelSelection(value.model),
102
105
  agentPreset: normalizeAgentPresetId(value.agentPreset),
103
106
  contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
104
107
  ...(Object.hasOwn(value, 'accessPolicy')
@@ -131,6 +134,7 @@ export function normalizeSnapshot(value) {
131
134
  provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
132
135
  testMessage: normalizeTestMessage(source.testMessage),
133
136
  agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
137
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
134
138
  };
135
139
  }
136
140
 
@@ -10,6 +10,11 @@ import {
10
10
  AgentPresetEditor,
11
11
  EMPTY_AGENT_PRESET_CATALOG,
12
12
  } from '../../agent-preset.js';
13
+ import {
14
+ EMPTY_MODEL_CATALOG,
15
+ ModelCatalogContext,
16
+ ModelEditor,
17
+ } from '../../model-setting.js';
13
18
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
14
19
  import {
15
20
  BotSettingsButton,
@@ -166,6 +171,7 @@ export function AccountCard({
166
171
  removing,
167
172
  onReconnect,
168
173
  onWorkspaceSave,
174
+ onModelSave,
169
175
  onAgentPresetSave,
170
176
  onContextEnhancementSave,
171
177
  onRequestRemove,
@@ -203,6 +209,11 @@ export function AccountCard({
203
209
  disabled: Boolean(busy),
204
210
  onSave: onWorkspaceSave,
205
211
  }),
212
+ h(ModelEditor, {
213
+ model: account.model,
214
+ disabled: Boolean(busy),
215
+ onSave: onModelSave,
216
+ }),
206
217
  h(AgentPresetEditor, {
207
218
  agentPreset: account.agentPreset,
208
219
  disabled: Boolean(busy),
@@ -237,6 +248,7 @@ export function WecomSettingsTab({ rpcCall }) {
237
248
  const [model, setModel] = React.useState({
238
249
  phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
239
250
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
251
+ modelCatalog: EMPTY_MODEL_CATALOG,
240
252
  });
241
253
  const [provision, setProvision] = React.useState(null);
242
254
  const [busy, setBusy] = React.useState(false);
@@ -298,6 +310,7 @@ export function WecomSettingsTab({ rpcCall }) {
298
310
  setModel({
299
311
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
300
312
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
313
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
301
314
  });
302
315
  if (restore && snapshot.provisioning) setProvision({
303
316
  ...snapshot.provisioning,
@@ -365,6 +378,7 @@ export function WecomSettingsTab({ rpcCall }) {
365
378
  setModel({
366
379
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
367
380
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
381
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
368
382
  });
369
383
  }
370
384
  setCredentialOpen(false);
@@ -428,6 +442,7 @@ export function WecomSettingsTab({ rpcCall }) {
428
442
  setModel({
429
443
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
430
444
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
445
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
431
446
  });
432
447
  }
433
448
  return snapshot;
@@ -509,6 +524,12 @@ export function WecomSettingsTab({ rpcCall }) {
509
524
  WECOM_ENDPOINTS.setWorkspace,
510
525
  { botId: account.botId, workspace },
511
526
  ),
527
+ onModelSave: (selectedModel) => botAction(
528
+ account,
529
+ 'model',
530
+ WECOM_ENDPOINTS.setModel,
531
+ { botId: account.botId, model: selectedModel },
532
+ ),
512
533
  onAgentPresetSave: (agentPreset) => botAction(
513
534
  account,
514
535
  'preset',
@@ -544,7 +565,9 @@ export function WecomSettingsTab({ rpcCall }) {
544
565
  })
545
566
  : null;
546
567
 
547
- return h(AgentPresetCatalogContext.Provider, {
568
+ return h(ModelCatalogContext.Provider, {
569
+ value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
570
+ }, h(AgentPresetCatalogContext.Provider, {
548
571
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
549
572
  }, h('section', { className: 'ddt-page dwecom-page dim-channelPage', 'aria-label': '企业微信设置' },
550
573
  h(Heading, {
@@ -565,5 +588,5 @@ export function WecomSettingsTab({ rpcCall }) {
565
588
  provisionView,
566
589
  model.bots.length === 0 && !provision && !credentialOpen
567
590
  ? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
568
- botList)));
591
+ botList))));
569
592
  }
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
2
3
  import { normalizeLastMessageError } from '../../last-message-error.js';
3
4
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
4
5
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -13,6 +14,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
13
14
  reconnectBot: 'bot.reconnect',
14
15
  deleteBot: 'bot.delete',
15
16
  setWorkspace: 'bot.workspace.set',
17
+ setModel: SET_MODEL_ENDPOINT,
16
18
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
17
19
  setContextEnhancement: 'bot.context-enhancement.set',
18
20
  setAccessPolicy: 'bot.access-policy.set',
@@ -124,6 +126,7 @@ function normalizeBot(value) {
124
126
  connected,
125
127
  configured: value.configured === true,
126
128
  workspace: string(value.workspace).slice(0, 4_096),
129
+ model: normalizeModelSelection(value.model),
127
130
  agentPreset: normalizeAgentPresetId(value.agentPreset),
128
131
  contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
129
132
  ...(Object.hasOwn(value, 'accessPolicy')
@@ -169,6 +172,7 @@ export function normalizeSnapshot(value) {
169
172
  provisioning: value.provisioning ? normalizeProvisioning(value.provisioning) : null,
170
173
  testMessage: normalizeTestMessage(value.testMessage),
171
174
  agentPresetCatalog: normalizeAgentPresetCatalog(value.agentPresetCatalog),
175
+ modelCatalog: normalizeModelCatalog(value.modelCatalog),
172
176
  };
173
177
  }
174
178