@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
@@ -61,6 +61,15 @@ const CSS = String.raw`
61
61
  .dim-githubArrow { font-size: 13px; line-height: 1; }
62
62
  .dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
63
63
  .dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
64
+ .dim-generalSettingsAction { position: relative; display: inline-flex; flex: none; }
65
+ .dim-generalSettingsButton { width: 30px; height: 30px; display: grid; place-items: center; flex: none; padding: 0; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); cursor: pointer; transition: border-color .15s ease, color .15s ease, background .15s ease, box-shadow .15s ease; }
66
+ .dim-generalSettingsButton svg { display: block; }
67
+ .dim-generalSettingsButton:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
68
+ .dim-generalSettingsButton[aria-current="page"] { border-color: color-mix(in srgb, var(--dim-blue) 42%, var(--dsw-alias-border-l2, #dfe1e5)); color: var(--dim-blue); background: var(--dim-blue-soft); }
69
+ .dim-generalSettingsButton:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; }
70
+ .dim-generalSettingsTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
71
+ .dim-generalSettingsAction:hover .dim-generalSettingsTooltip, .dim-generalSettingsButton:focus-visible + .dim-generalSettingsTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
72
+ .dim-generalSettingsButton[aria-current="page"] + .dim-generalSettingsTooltip { opacity: 0; visibility: hidden; transform: translateY(-3px); }
64
73
  .dim-layout { display: grid; grid-template-columns: 174px 1px minmax(0, 1fr); gap: 24px; align-items: start; }
65
74
  .dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding: 0 4px 1px 1px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l2, #dfe1e5) transparent; }
66
75
  .dim-rail::-webkit-scrollbar { width: 4px; }
@@ -382,6 +391,45 @@ const CSS = String.raw`
382
391
  .dim-botSettingsTab[aria-selected="true"]::after { background: var(--dsw-alias-state-business-primary, #3370ff); transform: scaleX(1); }
383
392
  .dim-botSettingsTab:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: -2px; border-radius: 5px; }
384
393
  .dim-botSettingsTabPanel { min-width: 0; display: grid; gap: 14px; padding-top: 14px; }
394
+ .dim-feishuGroupSettings { min-width: 0; display: grid; gap: 14px; }
395
+ .dim-feishuGroupControls { min-width: 0; display: grid; gap: 12px; }
396
+ .dim-feishuGroupControl { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); }
397
+ .dim-feishuGroupControlHeader { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
398
+ .dim-feishuGroupControlHeader h3 { min-width: 0; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: 21px; font-weight: 650; }
399
+ .dim-feishuGroupControlStatus { flex: none; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; }
400
+ .dim-feishuGroupSelect { min-width: 0; width: 100%; height: 38px; padding: 0 34px 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; line-height: 38px; cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease; }
401
+ .dim-feishuGroupSelect:hover:not(:disabled) { border-color: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 42%, var(--dsw-alias-border-l2, #dfe1e5)); }
402
+ .dim-feishuGroupSelect:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary, #3370ff); box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 15%, transparent); }
403
+ .dim-feishuGroupSelect:disabled { cursor: not-allowed; opacity: .55; }
404
+ .dim-feishuGroupHelp { margin: -2px 0 0; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: 19px; }
405
+ .dim-feishuGroupPermissionAction { display: flex; justify-content: flex-start; }
406
+ .dim-feishuGroupPermissionAction .dim-deliveryButton { color: var(--dsw-alias-state-business-primary, #3370ff); border-color: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 30%, var(--dsw-alias-border-l2, #dfe1e5)); }
407
+ .dim-feishuGroupError, .dim-feishuGroupRefreshError { margin: 0; padding: 9px 11px; border-radius: 8px; font-size: 12px; line-height: 18px; }
408
+ .dim-feishuGroupError, .dim-feishuGroupRefreshError { color: var(--dsw-alias-state-error-primary, #d54941); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 7%, var(--dsw-alias-bg-layer-1, #fff)); }
409
+ .dim-feishuGroupAuthorization { min-width: 0; display: grid; grid-template-columns: 184px minmax(0, 1fr); align-items: start; gap: 24px; padding: 18px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 12px; background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 2.5%, var(--dsw-alias-bg-layer-3, #fff)); }
410
+ .dim-feishuGroupAuthorizationState { min-height: 126px; grid-template-columns: 32px minmax(0, 1fr); align-items: center; }
411
+ .dim-feishuGroupAuthorizationState h3, .dim-feishuGroupAuthorizationError h3, .dim-feishuGroupAuthorizationCopy h3 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 16px; line-height: 24px; font-weight: 650; }
412
+ .dim-feishuGroupAuthorizationState p, .dim-feishuGroupAuthorizationError p, .dim-feishuGroupAuthorizationCopy > p { margin: 5px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 19px; }
413
+ .dim-feishuGroupSpinner { width: 24px; height: 24px; border: 3px solid var(--dsw-alias-border-l2, #dfe1e5); border-top-color: var(--dsw-alias-state-business-primary, #3370ff); border-radius: 50%; animation: dim-spin .8s linear infinite; }
414
+ .dim-feishuGroupAuthorizationError { grid-template-columns: minmax(0, 1fr) max-content; align-items: center; border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 28%, var(--dsw-alias-border-l2, #dfe1e5)); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 4%, var(--dsw-alias-bg-layer-3, #fff)); }
415
+ .dim-feishuGroupAuthorizationError code { display: inline-block; margin-top: 6px; color: var(--dsw-alias-label-tertiary, #8f959e); font: 11px/17px ui-monospace, SFMono-Regular, Menlo, monospace; }
416
+ .dim-feishuGroupQrColumn { min-width: 0; }
417
+ .dim-feishuGroupQrFrame { position: relative; width: 176px; height: 176px; display: grid; place-items: center; padding: 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 11px; background: #fff; box-shadow: 0 6px 18px rgb(31 35 41 / 7%); }
418
+ .dim-feishuGroupQrFrame img { width: 100%; height: 100%; display: block; object-fit: contain; }
419
+ .dim-feishuGroupQrFallback { width: 100%; height: 100%; display: grid; place-content: center; justify-items: center; gap: 7px; border-radius: 7px; color: var(--dsw-alias-state-business-primary, #3370ff); background: #f7f9ff; text-align: center; }
420
+ .dim-feishuGroupQrFallback span { color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; }
421
+ .dim-feishuGroupQrExpired { position: absolute; inset: 10px; display: grid; place-content: center; gap: 2px; border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: rgb(255 255 255 / 94%); backdrop-filter: blur(3px); text-align: center; }
422
+ .dim-feishuGroupQrExpired span { font-size: 13px; line-height: 20px; font-weight: 650; }
423
+ .dim-feishuGroupQrExpired small { color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; }
424
+ .dim-feishuGroupCountdown { width: 176px; display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 5px 10px; margin-top: 9px; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; font-variant-numeric: tabular-nums; }
425
+ .dim-feishuGroupCountdown strong { color: var(--dsw-alias-label-secondary, #646a73); font-weight: 600; }
426
+ .dim-feishuGroupProgress { grid-column: 1 / -1; height: 3px; overflow: hidden; border-radius: 99px; background: var(--dsw-alias-bg-module-platform, #f2f3f5); }
427
+ .dim-feishuGroupProgress > span { display: block; height: 100%; border-radius: inherit; background: var(--dsw-alias-state-business-primary, #3370ff); transition: width 1s linear; }
428
+ .dim-feishuGroupAuthorizationCopy { min-width: 0; }
429
+ .dim-feishuGroupAuthorizationEyebrow { display: block; margin-bottom: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; font-weight: 600; }
430
+ .dim-feishuGroupAuthorizationCopy ol { display: grid; gap: 6px; margin: 13px 0 0; padding-left: 18px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 18px; }
431
+ .dim-feishuGroupAuthorizationActions { display: flex; align-items: center; justify-content: flex-start; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
432
+ .dim-feishuGroupAuthorizationLink { text-decoration: none; }
385
433
  .dim-deliveryDocsLink { min-height: 30px; flex: none; display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 7px; color: var(--dsw-alias-state-business-primary, #3370ff); font-size: 12px; line-height: 18px; text-decoration: none; white-space: nowrap; }
386
434
  .dim-deliveryDocsLink:hover { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, transparent); }
387
435
  .dim-deliveryDocsLink:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
@@ -411,6 +459,12 @@ const CSS = String.raw`
411
459
  .dim-targetTitle span { flex: none; padding: 1px 6px; border-radius: 5px; color: var(--dsw-alias-state-business-primary, #3370ff); background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 9%, transparent); font-size: 10px; line-height: 16px; }
412
460
  .dim-targetSummary code { display: block; overflow: hidden; margin-top: 3px; color: var(--dsw-alias-label-secondary, #646a73); font: 11px/17px ui-monospace, SFMono-Regular, Menlo, monospace; text-overflow: ellipsis; white-space: nowrap; }
413
461
  .dim-targetActions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-wrap: wrap; }
462
+ .dim-targetSessionSync { grid-column: 1 / -1; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 9px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); cursor: pointer; }
463
+ .dim-targetSessionSyncCopy { min-width: 0; display: grid; gap: 1px; }
464
+ .dim-targetSessionSyncCopy strong { color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; font-weight: 600; }
465
+ .dim-targetSessionSyncCopy small { color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; }
466
+ .dim-targetSessionSync input { width: 16px; height: 16px; flex: none; margin: 0; accent-color: var(--dsw-alias-state-business-primary, #3370ff); }
467
+ .dim-targetSessionSync:has(input:disabled) { cursor: default; }
414
468
  .dim-targetFeedback { grid-column: 1 / -1; margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; }
415
469
  .dim-targetFeedback[data-tone="success"] { color: var(--dsw-alias-state-success-primary, #20a162); }
416
470
  .dim-targetFeedback[data-tone="error"], .dim-targetFormError { color: var(--dsw-alias-state-error-primary, #d54941); }
@@ -468,6 +522,46 @@ const CSS = String.raw`
468
522
  .dim-accessFeedback[data-tone="success"] { color: var(--dsw-alias-state-success-primary, #20a162); }
469
523
  .dim-accessFeedback[data-tone="error"] { color: var(--dsw-alias-state-error-primary, #d54941); }
470
524
  .dim-accessActions { display: flex; justify-content: flex-end; }
525
+ .dim-generalSettingsPage { min-width: 0; display: grid; }
526
+ .dim-generalSettingsHeader { min-width: 0; margin: 0 0 8px; }
527
+ .dim-generalSettingsHeader h2 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 18px; line-height: 25px; font-weight: 680; }
528
+ .dim-generalSettingsTabsBar { min-width: 0; border-bottom: 1px solid var(--dsw-alias-border-l2, #dfe1e5); }
529
+ .dim-generalSettingsTabs { min-width: 0; display: flex; align-items: flex-end; gap: 24px; overflow-x: auto; scrollbar-width: none; }
530
+ .dim-generalSettingsTabs::-webkit-scrollbar { display: none; }
531
+ .dim-generalSettingsTab { position: relative; min-height: 38px; flex: none; display: inline-flex; align-items: center; justify-content: center; padding: 6px 2px 9px; border: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 13px; line-height: 20px; font-weight: 560; white-space: nowrap; cursor: pointer; transition: color .15s ease; }
532
+ .dim-generalSettingsTab::after { content: ''; position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: 2px 2px 0 0; background: transparent; transform: scaleX(.45); transition: background .15s ease, transform .15s ease; }
533
+ .dim-generalSettingsTab:hover:not([aria-selected="true"]) { color: var(--dsw-alias-label-primary, #1f2329); }
534
+ .dim-generalSettingsTab[aria-selected="true"] { color: var(--dim-blue); font-weight: 650; }
535
+ .dim-generalSettingsTab[aria-selected="true"]::after { background: var(--dim-blue); transform: scaleX(1); }
536
+ .dim-generalSettingsTab:focus-visible { outline: 2px solid var(--dim-blue); outline-offset: -2px; border-radius: 5px; }
537
+ .dim-generalSettingsTabPanel { min-width: 0; padding-top: 14px; }
538
+ .dim-globalSection { min-width: 0; padding: 14px 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); }
539
+ .dim-globalHead { min-width: 0; display: flex; align-items: center; }
540
+ .dim-globalHeadTitle { min-width: 0; display: inline-flex; align-items: center; gap: 6px; }
541
+ .dim-globalHead h3 { min-width: 0; overflow: hidden; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; line-height: 22px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
542
+ .dim-globalTtlHelp { position: relative; display: inline-flex; flex: none; }
543
+ .dim-globalTtlTooltip { position: absolute; top: calc(100% + 7px); left: 0; z-index: 30; width: max-content; max-width: min(280px, calc(100vw - 48px)); padding: 9px 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 10px 28px rgb(31 35 41 / 16%); opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
544
+ .dim-globalTtlHelp:hover .dim-globalTtlTooltip, .dim-globalTtlHelpButton:focus-visible + .dim-globalTtlTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
545
+ .dim-globalTtlHints { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }
546
+ .dim-globalTtlHints li { min-width: 0; display: flex; align-items: baseline; gap: 8px; }
547
+ .dim-globalTtlHints code { flex: none; min-width: 44px; padding: 0 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: 10px/16px ui-monospace, SFMono-Regular, Menlo, monospace; text-align: center; }
548
+ .dim-globalTtlHints span { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 16px; }
549
+ .dim-globalTtlRow { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; margin-top: 12px; }
550
+ .dim-globalTtlInput { width: min(160px, 100%); min-width: 110px; max-width: 160px; flex: 1 1 130px; height: 34px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; transition: border-color .16s ease, box-shadow .16s ease; }
551
+ .dim-globalTtlInput:hover:not(:disabled) { border-color: var(--dsw-alias-border-l3, #bbbfc4); }
552
+ .dim-globalTtlInput:focus { outline: none; border-color: var(--dim-blue); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dim-blue) 14%, transparent); }
553
+ .dim-globalTtlInput[aria-invalid="true"] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 62%, var(--dsw-alias-border-l2, #dfe1e5)); }
554
+ .dim-globalTtlInput:disabled { cursor: not-allowed; opacity: .55; }
555
+ .dim-globalSaveButton { min-width: 58px; min-height: 34px; }
556
+ .dim-globalSweepAction { position: relative; display: inline-flex; flex: none; margin-left: auto; }
557
+ .dim-globalSweepButton { flex: none; }
558
+ .dim-globalSweepConfirm { position: absolute; top: calc(100% + 8px); right: 0; z-index: 35; width: 218px; padding: 11px 12px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 9px; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 12px 30px rgb(31 35 41 / 18%); }
559
+ .dim-globalSweepConfirm p { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; }
560
+ .dim-globalSweepConfirmActions { display: flex; justify-content: flex-end; gap: 7px; margin-top: 10px; }
561
+ .dim-deliveryButton.dim-globalSweepConfirmButton { border-color: var(--dsw-alias-state-error-primary, #d54941); color: #fff; background: var(--dsw-alias-state-error-primary, #d54941); }
562
+ .dim-deliveryButton.dim-globalSweepConfirmButton:hover:not(:disabled) { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 82%, #000); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 88%, #000); }
563
+ .dim-globalInline { flex: 1 0 100%; min-width: 0; margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 16px; overflow-wrap: anywhere; }
564
+ .dim-globalInline[data-tone="error"] { color: var(--dsw-alias-state-error-primary, #d54941); }
471
565
  .dim-panel .dim-botCard .dim-cardFooter { margin-top: 0; }
472
566
  .dim-panel .ddt-headingCopy { display: none; }
473
567
  .dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); }
@@ -494,6 +588,10 @@ const CSS = String.raw`
494
588
  .dim-accessControls { grid-template-columns: minmax(0, 1fr); }
495
589
  .dim-accessUserRow { grid-template-columns: minmax(0, 1fr); }
496
590
  .dim-accessDeleteUser { justify-self: start; }
591
+ .dim-feishuGroupAuthorization { grid-template-columns: minmax(0, 1fr); justify-items: center; }
592
+ .dim-feishuGroupAuthorizationCopy { width: 100%; }
593
+ .dim-feishuGroupAuthorizationState { grid-template-columns: 32px minmax(0, 1fr); justify-items: stretch; }
594
+ .dim-feishuGroupAuthorizationError { justify-items: stretch; }
497
595
  }
498
596
  @media (max-width: 840px) {
499
597
  .dim-title { align-items: flex-start; }
@@ -523,6 +621,7 @@ const CSS = String.raw`
523
621
  .dim-deliveryBotId > span { grid-column: 1 / -1; }
524
622
  .dim-targetActions .dim-deliveryButton { flex: 1 1 auto; }
525
623
  .dim-accessActions .dim-deliveryButton { width: 100%; }
624
+ .dim-feishuGroupAuthorizationActions .dim-deliveryButton { flex: 1 1 auto; }
526
625
  .dim-directoryPickerBackdrop { padding: 10px; }
527
626
  .dim-directoryPicker { height: calc(100vh - 20px); min-height: 0; border-radius: 14px; }
528
627
  .dim-directoryPickerHeader { padding: 18px 17px 14px; }
@@ -15,11 +15,16 @@ import {
15
15
  observeBotWorkspaceRemovals,
16
16
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
17
17
  import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
18
+ import { listModelCatalog } from '../../../../src/channels/shared/model-setting.mjs';
18
19
  import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
19
20
  import { createConnectionSupervisor } from './connection-supervisor.mjs';
20
21
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
21
22
  import { harnessConnection } from '../../harness-connection.mjs';
22
23
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
24
+ import {
25
+ getInboundTtlRuntime,
26
+ registerInboundTtlWorkspaces,
27
+ } from '../../inbound-ttl-runtime.mjs';
23
28
  import {
24
29
  accessPolicyProvider,
25
30
  initialAccessPolicyFor,
@@ -84,10 +89,18 @@ export async function createProductionController(ctx, config = {}, internals = {
84
89
  return state;
85
90
  };
86
91
  const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
92
+ const inboundTtl = internals.inboundTtl ?? getInboundTtlRuntime(ctx, config);
93
+ const inboundTtlService = inboundTtl?.service ?? inboundTtl;
94
+ registerInboundTtlWorkspaces(ctx, inboundTtlService, {
95
+ workspaces,
96
+ configStore: observedConfigStore,
97
+ defaultWorkspace,
98
+ });
87
99
  const { controlExecutor, sessionMaintenanceExecutor, fileIngressExecutor } = createHarnessSessionExecutors(ctx, {
88
100
  controlExecutor: internals.controlExecutor,
89
101
  sessionMaintenanceExecutor: internals.sessionMaintenanceExecutor,
90
102
  fileIngressExecutor: internals.fileIngressExecutor,
103
+ inboundTtlService,
91
104
  });
92
105
  const harness = new Harness({
93
106
  ...connection,
@@ -99,6 +112,7 @@ export async function createProductionController(ctx, config = {}, internals = {
99
112
  ...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
100
113
  ...(fileIngressExecutor ? { fileIngressExecutor } : {}),
101
114
  });
115
+ const modelCatalog = () => listModelCatalog(harness);
102
116
  const coreController = new Controller({
103
117
  deviceAuth,
104
118
  credentials: ctx.credentials,
@@ -151,6 +165,7 @@ export async function createProductionController(ctx, config = {}, internals = {
151
165
  workspaces,
152
166
  stateFor,
153
167
  agentPresetCatalog,
168
+ modelCatalog,
154
169
  });
155
170
  const supervisor = createSupervisor({
156
171
  controller,
@@ -4,6 +4,7 @@ import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/
4
4
  import { resolveRpcAuthority } from '../../rpc-authority.mjs';
5
5
  import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
6
6
  import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
7
+ import { SET_MODEL_ENDPOINT, validModelPayload } from '../shared/model-setting-rpc.mjs';
7
8
  import {
8
9
  connectionTestTargetUnavailable,
9
10
  publicConnectionTestResult,
@@ -19,6 +20,7 @@ export const DINGTALK_ENDPOINTS = Object.freeze({
19
20
  reconnectBot: 'bot.reconnect',
20
21
  deleteBot: 'bot.delete',
21
22
  setWorkspace: SET_WORKSPACE_ENDPOINT,
23
+ setModel: SET_MODEL_ENDPOINT,
22
24
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
23
25
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
24
26
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
@@ -94,6 +96,9 @@ function payloadFailure(endpoint, payload) {
94
96
  return validWorkspacePayload(payload)
95
97
  ? null : '请输入工作区绝对路径。';
96
98
  }
99
+ if (endpoint === DINGTALK_ENDPOINTS.setModel) {
100
+ return validModelPayload(payload) ? null : '请选择有效模型。';
101
+ }
97
102
  if (endpoint === DINGTALK_ENDPOINTS.setAgentPreset) {
98
103
  return validAgentPresetPayload(payload)
99
104
  ? null : '请选择 Agent Preset。';
@@ -284,6 +289,12 @@ export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } =
284
289
  await controller.updateWorkspace(payload.botId, payload.workspace),
285
290
  cachedEncode,
286
291
  );
292
+ } else if (endpoint === DINGTALK_ENDPOINTS.setModel) {
293
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
294
+ value = await publicStatus(
295
+ await controller.updateModel(payload.botId, payload.model),
296
+ cachedEncode,
297
+ );
287
298
  } else if (endpoint === DINGTALK_ENDPOINTS.setContextEnhancement) {
288
299
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
289
300
  value = await controller.updateContextEnhancement(
@@ -7,6 +7,10 @@ import { createConnectionSupervisor } from './connection-supervisor.mjs';
7
7
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
8
8
  import { harnessConnection } from '../../harness-connection.mjs';
9
9
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
10
+ import {
11
+ getInboundTtlRuntime,
12
+ registerInboundTtlWorkspaces,
13
+ } from '../../inbound-ttl-runtime.mjs';
10
14
  import { verifyFeishuApp } from '../../../../src/channels/feishu/feishu-app.mjs';
11
15
  import { FeishuRuntime } from '../../../../src/channels/feishu/feishu-runtime.mjs';
12
16
  import { HarnessClient } from '../../../../src/channels/feishu/harness-client.mjs';
@@ -23,6 +27,7 @@ import {
23
27
  observeBotWorkspaceRemovals,
24
28
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
25
29
  import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
30
+ import { listModelCatalog } from '../../../../src/channels/shared/model-setting.mjs';
26
31
  import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
27
32
  import {
28
33
  accessPolicyProvider,
@@ -158,10 +163,19 @@ export async function createProductionController(ctx, config = {}, internals = {
158
163
  return stateFor(botConfig);
159
164
  };
160
165
  const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
166
+ const inboundTtl = internals.inboundTtl ?? getInboundTtlRuntime(ctx, config);
167
+ const inboundTtlService = inboundTtl?.service ?? inboundTtl;
168
+ registerInboundTtlWorkspaces(ctx, inboundTtlService, {
169
+ workspaces,
170
+ configStore: observedConfigStore,
171
+ defaultWorkspace,
172
+ botIdFrom: (bot) => bot?.id,
173
+ });
161
174
  const { controlExecutor, sessionMaintenanceExecutor, fileIngressExecutor } = createHarnessSessionExecutors(ctx, {
162
175
  controlExecutor: internals.controlExecutor,
163
176
  sessionMaintenanceExecutor: internals.sessionMaintenanceExecutor,
164
177
  fileIngressExecutor: internals.fileIngressExecutor,
178
+ inboundTtlService,
165
179
  });
166
180
  const harness = new Harness({
167
181
  ...connection,
@@ -178,6 +192,7 @@ export async function createProductionController(ctx, config = {}, internals = {
178
192
  const proxyEnv = internals.proxyEnv ?? process.env;
179
193
  const wsAgent = createFeishuWebSocketAgent(proxyEnv, internals.createProxyAgent);
180
194
 
195
+ const modelCatalog = () => listModelCatalog(harness);
181
196
  const coreController = new Controller({
182
197
  registerApp: (options) => lark.registerApp(options),
183
198
  verifyApp,
@@ -202,6 +217,7 @@ export async function createProductionController(ctx, config = {}, internals = {
202
217
  domain: botConfig.domain,
203
218
  botOpenId: botConfig.botOpenId,
204
219
  groupResponseMode: botConfig.groupResponseMode,
220
+ groupTopicReply: botConfig.groupTopicReply,
205
221
  ownerOpenIds: botConfig.ownerOpenIds ?? [botConfig.ownerOpenId],
206
222
  harness: workspaceScope.harness,
207
223
  state: workspaceScope.state,
@@ -233,6 +249,7 @@ export async function createProductionController(ctx, config = {}, internals = {
233
249
  workspaces,
234
250
  stateFor: stateForBotId,
235
251
  agentPresetCatalog,
252
+ modelCatalog,
236
253
  });
237
254
 
238
255
  const supervisor = createSupervisor({
@@ -5,10 +5,15 @@ import {
5
5
  } from '../../../../src/channels/shared/agent-preset.mjs';
6
6
  import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
7
7
  import { publicMessageFailure } from '../../../../src/channels/shared/message-failure.mjs';
8
+ import {
9
+ normalizeModelCatalog,
10
+ normalizeModelSelection,
11
+ } from '../../../../src/channels/shared/model-setting.mjs';
8
12
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
9
13
  import { resolveRpcAuthority } from '../../rpc-authority.mjs';
10
14
  import { publicWorkspaceError, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
11
15
  import { validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
16
+ import { validModelPayload } from '../shared/model-setting-rpc.mjs';
12
17
  import { validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
13
18
  import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
14
19
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -283,10 +288,12 @@ function publicBotEntry(entry) {
283
288
  state: connectionState(source, registration, connected),
284
289
  connected,
285
290
  configured: source.configured === true,
291
+ model: normalizeModelSelection(source.model),
286
292
  agentPreset: normalizeAgentPresetId(source.agentPreset),
287
293
  contextEnhancement: normalizeContextEnhancementConfig(source.contextEnhancement),
288
294
  accessPolicy: normalizeAccessPolicy(source.accessPolicy),
289
295
  groupResponseMode: normalizeFeishuGroupResponseMode(source.groupResponseMode),
296
+ groupTopicReply: source.groupTopicReply === true,
290
297
  groupMessagePermissionGranted: source.groupMessagePermissionGranted === true,
291
298
  bot: publicBot(source.bot),
292
299
  health: publicHealth(source, connected),
@@ -332,6 +339,7 @@ export async function toPublicFeishuStatus(status, { encodeQr = qrCodeDataUrl }
332
339
  health: publicHealth(source, connected),
333
340
  bots,
334
341
  agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
342
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
335
343
  totals: {
336
344
  configured: bots.length || (source.configured === true ? 1 : 0),
337
345
  connected: bots.length ? bots.filter((bot) => bot.connected).length : (connected ? 1 : 0),
@@ -421,6 +429,9 @@ function validPayload(endpoint, payload) {
421
429
  return validWorkspacePayload(payload)
422
430
  ? null : '请输入工作区绝对路径。';
423
431
  }
432
+ if (endpoint === FEISHU_ENDPOINTS.setModel) {
433
+ return validModelPayload(payload) ? null : '请选择有效模型。';
434
+ }
424
435
  if (endpoint === FEISHU_ENDPOINTS.setAgentPreset) {
425
436
  return validAgentPresetPayload(payload)
426
437
  ? null : '请选择 Agent Preset。';
@@ -440,6 +451,13 @@ function validPayload(endpoint, payload) {
440
451
  ? null
441
452
  : '请选择群聊响应方式。';
442
453
  }
454
+ if (endpoint === FEISHU_ENDPOINTS.setGroupTopicReply) {
455
+ return hasOnlyKeys(payload, new Set(['botId', 'groupTopicReply']))
456
+ && safeOpaqueId(payload.botId)
457
+ && typeof payload.groupTopicReply === 'boolean'
458
+ ? null
459
+ : '请选择是否以话题方式回复。';
460
+ }
443
461
  return 'Unknown Feishu endpoint.';
444
462
  }
445
463
 
@@ -684,6 +702,12 @@ export function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl }
684
702
  await controller.updateWorkspace(payload.botId, payload.workspace),
685
703
  { encodeQr: cachedEncodeQr },
686
704
  );
705
+ } else if (endpoint === FEISHU_ENDPOINTS.setModel) {
706
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
707
+ value = await toPublicFeishuStatus(
708
+ await controller.updateModel(payload.botId, payload.model),
709
+ { encodeQr: cachedEncodeQr },
710
+ );
687
711
  } else if (endpoint === FEISHU_ENDPOINTS.setContextEnhancement) {
688
712
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
689
713
  value = await controller.updateContextEnhancement(
@@ -710,6 +734,14 @@ export function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl }
710
734
  await controller.updateGroupResponseMode(payload.botId, payload.groupResponseMode),
711
735
  { encodeQr: cachedEncodeQr },
712
736
  );
737
+ } else if (endpoint === FEISHU_ENDPOINTS.setGroupTopicReply) {
738
+ if (typeof controller.updateGroupTopicReply !== 'function') {
739
+ throw new Error('Group topic reply update is unavailable');
740
+ }
741
+ value = await toPublicFeishuStatus(
742
+ await controller.updateGroupTopicReply(payload.botId, payload.groupTopicReply),
743
+ { encodeQr: cachedEncodeQr },
744
+ );
713
745
  } else {
714
746
  if (typeof controller.deleteBot !== 'function') throw new Error('Multi-bot delete is unavailable');
715
747
  value = await toPublicFeishuStatus(await controller.deleteBot(payload.botId), { encodeQr: cachedEncodeQr });
@@ -15,11 +15,16 @@ import {
15
15
  observeBotWorkspaceRemovals,
16
16
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
17
17
  import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
18
+ import { listModelCatalog } from '../../../../src/channels/shared/model-setting.mjs';
18
19
  import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
19
20
  import { createConnectionSupervisor } from './connection-supervisor.mjs';
20
21
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
21
22
  import { harnessConnection } from '../../harness-connection.mjs';
22
23
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
24
+ import {
25
+ getInboundTtlRuntime,
26
+ registerInboundTtlWorkspaces,
27
+ } from '../../inbound-ttl-runtime.mjs';
23
28
  import {
24
29
  accessPolicyProvider,
25
30
  initialAccessPolicyFor,
@@ -75,10 +80,18 @@ export async function createProductionController(ctx, config = {}, internals = {
75
80
  return state;
76
81
  };
77
82
  const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
83
+ const inboundTtl = internals.inboundTtl ?? getInboundTtlRuntime(ctx, config);
84
+ const inboundTtlService = inboundTtl?.service ?? inboundTtl;
85
+ registerInboundTtlWorkspaces(ctx, inboundTtlService, {
86
+ workspaces,
87
+ configStore: observedConfigStore,
88
+ defaultWorkspace,
89
+ });
78
90
  const { controlExecutor, sessionMaintenanceExecutor, fileIngressExecutor } = createHarnessSessionExecutors(ctx, {
79
91
  controlExecutor: internals.controlExecutor,
80
92
  sessionMaintenanceExecutor: internals.sessionMaintenanceExecutor,
81
93
  fileIngressExecutor: internals.fileIngressExecutor,
94
+ inboundTtlService,
82
95
  });
83
96
  const harness = new Harness({
84
97
  ...connection,
@@ -90,6 +103,7 @@ export async function createProductionController(ctx, config = {}, internals = {
90
103
  ...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
91
104
  ...(fileIngressExecutor ? { fileIngressExecutor } : {}),
92
105
  });
106
+ const modelCatalog = () => listModelCatalog(harness);
93
107
  const coreController = new Controller({
94
108
  qrAuth,
95
109
  credentials: ctx.credentials,
@@ -141,6 +155,7 @@ export async function createProductionController(ctx, config = {}, internals = {
141
155
  workspaces,
142
156
  stateFor,
143
157
  agentPresetCatalog,
158
+ modelCatalog,
144
159
  });
145
160
  const supervisor = createSupervisor({
146
161
  controller,
@@ -8,6 +8,7 @@ import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/
8
8
  import { resolveRpcAuthority } from '../../rpc-authority.mjs';
9
9
  import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
10
10
  import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
11
+ import { SET_MODEL_ENDPOINT, validModelPayload } from '../shared/model-setting-rpc.mjs';
11
12
 
12
13
  export const QQ_RPC_CHANNEL = '/qq';
13
14
  export const QQ_ENDPOINTS = Object.freeze({
@@ -19,6 +20,7 @@ export const QQ_ENDPOINTS = Object.freeze({
19
20
  reconnectBot: 'bot.reconnect',
20
21
  deleteBot: 'bot.delete',
21
22
  setWorkspace: SET_WORKSPACE_ENDPOINT,
23
+ setModel: SET_MODEL_ENDPOINT,
22
24
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
23
25
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
24
26
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
@@ -76,6 +78,9 @@ function payloadFailure(endpoint, payload) {
76
78
  return validWorkspacePayload(payload)
77
79
  ? null : '请输入工作区绝对路径。';
78
80
  }
81
+ if (endpoint === QQ_ENDPOINTS.setModel) {
82
+ return validModelPayload(payload) ? null : '请选择有效模型。';
83
+ }
79
84
  if (endpoint === QQ_ENDPOINTS.setAgentPreset) {
80
85
  return validAgentPresetPayload(payload)
81
86
  ? null : '请选择 Agent Preset。';
@@ -183,6 +188,12 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
183
188
  await controller.updateWorkspace(payload.botId, payload.workspace),
184
189
  cachedEncode,
185
190
  );
191
+ } else if (endpoint === QQ_ENDPOINTS.setModel) {
192
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
193
+ value = await publicStatus(
194
+ await controller.updateModel(payload.botId, payload.model),
195
+ cachedEncode,
196
+ );
186
197
  } else if (endpoint === QQ_ENDPOINTS.setContextEnhancement) {
187
198
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
188
199
  value = await controller.updateContextEnhancement(
@@ -0,0 +1,19 @@
1
+ import { normalizeModelSelection } from '../../../../src/channels/shared/model-setting.mjs';
2
+
3
+ export const SET_MODEL_ENDPOINT = 'bot.model.set';
4
+
5
+ function isRecord(value) {
6
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ }
8
+
9
+ export function validModelPayload(payload) {
10
+ if (!isRecord(payload)
11
+ || !Object.keys(payload).every((key) => ['botId', 'model'].includes(key))
12
+ || typeof payload.botId !== 'string'
13
+ || !/^[A-Za-z0-9_-]{1,128}$/.test(payload.botId)) return false;
14
+ if (payload.model === null) return true;
15
+ return isRecord(payload.model)
16
+ && Object.keys(payload.model).length === 2
17
+ && Object.keys(payload.model).every((key) => ['provider', 'model'].includes(key))
18
+ && normalizeModelSelection(payload.model) !== null;
19
+ }
@@ -6,6 +6,10 @@ import { createTokenConnectionSupervisor } from './connection-supervisor.mjs';
6
6
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
7
7
  import { harnessConnection } from '../../harness-connection.mjs';
8
8
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
9
+ import {
10
+ getInboundTtlRuntime,
11
+ registerInboundTtlWorkspaces,
12
+ } from '../../inbound-ttl-runtime.mjs';
9
13
  import {
10
14
  BotWorkspaceStore,
11
15
  createBotWorkspaceScope,
@@ -13,6 +17,7 @@ import {
13
17
  observeBotWorkspaceRemovals,
14
18
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
15
19
  import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
20
+ import { listModelCatalog } from '../../../../src/channels/shared/model-setting.mjs';
16
21
  import {
17
22
  createDeliveryAdapter,
18
23
  supportsDeliveryChannel,
@@ -84,10 +89,18 @@ export async function createTokenProductionController(ctx, config, internals, de
84
89
  return state;
85
90
  };
86
91
  const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
92
+ const inboundTtl = internals.inboundTtl ?? getInboundTtlRuntime(ctx, config);
93
+ const inboundTtlService = inboundTtl?.service ?? inboundTtl;
94
+ registerInboundTtlWorkspaces(ctx, inboundTtlService, {
95
+ workspaces,
96
+ configStore: observedConfigStore,
97
+ defaultWorkspace,
98
+ });
87
99
  const { controlExecutor, sessionMaintenanceExecutor, fileIngressExecutor } = createHarnessSessionExecutors(ctx, {
88
100
  controlExecutor: internals.controlExecutor,
89
101
  sessionMaintenanceExecutor: internals.sessionMaintenanceExecutor,
90
102
  fileIngressExecutor: internals.fileIngressExecutor,
103
+ inboundTtlService,
91
104
  });
92
105
  const harness = new ResolvedHarness({
93
106
  ...connection,
@@ -99,6 +112,7 @@ export async function createTokenProductionController(ctx, config, internals, de
99
112
  ...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
100
113
  ...(fileIngressExecutor ? { fileIngressExecutor } : {}),
101
114
  });
115
+ const modelCatalog = () => listModelCatalog(harness);
102
116
  const coreController = new ResolvedController({
103
117
  credentials: ctx.credentials,
104
118
  configStore: observedConfigStore,
@@ -149,6 +163,7 @@ export async function createTokenProductionController(ctx, config, internals, de
149
163
  workspaces,
150
164
  stateFor,
151
165
  agentPresetCatalog,
166
+ modelCatalog,
152
167
  });
153
168
  const supervisor = createSupervisor({
154
169
  channel,
@@ -11,6 +11,7 @@ import {
11
11
  SET_AGENT_PRESET_ENDPOINT,
12
12
  validAgentPresetPayload,
13
13
  } from './agent-preset-rpc.mjs';
14
+ import { SET_MODEL_ENDPOINT, validModelPayload } from './model-setting-rpc.mjs';
14
15
 
15
16
  export const TOKEN_BOT_ENDPOINTS = Object.freeze({
16
17
  status: 'connection.status',
@@ -18,6 +19,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
18
19
  reconnectBot: 'bot.reconnect',
19
20
  deleteBot: 'bot.delete',
20
21
  setWorkspace: SET_WORKSPACE_ENDPOINT,
22
+ setModel: SET_MODEL_ENDPOINT,
21
23
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
22
24
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
23
25
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
@@ -71,6 +73,10 @@ function payloadFailure(endpoint, payload) {
71
73
  return validWorkspacePayload(payload)
72
74
  ? null : '请输入工作区绝对路径。';
73
75
  }
76
+ if (endpoint === TOKEN_BOT_ENDPOINTS.setModel) {
77
+ return validModelPayload(payload)
78
+ ? null : '请选择有效模型。';
79
+ }
74
80
  if (endpoint === TOKEN_BOT_ENDPOINTS.setAgentPreset) {
75
81
  return validAgentPresetPayload(payload)
76
82
  ? null : '请选择 Agent Preset。';
@@ -164,6 +170,9 @@ export function createTokenBotRpcHandler(controller, { channel }) {
164
170
  } else if (endpoint === TOKEN_BOT_ENDPOINTS.setWorkspace) {
165
171
  if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
166
172
  value = await controller.updateWorkspace(payload.botId, payload.workspace);
173
+ } else if (endpoint === TOKEN_BOT_ENDPOINTS.setModel) {
174
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
175
+ value = await controller.updateModel(payload.botId, payload.model);
167
176
  } else if (endpoint === TOKEN_BOT_ENDPOINTS.setContextEnhancement) {
168
177
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
169
178
  value = await controller.updateContextEnhancement(payload.botId, payload.config);
@@ -22,6 +22,8 @@ export function publicWorkspaceError(error) {
22
22
  'workspace-bot-not-found',
23
23
  'agent-preset-invalid',
24
24
  'agent-preset-unavailable',
25
+ 'model-selection-invalid',
26
+ 'model-selection-unavailable',
25
27
  'context-enhancement-invalid',
26
28
  ].includes(error?.code)) return null;
27
29
  return { code: error.code, message: error.message };