@xmanrui/dsh-im 4.10.0 → 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.
- package/PROACTIVE_DELIVERY.en.md +15 -1
- package/PROACTIVE_DELIVERY.md +15 -1
- package/README.en.md +5 -1
- package/README.md +6 -2
- package/lib/client.js +1619 -769
- package/lib/index.js +248 -244
- package/package.json +4 -3
- package/plugin-src/client/channel-card-meta.js +9 -1
- package/plugin-src/client/channels/dingtalk/api.js +4 -0
- package/plugin-src/client/channels/dingtalk/index.js +25 -2
- package/plugin-src/client/channels/feishu/api.js +4 -0
- package/plugin-src/client/channels/feishu/group-settings.js +581 -0
- package/plugin-src/client/channels/feishu/index.js +26 -204
- package/plugin-src/client/channels/qq/api.js +4 -0
- package/plugin-src/client/channels/qq/index.js +25 -2
- package/plugin-src/client/channels/shared/token-api.js +4 -0
- package/plugin-src/client/channels/shared/token-channel.js +25 -3
- package/plugin-src/client/channels/wecom/api.js +4 -0
- package/plugin-src/client/channels/wecom/index.js +25 -2
- package/plugin-src/client/channels/weixin/api.js +4 -0
- package/plugin-src/client/channels/weixin/index.js +25 -2
- package/plugin-src/client/channels/whatsapp/api.js +4 -0
- package/plugin-src/client/channels/whatsapp/index.js +24 -2
- package/plugin-src/client/delivery-settings.js +73 -5
- package/plugin-src/client/i18n.js +20 -0
- package/plugin-src/client/model-setting.js +104 -0
- package/plugin-src/client/styles.js +50 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +3 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +16 -0
- package/plugin-src/host/channels/qq/production.mjs +3 -0
- package/plugin-src/host/channels/qq/rpc.mjs +11 -0
- package/plugin-src/host/channels/shared/model-setting-rpc.mjs +19 -0
- package/plugin-src/host/channels/shared/production.mjs +3 -0
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
- package/plugin-src/host/channels/slack/production.mjs +3 -0
- package/plugin-src/host/channels/slack/rpc.mjs +9 -0
- package/plugin-src/host/channels/wecom/production.mjs +3 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/weixin/production.mjs +3 -0
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +3 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
- package/plugin-src/host/delivery-adapter.mjs +141 -3
- package/plugin-src/host/delivery-rpc.mjs +12 -0
- package/plugin-src/host/delivery-service.mjs +110 -3
- package/plugin-src/host/delivery-suggestions.mjs +46 -0
- package/plugin-src/host/harness-session-coordinator.mjs +6 -5
- package/plugin-src/host/index.mjs +18 -1
- package/plugin-src/host/session-sync-coordinator.mjs +217 -0
- package/scripts/verify-package.mjs +12 -6
- package/src/channels/shared/bot-workspace-store.mjs +285 -38
- package/src/channels/shared/harness-client.mjs +124 -43
- package/src/channels/shared/model-setting.mjs +110 -0
|
@@ -23,8 +23,16 @@ import {
|
|
|
23
23
|
normalizeContextEnhancementConfig,
|
|
24
24
|
validateContextEnhancementConfig,
|
|
25
25
|
} from './context-enhancement.mjs';
|
|
26
|
+
import {
|
|
27
|
+
modelCatalogHas,
|
|
28
|
+
normalizeModelCatalog,
|
|
29
|
+
sameModelSelection,
|
|
30
|
+
validateModelSelection,
|
|
31
|
+
} from './model-setting.mjs';
|
|
26
32
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
27
33
|
|
|
34
|
+
const DELIVERY_DOCUMENT_VERSION = 2;
|
|
35
|
+
export const CURRENT_DOCUMENT_VERSION = 3;
|
|
28
36
|
const EMPTY_DOCUMENT = Object.freeze({ version: 1, workspaces: Object.freeze({}) });
|
|
29
37
|
|
|
30
38
|
function workspaceSessionStale(message) {
|
|
@@ -84,13 +92,21 @@ function jsonRecord(value) {
|
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
function
|
|
95
|
+
function conversationKeyOf(value) {
|
|
96
|
+
if (typeof value !== 'string' || !value || value.length > 1_024
|
|
97
|
+
|| value.trim() !== value || /[\u0000-\u001f\u007f]/u.test(value)) {
|
|
98
|
+
throw deliveryTargetError('invalid-target', 'Invalid private conversation key');
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeDeliveryTarget(value, { targetId, allowSessionSync = false } = {}) {
|
|
88
104
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
89
105
|
throw deliveryTargetError('invalid-target', 'Invalid delivery target');
|
|
90
106
|
}
|
|
91
107
|
const allowed = targetId === undefined
|
|
92
|
-
? new Set(['targetId', 'name', 'kind', 'route'])
|
|
93
|
-
: new Set(['name', 'kind', 'route']);
|
|
108
|
+
? new Set(['targetId', 'name', 'kind', 'route', ...(allowSessionSync ? ['sessionSync'] : [])])
|
|
109
|
+
: new Set(['name', 'kind', 'route', ...(allowSessionSync ? ['sessionSync'] : [])]);
|
|
94
110
|
if (Object.keys(value).some((key) => !allowed.has(key))) {
|
|
95
111
|
throw deliveryTargetError('invalid-target', 'Invalid delivery target');
|
|
96
112
|
}
|
|
@@ -105,15 +121,37 @@ function normalizeDeliveryTarget(value, { targetId } = {}) {
|
|
|
105
121
|
}
|
|
106
122
|
name = value.name.trim();
|
|
107
123
|
}
|
|
124
|
+
let sessionSync;
|
|
125
|
+
if (value.sessionSync !== undefined) {
|
|
126
|
+
if (!allowSessionSync || !value.sessionSync || typeof value.sessionSync !== 'object'
|
|
127
|
+
|| Array.isArray(value.sessionSync)
|
|
128
|
+
|| Object.keys(value.sessionSync).length !== 1
|
|
129
|
+
|| !Object.hasOwn(value.sessionSync, 'conversationKey')) {
|
|
130
|
+
throw deliveryTargetError('invalid-target', 'Invalid delivery target session sync');
|
|
131
|
+
}
|
|
132
|
+
sessionSync = { conversationKey: conversationKeyOf(value.sessionSync.conversationKey) };
|
|
133
|
+
}
|
|
108
134
|
return {
|
|
109
135
|
targetId: id,
|
|
110
136
|
...(name === undefined ? {} : { name }),
|
|
111
137
|
kind: value.kind,
|
|
112
138
|
route: jsonRecord(value.route),
|
|
139
|
+
...(sessionSync === undefined ? {} : { sessionSync }),
|
|
113
140
|
};
|
|
114
141
|
}
|
|
115
142
|
|
|
116
|
-
function
|
|
143
|
+
function publicDeliveryTarget(value, { targetId } = {}) {
|
|
144
|
+
const normalized = normalizeDeliveryTarget(value, { targetId, allowSessionSync: true });
|
|
145
|
+
const { sessionSync: _sessionSync, ...target } = normalized;
|
|
146
|
+
return target;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function sameDeliveryRoute(left, right) {
|
|
150
|
+
return left?.kind === right?.kind
|
|
151
|
+
&& JSON.stringify(left?.route) === JSON.stringify(right?.route);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeDeliveryTargets(value, { version } = {}) {
|
|
117
155
|
const deliveryTargets = Object.create(null);
|
|
118
156
|
if (value === undefined) return deliveryTargets;
|
|
119
157
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
@@ -123,7 +161,10 @@ function normalizeDeliveryTargets(value) {
|
|
|
123
161
|
if (!targets || typeof targets !== 'object' || Array.isArray(targets)) return null;
|
|
124
162
|
const normalizedTargets = Object.create(null);
|
|
125
163
|
for (const [targetId, target] of Object.entries(targets)) {
|
|
126
|
-
const normalized = normalizeDeliveryTarget(target, {
|
|
164
|
+
const normalized = normalizeDeliveryTarget(target, {
|
|
165
|
+
targetId,
|
|
166
|
+
allowSessionSync: version >= CURRENT_DOCUMENT_VERSION,
|
|
167
|
+
});
|
|
127
168
|
const { targetId: _targetId, ...stored } = normalized;
|
|
128
169
|
normalizedTargets[targetId] = stored;
|
|
129
170
|
}
|
|
@@ -156,7 +197,8 @@ function normalizeAccessPolicies(value, workspaces) {
|
|
|
156
197
|
}
|
|
157
198
|
|
|
158
199
|
function normalizeDocument(value) {
|
|
159
|
-
if (!value || ![1,
|
|
200
|
+
if (!value || ![1, DELIVERY_DOCUMENT_VERSION, CURRENT_DOCUMENT_VERSION].includes(value.version)
|
|
201
|
+
|| !value.workspaces
|
|
160
202
|
|| typeof value.workspaces !== 'object' || Array.isArray(value.workspaces)) return null;
|
|
161
203
|
const workspaces = {};
|
|
162
204
|
for (const [botId, workspace] of Object.entries(value.workspaces)) {
|
|
@@ -179,6 +221,22 @@ function normalizeDocument(value) {
|
|
|
179
221
|
}
|
|
180
222
|
}
|
|
181
223
|
}
|
|
224
|
+
const models = {};
|
|
225
|
+
if (value.models !== undefined) {
|
|
226
|
+
if (!value.models || typeof value.models !== 'object' || Array.isArray(value.models)) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
for (const [botId, model] of Object.entries(value.models)) {
|
|
230
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return null;
|
|
231
|
+
try {
|
|
232
|
+
const normalized = validateModelSelection(model);
|
|
233
|
+
if (!normalized) return null;
|
|
234
|
+
models[botId] = normalized;
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
182
240
|
const contextEnhancement = Object.create(null);
|
|
183
241
|
// Enhancement damage is isolated from the existing workspace/preset document.
|
|
184
242
|
if (value.contextEnhancement && typeof value.contextEnhancement === 'object'
|
|
@@ -190,16 +248,20 @@ function normalizeDocument(value) {
|
|
|
190
248
|
}
|
|
191
249
|
}
|
|
192
250
|
if (value.version === 1 && value.deliveryTargets !== undefined) return null;
|
|
193
|
-
const deliveryTargets = normalizeDeliveryTargets(value.deliveryTargets);
|
|
251
|
+
const deliveryTargets = normalizeDeliveryTargets(value.deliveryTargets, { version: value.version });
|
|
194
252
|
if (!deliveryTargets) return null;
|
|
195
253
|
const accessPolicies = normalizeAccessPolicies(value.accessPolicies, workspaces);
|
|
196
|
-
const version =
|
|
254
|
+
const version = Math.max(
|
|
255
|
+
value.version,
|
|
256
|
+
value.accessPolicies === undefined ? 1 : DELIVERY_DOCUMENT_VERSION,
|
|
257
|
+
);
|
|
197
258
|
return {
|
|
198
259
|
// A v1 file cannot be emitted with this optional v2 section. If one is
|
|
199
260
|
// recovered from an interrupted/manual edit, retain it on the next write.
|
|
200
261
|
version,
|
|
201
262
|
workspaces,
|
|
202
263
|
agentPresets,
|
|
264
|
+
models,
|
|
203
265
|
contextEnhancement,
|
|
204
266
|
deliveryTargets,
|
|
205
267
|
accessPolicies,
|
|
@@ -210,19 +272,21 @@ function storedDocument({
|
|
|
210
272
|
version,
|
|
211
273
|
workspaces,
|
|
212
274
|
agentPresets,
|
|
275
|
+
models,
|
|
213
276
|
contextEnhancement,
|
|
214
277
|
deliveryTargets,
|
|
215
278
|
accessPolicies,
|
|
216
279
|
}) {
|
|
217
280
|
const document = { version, workspaces };
|
|
218
281
|
if (Object.keys(agentPresets).length > 0) document.agentPresets = agentPresets;
|
|
282
|
+
if (Object.keys(models).length > 0) document.models = models;
|
|
219
283
|
if (Object.keys(contextEnhancement).length > 0) {
|
|
220
284
|
document.contextEnhancement = contextEnhancement;
|
|
221
285
|
}
|
|
222
|
-
if (version >=
|
|
286
|
+
if (version >= DELIVERY_DOCUMENT_VERSION && Object.keys(deliveryTargets).length > 0) {
|
|
223
287
|
document.deliveryTargets = deliveryTargets;
|
|
224
288
|
}
|
|
225
|
-
if (version >=
|
|
289
|
+
if (version >= DELIVERY_DOCUMENT_VERSION && Object.keys(accessPolicies).length > 0) {
|
|
226
290
|
document.accessPolicies = accessPolicies;
|
|
227
291
|
}
|
|
228
292
|
return document;
|
|
@@ -267,6 +331,7 @@ export class BotWorkspaceStore {
|
|
|
267
331
|
#version = 1;
|
|
268
332
|
#workspaces = {};
|
|
269
333
|
#agentPresets = {};
|
|
334
|
+
#models = {};
|
|
270
335
|
#contextEnhancement = {};
|
|
271
336
|
#deliveryTargets = Object.create(null);
|
|
272
337
|
#accessPolicies = Object.create(null);
|
|
@@ -293,6 +358,7 @@ export class BotWorkspaceStore {
|
|
|
293
358
|
this.#version = normalized.version;
|
|
294
359
|
this.#workspaces = normalized.workspaces;
|
|
295
360
|
this.#agentPresets = normalized.agentPresets;
|
|
361
|
+
this.#models = normalized.models;
|
|
296
362
|
this.#contextEnhancement = normalized.contextEnhancement;
|
|
297
363
|
this.#deliveryTargets = normalized.deliveryTargets;
|
|
298
364
|
this.#accessPolicies = normalized.accessPolicies;
|
|
@@ -301,6 +367,7 @@ export class BotWorkspaceStore {
|
|
|
301
367
|
this.#version = 1;
|
|
302
368
|
this.#workspaces = {};
|
|
303
369
|
this.#agentPresets = {};
|
|
370
|
+
this.#models = {};
|
|
304
371
|
this.#contextEnhancement = {};
|
|
305
372
|
this.#deliveryTargets = Object.create(null);
|
|
306
373
|
this.#accessPolicies = Object.create(null);
|
|
@@ -341,6 +408,11 @@ export class BotWorkspaceStore {
|
|
|
341
408
|
return this.#agentPresets[botIdOf(botId)] ?? null;
|
|
342
409
|
}
|
|
343
410
|
|
|
411
|
+
modelFor(botId) {
|
|
412
|
+
const selection = this.#models[botIdOf(botId)];
|
|
413
|
+
return selection ? { ...selection } : null;
|
|
414
|
+
}
|
|
415
|
+
|
|
344
416
|
contextEnhancementFor(botId) {
|
|
345
417
|
const id = botIdOf(botId);
|
|
346
418
|
return this.has(id) && Object.hasOwn(this.#contextEnhancement, id)
|
|
@@ -359,7 +431,7 @@ export class BotWorkspaceStore {
|
|
|
359
431
|
const id = botIdOf(botId);
|
|
360
432
|
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
361
433
|
return Object.entries(this.#deliveryTargets[id] ?? {})
|
|
362
|
-
.map(([targetId, target]) =>
|
|
434
|
+
.map(([targetId, target]) => publicDeliveryTarget(target, { targetId }))
|
|
363
435
|
.sort((left, right) => left.targetId.localeCompare(right.targetId));
|
|
364
436
|
}
|
|
365
437
|
|
|
@@ -368,7 +440,29 @@ export class BotWorkspaceStore {
|
|
|
368
440
|
const targetKey = targetIdOf(targetId);
|
|
369
441
|
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
370
442
|
const target = this.#deliveryTargets[id]?.[targetKey];
|
|
371
|
-
return target ?
|
|
443
|
+
return target ? publicDeliveryTarget(target, { targetId: targetKey }) : null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
listSessionSyncTargets() {
|
|
447
|
+
const targets = [];
|
|
448
|
+
for (const [botId, botTargets] of Object.entries(this.#deliveryTargets)) {
|
|
449
|
+
if (!this.has(botId)) continue;
|
|
450
|
+
for (const [targetId, target] of Object.entries(botTargets)) {
|
|
451
|
+
const normalized = normalizeDeliveryTarget(target, {
|
|
452
|
+
targetId,
|
|
453
|
+
allowSessionSync: true,
|
|
454
|
+
});
|
|
455
|
+
if (!normalized.sessionSync) continue;
|
|
456
|
+
targets.push({
|
|
457
|
+
botId,
|
|
458
|
+
targetId,
|
|
459
|
+
conversationKey: normalized.sessionSync.conversationKey,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return targets.sort((left, right) => (
|
|
464
|
+
left.botId.localeCompare(right.botId) || left.targetId.localeCompare(right.targetId)
|
|
465
|
+
));
|
|
372
466
|
}
|
|
373
467
|
|
|
374
468
|
async createDeliveryTarget(botId, value) {
|
|
@@ -384,10 +478,11 @@ export class BotWorkspaceStore {
|
|
|
384
478
|
...this.#deliveryTargets,
|
|
385
479
|
[id]: { ...(this.#deliveryTargets[id] ?? {}), [targetId]: stored },
|
|
386
480
|
};
|
|
387
|
-
|
|
481
|
+
const nextVersion = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
482
|
+
await this.#persist(this.#contextEnhancement, next, nextVersion);
|
|
388
483
|
this.#deliveryTargets = next;
|
|
389
|
-
this.#version =
|
|
390
|
-
return
|
|
484
|
+
this.#version = nextVersion;
|
|
485
|
+
return publicDeliveryTarget(stored, { targetId });
|
|
391
486
|
});
|
|
392
487
|
}
|
|
393
488
|
|
|
@@ -401,14 +496,19 @@ export class BotWorkspaceStore {
|
|
|
401
496
|
throw deliveryTargetError('unknown-target', 'Unknown target');
|
|
402
497
|
}
|
|
403
498
|
const { targetId: _targetId, ...stored } = replacement;
|
|
499
|
+
const previous = this.#deliveryTargets[id][targetKey];
|
|
500
|
+
const nextStored = previous.sessionSync && sameDeliveryRoute(previous, stored)
|
|
501
|
+
? { ...stored, sessionSync: previous.sessionSync }
|
|
502
|
+
: stored;
|
|
404
503
|
const next = {
|
|
405
504
|
...this.#deliveryTargets,
|
|
406
|
-
[id]: { ...this.#deliveryTargets[id], [targetKey]:
|
|
505
|
+
[id]: { ...this.#deliveryTargets[id], [targetKey]: nextStored },
|
|
407
506
|
};
|
|
408
|
-
|
|
507
|
+
const nextVersion = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
508
|
+
await this.#persist(this.#contextEnhancement, next, nextVersion);
|
|
409
509
|
this.#deliveryTargets = next;
|
|
410
|
-
this.#version =
|
|
411
|
-
return
|
|
510
|
+
this.#version = nextVersion;
|
|
511
|
+
return publicDeliveryTarget(nextStored, { targetId: targetKey });
|
|
412
512
|
});
|
|
413
513
|
}
|
|
414
514
|
|
|
@@ -425,13 +525,40 @@ export class BotWorkspaceStore {
|
|
|
425
525
|
const next = { ...this.#deliveryTargets };
|
|
426
526
|
if (Object.keys(botTargets).length > 0) next[id] = botTargets;
|
|
427
527
|
else delete next[id];
|
|
428
|
-
|
|
528
|
+
const nextVersion = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
529
|
+
await this.#persist(this.#contextEnhancement, next, nextVersion);
|
|
429
530
|
this.#deliveryTargets = next;
|
|
430
|
-
this.#version =
|
|
531
|
+
this.#version = nextVersion;
|
|
431
532
|
return true;
|
|
432
533
|
});
|
|
433
534
|
}
|
|
434
535
|
|
|
536
|
+
async setDeliveryTargetSessionSync(botId, targetId, conversationKeyOrNull) {
|
|
537
|
+
const id = botIdOf(botId);
|
|
538
|
+
const targetKey = targetIdOf(targetId);
|
|
539
|
+
const conversationKey = conversationKeyOrNull === null
|
|
540
|
+
? null
|
|
541
|
+
: conversationKeyOf(conversationKeyOrNull);
|
|
542
|
+
return this.#enqueue(id, async () => {
|
|
543
|
+
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
544
|
+
const previous = this.#deliveryTargets[id]?.[targetKey];
|
|
545
|
+
if (!previous) throw deliveryTargetError('unknown-target', 'Unknown target');
|
|
546
|
+
const previousKey = previous.sessionSync?.conversationKey ?? null;
|
|
547
|
+
if (previousKey === conversationKey) return conversationKey !== null;
|
|
548
|
+
const nextStored = { ...previous };
|
|
549
|
+
if (conversationKey === null) delete nextStored.sessionSync;
|
|
550
|
+
else nextStored.sessionSync = { conversationKey };
|
|
551
|
+
const next = {
|
|
552
|
+
...this.#deliveryTargets,
|
|
553
|
+
[id]: { ...this.#deliveryTargets[id], [targetKey]: nextStored },
|
|
554
|
+
};
|
|
555
|
+
await this.#persist(this.#contextEnhancement, next, CURRENT_DOCUMENT_VERSION);
|
|
556
|
+
this.#deliveryTargets = next;
|
|
557
|
+
this.#version = CURRENT_DOCUMENT_VERSION;
|
|
558
|
+
return conversationKey !== null;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
435
562
|
generationFor(botId) {
|
|
436
563
|
return this.#generations.get(botIdOf(botId)) ?? null;
|
|
437
564
|
}
|
|
@@ -477,7 +604,9 @@ export class BotWorkspaceStore {
|
|
|
477
604
|
this.#generations.set(id, this.#freshGeneration());
|
|
478
605
|
this.#incarnations.set(id, this.#freshIncarnation());
|
|
479
606
|
}
|
|
480
|
-
const nextVersion = initializesAccessPolicy
|
|
607
|
+
const nextVersion = initializesAccessPolicy
|
|
608
|
+
? Math.max(this.#version, DELIVERY_DOCUMENT_VERSION)
|
|
609
|
+
: this.#version;
|
|
481
610
|
try {
|
|
482
611
|
await this.#persist(
|
|
483
612
|
this.#contextEnhancement,
|
|
@@ -571,6 +700,37 @@ export class BotWorkspaceStore {
|
|
|
571
700
|
});
|
|
572
701
|
}
|
|
573
702
|
|
|
703
|
+
async setModel(botId, value, { incarnation } = {}) {
|
|
704
|
+
const id = botIdOf(botId);
|
|
705
|
+
if (!this.has(id)
|
|
706
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
707
|
+
const error = new Error('找不到要修改的机器人。');
|
|
708
|
+
error.code = 'workspace-bot-not-found';
|
|
709
|
+
throw error;
|
|
710
|
+
}
|
|
711
|
+
const model = validateModelSelection(value);
|
|
712
|
+
return this.#enqueue(id, async () => {
|
|
713
|
+
if (!this.has(id)
|
|
714
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
715
|
+
const error = new Error('找不到要修改的机器人。');
|
|
716
|
+
error.code = 'workspace-bot-not-found';
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
719
|
+
const previous = this.#models[id] ?? null;
|
|
720
|
+
if (sameModelSelection(previous, model)) return model ? { ...model } : null;
|
|
721
|
+
if (model) this.#models[id] = model;
|
|
722
|
+
else delete this.#models[id];
|
|
723
|
+
try {
|
|
724
|
+
await this.#persist();
|
|
725
|
+
} catch (error) {
|
|
726
|
+
if (previous) this.#models[id] = previous;
|
|
727
|
+
else delete this.#models[id];
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
return model ? { ...model } : null;
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
574
734
|
async setContextEnhancement(botId, value, { incarnation } = {}) {
|
|
575
735
|
const id = botIdOf(botId);
|
|
576
736
|
const expectedIncarnation = incarnation === undefined ? this.incarnationFor(id) : incarnation;
|
|
@@ -604,11 +764,11 @@ export class BotWorkspaceStore {
|
|
|
604
764
|
await this.#persist(
|
|
605
765
|
this.#contextEnhancement,
|
|
606
766
|
this.#deliveryTargets,
|
|
607
|
-
|
|
767
|
+
Math.max(this.#version, DELIVERY_DOCUMENT_VERSION),
|
|
608
768
|
next,
|
|
609
769
|
);
|
|
610
770
|
this.#accessPolicies = next;
|
|
611
|
-
this.#version =
|
|
771
|
+
this.#version = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
612
772
|
return policy;
|
|
613
773
|
});
|
|
614
774
|
}
|
|
@@ -770,6 +930,7 @@ export class BotWorkspaceStore {
|
|
|
770
930
|
const candidates = new Set([
|
|
771
931
|
...Object.keys(this.#workspaces),
|
|
772
932
|
...Object.keys(this.#agentPresets),
|
|
933
|
+
...Object.keys(this.#models),
|
|
773
934
|
...Object.keys(this.#contextEnhancement),
|
|
774
935
|
...Object.keys(this.#deliveryTargets),
|
|
775
936
|
...Object.keys(this.#accessPolicies),
|
|
@@ -789,6 +950,7 @@ export class BotWorkspaceStore {
|
|
|
789
950
|
...bot,
|
|
790
951
|
workspace: this.workspaceFor(bot.botId),
|
|
791
952
|
agentPreset: this.agentPresetFor(bot.botId),
|
|
953
|
+
model: this.modelFor(bot.botId),
|
|
792
954
|
contextEnhancement: this.contextEnhancementFor(bot.botId),
|
|
793
955
|
accessPolicy: this.accessPolicyFor(bot.botId),
|
|
794
956
|
}
|
|
@@ -820,13 +982,15 @@ export class BotWorkspaceStore {
|
|
|
820
982
|
async #retireCurrentIncarnation(id) {
|
|
821
983
|
const hadWorkspace = Object.hasOwn(this.#workspaces, id);
|
|
822
984
|
const hadPreset = Object.hasOwn(this.#agentPresets, id);
|
|
985
|
+
const hadModel = Object.hasOwn(this.#models, id);
|
|
823
986
|
const hadContextEnhancement = Object.hasOwn(this.#contextEnhancement, id);
|
|
824
987
|
const hadDeliveryTargets = Object.hasOwn(this.#deliveryTargets, id);
|
|
825
988
|
const hadAccessPolicy = Object.hasOwn(this.#accessPolicies, id);
|
|
826
|
-
const needsCleanup = hadWorkspace || hadPreset || hadContextEnhancement
|
|
989
|
+
const needsCleanup = hadWorkspace || hadPreset || hadModel || hadContextEnhancement
|
|
827
990
|
|| hadDeliveryTargets || hadAccessPolicy || this.#dirtyRemovals.has(id);
|
|
828
991
|
delete this.#workspaces[id];
|
|
829
992
|
delete this.#agentPresets[id];
|
|
993
|
+
delete this.#models[id];
|
|
830
994
|
delete this.#contextEnhancement[id];
|
|
831
995
|
delete this.#deliveryTargets[id];
|
|
832
996
|
delete this.#accessPolicies[id];
|
|
@@ -869,6 +1033,7 @@ export class BotWorkspaceStore {
|
|
|
869
1033
|
version,
|
|
870
1034
|
workspaces: this.#workspaces,
|
|
871
1035
|
agentPresets: this.#agentPresets,
|
|
1036
|
+
models: this.#models,
|
|
872
1037
|
contextEnhancement,
|
|
873
1038
|
deliveryTargets,
|
|
874
1039
|
accessPolicies,
|
|
@@ -879,6 +1044,7 @@ export class BotWorkspaceStore {
|
|
|
879
1044
|
async #persistCurrentDocument() {
|
|
880
1045
|
if (Object.keys(this.#workspaces).length > 0
|
|
881
1046
|
|| Object.keys(this.#agentPresets).length > 0
|
|
1047
|
+
|| Object.keys(this.#models).length > 0
|
|
882
1048
|
|| Object.keys(this.#contextEnhancement).length > 0
|
|
883
1049
|
|| Object.keys(this.#deliveryTargets).length > 0
|
|
884
1050
|
|| Object.keys(this.#accessPolicies).length > 0) {
|
|
@@ -903,12 +1069,26 @@ function resolveAgentPresetCatalog(catalog) {
|
|
|
903
1069
|
: normalizeAgentPresetCatalog(value);
|
|
904
1070
|
}
|
|
905
1071
|
|
|
1072
|
+
function resolveModelCatalog(catalog) {
|
|
1073
|
+
if (!catalog) return null;
|
|
1074
|
+
const value = typeof catalog === 'function' ? catalog() : catalog;
|
|
1075
|
+
return value && typeof value.then === 'function'
|
|
1076
|
+
? value.then(normalizeModelCatalog)
|
|
1077
|
+
: normalizeModelCatalog(value);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
906
1080
|
function unavailableAgentPreset() {
|
|
907
1081
|
const error = new Error('Agent Preset 不存在或不可用。');
|
|
908
1082
|
error.code = 'agent-preset-unavailable';
|
|
909
1083
|
return error;
|
|
910
1084
|
}
|
|
911
1085
|
|
|
1086
|
+
function unavailableModel() {
|
|
1087
|
+
const error = new Error('模型不存在或不可用。');
|
|
1088
|
+
error.code = 'model-selection-unavailable';
|
|
1089
|
+
return error;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
912
1092
|
function assertCurrentBotScope(isCurrentScope) {
|
|
913
1093
|
if (isCurrentScope()) return;
|
|
914
1094
|
const error = new Error('找不到要修改的机器人。');
|
|
@@ -916,17 +1096,23 @@ function assertCurrentBotScope(isCurrentScope) {
|
|
|
916
1096
|
throw error;
|
|
917
1097
|
}
|
|
918
1098
|
|
|
919
|
-
function decorateResult(workspaces, result,
|
|
1099
|
+
function decorateResult(workspaces, result, agentPresetCatalogSource, modelCatalogSource) {
|
|
920
1100
|
const decorate = (value) => {
|
|
921
1101
|
const decorated = workspaces.decorateStatus(value);
|
|
922
|
-
if (!
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1102
|
+
if ((!agentPresetCatalogSource && !modelCatalogSource)
|
|
1103
|
+
|| !decorated || typeof decorated !== 'object') return decorated;
|
|
1104
|
+
const attachCatalogs = ([agentPresetCatalog, modelCatalog]) => ({
|
|
1105
|
+
...decorated,
|
|
1106
|
+
...(agentPresetCatalog ? { agentPresetCatalog } : {}),
|
|
1107
|
+
...(modelCatalog ? { modelCatalog } : {}),
|
|
1108
|
+
});
|
|
1109
|
+
const catalogs = [
|
|
1110
|
+
resolveAgentPresetCatalog(agentPresetCatalogSource),
|
|
1111
|
+
resolveModelCatalog(modelCatalogSource),
|
|
1112
|
+
];
|
|
1113
|
+
return catalogs.some((catalog) => catalog && typeof catalog.then === 'function')
|
|
1114
|
+
? Promise.all(catalogs).then(attachCatalogs)
|
|
1115
|
+
: attachCatalogs(catalogs);
|
|
930
1116
|
};
|
|
931
1117
|
return result && typeof result.then === 'function'
|
|
932
1118
|
? result.then(decorate)
|
|
@@ -1158,11 +1344,27 @@ export function createBotWorkspaceScope(
|
|
|
1158
1344
|
}
|
|
1159
1345
|
const generation = workspaces.generationFor(botId);
|
|
1160
1346
|
const agentPreset = workspaces.agentPresetFor(botId);
|
|
1347
|
+
const model = workspaces.modelFor(botId);
|
|
1161
1348
|
const sessionId = await target.createSession({
|
|
1162
1349
|
...options,
|
|
1163
1350
|
workspace: workspaces.workspaceFor(botId),
|
|
1164
1351
|
...(agentPreset == null ? {} : { agentPreset }),
|
|
1165
1352
|
});
|
|
1353
|
+
if (model) {
|
|
1354
|
+
if (typeof target.selectSessionModel !== 'function') {
|
|
1355
|
+
throw new TypeError('Harness does not support model selection');
|
|
1356
|
+
}
|
|
1357
|
+
const selected = await target.selectSessionModel(
|
|
1358
|
+
sessionId,
|
|
1359
|
+
model,
|
|
1360
|
+
options.signal ? { signal: options.signal } : {},
|
|
1361
|
+
);
|
|
1362
|
+
if (!sameModelSelection(selected?.selected, model)) {
|
|
1363
|
+
const error = new Error('Harness did not confirm the selected model');
|
|
1364
|
+
error.code = 'model-selection-mismatch';
|
|
1365
|
+
throw error;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1166
1368
|
sessionGenerations.set(sessionId, generation);
|
|
1167
1369
|
return sessionId;
|
|
1168
1370
|
};
|
|
@@ -1324,7 +1526,12 @@ export function createBotScopedHarness(harness, options) {
|
|
|
1324
1526
|
return createBotWorkspaceScope(harness, options).harness;
|
|
1325
1527
|
}
|
|
1326
1528
|
|
|
1327
|
-
export function createWorkspaceAwareController(controller, {
|
|
1529
|
+
export function createWorkspaceAwareController(controller, {
|
|
1530
|
+
workspaces,
|
|
1531
|
+
stateFor,
|
|
1532
|
+
agentPresetCatalog,
|
|
1533
|
+
modelCatalog,
|
|
1534
|
+
} = {}) {
|
|
1328
1535
|
if (!controller || !workspaces || typeof stateFor !== 'function') {
|
|
1329
1536
|
throw new TypeError('controller, workspaces, and stateFor are required');
|
|
1330
1537
|
}
|
|
@@ -1337,7 +1544,12 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1337
1544
|
if (transitions.get(botId) === current) transitions.delete(botId);
|
|
1338
1545
|
});
|
|
1339
1546
|
};
|
|
1340
|
-
const decorate = (value) => decorateResult(
|
|
1547
|
+
const decorate = (value) => decorateResult(
|
|
1548
|
+
workspaces,
|
|
1549
|
+
value,
|
|
1550
|
+
agentPresetCatalog,
|
|
1551
|
+
modelCatalog,
|
|
1552
|
+
);
|
|
1341
1553
|
const updateWorkspace = (botId, workspace) => {
|
|
1342
1554
|
// Capture at API invocation, before even waiting for an older outer
|
|
1343
1555
|
// transition. A queued request still belongs to the incarnation that the
|
|
@@ -1380,6 +1592,32 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1380
1592
|
workspaces,
|
|
1381
1593
|
await controller.status(),
|
|
1382
1594
|
catalog ?? agentPresetCatalog,
|
|
1595
|
+
modelCatalog,
|
|
1596
|
+
);
|
|
1597
|
+
});
|
|
1598
|
+
};
|
|
1599
|
+
const updateModel = (botId, value) => {
|
|
1600
|
+
const incarnation = workspaces.incarnationFor(botId);
|
|
1601
|
+
const model = validateModelSelection(value);
|
|
1602
|
+
return withBotTransition(botId, async () => {
|
|
1603
|
+
const snapshot = await controller.status();
|
|
1604
|
+
if (!snapshot?.bots?.some((bot) => bot?.botId === botId)) {
|
|
1605
|
+
const error = new Error('找不到要修改的机器人。');
|
|
1606
|
+
error.code = 'workspace-bot-not-found';
|
|
1607
|
+
throw error;
|
|
1608
|
+
}
|
|
1609
|
+
const catalog = model && modelCatalog
|
|
1610
|
+
? await resolveModelCatalog(modelCatalog)
|
|
1611
|
+
: null;
|
|
1612
|
+
if (model && (!modelCatalog || !modelCatalogHas(catalog, model))) {
|
|
1613
|
+
throw unavailableModel();
|
|
1614
|
+
}
|
|
1615
|
+
await workspaces.setModel(botId, model, { incarnation });
|
|
1616
|
+
return decorateResult(
|
|
1617
|
+
workspaces,
|
|
1618
|
+
await controller.status(),
|
|
1619
|
+
agentPresetCatalog,
|
|
1620
|
+
catalog ?? modelCatalog,
|
|
1383
1621
|
);
|
|
1384
1622
|
});
|
|
1385
1623
|
};
|
|
@@ -1393,13 +1631,17 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1393
1631
|
error.code = 'workspace-bot-not-found';
|
|
1394
1632
|
throw error;
|
|
1395
1633
|
}
|
|
1396
|
-
const catalog = await
|
|
1634
|
+
const [catalog, models] = await Promise.all([
|
|
1635
|
+
resolveAgentPresetCatalog(agentPresetCatalog),
|
|
1636
|
+
resolveModelCatalog(modelCatalog),
|
|
1637
|
+
]);
|
|
1397
1638
|
const decorated = workspaces.decorateStatus(snapshot);
|
|
1398
1639
|
const updated = {
|
|
1399
1640
|
...decorated,
|
|
1400
1641
|
bots: decorated.bots.map((bot) => bot?.botId === botId
|
|
1401
1642
|
? { ...bot, contextEnhancement: config } : bot),
|
|
1402
1643
|
...(catalog ? { agentPresetCatalog: catalog } : {}),
|
|
1644
|
+
...(models ? { modelCatalog: models } : {}),
|
|
1403
1645
|
};
|
|
1404
1646
|
// QR/status projection can fail too. Prepare the complete response before
|
|
1405
1647
|
// commit so a failed save never publishes new running settings.
|
|
@@ -1418,13 +1660,17 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1418
1660
|
error.code = 'workspace-bot-not-found';
|
|
1419
1661
|
throw error;
|
|
1420
1662
|
}
|
|
1421
|
-
const catalog = await
|
|
1663
|
+
const [catalog, models] = await Promise.all([
|
|
1664
|
+
resolveAgentPresetCatalog(agentPresetCatalog),
|
|
1665
|
+
resolveModelCatalog(modelCatalog),
|
|
1666
|
+
]);
|
|
1422
1667
|
const decorated = workspaces.decorateStatus(snapshot);
|
|
1423
1668
|
const updated = {
|
|
1424
1669
|
...decorated,
|
|
1425
1670
|
bots: decorated.bots.map((bot) => bot?.botId === botId
|
|
1426
1671
|
? { ...bot, accessPolicy: policy } : bot),
|
|
1427
1672
|
...(catalog ? { agentPresetCatalog: catalog } : {}),
|
|
1673
|
+
...(models ? { modelCatalog: models } : {}),
|
|
1428
1674
|
};
|
|
1429
1675
|
// Prepare the complete channel-specific response before commit. Failed
|
|
1430
1676
|
// projections and disk writes must leave the live policy unchanged.
|
|
@@ -1472,6 +1718,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1472
1718
|
get(target, property) {
|
|
1473
1719
|
if (property === 'updateWorkspace') return updateWorkspace;
|
|
1474
1720
|
if (property === 'updateAgentPreset') return updateAgentPreset;
|
|
1721
|
+
if (property === 'updateModel') return updateModel;
|
|
1475
1722
|
if (property === 'updateContextEnhancement') return updateContextEnhancement;
|
|
1476
1723
|
if (property === 'updateAccessPolicy') return updateAccessPolicy;
|
|
1477
1724
|
const value = Reflect.get(target, property, target);
|