@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
@@ -13,12 +13,17 @@ import {
13
13
  observeBotWorkspaceRemovals,
14
14
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
15
15
  import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
16
+ import { listModelCatalog } from '../../../../src/channels/shared/model-setting.mjs';
16
17
  import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
17
18
  import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
18
19
  import { pluginPaths } from '../shared/production.mjs';
19
20
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
20
21
  import { harnessConnection } from '../../harness-connection.mjs';
21
22
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
23
+ import {
24
+ getInboundTtlRuntime,
25
+ registerInboundTtlWorkspaces,
26
+ } from '../../inbound-ttl-runtime.mjs';
22
27
  import {
23
28
  accessPolicyProvider,
24
29
  initialAccessPolicyFor,
@@ -63,10 +68,18 @@ export async function createProductionController(ctx, config = {}, internals = {
63
68
  return state;
64
69
  };
65
70
  const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
71
+ const inboundTtl = internals.inboundTtl ?? getInboundTtlRuntime(ctx, config);
72
+ const inboundTtlService = inboundTtl?.service ?? inboundTtl;
73
+ registerInboundTtlWorkspaces(ctx, inboundTtlService, {
74
+ workspaces,
75
+ configStore: observedConfigStore,
76
+ defaultWorkspace,
77
+ });
66
78
  const { controlExecutor, sessionMaintenanceExecutor, fileIngressExecutor } = createHarnessSessionExecutors(ctx, {
67
79
  controlExecutor: internals.controlExecutor,
68
80
  sessionMaintenanceExecutor: internals.sessionMaintenanceExecutor,
69
81
  fileIngressExecutor: internals.fileIngressExecutor,
82
+ inboundTtlService,
70
83
  });
71
84
  const harness = new ResolvedHarness({
72
85
  ...connection,
@@ -78,6 +91,7 @@ export async function createProductionController(ctx, config = {}, internals = {
78
91
  ...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
79
92
  ...(fileIngressExecutor ? { fileIngressExecutor } : {}),
80
93
  });
94
+ const modelCatalog = () => listModelCatalog(harness);
81
95
  const coreController = new ResolvedController({
82
96
  credentials: ctx.credentials,
83
97
  configStore: observedConfigStore,
@@ -130,6 +144,7 @@ export async function createProductionController(ctx, config = {}, internals = {
130
144
  workspaces,
131
145
  stateFor,
132
146
  agentPresetCatalog,
147
+ modelCatalog,
133
148
  });
134
149
  const supervisor = createSupervisor({
135
150
  channel: 'slack',
@@ -11,6 +11,7 @@ import {
11
11
  SET_AGENT_PRESET_ENDPOINT,
12
12
  validAgentPresetPayload,
13
13
  } from '../shared/agent-preset-rpc.mjs';
14
+ import { SET_MODEL_ENDPOINT, validModelPayload } from '../shared/model-setting-rpc.mjs';
14
15
 
15
16
  export const SLACK_RPC_CHANNEL = '/slack';
16
17
  export const SLACK_ENDPOINTS = Object.freeze({
@@ -19,6 +20,7 @@ export const SLACK_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,
@@ -75,6 +77,9 @@ function payloadFailure(endpoint, payload) {
75
77
  return validWorkspacePayload(payload)
76
78
  ? null : '请输入工作区绝对路径。';
77
79
  }
80
+ if (endpoint === SLACK_ENDPOINTS.setModel) {
81
+ return validModelPayload(payload) ? null : '请选择有效模型。';
82
+ }
78
83
  if (endpoint === SLACK_ENDPOINTS.setAgentPreset) {
79
84
  return validAgentPresetPayload(payload)
80
85
  ? null : '请选择 Agent Preset。';
@@ -166,6 +171,10 @@ export function createSlackRpcHandler(controller) {
166
171
  if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
167
172
  value = await controller.updateWorkspace(payload.botId, payload.workspace);
168
173
  }
174
+ else if (endpoint === SLACK_ENDPOINTS.setModel) {
175
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
176
+ value = await controller.updateModel(payload.botId, payload.model);
177
+ }
169
178
  else if (endpoint === SLACK_ENDPOINTS.setContextEnhancement) {
170
179
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
171
180
  value = await controller.updateContextEnhancement(payload.botId, payload.config);
@@ -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,
@@ -78,10 +83,18 @@ export async function createProductionController(ctx, config = {}, internals = {
78
83
  return state;
79
84
  };
80
85
  const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
86
+ const inboundTtl = internals.inboundTtl ?? getInboundTtlRuntime(ctx, config);
87
+ const inboundTtlService = inboundTtl?.service ?? inboundTtl;
88
+ registerInboundTtlWorkspaces(ctx, inboundTtlService, {
89
+ workspaces,
90
+ configStore: observedConfigStore,
91
+ defaultWorkspace,
92
+ });
81
93
  const { controlExecutor, sessionMaintenanceExecutor, fileIngressExecutor } = createHarnessSessionExecutors(ctx, {
82
94
  controlExecutor: internals.controlExecutor,
83
95
  sessionMaintenanceExecutor: internals.sessionMaintenanceExecutor,
84
96
  fileIngressExecutor: internals.fileIngressExecutor,
97
+ inboundTtlService,
85
98
  });
86
99
  const harness = new Harness({
87
100
  ...connection,
@@ -93,6 +106,7 @@ export async function createProductionController(ctx, config = {}, internals = {
93
106
  ...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
94
107
  ...(fileIngressExecutor ? { fileIngressExecutor } : {}),
95
108
  });
109
+ const modelCatalog = () => listModelCatalog(harness);
96
110
  const coreController = new Controller({
97
111
  qrAuth,
98
112
  credentials: ctx.credentials,
@@ -145,6 +159,7 @@ export async function createProductionController(ctx, config = {}, internals = {
145
159
  workspaces,
146
160
  stateFor,
147
161
  agentPresetCatalog,
162
+ modelCatalog,
148
163
  });
149
164
  const supervisor = createSupervisor({
150
165
  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 WECOM_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 === WECOM_ENDPOINTS.setModel) {
82
+ return validModelPayload(payload) ? null : '请选择有效模型。';
83
+ }
79
84
  if (endpoint === WECOM_ENDPOINTS.setAgentPreset) {
80
85
  return validAgentPresetPayload(payload)
81
86
  ? null : '请选择 Agent Preset。';
@@ -184,6 +189,12 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
184
189
  await controller.updateWorkspace(payload.botId, payload.workspace),
185
190
  cachedEncode,
186
191
  );
192
+ } else if (endpoint === WECOM_ENDPOINTS.setModel) {
193
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
194
+ value = await publicStatus(
195
+ await controller.updateModel(payload.botId, payload.model),
196
+ cachedEncode,
197
+ );
187
198
  } else if (endpoint === WECOM_ENDPOINTS.setContextEnhancement) {
188
199
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
189
200
  value = await controller.updateContextEnhancement(
@@ -18,11 +18,16 @@ import {
18
18
  observeBotWorkspaceRemovals,
19
19
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
20
20
  import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
21
+ import { listModelCatalog } from '../../../../src/channels/shared/model-setting.mjs';
21
22
  import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
22
23
  import { createConnectionSupervisor } from './connection-supervisor.mjs';
23
24
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
24
25
  import { harnessConnection } from '../../harness-connection.mjs';
25
26
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
27
+ import {
28
+ getInboundTtlRuntime,
29
+ registerInboundTtlWorkspaces,
30
+ } from '../../inbound-ttl-runtime.mjs';
26
31
  import {
27
32
  accessPolicyProvider,
28
33
  initialAccessPolicyFor,
@@ -81,10 +86,18 @@ export async function createProductionController(ctx, config = {}, internals = {
81
86
  return state;
82
87
  };
83
88
  const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
89
+ const inboundTtl = internals.inboundTtl ?? getInboundTtlRuntime(ctx, config);
90
+ const inboundTtlService = inboundTtl?.service ?? inboundTtl;
91
+ registerInboundTtlWorkspaces(ctx, inboundTtlService, {
92
+ workspaces,
93
+ configStore: observedConfigStore,
94
+ defaultWorkspace,
95
+ });
84
96
  const { controlExecutor, sessionMaintenanceExecutor, fileIngressExecutor } = createHarnessSessionExecutors(ctx, {
85
97
  controlExecutor: internals.controlExecutor,
86
98
  sessionMaintenanceExecutor: internals.sessionMaintenanceExecutor,
87
99
  fileIngressExecutor: internals.fileIngressExecutor,
100
+ inboundTtlService,
88
101
  });
89
102
  const harness = new Harness({
90
103
  ...connection,
@@ -96,6 +109,7 @@ export async function createProductionController(ctx, config = {}, internals = {
96
109
  ...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
97
110
  ...(fileIngressExecutor ? { fileIngressExecutor } : {}),
98
111
  });
112
+ const modelCatalog = () => listModelCatalog(harness);
99
113
  const coreController = new Controller({
100
114
  api,
101
115
  credentials: ctx.credentials,
@@ -148,6 +162,7 @@ export async function createProductionController(ctx, config = {}, internals = {
148
162
  workspaces,
149
163
  stateFor,
150
164
  agentPresetCatalog,
165
+ modelCatalog,
151
166
  });
152
167
  const supervisor = createSupervisor({
153
168
  controller,
@@ -11,6 +11,7 @@ import {
11
11
  SET_AGENT_PRESET_ENDPOINT,
12
12
  validAgentPresetPayload,
13
13
  } from '../shared/agent-preset-rpc.mjs';
14
+ import { SET_MODEL_ENDPOINT, validModelPayload } from '../shared/model-setting-rpc.mjs';
14
15
  import {
15
16
  connectionTestTargetUnavailable,
16
17
  publicConnectionTestResult,
@@ -26,6 +27,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
26
27
  reconnectBot: 'bot.reconnect',
27
28
  deleteBot: 'bot.delete',
28
29
  setWorkspace: SET_WORKSPACE_ENDPOINT,
30
+ setModel: SET_MODEL_ENDPOINT,
29
31
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
30
32
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
31
33
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
@@ -83,6 +85,9 @@ function payloadFailure(endpoint, payload) {
83
85
  return validWorkspacePayload(payload)
84
86
  ? null : '请输入工作区绝对路径。';
85
87
  }
88
+ if (endpoint === WEIXIN_ENDPOINTS.setModel) {
89
+ return validModelPayload(payload) ? null : '请选择有效模型。';
90
+ }
86
91
  if (endpoint === WEIXIN_ENDPOINTS.setAgentPreset) {
87
92
  return validAgentPresetPayload(payload)
88
93
  ? null : '请选择 Agent Preset。';
@@ -219,6 +224,12 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
219
224
  await controller.updateWorkspace(payload.botId, payload.workspace),
220
225
  cachedEncode,
221
226
  );
227
+ } else if (endpoint === WEIXIN_ENDPOINTS.setModel) {
228
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
229
+ value = await publicStatus(
230
+ await controller.updateModel(payload.botId, payload.model),
231
+ cachedEncode,
232
+ );
222
233
  } else if (endpoint === WEIXIN_ENDPOINTS.setContextEnhancement) {
223
234
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
224
235
  value = await controller.updateContextEnhancement(
@@ -18,11 +18,16 @@ import {
18
18
  observeBotWorkspaceRemovals,
19
19
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
20
20
  import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
21
+ import { listModelCatalog } from '../../../../src/channels/shared/model-setting.mjs';
21
22
  import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
22
23
  import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
23
24
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
24
25
  import { harnessConnection } from '../../harness-connection.mjs';
25
26
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
27
+ import {
28
+ getInboundTtlRuntime,
29
+ registerInboundTtlWorkspaces,
30
+ } from '../../inbound-ttl-runtime.mjs';
26
31
  import {
27
32
  accessPolicyProvider,
28
33
  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
  configStore: observedConfigStore,
104
118
  authPath: paths.authPath,
@@ -155,6 +169,7 @@ export async function createProductionController(ctx, config = {}, internals = {
155
169
  workspaces,
156
170
  stateFor,
157
171
  agentPresetCatalog,
172
+ modelCatalog,
158
173
  });
159
174
  const supervisor = createSupervisor({
160
175
  channel: 'whatsapp',
@@ -6,6 +6,7 @@ import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from
6
6
  import { resolveRpcAuthority } from '../../rpc-authority.mjs';
7
7
  import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
8
8
  import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
9
+ import { SET_MODEL_ENDPOINT, validModelPayload } from '../shared/model-setting-rpc.mjs';
9
10
 
10
11
  export const WHATSAPP_RPC_CHANNEL = '/whatsapp';
11
12
  export const WHATSAPP_ENDPOINTS = Object.freeze({
@@ -17,6 +18,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
17
18
  deleteBot: 'bot.delete',
18
19
  setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
19
20
  setWorkspace: SET_WORKSPACE_ENDPOINT,
21
+ setModel: SET_MODEL_ENDPOINT,
20
22
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
21
23
  setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
22
24
  });
@@ -62,6 +64,9 @@ function payloadFailure(endpoint, payload) {
62
64
  return validWorkspacePayload(payload)
63
65
  ? null : '请输入工作区绝对路径。';
64
66
  }
67
+ if (endpoint === WHATSAPP_ENDPOINTS.setModel) {
68
+ return validModelPayload(payload) ? null : '请选择有效模型。';
69
+ }
65
70
  if (endpoint === WHATSAPP_ENDPOINTS.setAgentPreset) {
66
71
  return validAgentPresetPayload(payload)
67
72
  ? null : '请选择 Agent Preset。';
@@ -172,6 +177,12 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
172
177
  await controller.updateWorkspace(payload.botId, payload.workspace),
173
178
  cachedEncode,
174
179
  );
180
+ } else if (endpoint === WHATSAPP_ENDPOINTS.setModel) {
181
+ if (typeof controller.updateModel !== 'function') throw new Error('Model update is unavailable');
182
+ value = await publicStatus(
183
+ await controller.updateModel(payload.botId, payload.model),
184
+ cachedEncode,
185
+ );
175
186
  } else if (endpoint === WHATSAPP_ENDPOINTS.setContextEnhancement) {
176
187
  if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
177
188
  value = await controller.updateContextEnhancement(
@@ -1,4 +1,8 @@
1
- import { deliverySuggestionsFromSessions } from './delivery-suggestions.mjs';
1
+ import {
2
+ deliverySuggestionsFromSessions,
3
+ privateConversationKeyMatchesTarget,
4
+ resolvePrivateConversationKey,
5
+ } from './delivery-suggestions.mjs';
2
6
 
3
7
  const CHANNELS = new Set([
4
8
  'weixin',
@@ -22,6 +26,12 @@ function invalidTarget(message) {
22
26
  return error;
23
27
  }
24
28
 
29
+ function sessionSyncUnavailable(message = 'Session sync is unavailable for this target') {
30
+ const error = new Error(message);
31
+ error.code = 'session-sync-unavailable';
32
+ return error;
33
+ }
34
+
25
35
  function isRecord(value) {
26
36
  return value !== null && typeof value === 'object' && !Array.isArray(value);
27
37
  }
@@ -129,6 +139,49 @@ export function normalizeDeliveryTarget(channel, value, { targetIdRequired = tru
129
139
  return normalized;
130
140
  }
131
141
 
142
+ function targetWithoutSessionSync(target) {
143
+ return {
144
+ targetId: target.targetId,
145
+ ...(target.name === undefined ? {} : { name: target.name }),
146
+ kind: target.kind,
147
+ route: target.route,
148
+ };
149
+ }
150
+
151
+ function sessionSyncConfigs(workspaces, botId) {
152
+ if (typeof workspaces.listSessionSyncTargets !== 'function') return [];
153
+ const targets = workspaces.listSessionSyncTargets();
154
+ return botId === undefined ? targets : targets.filter((target) => target.botId === botId);
155
+ }
156
+
157
+ function stateSessions(state) {
158
+ if (!state || typeof state.snapshot !== 'function') return null;
159
+ const sessions = state.snapshot()?.sessions;
160
+ return sessions && typeof sessions === 'object' && !Array.isArray(sessions) ? sessions : null;
161
+ }
162
+
163
+ function sessionSyncView(channel, target, sessions, configured) {
164
+ if (configured) {
165
+ const valid = privateConversationKeyMatchesTarget(
166
+ channel,
167
+ configured.conversationKey,
168
+ target,
169
+ );
170
+ if (!valid || !sessions) return { enabled: true, state: 'unavailable' };
171
+ return {
172
+ enabled: true,
173
+ state: typeof sessions[configured.conversationKey] === 'string'
174
+ && Boolean(sessions[configured.conversationKey])
175
+ ? 'active'
176
+ : 'waiting',
177
+ };
178
+ }
179
+ const conversationKey = sessions
180
+ ? resolvePrivateConversationKey(channel, sessions, target)
181
+ : null;
182
+ return { enabled: false, state: conversationKey ? 'off' : 'unavailable' };
183
+ }
184
+
132
185
  /** Bind one channel's existing workspace store and unwrapped controller to DeliveryService. */
133
186
  export function createDeliveryAdapter({ channel, workspaces, coreController, stateFor }) {
134
187
  if (!supportsDeliveryChannel(channel)) throw new TypeError(`Unsupported delivery channel: ${channel}`);
@@ -144,7 +197,23 @@ export function createDeliveryAdapter({ channel, workspaces, coreController, sta
144
197
  return Object.freeze({
145
198
  channel,
146
199
  ownsBot: (botId) => workspaces.has(botId),
147
- listTargets: (botId) => workspaces.listDeliveryTargets(botId),
200
+ listBots: () => workspaces.listBotIds(),
201
+ async listTargets(botId) {
202
+ const targets = workspaces.listDeliveryTargets(botId);
203
+ const configured = new Map(
204
+ sessionSyncConfigs(workspaces, botId).map((target) => [target.targetId, target]),
205
+ );
206
+ let sessions = null;
207
+ try {
208
+ sessions = stateSessions(await stateFor(botId));
209
+ } catch {
210
+ // Target CRUD remains usable when its persisted conversation state cannot be read.
211
+ }
212
+ return targets.map((target) => ({
213
+ ...target,
214
+ sessionSync: sessionSyncView(channel, target, sessions, configured.get(target.targetId)),
215
+ }));
216
+ },
148
217
  async listSuggestions(botId) {
149
218
  const state = await stateFor(botId);
150
219
  if (!state || typeof state.snapshot !== 'function') {
@@ -168,12 +237,82 @@ export function createDeliveryAdapter({ channel, workspaces, coreController, sta
168
237
  ),
169
238
  deleteTarget: (botId, targetId) => workspaces.deleteDeliveryTarget(botId, targetId),
170
239
  async sendText(botId, target, text, options = {}) {
171
- const normalized = normalizeDeliveryTarget(channel, target);
240
+ const normalized = normalizeDeliveryTarget(channel, targetWithoutSessionSync(target));
172
241
  if (typeof coreController.sendProactiveText !== 'function') {
173
242
  throw new TypeError('delivery controller cannot send proactive text');
174
243
  }
175
244
  await coreController.sendProactiveText(botId, normalized, text, options);
176
245
  return { sent: true };
177
246
  },
247
+ async setSessionSync(botId, targetId, enabled) {
248
+ if (typeof workspaces.setDeliveryTargetSessionSync !== 'function') {
249
+ throw sessionSyncUnavailable();
250
+ }
251
+ const target = workspaces.deliveryTargetFor(botId, targetId);
252
+ if (!target) {
253
+ const error = new Error('Unknown target');
254
+ error.code = 'unknown-target';
255
+ throw error;
256
+ }
257
+ if (!enabled) {
258
+ await workspaces.setDeliveryTargetSessionSync(botId, targetId, null);
259
+ let sessions = null;
260
+ try {
261
+ sessions = stateSessions(await stateFor(botId));
262
+ } catch {
263
+ // The setting is already safely disabled; only its availability is unknown.
264
+ }
265
+ return sessionSyncView(channel, target, sessions, null);
266
+ }
267
+ const sessions = stateSessions(await stateFor(botId));
268
+ const conversationKey = resolvePrivateConversationKey(channel, sessions, target);
269
+ if (!conversationKey) throw sessionSyncUnavailable();
270
+ await workspaces.setDeliveryTargetSessionSync(botId, targetId, conversationKey);
271
+ return { enabled: true, state: 'active' };
272
+ },
273
+ async listSessionSyncTargets(sessionId) {
274
+ if (typeof sessionId !== 'string' || !sessionId) {
275
+ throw new TypeError('sessionId is required');
276
+ }
277
+ const matches = [];
278
+ for (const configured of sessionSyncConfigs(workspaces)) {
279
+ const target = workspaces.deliveryTargetFor(configured.botId, configured.targetId);
280
+ if (!target || !privateConversationKeyMatchesTarget(
281
+ channel,
282
+ configured.conversationKey,
283
+ target,
284
+ )) continue;
285
+ let sessions;
286
+ try {
287
+ sessions = stateSessions(await stateFor(configured.botId));
288
+ } catch {
289
+ continue;
290
+ }
291
+ if (sessions?.[configured.conversationKey] === sessionId) {
292
+ matches.push({ botId: configured.botId, targetId: configured.targetId });
293
+ }
294
+ }
295
+ return matches;
296
+ },
297
+ async sendSessionSyncText(botId, targetId, sessionId, text, options = {}) {
298
+ if (typeof text !== 'string' || !text.trim()) throw new TypeError('text is required');
299
+ const configured = sessionSyncConfigs(workspaces, botId)
300
+ .find((target) => target.targetId === targetId);
301
+ const target = workspaces.deliveryTargetFor(botId, targetId);
302
+ if (!configured || !target || !privateConversationKeyMatchesTarget(
303
+ channel,
304
+ configured.conversationKey,
305
+ target,
306
+ )) throw sessionSyncUnavailable('Session sync target is no longer enabled');
307
+ const sessions = stateSessions(await stateFor(botId));
308
+ if (sessions?.[configured.conversationKey] !== sessionId) {
309
+ throw sessionSyncUnavailable('Session sync target is no longer bound to this Session');
310
+ }
311
+ if (typeof coreController.sendProactiveText !== 'function') {
312
+ throw new TypeError('delivery controller cannot send proactive text');
313
+ }
314
+ await coreController.sendProactiveText(botId, target, text, options);
315
+ return { sent: true };
316
+ },
178
317
  });
179
318
  }
@@ -9,6 +9,7 @@ export const DELIVERY_ENDPOINTS = Object.freeze({
9
9
  createTarget: 'target.create',
10
10
  updateTarget: 'target.update',
11
11
  deleteTarget: 'target.delete',
12
+ setSessionSync: 'target.session-sync.set',
12
13
  testTarget: 'target.test',
13
14
  });
14
15
 
@@ -22,6 +23,7 @@ const PUBLIC_ERRORS = new Set([
22
23
  'bot-not-connected',
23
24
  'target-rejected',
24
25
  'delivery-failed',
26
+ 'session-sync-unavailable',
25
27
  'cancelled',
26
28
  ]);
27
29
 
@@ -85,6 +87,11 @@ function validPayload(endpoint, payload) {
85
87
  || (exactKeys(payload, ['botId', 'target'])
86
88
  && validBotId(payload.botId) && validDraftTarget(payload.target));
87
89
  }
90
+ if (endpoint === DELIVERY_ENDPOINTS.setSessionSync) {
91
+ return exactKeys(payload, ['botId', 'targetId', 'enabled'])
92
+ && validBotId(payload.botId) && validTargetId(payload.targetId)
93
+ && typeof payload.enabled === 'boolean';
94
+ }
88
95
  return exactKeys(payload, ['botId', 'targetId'])
89
96
  && validBotId(payload.botId) && validTargetId(payload.targetId);
90
97
  }
@@ -131,6 +138,11 @@ export function createDeliveryRpcHandler(service) {
131
138
  value = await service.updateTarget(payload.botId, payload.targetId, payload.target);
132
139
  } else if (endpoint === DELIVERY_ENDPOINTS.deleteTarget) {
133
140
  value = await service.deleteTarget(payload.botId, payload.targetId);
141
+ } else if (endpoint === DELIVERY_ENDPOINTS.setSessionSync) {
142
+ if (typeof service.setSessionSync !== 'function') {
143
+ throw new TypeError('Session sync is unavailable');
144
+ }
145
+ value = await service.setSessionSync(payload.botId, payload.targetId, payload.enabled);
134
146
  } else {
135
147
  value = await service.send(
136
148
  payload.botId,