@xmanrui/dsh-im 0.17.1 → 0.19.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 (56) hide show
  1. package/README.en.md +3 -2
  2. package/README.md +3 -2
  3. package/lib/client.js +1297 -600
  4. package/lib/index.js +190 -145
  5. package/package.json +1 -1
  6. package/plugin-src/client/agent-preset.js +110 -0
  7. package/plugin-src/client/channels/dingtalk/api.js +5 -0
  8. package/plugin-src/client/channels/dingtalk/index.js +48 -2
  9. package/plugin-src/client/channels/feishu/api.js +28 -5
  10. package/plugin-src/client/channels/feishu/index.js +330 -41
  11. package/plugin-src/client/channels/feishu/styles.js +46 -0
  12. package/plugin-src/client/channels/qq/api.js +5 -0
  13. package/plugin-src/client/channels/qq/index.js +37 -6
  14. package/plugin-src/client/channels/shared/token-api.js +5 -0
  15. package/plugin-src/client/channels/shared/token-channel.js +34 -6
  16. package/plugin-src/client/channels/wecom/api.js +5 -0
  17. package/plugin-src/client/channels/wecom/index.js +37 -6
  18. package/plugin-src/client/channels/weixin/api.js +5 -0
  19. package/plugin-src/client/channels/weixin/index.js +50 -4
  20. package/plugin-src/client/channels/whatsapp/api.js +5 -0
  21. package/plugin-src/client/channels/whatsapp/index.js +30 -4
  22. package/plugin-src/client/i18n.js +59 -0
  23. package/plugin-src/client/styles.js +8 -0
  24. package/plugin-src/host/channels/dingtalk/production.mjs +10 -4
  25. package/plugin-src/host/channels/dingtalk/rpc.mjs +12 -0
  26. package/plugin-src/host/channels/feishu/connection-supervisor.mjs +1 -1
  27. package/plugin-src/host/channels/feishu/production.mjs +8 -3
  28. package/plugin-src/host/channels/feishu/rpc.mjs +78 -7
  29. package/plugin-src/host/channels/qq/production.mjs +10 -4
  30. package/plugin-src/host/channels/qq/rpc.mjs +12 -0
  31. package/plugin-src/host/channels/shared/agent-preset-rpc.mjs +25 -0
  32. package/plugin-src/host/channels/shared/production.mjs +10 -4
  33. package/plugin-src/host/channels/shared/rpc.mjs +12 -0
  34. package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
  35. package/plugin-src/host/channels/slack/production.mjs +10 -4
  36. package/plugin-src/host/channels/slack/rpc.mjs +13 -0
  37. package/plugin-src/host/channels/wecom/production.mjs +10 -4
  38. package/plugin-src/host/channels/wecom/rpc.mjs +12 -0
  39. package/plugin-src/host/channels/weixin/production.mjs +10 -4
  40. package/plugin-src/host/channels/weixin/rpc.mjs +15 -0
  41. package/plugin-src/host/channels/whatsapp/production.mjs +10 -4
  42. package/plugin-src/host/channels/whatsapp/rpc.mjs +12 -0
  43. package/src/channels/discord/discord-api.mjs +1 -1
  44. package/src/channels/feishu/bridge.mjs +29 -4
  45. package/src/channels/feishu/feishu-runtime.mjs +14 -0
  46. package/src/channels/feishu/group-message-permission-manager.mjs +71 -0
  47. package/src/channels/feishu/group-response-mode.mjs +15 -0
  48. package/src/channels/feishu/multi-bot-controller.mjs +258 -9
  49. package/src/channels/feishu/plugin-config-store.mjs +3 -0
  50. package/src/channels/feishu/repair-manager.mjs +17 -3
  51. package/src/channels/shared/agent-preset.mjs +74 -0
  52. package/src/channels/shared/bot-workspace-store.mjs +141 -14
  53. package/src/channels/shared/harness-client.mjs +6 -4
  54. package/src/channels/telegram/telegram-api.mjs +31 -0
  55. package/src/channels/telegram/telegram-runtime.mjs +25 -1
  56. package/src/channels/weixin/weixin-api.mjs +1 -1
@@ -5,6 +5,7 @@ import {
5
5
  } from '../../../../src/channels/shared/connection-test.mjs';
6
6
  import { resolveRpcAuthority } from '../../rpc-authority.mjs';
7
7
  import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
8
+ import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
8
9
 
9
10
  export const QQ_RPC_CHANNEL = '/qq';
10
11
  export const QQ_ENDPOINTS = Object.freeze({
@@ -16,6 +17,7 @@ export const QQ_ENDPOINTS = Object.freeze({
16
17
  reconnectBot: 'bot.reconnect',
17
18
  deleteBot: 'bot.delete',
18
19
  setWorkspace: SET_WORKSPACE_ENDPOINT,
20
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
19
21
  });
20
22
  export const QQ_RPC_ENDPOINTS = Object.freeze(Object.values(QQ_ENDPOINTS));
21
23
 
@@ -70,6 +72,10 @@ function payloadFailure(endpoint, payload) {
70
72
  return validWorkspacePayload(payload)
71
73
  ? null : '请输入工作区绝对路径。';
72
74
  }
75
+ if (endpoint === QQ_ENDPOINTS.setAgentPreset) {
76
+ return validAgentPresetPayload(payload)
77
+ ? null : '请选择 Agent Preset。';
78
+ }
73
79
  return 'Unknown QQ endpoint.';
74
80
  }
75
81
 
@@ -165,6 +171,12 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
165
171
  await controller.updateWorkspace(payload.botId, payload.workspace),
166
172
  cachedEncode,
167
173
  );
174
+ } else if (endpoint === QQ_ENDPOINTS.setAgentPreset) {
175
+ if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
176
+ value = await publicStatus(
177
+ await controller.updateAgentPreset(payload.botId, payload.agentPreset),
178
+ cachedEncode,
179
+ );
168
180
  } else {
169
181
  value = await publicStatus(await controller.deleteBot(payload.botId), cachedEncode);
170
182
  }
@@ -0,0 +1,25 @@
1
+ import { AGENT_PRESET_ID } from '../../../../src/channels/shared/agent-preset.mjs';
2
+
3
+ export const SET_AGENT_PRESET_ENDPOINT = 'bot.preset.set';
4
+
5
+ function isRecord(value) {
6
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ }
8
+
9
+ export function validAgentPresetPayload(payload) {
10
+ return isRecord(payload)
11
+ && Object.keys(payload).every((key) => ['botId', 'agentPreset'].includes(key))
12
+ && typeof payload.botId === 'string'
13
+ && /^[A-Za-z0-9_-]{1,128}$/.test(payload.botId)
14
+ && (payload.agentPreset === null
15
+ || (typeof payload.agentPreset === 'string' && AGENT_PRESET_ID.test(payload.agentPreset)));
16
+ }
17
+
18
+ export function publicAgentPresetError(error) {
19
+ if (![
20
+ 'agent-preset-invalid',
21
+ 'agent-preset-unavailable',
22
+ 'workspace-bot-not-found',
23
+ ].includes(error?.code)) return null;
24
+ return { code: error.code, message: error.message };
25
+ }
@@ -11,6 +11,7 @@ import {
11
11
  createWorkspaceAwareController,
12
12
  observeBotWorkspaceRemovals,
13
13
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
14
+ import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
14
15
 
15
16
  export function harnessOrigin(webServer, configured) {
16
17
  if (configured !== undefined) return new URL(configured);
@@ -59,7 +60,9 @@ export async function createTokenProductionController(ctx, config, internals, de
59
60
  ?? await new WorkspaceStore(paths.workspaces, { defaultWorkspace }).load();
60
61
  const configuredBots = configStore.list();
61
62
  await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
62
- await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId)));
63
+ await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
64
+ defaultAgentPreset: config.agentPreset,
65
+ })));
63
66
  const observedConfigStore = typeof configStore.remove === 'function'
64
67
  ? observeBotWorkspaceRemovals(configStore, { workspaces })
65
68
  : configStore;
@@ -81,7 +84,6 @@ export async function createTokenProductionController(ctx, config, internals, de
81
84
  const harness = new ResolvedHarness({
82
85
  baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
83
86
  workspace: defaultWorkspace,
84
- ...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
85
87
  autostart: false,
86
88
  dshBin: config.dshBin ?? 'dsh',
87
89
  ...(commandExecutor ? { commandExecutor } : {}),
@@ -95,7 +97,7 @@ export async function createTokenProductionController(ctx, config, internals, de
95
97
  ...(internals.inspectToken ? { inspectToken: internals.inspectToken } : {}),
96
98
  createRuntime: async ({ botId, config: botConfig, token }) => {
97
99
  const state = await stateFor(botId);
98
- await workspaces.ensure(botId);
100
+ await workspaces.ensure(botId, { defaultAgentPreset: config.agentPreset });
99
101
  const workspaceScope = createBotWorkspaceScope(harness, { botId, workspaces, state });
100
102
  return new ResolvedRuntime({
101
103
  ...channelRuntimeOptions,
@@ -127,7 +129,11 @@ export async function createTokenProductionController(ctx, config, internals, de
127
129
  }
128
130
  },
129
131
  });
130
- const controller = createWorkspaceAwareController(coreController, { workspaces, stateFor });
132
+ const controller = createWorkspaceAwareController(coreController, {
133
+ workspaces,
134
+ stateFor,
135
+ agentPresetCatalog: () => listAgentPresetCatalog(ctx),
136
+ });
131
137
  const supervisor = createSupervisor({
132
138
  channel,
133
139
  controller,
@@ -5,6 +5,10 @@ import {
5
5
  SET_WORKSPACE_ENDPOINT,
6
6
  validWorkspacePayload,
7
7
  } from './workspace-rpc.mjs';
8
+ import {
9
+ SET_AGENT_PRESET_ENDPOINT,
10
+ validAgentPresetPayload,
11
+ } from './agent-preset-rpc.mjs';
8
12
 
9
13
  export const TOKEN_BOT_ENDPOINTS = Object.freeze({
10
14
  status: 'connection.status',
@@ -12,6 +16,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
12
16
  reconnectBot: 'bot.reconnect',
13
17
  deleteBot: 'bot.delete',
14
18
  setWorkspace: SET_WORKSPACE_ENDPOINT,
19
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
15
20
  });
16
21
 
17
22
  const ENDPOINTS = Object.freeze(Object.values(TOKEN_BOT_ENDPOINTS));
@@ -57,6 +62,10 @@ function payloadFailure(endpoint, payload) {
57
62
  return validWorkspacePayload(payload)
58
63
  ? null : '请输入工作区绝对路径。';
59
64
  }
65
+ if (endpoint === TOKEN_BOT_ENDPOINTS.setAgentPreset) {
66
+ return validAgentPresetPayload(payload)
67
+ ? null : '请选择 Agent Preset。';
68
+ }
60
69
  return 'Unknown bot endpoint.';
61
70
  }
62
71
 
@@ -132,6 +141,9 @@ export function createTokenBotRpcHandler(controller, { channel }) {
132
141
  } else if (endpoint === TOKEN_BOT_ENDPOINTS.setWorkspace) {
133
142
  if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
134
143
  value = await controller.updateWorkspace(payload.botId, payload.workspace);
144
+ } else if (endpoint === TOKEN_BOT_ENDPOINTS.setAgentPreset) {
145
+ if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
146
+ value = await controller.updateAgentPreset(payload.botId, payload.agentPreset);
135
147
  } else {
136
148
  value = await controller.deleteBot(payload.botId);
137
149
  }
@@ -20,6 +20,8 @@ export function publicWorkspaceError(error) {
20
20
  'workspace-not-found',
21
21
  'workspace-not-directory',
22
22
  'workspace-bot-not-found',
23
+ 'agent-preset-invalid',
24
+ 'agent-preset-unavailable',
23
25
  ].includes(error?.code)) return null;
24
26
  return { code: error.code, message: error.message };
25
27
  }
@@ -12,6 +12,7 @@ import {
12
12
  createWorkspaceAwareController,
13
13
  observeBotWorkspaceRemovals,
14
14
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
15
+ import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
15
16
  import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
16
17
  import { harnessOrigin, pluginPaths } from '../shared/production.mjs';
17
18
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
@@ -37,7 +38,9 @@ export async function createProductionController(ctx, config = {}, internals = {
37
38
  ?? await new WorkspaceStore(paths.workspaces, { defaultWorkspace }).load();
38
39
  const configuredBots = configStore.list();
39
40
  await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
40
- await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId)));
41
+ await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
42
+ defaultAgentPreset: config.agentPreset,
43
+ })));
41
44
  const observedConfigStore = typeof configStore.remove === 'function'
42
45
  ? observeBotWorkspaceRemovals(configStore, { workspaces })
43
46
  : configStore;
@@ -59,7 +62,6 @@ export async function createProductionController(ctx, config = {}, internals = {
59
62
  const harness = new ResolvedHarness({
60
63
  baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
61
64
  workspace: defaultWorkspace,
62
- ...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
63
65
  autostart: false,
64
66
  dshBin: config.dshBin ?? 'dsh',
65
67
  ...(commandExecutor ? { commandExecutor } : {}),
@@ -73,7 +75,7 @@ export async function createProductionController(ctx, config = {}, internals = {
73
75
  ...(internals.inspectCredentials ? { inspectCredentials: internals.inspectCredentials } : {}),
74
76
  createRuntime: async ({ botId, config: botConfig, botToken, appToken }) => {
75
77
  const state = await stateFor(botId);
76
- await workspaces.ensure(botId);
78
+ await workspaces.ensure(botId, { defaultAgentPreset: config.agentPreset });
77
79
  const workspaceScope = createBotWorkspaceScope(harness, { botId, workspaces, state });
78
80
  return new ResolvedRuntime({
79
81
  config: botConfig,
@@ -105,7 +107,11 @@ export async function createProductionController(ctx, config = {}, internals = {
105
107
  }
106
108
  },
107
109
  });
108
- const controller = createWorkspaceAwareController(coreController, { workspaces, stateFor });
110
+ const controller = createWorkspaceAwareController(coreController, {
111
+ workspaces,
112
+ stateFor,
113
+ agentPresetCatalog: () => listAgentPresetCatalog(ctx),
114
+ });
109
115
  const supervisor = createSupervisor({
110
116
  channel: 'slack',
111
117
  controller,
@@ -5,6 +5,10 @@ import {
5
5
  SET_WORKSPACE_ENDPOINT,
6
6
  validWorkspacePayload,
7
7
  } from '../shared/workspace-rpc.mjs';
8
+ import {
9
+ SET_AGENT_PRESET_ENDPOINT,
10
+ validAgentPresetPayload,
11
+ } from '../shared/agent-preset-rpc.mjs';
8
12
 
9
13
  export const SLACK_RPC_CHANNEL = '/slack';
10
14
  export const SLACK_ENDPOINTS = Object.freeze({
@@ -13,6 +17,7 @@ export const SLACK_ENDPOINTS = Object.freeze({
13
17
  reconnectBot: 'bot.reconnect',
14
18
  deleteBot: 'bot.delete',
15
19
  setWorkspace: SET_WORKSPACE_ENDPOINT,
20
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
16
21
  });
17
22
  export const SLACK_RPC_ENDPOINTS = Object.freeze(Object.values(SLACK_ENDPOINTS));
18
23
 
@@ -66,6 +71,10 @@ function payloadFailure(endpoint, payload) {
66
71
  return validWorkspacePayload(payload)
67
72
  ? null : '请输入工作区绝对路径。';
68
73
  }
74
+ if (endpoint === SLACK_ENDPOINTS.setAgentPreset) {
75
+ return validAgentPresetPayload(payload)
76
+ ? null : '请选择 Agent Preset。';
77
+ }
69
78
  return 'Unknown Slack endpoint.';
70
79
  }
71
80
 
@@ -145,6 +154,10 @@ export function createSlackRpcHandler(controller) {
145
154
  if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
146
155
  value = await controller.updateWorkspace(payload.botId, payload.workspace);
147
156
  }
157
+ else if (endpoint === SLACK_ENDPOINTS.setAgentPreset) {
158
+ if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
159
+ value = await controller.updateAgentPreset(payload.botId, payload.agentPreset);
160
+ }
148
161
  else value = await controller.deleteBot(payload.botId);
149
162
  return signal?.aborted
150
163
  ? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
@@ -14,6 +14,7 @@ import {
14
14
  createWorkspaceAwareController,
15
15
  observeBotWorkspaceRemovals,
16
16
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
17
+ import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
17
18
  import { createConnectionSupervisor } from './connection-supervisor.mjs';
18
19
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
19
20
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
@@ -57,7 +58,9 @@ export async function createProductionController(ctx, config = {}, internals = {
57
58
  ?? await new WorkspaceStore(paths.workspaces, { defaultWorkspace }).load();
58
59
  const configuredBots = configStore.list();
59
60
  await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
60
- await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId)));
61
+ await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
62
+ defaultAgentPreset: config.agentPreset,
63
+ })));
61
64
  const observedConfigStore = typeof configStore.remove === 'function'
62
65
  ? observeBotWorkspaceRemovals(configStore, { workspaces })
63
66
  : configStore;
@@ -83,7 +86,6 @@ export async function createProductionController(ctx, config = {}, internals = {
83
86
  const harness = new Harness({
84
87
  baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
85
88
  workspace: defaultWorkspace,
86
- ...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
87
89
  autostart: false,
88
90
  dshBin: config.dshBin ?? 'dsh',
89
91
  ...(commandExecutor ? { commandExecutor } : {}),
@@ -97,7 +99,7 @@ export async function createProductionController(ctx, config = {}, internals = {
97
99
  logger,
98
100
  createRuntime: async ({ botId, config: botConfig, secret }) => {
99
101
  const state = await stateFor(botId);
100
- await workspaces.ensure(botId);
102
+ await workspaces.ensure(botId, { defaultAgentPreset: config.agentPreset });
101
103
  const workspaceScope = createBotWorkspaceScope(harness, { botId, workspaces, state });
102
104
  return new Runtime({
103
105
  config: botConfig,
@@ -129,7 +131,11 @@ export async function createProductionController(ctx, config = {}, internals = {
129
131
  }
130
132
  },
131
133
  });
132
- const controller = createWorkspaceAwareController(coreController, { workspaces, stateFor });
134
+ const controller = createWorkspaceAwareController(coreController, {
135
+ workspaces,
136
+ stateFor,
137
+ agentPresetCatalog: () => listAgentPresetCatalog(ctx),
138
+ });
133
139
  const supervisor = createSupervisor({
134
140
  controller,
135
141
  harness,
@@ -1,6 +1,7 @@
1
1
  import QRCode from 'qrcode';
2
2
  import { resolveRpcAuthority } from '../../rpc-authority.mjs';
3
3
  import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
4
+ import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
4
5
  import {
5
6
  connectionTestTargetUnavailable,
6
7
  publicConnectionTestResult,
@@ -16,6 +17,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
16
17
  reconnectBot: 'bot.reconnect',
17
18
  deleteBot: 'bot.delete',
18
19
  setWorkspace: SET_WORKSPACE_ENDPOINT,
20
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
19
21
  });
20
22
  export const WECOM_RPC_ENDPOINTS = Object.freeze(Object.values(WECOM_ENDPOINTS));
21
23
 
@@ -70,6 +72,10 @@ function payloadFailure(endpoint, payload) {
70
72
  return validWorkspacePayload(payload)
71
73
  ? null : '请输入工作区绝对路径。';
72
74
  }
75
+ if (endpoint === WECOM_ENDPOINTS.setAgentPreset) {
76
+ return validAgentPresetPayload(payload)
77
+ ? null : '请选择 Agent Preset。';
78
+ }
73
79
  return 'Unknown Enterprise WeChat endpoint.';
74
80
  }
75
81
 
@@ -166,6 +172,12 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
166
172
  await controller.updateWorkspace(payload.botId, payload.workspace),
167
173
  cachedEncode,
168
174
  );
175
+ } else if (endpoint === WECOM_ENDPOINTS.setAgentPreset) {
176
+ if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
177
+ value = await publicStatus(
178
+ await controller.updateAgentPreset(payload.botId, payload.agentPreset),
179
+ cachedEncode,
180
+ );
169
181
  } else {
170
182
  value = await publicStatus(await controller.deleteBot(payload.botId), cachedEncode);
171
183
  }
@@ -14,6 +14,7 @@ import {
14
14
  createWorkspaceAwareController,
15
15
  observeBotWorkspaceRemovals,
16
16
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
17
+ import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
17
18
  import { createConnectionSupervisor } from './connection-supervisor.mjs';
18
19
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
19
20
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
@@ -60,7 +61,9 @@ export async function createProductionController(ctx, config = {}, internals = {
60
61
  ?? await new WorkspaceStore(paths.workspaces, { defaultWorkspace }).load();
61
62
  const configuredBots = configStore.list();
62
63
  await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
63
- await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId)));
64
+ await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
65
+ defaultAgentPreset: config.agentPreset,
66
+ })));
64
67
  const observedConfigStore = typeof configStore.remove === 'function'
65
68
  ? observeBotWorkspaceRemovals(configStore, { workspaces })
66
69
  : configStore;
@@ -83,7 +86,6 @@ export async function createProductionController(ctx, config = {}, internals = {
83
86
  const harness = new Harness({
84
87
  baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
85
88
  workspace: defaultWorkspace,
86
- ...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
87
89
  autostart: false,
88
90
  dshBin: config.dshBin ?? 'dsh',
89
91
  ...(commandExecutor ? { commandExecutor } : {}),
@@ -97,7 +99,7 @@ export async function createProductionController(ctx, config = {}, internals = {
97
99
  logger,
98
100
  createRuntime: async ({ botId, config: accountConfig, token }) => {
99
101
  const state = await stateFor(botId);
100
- await workspaces.ensure(botId);
102
+ await workspaces.ensure(botId, { defaultAgentPreset: config.agentPreset });
101
103
  const workspaceScope = createBotWorkspaceScope(harness, { botId, workspaces, state });
102
104
  return new Runtime({
103
105
  api,
@@ -129,7 +131,11 @@ export async function createProductionController(ctx, config = {}, internals = {
129
131
  }
130
132
  },
131
133
  });
132
- const controller = createWorkspaceAwareController(coreController, { workspaces, stateFor });
134
+ const controller = createWorkspaceAwareController(coreController, {
135
+ workspaces,
136
+ stateFor,
137
+ agentPresetCatalog: () => listAgentPresetCatalog(ctx),
138
+ });
133
139
  const supervisor = createSupervisor({
134
140
  controller,
135
141
  harness,
@@ -5,6 +5,10 @@ import {
5
5
  SET_WORKSPACE_ENDPOINT,
6
6
  validWorkspacePayload,
7
7
  } from '../shared/workspace-rpc.mjs';
8
+ import {
9
+ SET_AGENT_PRESET_ENDPOINT,
10
+ validAgentPresetPayload,
11
+ } from '../shared/agent-preset-rpc.mjs';
8
12
  import {
9
13
  connectionTestTargetUnavailable,
10
14
  publicConnectionTestResult,
@@ -20,6 +24,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
20
24
  reconnectBot: 'bot.reconnect',
21
25
  deleteBot: 'bot.delete',
22
26
  setWorkspace: SET_WORKSPACE_ENDPOINT,
27
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
23
28
  });
24
29
  export const WEIXIN_RPC_ENDPOINTS = Object.freeze(Object.values(WEIXIN_ENDPOINTS));
25
30
 
@@ -74,6 +79,10 @@ function payloadFailure(endpoint, payload) {
74
79
  return validWorkspacePayload(payload)
75
80
  ? null : '请输入工作区绝对路径。';
76
81
  }
82
+ if (endpoint === WEIXIN_ENDPOINTS.setAgentPreset) {
83
+ return validAgentPresetPayload(payload)
84
+ ? null : '请选择 Agent Preset。';
85
+ }
77
86
  return 'Unknown Weixin endpoint.';
78
87
  }
79
88
 
@@ -198,6 +207,12 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
198
207
  await controller.updateWorkspace(payload.botId, payload.workspace),
199
208
  cachedEncode,
200
209
  );
210
+ } else if (endpoint === WEIXIN_ENDPOINTS.setAgentPreset) {
211
+ if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
212
+ value = await publicStatus(
213
+ await controller.updateAgentPreset(payload.botId, payload.agentPreset),
214
+ cachedEncode,
215
+ );
201
216
  } else {
202
217
  value = await publicStatus(await controller.deleteBot(payload.botId), cachedEncode);
203
218
  }
@@ -14,6 +14,7 @@ import {
14
14
  createWorkspaceAwareController,
15
15
  observeBotWorkspaceRemovals,
16
16
  } from '../../../../src/channels/shared/bot-workspace-store.mjs';
17
+ import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
17
18
  import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
18
19
  import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
19
20
  import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
@@ -64,7 +65,9 @@ export async function createProductionController(ctx, config = {}, internals = {
64
65
  ?? await new WorkspaceStore(paths.workspaces, { defaultWorkspace }).load();
65
66
  const configuredBots = configStore.list();
66
67
  await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
67
- await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId)));
68
+ await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
69
+ defaultAgentPreset: config.agentPreset,
70
+ })));
68
71
  const observedConfigStore = typeof configStore.remove === 'function'
69
72
  ? observeBotWorkspaceRemovals(configStore, { workspaces })
70
73
  : configStore;
@@ -86,7 +89,6 @@ export async function createProductionController(ctx, config = {}, internals = {
86
89
  const harness = new Harness({
87
90
  baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
88
91
  workspace: defaultWorkspace,
89
- ...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
90
92
  autostart: false,
91
93
  dshBin: config.dshBin ?? 'dsh',
92
94
  ...(commandExecutor ? { commandExecutor } : {}),
@@ -100,7 +102,7 @@ export async function createProductionController(ctx, config = {}, internals = {
100
102
  logger,
101
103
  createRuntime: async ({ botId, config: botConfig, authDir }) => {
102
104
  const state = await stateFor(botId);
103
- await workspaces.ensure(botId);
105
+ await workspaces.ensure(botId, { defaultAgentPreset: config.agentPreset });
104
106
  const workspaceScope = createBotWorkspaceScope(harness, { botId, workspaces, state });
105
107
  return new Runtime({
106
108
  config: botConfig,
@@ -136,7 +138,11 @@ export async function createProductionController(ctx, config = {}, internals = {
136
138
  }
137
139
  },
138
140
  });
139
- const controller = createWorkspaceAwareController(coreController, { workspaces, stateFor });
141
+ const controller = createWorkspaceAwareController(coreController, {
142
+ workspaces,
143
+ stateFor,
144
+ agentPresetCatalog: () => listAgentPresetCatalog(ctx),
145
+ });
140
146
  const supervisor = createSupervisor({
141
147
  channel: 'whatsapp',
142
148
  controller,
@@ -3,6 +3,7 @@ import QRCode from 'qrcode';
3
3
  import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
4
4
  import { resolveRpcAuthority } from '../../rpc-authority.mjs';
5
5
  import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
6
+ import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
6
7
 
7
8
  export const WHATSAPP_RPC_CHANNEL = '/whatsapp';
8
9
  export const WHATSAPP_ENDPOINTS = Object.freeze({
@@ -13,6 +14,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
13
14
  reconnectBot: 'bot.reconnect',
14
15
  deleteBot: 'bot.delete',
15
16
  setWorkspace: SET_WORKSPACE_ENDPOINT,
17
+ setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
16
18
  });
17
19
  export const WHATSAPP_RPC_ENDPOINTS = Object.freeze(Object.values(WHATSAPP_ENDPOINTS));
18
20
 
@@ -52,6 +54,10 @@ function payloadFailure(endpoint, payload) {
52
54
  return validWorkspacePayload(payload)
53
55
  ? null : '请输入工作区绝对路径。';
54
56
  }
57
+ if (endpoint === WHATSAPP_ENDPOINTS.setAgentPreset) {
58
+ return validAgentPresetPayload(payload)
59
+ ? null : '请选择 Agent Preset。';
60
+ }
55
61
  return 'Unknown WhatsApp endpoint.';
56
62
  }
57
63
 
@@ -154,6 +160,12 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
154
160
  await controller.updateWorkspace(payload.botId, payload.workspace),
155
161
  cachedEncode,
156
162
  );
163
+ } else if (endpoint === WHATSAPP_ENDPOINTS.setAgentPreset) {
164
+ if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
165
+ value = await publicStatus(
166
+ await controller.updateAgentPreset(payload.botId, payload.agentPreset),
167
+ cachedEncode,
168
+ );
157
169
  } else {
158
170
  value = await publicStatus(await controller.deleteBot(payload.botId), cachedEncode);
159
171
  }
@@ -108,7 +108,7 @@ export class DiscordApi {
108
108
  headers: {
109
109
  authorization: `Bot ${this.#token}`,
110
110
  'content-type': 'application/json',
111
- 'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.17.1)',
111
+ 'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.19.0)',
112
112
  },
113
113
  ...(body === undefined ? {} : { body: JSON.stringify(body) }),
114
114
  signal: requestSignal(signal, timeoutMs),
@@ -40,6 +40,10 @@ import {
40
40
  workspaceListCard,
41
41
  } from './feishu-cards.mjs';
42
42
  import { MAX_WATCHES_PER_KEY } from './state-store.mjs';
43
+ import {
44
+ FEISHU_GROUP_RESPONSE_MODES,
45
+ normalizeFeishuGroupResponseMode,
46
+ } from './group-response-mode.mjs';
43
47
 
44
48
  const INTERACTION_RESOLVED_TEXT = '这个问题已在其他客户端处理,无需再次回答。';
45
49
  const RESOLVED_REPLY_TTL_MS = 30 * 60_000;
@@ -249,6 +253,8 @@ export class FeishuHarnessBridge {
249
253
  #signal;
250
254
  #botId;
251
255
  #appId;
256
+ #botOpenId;
257
+ #groupResponseMode;
252
258
  #repair;
253
259
  #repairOwnerOpenIds;
254
260
  #repairAttempt = null;
@@ -275,6 +281,8 @@ export class FeishuHarnessBridge {
275
281
  allowedSenderOpenIds = new Set(),
276
282
  botId,
277
283
  appId,
284
+ botOpenId,
285
+ groupResponseMode = FEISHU_GROUP_RESPONSE_MODES.ALL,
278
286
  repair,
279
287
  repairOwnerOpenIds,
280
288
  repairPollIntervalMs = REPAIR_POLL_INTERVAL_MS,
@@ -308,6 +316,8 @@ export class FeishuHarnessBridge {
308
316
  this.#allowedSenderOpenIds = allowedSenderOpenIds;
309
317
  this.#botId = nonEmptyString(botId);
310
318
  this.#appId = nonEmptyString(appId);
319
+ this.#botOpenId = nonEmptyString(botOpenId);
320
+ this.#groupResponseMode = normalizeFeishuGroupResponseMode(groupResponseMode);
311
321
  this.#repair = repair ?? null;
312
322
  const repairOwners = repairOwnerOpenIds ?? allowedSenderOpenIds;
313
323
  this.#repairOwnerOpenIds = new Set(
@@ -327,6 +337,18 @@ export class FeishuHarnessBridge {
327
337
  }
328
338
  }
329
339
 
340
+ setGroupResponseMode(value) {
341
+ this.#groupResponseMode = normalizeFeishuGroupResponseMode(value);
342
+ }
343
+
344
+ #isAddressed(event) {
345
+ if (event?.message?.chat_type === 'p2p') return true;
346
+ const mentions = Array.isArray(event?.message?.mentions) ? event.message.mentions : [];
347
+ if (!this.#botOpenId) return mentions.length > 0;
348
+ return mentions.some((mention) => mention?.id?.open_id === this.#botOpenId
349
+ || mention?.open_id === this.#botOpenId);
350
+ }
351
+
330
352
  accept(event) {
331
353
  if (this.#signal?.aborted) return Promise.resolve();
332
354
  const messageId = nonEmptyString(event?.message?.message_id);
@@ -337,6 +359,12 @@ export class FeishuHarnessBridge {
337
359
  this.#logger.warn?.('[dsh-feishu] ignored a message from a sender outside the allowlist');
338
360
  return Promise.resolve();
339
361
  }
362
+ const addressed = this.#isAddressed(event);
363
+ if (event?.message?.chat_type !== 'p2p'
364
+ && this.#groupResponseMode === FEISHU_GROUP_RESPONSE_MODES.MENTION
365
+ && !addressed) {
366
+ return Promise.resolve();
367
+ }
340
368
  if (this.#state.hasSeen(messageId) || this.#acceptedMessageIds.has(messageId)) {
341
369
  return Promise.resolve();
342
370
  }
@@ -362,8 +390,6 @@ export class FeishuHarnessBridge {
362
390
  const commandRunner = isControlCommand(commandText)
363
391
  ? runControlCommand
364
392
  : (isModelCommand(commandText) ? runModelCommand : null);
365
- const addressed = event?.message?.chat_type === 'p2p'
366
- || (Array.isArray(event?.message?.mentions) && event.message.mentions.length > 0);
367
393
  if (commandRunner && addressed) {
368
394
  const processing = this.#processFastCommand(
369
395
  event,
@@ -407,8 +433,7 @@ export class FeishuHarnessBridge {
407
433
  actor: senderOpenId(event),
408
434
  messageId,
409
435
  text: extractText(event) ?? '',
410
- addressed: event?.message?.chat_type === 'p2p'
411
- || (Array.isArray(event?.message?.mentions) && event.message.mentions.length > 0),
436
+ addressed,
412
437
  hasPendingQuestion: Boolean(pending),
413
438
  questionCompletion: pending?.submitting || pending?.claimedReplyMessageId
414
439
  ? pending.queue