@xmanrui/dsh-im 0.17.0 → 0.18.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.
- package/README.en.md +3 -2
- package/README.md +3 -2
- package/lib/client.js +964 -538
- package/lib/index.js +189 -144
- package/package.json +1 -1
- package/plugin-src/client/agent-preset.js +110 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -0
- package/plugin-src/client/channels/dingtalk/index.js +48 -2
- package/plugin-src/client/channels/feishu/api.js +5 -0
- package/plugin-src/client/channels/feishu/index.js +39 -2
- package/plugin-src/client/channels/qq/api.js +5 -0
- package/plugin-src/client/channels/qq/index.js +37 -6
- package/plugin-src/client/channels/shared/token-api.js +5 -0
- package/plugin-src/client/channels/shared/token-channel.js +34 -6
- package/plugin-src/client/channels/wecom/api.js +5 -0
- package/plugin-src/client/channels/wecom/index.js +37 -6
- package/plugin-src/client/channels/weixin/api.js +15 -0
- package/plugin-src/client/channels/weixin/index.js +54 -4
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +30 -4
- package/plugin-src/client/i18n.js +29 -0
- package/plugin-src/client/styles.js +8 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +10 -4
- package/plugin-src/host/channels/dingtalk/rpc.mjs +12 -0
- package/plugin-src/host/channels/feishu/connection-supervisor.mjs +1 -1
- package/plugin-src/host/channels/feishu/production.mjs +6 -3
- package/plugin-src/host/channels/feishu/rpc.mjs +17 -0
- package/plugin-src/host/channels/qq/production.mjs +10 -4
- package/plugin-src/host/channels/qq/rpc.mjs +12 -0
- package/plugin-src/host/channels/shared/agent-preset-rpc.mjs +25 -0
- package/plugin-src/host/channels/shared/production.mjs +10 -4
- package/plugin-src/host/channels/shared/rpc.mjs +12 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
- package/plugin-src/host/channels/slack/production.mjs +10 -4
- package/plugin-src/host/channels/slack/rpc.mjs +13 -0
- package/plugin-src/host/channels/wecom/production.mjs +10 -4
- package/plugin-src/host/channels/wecom/rpc.mjs +12 -0
- package/plugin-src/host/channels/weixin/production.mjs +10 -4
- package/plugin-src/host/channels/weixin/rpc.mjs +15 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +10 -4
- package/plugin-src/host/channels/whatsapp/rpc.mjs +12 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/shared/agent-preset.mjs +74 -0
- package/src/channels/shared/bot-workspace-store.mjs +141 -14
- package/src/channels/shared/harness-client.mjs +6 -4
- package/src/channels/shared/image-prompt.mjs +32 -1
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +23 -3
- package/src/channels/weixin/weixin-controller.mjs +15 -0
|
@@ -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, {
|
|
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.
|
|
111
|
+
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.18.0)',
|
|
112
112
|
},
|
|
113
113
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
114
114
|
signal: requestSignal(signal, timeoutMs),
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Matches DeepSeek Harness agent-preset directory ids. */
|
|
2
|
+
export const AGENT_PRESET_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
3
|
+
|
|
4
|
+
export const EMPTY_AGENT_PRESET_CATALOG = Object.freeze({
|
|
5
|
+
defaultId: '',
|
|
6
|
+
items: Object.freeze([]),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export function normalizeAgentPresetId(value) {
|
|
10
|
+
if (value == null) return null;
|
|
11
|
+
if (typeof value !== 'string') return null;
|
|
12
|
+
const id = value.trim();
|
|
13
|
+
return AGENT_PRESET_ID.test(id) ? id : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function validateAgentPresetId(value) {
|
|
17
|
+
if (value == null || value === '') return null;
|
|
18
|
+
const id = normalizeAgentPresetId(value);
|
|
19
|
+
if (!id) {
|
|
20
|
+
const error = new Error('Agent Preset 无效。');
|
|
21
|
+
error.code = 'agent-preset-invalid';
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function catalogItem(value) {
|
|
28
|
+
if (typeof value === 'string') {
|
|
29
|
+
const id = normalizeAgentPresetId(value);
|
|
30
|
+
return id ? { id, label: id } : null;
|
|
31
|
+
}
|
|
32
|
+
if (!value || typeof value !== 'object') return null;
|
|
33
|
+
if (value.broken !== undefined) return null;
|
|
34
|
+
const id = normalizeAgentPresetId(value.id);
|
|
35
|
+
if (!id) return null;
|
|
36
|
+
const label = typeof value.name === 'string' && value.name.trim()
|
|
37
|
+
? value.name.trim().slice(0, 128)
|
|
38
|
+
: typeof value.label === 'string' && value.label.trim()
|
|
39
|
+
? value.label.trim().slice(0, 128)
|
|
40
|
+
: id;
|
|
41
|
+
return { id, label };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizeAgentPresetCatalog(value) {
|
|
45
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
46
|
+
return { defaultId: '', items: [] };
|
|
47
|
+
}
|
|
48
|
+
const items = [];
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
for (const entry of Array.isArray(value.items) ? value.items : []) {
|
|
51
|
+
const item = catalogItem(entry);
|
|
52
|
+
if (!item || seen.has(item.id)) continue;
|
|
53
|
+
seen.add(item.id);
|
|
54
|
+
items.push(item);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
defaultId: normalizeAgentPresetId(value.defaultId) ?? '',
|
|
58
|
+
items,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function listAgentPresetCatalog(ctx) {
|
|
63
|
+
try {
|
|
64
|
+
const service = typeof ctx?.get === 'function' ? ctx.get('agentPresets') : ctx?.agentPresets;
|
|
65
|
+
if (!service || typeof service.list !== 'function') return { defaultId: '', items: [] };
|
|
66
|
+
const listed = await service.list();
|
|
67
|
+
return normalizeAgentPresetCatalog({
|
|
68
|
+
defaultId: typeof service.defaultId === 'string' ? service.defaultId : '',
|
|
69
|
+
items: Array.isArray(listed) ? listed : [],
|
|
70
|
+
});
|
|
71
|
+
} catch {
|
|
72
|
+
return { defaultId: '', items: [] };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
} from 'node:fs/promises';
|
|
10
10
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
normalizeAgentPresetCatalog,
|
|
14
|
+
validateAgentPresetId,
|
|
15
|
+
} from './agent-preset.mjs';
|
|
12
16
|
import { CONNECTION_TEST_STATE_IDENTITY } from './connection-test.mjs';
|
|
13
17
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
14
18
|
|
|
@@ -49,7 +53,22 @@ function normalizeDocument(value) {
|
|
|
49
53
|
|| typeof workspace !== 'string' || !isAbsolute(workspace)) return null;
|
|
50
54
|
workspaces[botId] = resolve(workspace);
|
|
51
55
|
}
|
|
52
|
-
|
|
56
|
+
let agentPresets = {};
|
|
57
|
+
if (value.agentPresets !== undefined) {
|
|
58
|
+
if (!value.agentPresets || typeof value.agentPresets !== 'object'
|
|
59
|
+
|| Array.isArray(value.agentPresets)) return null;
|
|
60
|
+
for (const [botId, agentPreset] of Object.entries(value.agentPresets)) {
|
|
61
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return null;
|
|
62
|
+
try {
|
|
63
|
+
const normalized = validateAgentPresetId(agentPreset);
|
|
64
|
+
if (!normalized) return null;
|
|
65
|
+
agentPresets[botId] = normalized;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { version: 1, workspaces, agentPresets };
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
export async function validateWorkspacePath(value) {
|
|
@@ -79,6 +98,7 @@ export class BotWorkspaceStore {
|
|
|
79
98
|
#path;
|
|
80
99
|
#defaultWorkspace;
|
|
81
100
|
#workspaces = {};
|
|
101
|
+
#agentPresets = {};
|
|
82
102
|
#generations = new Map();
|
|
83
103
|
#nextGeneration = 1;
|
|
84
104
|
#incarnations = new Map();
|
|
@@ -100,9 +120,11 @@ export class BotWorkspaceStore {
|
|
|
100
120
|
const normalized = normalizeDocument(JSON.parse(await readFile(this.#path, 'utf8')));
|
|
101
121
|
if (!normalized) throw new Error('dsh-im workspace config is invalid');
|
|
102
122
|
this.#workspaces = normalized.workspaces;
|
|
123
|
+
this.#agentPresets = normalized.agentPresets;
|
|
103
124
|
} catch (error) {
|
|
104
125
|
if (error?.code !== 'ENOENT') throw error;
|
|
105
126
|
this.#workspaces = {};
|
|
127
|
+
this.#agentPresets = {};
|
|
106
128
|
}
|
|
107
129
|
this.#generations.clear();
|
|
108
130
|
this.#nextGeneration = 1;
|
|
@@ -130,6 +152,10 @@ export class BotWorkspaceStore {
|
|
|
130
152
|
return this.#workspaces[botIdOf(botId)] ?? this.#defaultWorkspace;
|
|
131
153
|
}
|
|
132
154
|
|
|
155
|
+
agentPresetFor(botId) {
|
|
156
|
+
return this.#agentPresets[botIdOf(botId)] ?? null;
|
|
157
|
+
}
|
|
158
|
+
|
|
133
159
|
generationFor(botId) {
|
|
134
160
|
return this.#generations.get(botIdOf(botId)) ?? null;
|
|
135
161
|
}
|
|
@@ -148,18 +174,24 @@ export class BotWorkspaceStore {
|
|
|
148
174
|
}
|
|
149
175
|
}
|
|
150
176
|
|
|
151
|
-
async ensure(botId, { workspace = this.#defaultWorkspace } = {}) {
|
|
177
|
+
async ensure(botId, { workspace = this.#defaultWorkspace, defaultAgentPreset } = {}) {
|
|
152
178
|
const id = botIdOf(botId);
|
|
153
179
|
const initialWorkspace = resolve(workspace);
|
|
154
180
|
return this.#enqueue(id, async () => {
|
|
155
181
|
if (!this.#workspaces[id]) {
|
|
182
|
+
const agentPreset = validateAgentPresetId(defaultAgentPreset);
|
|
183
|
+
const hadAgentPreset = Object.hasOwn(this.#agentPresets, id);
|
|
184
|
+
const previousAgentPreset = this.#agentPresets[id];
|
|
156
185
|
this.#workspaces[id] = initialWorkspace;
|
|
186
|
+
if (agentPreset) this.#agentPresets[id] = agentPreset;
|
|
157
187
|
this.#generations.set(id, this.#freshGeneration());
|
|
158
188
|
this.#incarnations.set(id, this.#freshIncarnation());
|
|
159
189
|
try {
|
|
160
190
|
await this.#persist();
|
|
161
191
|
} catch (error) {
|
|
162
192
|
delete this.#workspaces[id];
|
|
193
|
+
if (hadAgentPreset) this.#agentPresets[id] = previousAgentPreset;
|
|
194
|
+
else delete this.#agentPresets[id];
|
|
163
195
|
this.#generations.delete(id);
|
|
164
196
|
this.#incarnations.delete(id);
|
|
165
197
|
throw error;
|
|
@@ -207,6 +239,37 @@ export class BotWorkspaceStore {
|
|
|
207
239
|
});
|
|
208
240
|
}
|
|
209
241
|
|
|
242
|
+
async setAgentPreset(botId, value, { incarnation } = {}) {
|
|
243
|
+
const id = botIdOf(botId);
|
|
244
|
+
if (!this.has(id)
|
|
245
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
246
|
+
const error = new Error('找不到要修改的机器人。');
|
|
247
|
+
error.code = 'workspace-bot-not-found';
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
const agentPreset = validateAgentPresetId(value);
|
|
251
|
+
return this.#enqueue(id, async () => {
|
|
252
|
+
if (!this.has(id)
|
|
253
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
254
|
+
const error = new Error('找不到要修改的机器人。');
|
|
255
|
+
error.code = 'workspace-bot-not-found';
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
const previous = this.#agentPresets[id] ?? null;
|
|
259
|
+
if (previous === agentPreset) return agentPreset;
|
|
260
|
+
if (agentPreset) this.#agentPresets[id] = agentPreset;
|
|
261
|
+
else delete this.#agentPresets[id];
|
|
262
|
+
try {
|
|
263
|
+
await this.#persist();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (previous) this.#agentPresets[id] = previous;
|
|
266
|
+
else delete this.#agentPresets[id];
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
return agentPreset;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
210
273
|
async bindWorkspaceSession(botId, value, {
|
|
211
274
|
conversationKey,
|
|
212
275
|
sessionId,
|
|
@@ -353,7 +416,11 @@ export class BotWorkspaceStore {
|
|
|
353
416
|
|
|
354
417
|
async reconcile(activeBotIds) {
|
|
355
418
|
const active = new Set([...activeBotIds].map(botIdOf));
|
|
356
|
-
const candidates = new Set([
|
|
419
|
+
const candidates = new Set([
|
|
420
|
+
...Object.keys(this.#workspaces),
|
|
421
|
+
...Object.keys(this.#agentPresets),
|
|
422
|
+
...this.#dirtyRemovals,
|
|
423
|
+
]);
|
|
357
424
|
for (const botId of candidates) {
|
|
358
425
|
if (!active.has(botId)) await this.remove(botId);
|
|
359
426
|
}
|
|
@@ -364,7 +431,11 @@ export class BotWorkspaceStore {
|
|
|
364
431
|
return {
|
|
365
432
|
...status,
|
|
366
433
|
bots: status.bots.map((bot) => bot?.botId
|
|
367
|
-
? {
|
|
434
|
+
? {
|
|
435
|
+
...bot,
|
|
436
|
+
workspace: this.workspaceFor(bot.botId),
|
|
437
|
+
agentPreset: this.agentPresetFor(bot.botId),
|
|
438
|
+
}
|
|
368
439
|
: bot),
|
|
369
440
|
};
|
|
370
441
|
}
|
|
@@ -392,8 +463,10 @@ export class BotWorkspaceStore {
|
|
|
392
463
|
|
|
393
464
|
async #retireCurrentIncarnation(id) {
|
|
394
465
|
const hadWorkspace = Object.hasOwn(this.#workspaces, id);
|
|
395
|
-
const
|
|
466
|
+
const hadPreset = Object.hasOwn(this.#agentPresets, id);
|
|
467
|
+
const needsCleanup = hadWorkspace || hadPreset || this.#dirtyRemovals.has(id);
|
|
396
468
|
delete this.#workspaces[id];
|
|
469
|
+
delete this.#agentPresets[id];
|
|
397
470
|
this.#generations.delete(id);
|
|
398
471
|
this.#incarnations.delete(id);
|
|
399
472
|
if (!needsCleanup) return {
|
|
@@ -425,6 +498,9 @@ export class BotWorkspaceStore {
|
|
|
425
498
|
|
|
426
499
|
async #persist() {
|
|
427
500
|
const document = { version: 1, workspaces: this.#workspaces };
|
|
501
|
+
if (Object.keys(this.#agentPresets).length > 0) {
|
|
502
|
+
document.agentPresets = this.#agentPresets;
|
|
503
|
+
}
|
|
428
504
|
await mkdir(dirname(this.#path), { recursive: true, mode: 0o700 });
|
|
429
505
|
const temporary = `${this.#path}.tmp`;
|
|
430
506
|
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, {
|
|
@@ -436,7 +512,8 @@ export class BotWorkspaceStore {
|
|
|
436
512
|
}
|
|
437
513
|
|
|
438
514
|
async #persistCurrentDocument() {
|
|
439
|
-
if (Object.keys(this.#workspaces).length > 0
|
|
515
|
+
if (Object.keys(this.#workspaces).length > 0
|
|
516
|
+
|| Object.keys(this.#agentPresets).length > 0) {
|
|
440
517
|
await this.#persist();
|
|
441
518
|
return;
|
|
442
519
|
}
|
|
@@ -450,10 +527,29 @@ export class BotWorkspaceStore {
|
|
|
450
527
|
}
|
|
451
528
|
}
|
|
452
529
|
|
|
453
|
-
function
|
|
530
|
+
function resolveAgentPresetCatalog(catalog) {
|
|
531
|
+
if (!catalog) return null;
|
|
532
|
+
const value = typeof catalog === 'function' ? catalog() : catalog;
|
|
533
|
+
return value && typeof value.then === 'function'
|
|
534
|
+
? value.then(normalizeAgentPresetCatalog)
|
|
535
|
+
: normalizeAgentPresetCatalog(value);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function decorateResult(workspaces, result, catalog) {
|
|
539
|
+
const decorate = (value) => {
|
|
540
|
+
const decorated = workspaces.decorateStatus(value);
|
|
541
|
+
if (!catalog || !decorated || typeof decorated !== 'object') return decorated;
|
|
542
|
+
const attachCatalog = (agentPresetCatalog) => (
|
|
543
|
+
agentPresetCatalog ? { ...decorated, agentPresetCatalog } : decorated
|
|
544
|
+
);
|
|
545
|
+
const agentPresetCatalog = resolveAgentPresetCatalog(catalog);
|
|
546
|
+
return agentPresetCatalog && typeof agentPresetCatalog.then === 'function'
|
|
547
|
+
? agentPresetCatalog.then(attachCatalog)
|
|
548
|
+
: attachCatalog(agentPresetCatalog);
|
|
549
|
+
};
|
|
454
550
|
return result && typeof result.then === 'function'
|
|
455
|
-
? result.then(
|
|
456
|
-
:
|
|
551
|
+
? result.then(decorate)
|
|
552
|
+
: decorate(result);
|
|
457
553
|
}
|
|
458
554
|
|
|
459
555
|
function targetStatus(controller) {
|
|
@@ -608,9 +704,11 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
608
704
|
throw error;
|
|
609
705
|
}
|
|
610
706
|
const generation = workspaces.generationFor(botId);
|
|
707
|
+
const agentPreset = workspaces.agentPresetFor(botId);
|
|
611
708
|
const sessionId = await target.createSession({
|
|
612
709
|
...options,
|
|
613
710
|
workspace: workspaces.workspaceFor(botId),
|
|
711
|
+
...(agentPreset == null ? {} : { agentPreset }),
|
|
614
712
|
});
|
|
615
713
|
sessionGenerations.set(sessionId, generation);
|
|
616
714
|
return sessionId;
|
|
@@ -765,7 +863,7 @@ export function createBotScopedHarness(harness, options) {
|
|
|
765
863
|
return createBotWorkspaceScope(harness, options).harness;
|
|
766
864
|
}
|
|
767
865
|
|
|
768
|
-
export function createWorkspaceAwareController(controller, { workspaces, stateFor }) {
|
|
866
|
+
export function createWorkspaceAwareController(controller, { workspaces, stateFor, agentPresetCatalog } = {}) {
|
|
769
867
|
if (!controller || !workspaces || typeof stateFor !== 'function') {
|
|
770
868
|
throw new TypeError('controller, workspaces, and stateFor are required');
|
|
771
869
|
}
|
|
@@ -778,6 +876,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
778
876
|
if (transitions.get(botId) === current) transitions.delete(botId);
|
|
779
877
|
});
|
|
780
878
|
};
|
|
879
|
+
const decorate = (value) => decorateResult(workspaces, value, agentPresetCatalog);
|
|
781
880
|
const updateWorkspace = (botId, workspace) => {
|
|
782
881
|
// Capture at API invocation, before even waiting for an older outer
|
|
783
882
|
// transition. A queued request still belongs to the incarnation that the
|
|
@@ -795,7 +894,34 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
795
894
|
clearSessions: () => state.clearSessions(),
|
|
796
895
|
incarnation,
|
|
797
896
|
});
|
|
798
|
-
return
|
|
897
|
+
return decorate(await controller.status());
|
|
898
|
+
});
|
|
899
|
+
};
|
|
900
|
+
const updateAgentPreset = (botId, agentPreset) => {
|
|
901
|
+
const incarnation = workspaces.incarnationFor(botId);
|
|
902
|
+
const normalizedAgentPreset = validateAgentPresetId(agentPreset);
|
|
903
|
+
return withBotTransition(botId, async () => {
|
|
904
|
+
const snapshot = await controller.status();
|
|
905
|
+
if (!snapshot?.bots?.some((bot) => bot?.botId === botId)) {
|
|
906
|
+
const error = new Error('找不到要修改的机器人。');
|
|
907
|
+
error.code = 'workspace-bot-not-found';
|
|
908
|
+
throw error;
|
|
909
|
+
}
|
|
910
|
+
const catalog = normalizedAgentPreset && agentPresetCatalog
|
|
911
|
+
? await resolveAgentPresetCatalog(agentPresetCatalog)
|
|
912
|
+
: null;
|
|
913
|
+
if (normalizedAgentPreset && agentPresetCatalog
|
|
914
|
+
&& !catalog?.items.some((item) => item.id === normalizedAgentPreset)) {
|
|
915
|
+
const error = new Error('Agent Preset 不存在或不可用。');
|
|
916
|
+
error.code = 'agent-preset-unavailable';
|
|
917
|
+
throw error;
|
|
918
|
+
}
|
|
919
|
+
await workspaces.setAgentPreset(botId, normalizedAgentPreset, { incarnation });
|
|
920
|
+
return decorateResult(
|
|
921
|
+
workspaces,
|
|
922
|
+
await controller.status(),
|
|
923
|
+
catalog ?? agentPresetCatalog,
|
|
924
|
+
);
|
|
799
925
|
});
|
|
800
926
|
};
|
|
801
927
|
const deleteWithWorkspace = (botId, invokeDelete) => withBotTransition(botId, async () => {
|
|
@@ -822,7 +948,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
822
948
|
try {
|
|
823
949
|
const result = await invokeDelete();
|
|
824
950
|
await workspaces.finishRemoval(removal);
|
|
825
|
-
return
|
|
951
|
+
return decorate(result);
|
|
826
952
|
} catch (error) {
|
|
827
953
|
const after = await targetStatus(controller).catch(() => null);
|
|
828
954
|
const knownAbsent = Array.isArray(after?.bots)
|
|
@@ -836,6 +962,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
836
962
|
return new Proxy(controller, {
|
|
837
963
|
get(target, property) {
|
|
838
964
|
if (property === 'updateWorkspace') return updateWorkspace;
|
|
965
|
+
if (property === 'updateAgentPreset') return updateAgentPreset;
|
|
839
966
|
const value = Reflect.get(target, property, target);
|
|
840
967
|
if (typeof value !== 'function') return value;
|
|
841
968
|
if (property === 'deleteBot') {
|
|
@@ -848,11 +975,11 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
848
975
|
return async (...args) => {
|
|
849
976
|
const before = await target.status();
|
|
850
977
|
const botId = before?.bots?.[0]?.botId;
|
|
851
|
-
if (!botId) return
|
|
978
|
+
if (!botId) return decorate(value.apply(target, args));
|
|
852
979
|
return deleteWithWorkspace(botId, () => value.apply(target, args));
|
|
853
980
|
};
|
|
854
981
|
}
|
|
855
|
-
return (...args) =>
|
|
982
|
+
return (...args) => decorate(value.apply(target, args));
|
|
856
983
|
},
|
|
857
984
|
});
|
|
858
985
|
}
|
|
@@ -592,11 +592,13 @@ export class HarnessClient {
|
|
|
592
592
|
}
|
|
593
593
|
|
|
594
594
|
async createSession(options = {}) {
|
|
595
|
-
|
|
596
|
-
|
|
595
|
+
const { agentPreset: requestedPreset, ...rpcOptions } = options;
|
|
596
|
+
await this.ensureRunning(rpcOptions);
|
|
597
|
+
const workspaceId = await this.workspaceId(rpcOptions);
|
|
597
598
|
const payload = { workspaceId };
|
|
598
|
-
|
|
599
|
-
|
|
599
|
+
const agentPreset = requestedPreset !== undefined ? requestedPreset : this.#agentPreset;
|
|
600
|
+
if (agentPreset != null) payload.agentPreset = agentPreset;
|
|
601
|
+
const created = await this.rpc('session.create', payload, 30_000, rpcOptions);
|
|
600
602
|
return created.sessionId;
|
|
601
603
|
}
|
|
602
604
|
|
|
@@ -13,6 +13,18 @@ export class ImagePromptError extends Error {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
const HOST_ATTACHMENT_USER_MESSAGES = Object.freeze({
|
|
17
|
+
MODEL_DOES_NOT_SUPPORT_IMAGES:
|
|
18
|
+
'当前模型不支持图片,请用 /models 查看可用模型,再用 /model <序号> 切换后重发。',
|
|
19
|
+
IMAGE_TOO_LARGE: '图片超过宿主允许的大小,请压缩后重试。',
|
|
20
|
+
IMAGE_TOO_MANY_PIXELS: '图片分辨率过高,请压缩后重试。',
|
|
21
|
+
INVALID_IMAGE: '图片内容无效或格式不受支持,请重新发送。',
|
|
22
|
+
INVALID_IMAGE_BASE64: '未能读取图片内容,请重新发送。',
|
|
23
|
+
IMAGE_TYPE_MISMATCH: '图片格式与实际内容不一致,请重新发送。',
|
|
24
|
+
TOO_MANY_IMAGES: '一次发送的图片数量超过宿主限制,请减少后重试。',
|
|
25
|
+
IMAGES_TOO_LARGE: '图片总大小超过宿主限制,请减少图片或压缩后重试。',
|
|
26
|
+
});
|
|
27
|
+
|
|
16
28
|
function requestSignal(signal, timeoutMs) {
|
|
17
29
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
18
30
|
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
@@ -263,6 +275,25 @@ export async function promptContentForMessage(message, {
|
|
|
263
275
|
return content;
|
|
264
276
|
}
|
|
265
277
|
|
|
278
|
+
/** Return only allowlisted, user-safe image failure details. */
|
|
279
|
+
export function imagePromptDiagnostic(error) {
|
|
280
|
+
if (error instanceof ImagePromptError) {
|
|
281
|
+
return {
|
|
282
|
+
code: 'image-prompt-error',
|
|
283
|
+
reason: error.code,
|
|
284
|
+
userMessage: error.userMessage,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (error?.code !== 'attachment-error' || typeof error?.details?.reason !== 'string') {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const reason = error.details.reason;
|
|
291
|
+
const userMessage = Object.hasOwn(HOST_ATTACHMENT_USER_MESSAGES, reason)
|
|
292
|
+
? HOST_ATTACHMENT_USER_MESSAGES[reason]
|
|
293
|
+
: null;
|
|
294
|
+
return userMessage ? { code: 'attachment-error', reason, userMessage } : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
266
297
|
export function imagePromptUserMessage(error) {
|
|
267
|
-
return error
|
|
298
|
+
return imagePromptDiagnostic(error)?.userMessage ?? null;
|
|
268
299
|
}
|