@xmanrui/dsh-im 4.9.1 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROACTIVE_DELIVERY.en.md +23 -1
- package/PROACTIVE_DELIVERY.md +23 -1
- package/README.en.md +5 -1
- package/README.md +6 -2
- package/lib/client.js +2077 -673
- package/lib/index.js +277 -272
- package/package.json +13 -1
- 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 +6 -0
- package/plugin-src/client/channels/feishu/group-settings.js +581 -0
- package/plugin-src/client/channels/feishu/index.js +26 -150
- 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/global-settings.js +341 -0
- package/plugin-src/client/i18n.js +49 -0
- package/plugin-src/client/index.js +47 -6
- package/plugin-src/client/model-setting.js +104 -0
- package/plugin-src/client/styles.js +99 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +15 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/feishu/production.mjs +17 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +32 -0
- package/plugin-src/host/channels/qq/production.mjs +15 -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 +15 -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 +15 -0
- package/plugin-src/host/channels/slack/rpc.mjs +9 -0
- package/plugin-src/host/channels/wecom/production.mjs +15 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/weixin/production.mjs +15 -0
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +15 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
- package/plugin-src/host/delivery-adapter.mjs +142 -3
- package/plugin-src/host/delivery-rpc.mjs +12 -0
- package/plugin-src/host/delivery-service.mjs +125 -3
- package/plugin-src/host/delivery-suggestions.mjs +46 -0
- package/plugin-src/host/harness-session-coordinator.mjs +32 -10
- package/plugin-src/host/inbound-ttl-rpc.mjs +83 -0
- package/plugin-src/host/inbound-ttl-runtime.mjs +85 -0
- package/plugin-src/host/inbound-ttl-service.mjs +179 -0
- package/plugin-src/host/index.mjs +26 -1
- package/plugin-src/host/modern-harness-api.mjs +58 -2
- package/plugin-src/host/session-sync-coordinator.mjs +217 -0
- package/scripts/verify-package.mjs +12 -6
- package/src/channels/feishu/bridge.mjs +164 -4
- package/src/channels/feishu/feishu-channel.mjs +45 -10
- package/src/channels/feishu/feishu-runtime.mjs +9 -0
- package/src/channels/feishu/message-utils.mjs +13 -0
- package/src/channels/feishu/multi-bot-controller.mjs +16 -0
- package/src/channels/feishu/plugin-config-store.mjs +1 -0
- package/src/channels/feishu/state-store.mjs +25 -0
- package/src/channels/shared/bot-workspace-store.mjs +291 -38
- package/src/channels/shared/harness-client.mjs +124 -43
- package/src/channels/shared/inbound-file.mjs +117 -2
- package/src/channels/shared/inbound-ttl-store.mjs +110 -0
- package/src/channels/shared/inbound-ttl.mjs +22 -0
- 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);
|
|
@@ -323,6 +390,12 @@ export class BotWorkspaceStore {
|
|
|
323
390
|
return Object.hasOwn(this.#workspaces, id) && !this.#removals.has(id);
|
|
324
391
|
}
|
|
325
392
|
|
|
393
|
+
listBotIds() {
|
|
394
|
+
return Object.keys(this.#workspaces)
|
|
395
|
+
.filter((botId) => !this.#removals.has(botId))
|
|
396
|
+
.sort();
|
|
397
|
+
}
|
|
398
|
+
|
|
326
399
|
incarnationFor(botId) {
|
|
327
400
|
return this.#incarnations.get(botIdOf(botId)) ?? null;
|
|
328
401
|
}
|
|
@@ -335,6 +408,11 @@ export class BotWorkspaceStore {
|
|
|
335
408
|
return this.#agentPresets[botIdOf(botId)] ?? null;
|
|
336
409
|
}
|
|
337
410
|
|
|
411
|
+
modelFor(botId) {
|
|
412
|
+
const selection = this.#models[botIdOf(botId)];
|
|
413
|
+
return selection ? { ...selection } : null;
|
|
414
|
+
}
|
|
415
|
+
|
|
338
416
|
contextEnhancementFor(botId) {
|
|
339
417
|
const id = botIdOf(botId);
|
|
340
418
|
return this.has(id) && Object.hasOwn(this.#contextEnhancement, id)
|
|
@@ -353,7 +431,7 @@ export class BotWorkspaceStore {
|
|
|
353
431
|
const id = botIdOf(botId);
|
|
354
432
|
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
355
433
|
return Object.entries(this.#deliveryTargets[id] ?? {})
|
|
356
|
-
.map(([targetId, target]) =>
|
|
434
|
+
.map(([targetId, target]) => publicDeliveryTarget(target, { targetId }))
|
|
357
435
|
.sort((left, right) => left.targetId.localeCompare(right.targetId));
|
|
358
436
|
}
|
|
359
437
|
|
|
@@ -362,7 +440,29 @@ export class BotWorkspaceStore {
|
|
|
362
440
|
const targetKey = targetIdOf(targetId);
|
|
363
441
|
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
364
442
|
const target = this.#deliveryTargets[id]?.[targetKey];
|
|
365
|
-
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
|
+
));
|
|
366
466
|
}
|
|
367
467
|
|
|
368
468
|
async createDeliveryTarget(botId, value) {
|
|
@@ -378,10 +478,11 @@ export class BotWorkspaceStore {
|
|
|
378
478
|
...this.#deliveryTargets,
|
|
379
479
|
[id]: { ...(this.#deliveryTargets[id] ?? {}), [targetId]: stored },
|
|
380
480
|
};
|
|
381
|
-
|
|
481
|
+
const nextVersion = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
482
|
+
await this.#persist(this.#contextEnhancement, next, nextVersion);
|
|
382
483
|
this.#deliveryTargets = next;
|
|
383
|
-
this.#version =
|
|
384
|
-
return
|
|
484
|
+
this.#version = nextVersion;
|
|
485
|
+
return publicDeliveryTarget(stored, { targetId });
|
|
385
486
|
});
|
|
386
487
|
}
|
|
387
488
|
|
|
@@ -395,14 +496,19 @@ export class BotWorkspaceStore {
|
|
|
395
496
|
throw deliveryTargetError('unknown-target', 'Unknown target');
|
|
396
497
|
}
|
|
397
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;
|
|
398
503
|
const next = {
|
|
399
504
|
...this.#deliveryTargets,
|
|
400
|
-
[id]: { ...this.#deliveryTargets[id], [targetKey]:
|
|
505
|
+
[id]: { ...this.#deliveryTargets[id], [targetKey]: nextStored },
|
|
401
506
|
};
|
|
402
|
-
|
|
507
|
+
const nextVersion = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
508
|
+
await this.#persist(this.#contextEnhancement, next, nextVersion);
|
|
403
509
|
this.#deliveryTargets = next;
|
|
404
|
-
this.#version =
|
|
405
|
-
return
|
|
510
|
+
this.#version = nextVersion;
|
|
511
|
+
return publicDeliveryTarget(nextStored, { targetId: targetKey });
|
|
406
512
|
});
|
|
407
513
|
}
|
|
408
514
|
|
|
@@ -419,13 +525,40 @@ export class BotWorkspaceStore {
|
|
|
419
525
|
const next = { ...this.#deliveryTargets };
|
|
420
526
|
if (Object.keys(botTargets).length > 0) next[id] = botTargets;
|
|
421
527
|
else delete next[id];
|
|
422
|
-
|
|
528
|
+
const nextVersion = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
529
|
+
await this.#persist(this.#contextEnhancement, next, nextVersion);
|
|
423
530
|
this.#deliveryTargets = next;
|
|
424
|
-
this.#version =
|
|
531
|
+
this.#version = nextVersion;
|
|
425
532
|
return true;
|
|
426
533
|
});
|
|
427
534
|
}
|
|
428
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
|
+
|
|
429
562
|
generationFor(botId) {
|
|
430
563
|
return this.#generations.get(botIdOf(botId)) ?? null;
|
|
431
564
|
}
|
|
@@ -471,7 +604,9 @@ export class BotWorkspaceStore {
|
|
|
471
604
|
this.#generations.set(id, this.#freshGeneration());
|
|
472
605
|
this.#incarnations.set(id, this.#freshIncarnation());
|
|
473
606
|
}
|
|
474
|
-
const nextVersion = initializesAccessPolicy
|
|
607
|
+
const nextVersion = initializesAccessPolicy
|
|
608
|
+
? Math.max(this.#version, DELIVERY_DOCUMENT_VERSION)
|
|
609
|
+
: this.#version;
|
|
475
610
|
try {
|
|
476
611
|
await this.#persist(
|
|
477
612
|
this.#contextEnhancement,
|
|
@@ -565,6 +700,37 @@ export class BotWorkspaceStore {
|
|
|
565
700
|
});
|
|
566
701
|
}
|
|
567
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
|
+
|
|
568
734
|
async setContextEnhancement(botId, value, { incarnation } = {}) {
|
|
569
735
|
const id = botIdOf(botId);
|
|
570
736
|
const expectedIncarnation = incarnation === undefined ? this.incarnationFor(id) : incarnation;
|
|
@@ -598,11 +764,11 @@ export class BotWorkspaceStore {
|
|
|
598
764
|
await this.#persist(
|
|
599
765
|
this.#contextEnhancement,
|
|
600
766
|
this.#deliveryTargets,
|
|
601
|
-
|
|
767
|
+
Math.max(this.#version, DELIVERY_DOCUMENT_VERSION),
|
|
602
768
|
next,
|
|
603
769
|
);
|
|
604
770
|
this.#accessPolicies = next;
|
|
605
|
-
this.#version =
|
|
771
|
+
this.#version = Math.max(this.#version, DELIVERY_DOCUMENT_VERSION);
|
|
606
772
|
return policy;
|
|
607
773
|
});
|
|
608
774
|
}
|
|
@@ -764,6 +930,7 @@ export class BotWorkspaceStore {
|
|
|
764
930
|
const candidates = new Set([
|
|
765
931
|
...Object.keys(this.#workspaces),
|
|
766
932
|
...Object.keys(this.#agentPresets),
|
|
933
|
+
...Object.keys(this.#models),
|
|
767
934
|
...Object.keys(this.#contextEnhancement),
|
|
768
935
|
...Object.keys(this.#deliveryTargets),
|
|
769
936
|
...Object.keys(this.#accessPolicies),
|
|
@@ -783,6 +950,7 @@ export class BotWorkspaceStore {
|
|
|
783
950
|
...bot,
|
|
784
951
|
workspace: this.workspaceFor(bot.botId),
|
|
785
952
|
agentPreset: this.agentPresetFor(bot.botId),
|
|
953
|
+
model: this.modelFor(bot.botId),
|
|
786
954
|
contextEnhancement: this.contextEnhancementFor(bot.botId),
|
|
787
955
|
accessPolicy: this.accessPolicyFor(bot.botId),
|
|
788
956
|
}
|
|
@@ -814,13 +982,15 @@ export class BotWorkspaceStore {
|
|
|
814
982
|
async #retireCurrentIncarnation(id) {
|
|
815
983
|
const hadWorkspace = Object.hasOwn(this.#workspaces, id);
|
|
816
984
|
const hadPreset = Object.hasOwn(this.#agentPresets, id);
|
|
985
|
+
const hadModel = Object.hasOwn(this.#models, id);
|
|
817
986
|
const hadContextEnhancement = Object.hasOwn(this.#contextEnhancement, id);
|
|
818
987
|
const hadDeliveryTargets = Object.hasOwn(this.#deliveryTargets, id);
|
|
819
988
|
const hadAccessPolicy = Object.hasOwn(this.#accessPolicies, id);
|
|
820
|
-
const needsCleanup = hadWorkspace || hadPreset || hadContextEnhancement
|
|
989
|
+
const needsCleanup = hadWorkspace || hadPreset || hadModel || hadContextEnhancement
|
|
821
990
|
|| hadDeliveryTargets || hadAccessPolicy || this.#dirtyRemovals.has(id);
|
|
822
991
|
delete this.#workspaces[id];
|
|
823
992
|
delete this.#agentPresets[id];
|
|
993
|
+
delete this.#models[id];
|
|
824
994
|
delete this.#contextEnhancement[id];
|
|
825
995
|
delete this.#deliveryTargets[id];
|
|
826
996
|
delete this.#accessPolicies[id];
|
|
@@ -863,6 +1033,7 @@ export class BotWorkspaceStore {
|
|
|
863
1033
|
version,
|
|
864
1034
|
workspaces: this.#workspaces,
|
|
865
1035
|
agentPresets: this.#agentPresets,
|
|
1036
|
+
models: this.#models,
|
|
866
1037
|
contextEnhancement,
|
|
867
1038
|
deliveryTargets,
|
|
868
1039
|
accessPolicies,
|
|
@@ -873,6 +1044,7 @@ export class BotWorkspaceStore {
|
|
|
873
1044
|
async #persistCurrentDocument() {
|
|
874
1045
|
if (Object.keys(this.#workspaces).length > 0
|
|
875
1046
|
|| Object.keys(this.#agentPresets).length > 0
|
|
1047
|
+
|| Object.keys(this.#models).length > 0
|
|
876
1048
|
|| Object.keys(this.#contextEnhancement).length > 0
|
|
877
1049
|
|| Object.keys(this.#deliveryTargets).length > 0
|
|
878
1050
|
|| Object.keys(this.#accessPolicies).length > 0) {
|
|
@@ -897,12 +1069,26 @@ function resolveAgentPresetCatalog(catalog) {
|
|
|
897
1069
|
: normalizeAgentPresetCatalog(value);
|
|
898
1070
|
}
|
|
899
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
|
+
|
|
900
1080
|
function unavailableAgentPreset() {
|
|
901
1081
|
const error = new Error('Agent Preset 不存在或不可用。');
|
|
902
1082
|
error.code = 'agent-preset-unavailable';
|
|
903
1083
|
return error;
|
|
904
1084
|
}
|
|
905
1085
|
|
|
1086
|
+
function unavailableModel() {
|
|
1087
|
+
const error = new Error('模型不存在或不可用。');
|
|
1088
|
+
error.code = 'model-selection-unavailable';
|
|
1089
|
+
return error;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
906
1092
|
function assertCurrentBotScope(isCurrentScope) {
|
|
907
1093
|
if (isCurrentScope()) return;
|
|
908
1094
|
const error = new Error('找不到要修改的机器人。');
|
|
@@ -910,17 +1096,23 @@ function assertCurrentBotScope(isCurrentScope) {
|
|
|
910
1096
|
throw error;
|
|
911
1097
|
}
|
|
912
1098
|
|
|
913
|
-
function decorateResult(workspaces, result,
|
|
1099
|
+
function decorateResult(workspaces, result, agentPresetCatalogSource, modelCatalogSource) {
|
|
914
1100
|
const decorate = (value) => {
|
|
915
1101
|
const decorated = workspaces.decorateStatus(value);
|
|
916
|
-
if (!
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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);
|
|
924
1116
|
};
|
|
925
1117
|
return result && typeof result.then === 'function'
|
|
926
1118
|
? result.then(decorate)
|
|
@@ -1152,11 +1344,27 @@ export function createBotWorkspaceScope(
|
|
|
1152
1344
|
}
|
|
1153
1345
|
const generation = workspaces.generationFor(botId);
|
|
1154
1346
|
const agentPreset = workspaces.agentPresetFor(botId);
|
|
1347
|
+
const model = workspaces.modelFor(botId);
|
|
1155
1348
|
const sessionId = await target.createSession({
|
|
1156
1349
|
...options,
|
|
1157
1350
|
workspace: workspaces.workspaceFor(botId),
|
|
1158
1351
|
...(agentPreset == null ? {} : { agentPreset }),
|
|
1159
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
|
+
}
|
|
1160
1368
|
sessionGenerations.set(sessionId, generation);
|
|
1161
1369
|
return sessionId;
|
|
1162
1370
|
};
|
|
@@ -1318,7 +1526,12 @@ export function createBotScopedHarness(harness, options) {
|
|
|
1318
1526
|
return createBotWorkspaceScope(harness, options).harness;
|
|
1319
1527
|
}
|
|
1320
1528
|
|
|
1321
|
-
export function createWorkspaceAwareController(controller, {
|
|
1529
|
+
export function createWorkspaceAwareController(controller, {
|
|
1530
|
+
workspaces,
|
|
1531
|
+
stateFor,
|
|
1532
|
+
agentPresetCatalog,
|
|
1533
|
+
modelCatalog,
|
|
1534
|
+
} = {}) {
|
|
1322
1535
|
if (!controller || !workspaces || typeof stateFor !== 'function') {
|
|
1323
1536
|
throw new TypeError('controller, workspaces, and stateFor are required');
|
|
1324
1537
|
}
|
|
@@ -1331,7 +1544,12 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1331
1544
|
if (transitions.get(botId) === current) transitions.delete(botId);
|
|
1332
1545
|
});
|
|
1333
1546
|
};
|
|
1334
|
-
const decorate = (value) => decorateResult(
|
|
1547
|
+
const decorate = (value) => decorateResult(
|
|
1548
|
+
workspaces,
|
|
1549
|
+
value,
|
|
1550
|
+
agentPresetCatalog,
|
|
1551
|
+
modelCatalog,
|
|
1552
|
+
);
|
|
1335
1553
|
const updateWorkspace = (botId, workspace) => {
|
|
1336
1554
|
// Capture at API invocation, before even waiting for an older outer
|
|
1337
1555
|
// transition. A queued request still belongs to the incarnation that the
|
|
@@ -1374,6 +1592,32 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1374
1592
|
workspaces,
|
|
1375
1593
|
await controller.status(),
|
|
1376
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,
|
|
1377
1621
|
);
|
|
1378
1622
|
});
|
|
1379
1623
|
};
|
|
@@ -1387,13 +1631,17 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1387
1631
|
error.code = 'workspace-bot-not-found';
|
|
1388
1632
|
throw error;
|
|
1389
1633
|
}
|
|
1390
|
-
const catalog = await
|
|
1634
|
+
const [catalog, models] = await Promise.all([
|
|
1635
|
+
resolveAgentPresetCatalog(agentPresetCatalog),
|
|
1636
|
+
resolveModelCatalog(modelCatalog),
|
|
1637
|
+
]);
|
|
1391
1638
|
const decorated = workspaces.decorateStatus(snapshot);
|
|
1392
1639
|
const updated = {
|
|
1393
1640
|
...decorated,
|
|
1394
1641
|
bots: decorated.bots.map((bot) => bot?.botId === botId
|
|
1395
1642
|
? { ...bot, contextEnhancement: config } : bot),
|
|
1396
1643
|
...(catalog ? { agentPresetCatalog: catalog } : {}),
|
|
1644
|
+
...(models ? { modelCatalog: models } : {}),
|
|
1397
1645
|
};
|
|
1398
1646
|
// QR/status projection can fail too. Prepare the complete response before
|
|
1399
1647
|
// commit so a failed save never publishes new running settings.
|
|
@@ -1412,13 +1660,17 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1412
1660
|
error.code = 'workspace-bot-not-found';
|
|
1413
1661
|
throw error;
|
|
1414
1662
|
}
|
|
1415
|
-
const catalog = await
|
|
1663
|
+
const [catalog, models] = await Promise.all([
|
|
1664
|
+
resolveAgentPresetCatalog(agentPresetCatalog),
|
|
1665
|
+
resolveModelCatalog(modelCatalog),
|
|
1666
|
+
]);
|
|
1416
1667
|
const decorated = workspaces.decorateStatus(snapshot);
|
|
1417
1668
|
const updated = {
|
|
1418
1669
|
...decorated,
|
|
1419
1670
|
bots: decorated.bots.map((bot) => bot?.botId === botId
|
|
1420
1671
|
? { ...bot, accessPolicy: policy } : bot),
|
|
1421
1672
|
...(catalog ? { agentPresetCatalog: catalog } : {}),
|
|
1673
|
+
...(models ? { modelCatalog: models } : {}),
|
|
1422
1674
|
};
|
|
1423
1675
|
// Prepare the complete channel-specific response before commit. Failed
|
|
1424
1676
|
// projections and disk writes must leave the live policy unchanged.
|
|
@@ -1466,6 +1718,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
1466
1718
|
get(target, property) {
|
|
1467
1719
|
if (property === 'updateWorkspace') return updateWorkspace;
|
|
1468
1720
|
if (property === 'updateAgentPreset') return updateAgentPreset;
|
|
1721
|
+
if (property === 'updateModel') return updateModel;
|
|
1469
1722
|
if (property === 'updateContextEnhancement') return updateContextEnhancement;
|
|
1470
1723
|
if (property === 'updateAccessPolicy') return updateAccessPolicy;
|
|
1471
1724
|
const value = Reflect.get(target, property, target);
|