@frockbot/plugin-shell 0.1.2 → 0.1.4
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/package.json +28 -28
- package/src/backend-composition.ts +4 -4
- package/src/backend-configuration.test.ts +366 -1451
- package/src/backend-execution.ts +0 -4
- package/src/backend-image.test.ts +8 -8
- package/src/backend-image.ts +13 -13
- package/src/backend-isolate.test.ts +181 -35
- package/src/backend-isolate.ts +110 -72
- package/src/backend-recovery-integration.test.ts +17 -67
- package/src/backend-routines.ts +1 -1
- package/src/backend.ts +335 -1659
- package/src/client/FrockBotApp.vue +6 -73
- package/src/client/index.test.ts +320 -380
- package/src/client/index.ts +85 -256
- package/src/client/model-presentation.test.ts +21 -8
- package/src/client/model-presentation.ts +14 -7
- package/src/client/styles.css +0 -27
- package/src/settings-links.test.ts +2 -10
- package/src/settings-links.ts +1 -13
- package/src/shared.ts +0 -13
- package/src/backend-assignment.test.ts +0 -161
- package/src/backend-assignment.ts +0 -274
package/src/client/index.test.ts
CHANGED
|
@@ -173,7 +173,7 @@ describe("application manifest protocol", () => {
|
|
|
173
173
|
],
|
|
174
174
|
}),
|
|
175
175
|
).toEqual([]);
|
|
176
|
-
// A tool Package a User
|
|
176
|
+
// A tool Package a User enables with no credential at all:
|
|
177
177
|
// one Capability, no Connection Type. It stays in the catalog, because
|
|
178
178
|
// needing no Connection is not the same as offering nothing.
|
|
179
179
|
expect(
|
|
@@ -202,6 +202,55 @@ describe("application manifest protocol", () => {
|
|
|
202
202
|
connectionTypes: [],
|
|
203
203
|
}),
|
|
204
204
|
]);
|
|
205
|
+
// A settings-only Package still has User-visible enablement: turning it on
|
|
206
|
+
// is what makes its otherwise-inert controls available.
|
|
207
|
+
expect(
|
|
208
|
+
decodePluginCatalog({
|
|
209
|
+
...emptyManifest,
|
|
210
|
+
packages: [
|
|
211
|
+
{
|
|
212
|
+
id: "custom-models",
|
|
213
|
+
displayName: "Custom models",
|
|
214
|
+
version: "0.0.1",
|
|
215
|
+
contributions: ["client"],
|
|
216
|
+
configuration: {
|
|
217
|
+
settings: [
|
|
218
|
+
{
|
|
219
|
+
id: "account-model",
|
|
220
|
+
schemaVersion: 1,
|
|
221
|
+
scopes: ["user"],
|
|
222
|
+
role: "model",
|
|
223
|
+
schema: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
connectionId: { type: "string" },
|
|
227
|
+
providerModelId: { type: "string" },
|
|
228
|
+
},
|
|
229
|
+
required: ["connectionId", "providerModelId"],
|
|
230
|
+
additionalProperties: false,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
connectionTypes: [],
|
|
235
|
+
capabilities: [],
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
}),
|
|
240
|
+
).toEqual([
|
|
241
|
+
expect.objectContaining({
|
|
242
|
+
packageId: "custom-models",
|
|
243
|
+
capabilities: [],
|
|
244
|
+
connectionTypes: [],
|
|
245
|
+
settings: [
|
|
246
|
+
expect.objectContaining({
|
|
247
|
+
id: "account-model",
|
|
248
|
+
scopes: ["user"],
|
|
249
|
+
role: "model",
|
|
250
|
+
}),
|
|
251
|
+
],
|
|
252
|
+
}),
|
|
253
|
+
]);
|
|
205
254
|
// The same Package with a turn-type admission ceiling: manifest v4 is what
|
|
206
255
|
// the catalog wraps the served configuration as, so the field decodes.
|
|
207
256
|
expect(
|
|
@@ -289,121 +338,6 @@ describe("composer hydration context", () => {
|
|
|
289
338
|
});
|
|
290
339
|
});
|
|
291
340
|
|
|
292
|
-
describe("hosted Assignment commands", () => {
|
|
293
|
-
test("uses distinct atomic Assign, Replace, and Unassign commands", async () => {
|
|
294
|
-
let provided: Ref<FrockBotWebData> | undefined;
|
|
295
|
-
const commands: unknown[] = [];
|
|
296
|
-
await shellClientPlugin({
|
|
297
|
-
transport: {
|
|
298
|
-
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
299
|
-
readConfiguration: () =>
|
|
300
|
-
Promise.resolve(initializeBotSettingsV1("primary")),
|
|
301
|
-
executeConfiguration: (command) => {
|
|
302
|
-
commands.push(command);
|
|
303
|
-
return Promise.resolve({
|
|
304
|
-
schemaVersion: 1,
|
|
305
|
-
commandId: command.commandId,
|
|
306
|
-
revision: command.expectedRevision + 1,
|
|
307
|
-
status: "applied",
|
|
308
|
-
});
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
slot: () => () => {},
|
|
312
|
-
inject: () => {
|
|
313
|
-
throw new Error("unexpected client provider injection");
|
|
314
|
-
},
|
|
315
|
-
provide: (_key, value) => {
|
|
316
|
-
provided = value as Ref<FrockBotWebData>;
|
|
317
|
-
return () => {};
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
if (!provided) throw new Error("shell data was not provided");
|
|
321
|
-
provided.value.activeBotId = "primary";
|
|
322
|
-
provided.value.botSettings = initializeBotSettingsV1("primary");
|
|
323
|
-
const assignment = {
|
|
324
|
-
assignmentId: "mail",
|
|
325
|
-
packageId: "mail",
|
|
326
|
-
capabilityId: "send",
|
|
327
|
-
connectionId: "mail-1",
|
|
328
|
-
};
|
|
329
|
-
await provided.value.assignCapability(assignment);
|
|
330
|
-
provided.value.botSettings = {
|
|
331
|
-
...initializeBotSettingsV1("primary"),
|
|
332
|
-
revision: 1,
|
|
333
|
-
};
|
|
334
|
-
await provided.value.replaceCapability(assignment);
|
|
335
|
-
provided.value.botSettings = {
|
|
336
|
-
...initializeBotSettingsV1("primary"),
|
|
337
|
-
revision: 2,
|
|
338
|
-
};
|
|
339
|
-
await provided.value.unassignCapability("mail");
|
|
340
|
-
expect(commands).toMatchObject([
|
|
341
|
-
{ type: "bot/assign-capability", expectedRevision: 0, assignment },
|
|
342
|
-
{ type: "bot/replace-capability", expectedRevision: 1, assignment },
|
|
343
|
-
{
|
|
344
|
-
type: "bot/unassign-capability",
|
|
345
|
-
expectedRevision: 2,
|
|
346
|
-
assignmentId: "mail",
|
|
347
|
-
},
|
|
348
|
-
]);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
test("surfaces rejected and retrying Assignment receipts", async () => {
|
|
352
|
-
let provided: Ref<FrockBotWebData> | undefined;
|
|
353
|
-
const receipts = [
|
|
354
|
-
{
|
|
355
|
-
schemaVersion: 1 as const,
|
|
356
|
-
commandId: "rejected",
|
|
357
|
-
revision: 0,
|
|
358
|
-
status: "rejected" as const,
|
|
359
|
-
failure: "Connection is unavailable",
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
schemaVersion: 1 as const,
|
|
363
|
-
commandId: "pending",
|
|
364
|
-
revision: 0,
|
|
365
|
-
status: "pending" as const,
|
|
366
|
-
},
|
|
367
|
-
];
|
|
368
|
-
await shellClientPlugin({
|
|
369
|
-
transport: {
|
|
370
|
-
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
371
|
-
readConfiguration: () =>
|
|
372
|
-
Promise.resolve(initializeBotSettingsV1("primary")),
|
|
373
|
-
executeConfiguration: () => Promise.resolve(receipts.shift()!),
|
|
374
|
-
},
|
|
375
|
-
slot: () => () => {},
|
|
376
|
-
inject: () => {
|
|
377
|
-
throw new Error("unexpected client provider injection");
|
|
378
|
-
},
|
|
379
|
-
provide: (_key, value) => {
|
|
380
|
-
provided = value as Ref<FrockBotWebData>;
|
|
381
|
-
return () => {};
|
|
382
|
-
},
|
|
383
|
-
});
|
|
384
|
-
if (!provided) throw new Error("shell data was not provided");
|
|
385
|
-
provided.value.activeBotId = "primary";
|
|
386
|
-
provided.value.botSettings = initializeBotSettingsV1("primary");
|
|
387
|
-
const assignment = {
|
|
388
|
-
assignmentId: "mail",
|
|
389
|
-
packageId: "mail",
|
|
390
|
-
capabilityId: "send",
|
|
391
|
-
connectionId: "mail-1",
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
await expect(provided.value.assignCapability(assignment)).rejects.toThrow(
|
|
395
|
-
"Connection is unavailable",
|
|
396
|
-
);
|
|
397
|
-
expect(provided.value.settingsError).toBe("Connection is unavailable");
|
|
398
|
-
await expect(provided.value.assignCapability(assignment)).resolves.toBe(
|
|
399
|
-
undefined,
|
|
400
|
-
);
|
|
401
|
-
expect(provided.value.settingsError).toBe(
|
|
402
|
-
"Assignment operation is retrying.",
|
|
403
|
-
);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
|
|
407
341
|
describe("Bot selection", () => {
|
|
408
342
|
test("passes explicit Bot IDs and ignores stale hydration", async () => {
|
|
409
343
|
Object.defineProperty(globalThis, "window", {
|
|
@@ -576,197 +510,218 @@ describe("Bot selection", () => {
|
|
|
576
510
|
expect(provided.value.userSettings?.profile.name).toBe("Newer");
|
|
577
511
|
});
|
|
578
512
|
|
|
579
|
-
test(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
{
|
|
589
|
-
assignmentId: "ollama-model",
|
|
590
|
-
packageId: "provider-ollama-cloud",
|
|
591
|
-
capabilityId: "ollama-cloud-models",
|
|
592
|
-
connectionId: "ollama-work",
|
|
593
|
-
state: "enabled" as const,
|
|
594
|
-
},
|
|
595
|
-
],
|
|
596
|
-
};
|
|
597
|
-
const user: UserSettingsViewV1 = {
|
|
598
|
-
schemaVersion: 1,
|
|
599
|
-
revision: 1,
|
|
600
|
-
profile: { name: "User" },
|
|
601
|
-
packages: [
|
|
602
|
-
{
|
|
603
|
-
packageId: "provider-ollama-cloud",
|
|
604
|
-
version: "0.0.1",
|
|
605
|
-
state: "installed",
|
|
606
|
-
},
|
|
607
|
-
],
|
|
608
|
-
connections: [
|
|
609
|
-
{
|
|
610
|
-
connectionId: "ollama-work",
|
|
611
|
-
packageId: "provider-ollama-cloud",
|
|
612
|
-
connectionTypeId: "ollama-cloud-account",
|
|
613
|
-
displayName: "Work",
|
|
614
|
-
state: "ready",
|
|
615
|
-
providerType: "ollama-cloud",
|
|
616
|
-
safeMetadata: {},
|
|
513
|
+
test.each([
|
|
514
|
+
{
|
|
515
|
+
source: "bot" as const,
|
|
516
|
+
botValues: {
|
|
517
|
+
"custom-models": {
|
|
518
|
+
model: {
|
|
519
|
+
connectionId: "model-connection",
|
|
520
|
+
providerModelId: "model-id",
|
|
521
|
+
},
|
|
617
522
|
},
|
|
618
|
-
],
|
|
619
|
-
};
|
|
620
|
-
await shellClientPlugin({
|
|
621
|
-
transport: {
|
|
622
|
-
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
623
|
-
readConfiguration: (query) =>
|
|
624
|
-
Promise.resolve(query.type === "user/get" ? user : bot),
|
|
625
|
-
},
|
|
626
|
-
slot: () => () => {},
|
|
627
|
-
inject: () => {
|
|
628
|
-
throw new Error("unexpected client provider injection");
|
|
629
523
|
},
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
524
|
+
accountValues: undefined,
|
|
525
|
+
platformModel: undefined,
|
|
526
|
+
expectedLabel: "Model name · Model provider · Bot override",
|
|
527
|
+
expectedProjection: "bot",
|
|
528
|
+
ready: true,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
source: "account" as const,
|
|
532
|
+
botValues: {},
|
|
533
|
+
accountValues: {
|
|
534
|
+
model: {
|
|
535
|
+
connectionId: "model-connection",
|
|
536
|
+
providerModelId: "model-id",
|
|
537
|
+
},
|
|
633
538
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
539
|
+
platformModel: undefined,
|
|
540
|
+
expectedLabel: "Model name · Model provider · Account model",
|
|
541
|
+
expectedProjection: "default",
|
|
542
|
+
ready: true,
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
source: "platform" as const,
|
|
546
|
+
botValues: {},
|
|
547
|
+
accountValues: undefined,
|
|
548
|
+
platformModel: {
|
|
549
|
+
connectionId: "model-connection",
|
|
550
|
+
providerModelId: "model-id",
|
|
551
|
+
},
|
|
552
|
+
expectedLabel: "Model name · Model provider",
|
|
553
|
+
expectedProjection: "default",
|
|
554
|
+
ready: true,
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
source: "none" as const,
|
|
558
|
+
botValues: {},
|
|
559
|
+
accountValues: undefined,
|
|
560
|
+
platformModel: undefined,
|
|
561
|
+
expectedLabel: "Model unavailable",
|
|
562
|
+
expectedProjection: "none",
|
|
563
|
+
ready: false,
|
|
564
|
+
},
|
|
565
|
+
])(
|
|
566
|
+
"labels the $source effective model",
|
|
567
|
+
async ({
|
|
568
|
+
botValues,
|
|
569
|
+
accountValues,
|
|
570
|
+
platformModel,
|
|
571
|
+
expectedLabel,
|
|
572
|
+
expectedProjection,
|
|
573
|
+
ready,
|
|
574
|
+
}) => {
|
|
575
|
+
let provided: Ref<FrockBotWebData> | undefined;
|
|
576
|
+
const bot = {
|
|
577
|
+
...initializeBotSettingsV1("model-bot"),
|
|
578
|
+
packageValues: structuredClone(botValues) as Record<
|
|
579
|
+
string,
|
|
580
|
+
Record<string, unknown>
|
|
581
|
+
>,
|
|
582
|
+
};
|
|
583
|
+
const user: UserSettingsViewV1 = {
|
|
584
|
+
schemaVersion: 1,
|
|
585
|
+
revision: 1,
|
|
586
|
+
profile: { name: "User" },
|
|
587
|
+
packages: [
|
|
643
588
|
{
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
589
|
+
packageId: "custom-models",
|
|
590
|
+
version: "0.0.1",
|
|
591
|
+
state: "installed",
|
|
592
|
+
...(accountValues ? { values: accountValues } : {}),
|
|
647
593
|
},
|
|
648
594
|
{
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
595
|
+
packageId: "model-provider",
|
|
596
|
+
version: "0.0.1",
|
|
597
|
+
state: "installed",
|
|
652
598
|
},
|
|
653
599
|
],
|
|
654
|
-
|
|
600
|
+
connections: [
|
|
655
601
|
{
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
602
|
+
connectionId: "model-connection",
|
|
603
|
+
packageId: "model-provider",
|
|
604
|
+
connectionTypeId: "model-account",
|
|
605
|
+
displayName: "Work",
|
|
606
|
+
state: "ready",
|
|
607
|
+
providerType: "model-provider",
|
|
608
|
+
safeMetadata: {},
|
|
609
|
+
modelCatalog: {
|
|
610
|
+
schemaVersion: 1,
|
|
611
|
+
generation: "catalog-1",
|
|
612
|
+
state: "fresh",
|
|
613
|
+
models: [
|
|
614
|
+
{
|
|
615
|
+
providerModelId: "model-id",
|
|
616
|
+
displayName: "Model name",
|
|
617
|
+
capabilities: {
|
|
618
|
+
tools: true,
|
|
619
|
+
vision: false,
|
|
620
|
+
reasoning: false,
|
|
621
|
+
},
|
|
622
|
+
source: "discovered",
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
},
|
|
661
626
|
},
|
|
662
627
|
],
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
// label never says so: the Bot simply runs on that model.
|
|
684
|
-
bot.model = undefined as unknown as typeof bot.model;
|
|
685
|
-
user.newBotModelTemplate = {
|
|
686
|
-
connectionId: "ollama-work",
|
|
687
|
-
providerModelId: "llama-3:cloud",
|
|
688
|
-
};
|
|
689
|
-
user.newBotModelTemplateSource = "auto";
|
|
690
|
-
await provided.value.loadUserSettings();
|
|
691
|
-
await provided.value.loadBotSettings();
|
|
692
|
-
expect(provided.value.modelSource).toBe("default");
|
|
693
|
-
expect(provided.value.modelLabel).toBe("llama-3:cloud · Ollama Cloud");
|
|
694
|
-
expect(provided.value.modelReady).toBe(true);
|
|
695
|
-
|
|
696
|
-
user.newBotModelTemplate = undefined;
|
|
697
|
-
user.newBotModelTemplateSource = undefined;
|
|
698
|
-
await provided.value.loadUserSettings();
|
|
699
|
-
expect(provided.value.modelSource).toBe("none");
|
|
700
|
-
expect(provided.value.modelLabel).toBe("No default model");
|
|
701
|
-
expect(provided.value.modelReady).toBe(false);
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
test("assigns a newly connected model capability before model selection", async () => {
|
|
705
|
-
let provided: Ref<FrockBotWebData> | undefined;
|
|
706
|
-
let bot = initializeBotSettingsV1("ollama-bot");
|
|
707
|
-
const commands: Array<{ type: string; expectedRevision: number }> = [];
|
|
708
|
-
const user: UserSettingsViewV1 = {
|
|
709
|
-
schemaVersion: 1,
|
|
710
|
-
revision: 1,
|
|
711
|
-
profile: { name: "User" },
|
|
712
|
-
packages: [
|
|
628
|
+
...(platformModel ? { platformModel } : {}),
|
|
629
|
+
};
|
|
630
|
+
await shellClientPlugin({
|
|
631
|
+
transport: {
|
|
632
|
+
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
633
|
+
readConfiguration: (query) =>
|
|
634
|
+
Promise.resolve(query.type === "user/get" ? user : bot),
|
|
635
|
+
},
|
|
636
|
+
slot: () => () => {},
|
|
637
|
+
inject: () => {
|
|
638
|
+
throw new Error("unexpected client provider injection");
|
|
639
|
+
},
|
|
640
|
+
provide: (_key, value) => {
|
|
641
|
+
provided = value as Ref<FrockBotWebData>;
|
|
642
|
+
return () => {};
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
if (!provided) throw new Error("shell data was not provided");
|
|
646
|
+
provided.value.activeBotId = bot.botId;
|
|
647
|
+
provided.value.pluginCatalog = [
|
|
713
648
|
{
|
|
714
|
-
packageId: "
|
|
649
|
+
packageId: "custom-models",
|
|
650
|
+
displayName: "Custom models",
|
|
715
651
|
version: "0.0.1",
|
|
716
|
-
|
|
652
|
+
capabilities: [],
|
|
653
|
+
connectionTypes: [],
|
|
654
|
+
settings: [
|
|
655
|
+
{
|
|
656
|
+
id: "model",
|
|
657
|
+
schemaVersion: 1,
|
|
658
|
+
scopes: ["user", "bot"],
|
|
659
|
+
role: "model",
|
|
660
|
+
schema: {
|
|
661
|
+
type: "object",
|
|
662
|
+
properties: {
|
|
663
|
+
connectionId: { type: "string" },
|
|
664
|
+
providerModelId: { type: "string" },
|
|
665
|
+
},
|
|
666
|
+
required: ["connectionId", "providerModelId"],
|
|
667
|
+
additionalProperties: false,
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
],
|
|
717
671
|
},
|
|
718
|
-
],
|
|
719
|
-
connections: [
|
|
720
672
|
{
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
673
|
+
packageId: "model-provider",
|
|
674
|
+
displayName: "Model provider",
|
|
675
|
+
version: "0.0.1",
|
|
676
|
+
capabilities: [
|
|
677
|
+
{
|
|
678
|
+
id: "models",
|
|
679
|
+
kind: "model",
|
|
680
|
+
connectionTypes: ["model-account"],
|
|
681
|
+
},
|
|
682
|
+
],
|
|
683
|
+
connectionTypes: [
|
|
684
|
+
{
|
|
685
|
+
id: "model-account",
|
|
686
|
+
displayName: "Model account",
|
|
687
|
+
allowMultiple: false,
|
|
688
|
+
authorizationKind: "api-key",
|
|
689
|
+
capabilities: ["models"],
|
|
690
|
+
},
|
|
691
|
+
],
|
|
692
|
+
settings: [],
|
|
728
693
|
},
|
|
729
|
-
]
|
|
694
|
+
];
|
|
695
|
+
|
|
696
|
+
await provided.value.loadBotSettings();
|
|
697
|
+
|
|
698
|
+
expect(provided.value.modelLabel).toBe(expectedLabel);
|
|
699
|
+
expect(provided.value.modelReady).toBe(ready);
|
|
700
|
+
expect(provided.value.modelSource).toBe(expectedProjection);
|
|
701
|
+
},
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
test("saves Bot-scoped Package settings through the generic command", async () => {
|
|
705
|
+
type PackageSettingsWebData = FrockBotWebData & {
|
|
706
|
+
saveBotPackageSettings(
|
|
707
|
+
packageId: string,
|
|
708
|
+
values: Record<string, unknown>,
|
|
709
|
+
): Promise<void>;
|
|
730
710
|
};
|
|
711
|
+
let provided: Ref<PackageSettingsWebData> | undefined;
|
|
712
|
+
const commands: unknown[] = [];
|
|
713
|
+
const bot = initializeBotSettingsV1("package-bot");
|
|
731
714
|
await shellClientPlugin({
|
|
732
715
|
transport: {
|
|
733
716
|
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
734
|
-
readConfiguration: (
|
|
735
|
-
Promise.resolve(query.type === "user/get" ? user : bot),
|
|
717
|
+
readConfiguration: () => Promise.resolve(bot),
|
|
736
718
|
executeConfiguration: (command) => {
|
|
737
|
-
|
|
738
|
-
commands.push({
|
|
739
|
-
type: command.type,
|
|
740
|
-
expectedRevision: command.expectedRevision,
|
|
741
|
-
});
|
|
742
|
-
if (command.type === "bot/assign-capability") {
|
|
743
|
-
bot = {
|
|
744
|
-
...bot,
|
|
745
|
-
revision: 1,
|
|
746
|
-
model: command.model,
|
|
747
|
-
assignments: [{ ...command.assignment, state: "enabled" }],
|
|
748
|
-
};
|
|
749
|
-
} else if (command.type === "bot/select-model") {
|
|
750
|
-
bot = { ...bot, revision: bot.revision + 1, model: command.model };
|
|
751
|
-
} else if (command.type === "bot/unbind-model") {
|
|
752
|
-
bot = {
|
|
753
|
-
...bot,
|
|
754
|
-
revision: bot.revision + 1,
|
|
755
|
-
model: undefined,
|
|
756
|
-
assignments: bot.assignments.map((assignment) =>
|
|
757
|
-
assignment.assignmentId === command.assignmentId
|
|
758
|
-
? { ...assignment, state: "unavailable" }
|
|
759
|
-
: assignment,
|
|
760
|
-
),
|
|
761
|
-
};
|
|
762
|
-
} else {
|
|
763
|
-
throw new Error("unexpected Bot command");
|
|
764
|
-
}
|
|
719
|
+
commands.push(command);
|
|
765
720
|
return Promise.resolve({
|
|
766
721
|
schemaVersion: 1,
|
|
767
722
|
commandId: command.commandId,
|
|
768
|
-
revision:
|
|
769
|
-
status: "applied"
|
|
723
|
+
revision: command.expectedRevision + 1,
|
|
724
|
+
status: "applied",
|
|
770
725
|
});
|
|
771
726
|
},
|
|
772
727
|
},
|
|
@@ -775,102 +730,52 @@ describe("Bot selection", () => {
|
|
|
775
730
|
throw new Error("unexpected client provider injection");
|
|
776
731
|
},
|
|
777
732
|
provide: (_key, value) => {
|
|
778
|
-
provided = value as Ref<
|
|
733
|
+
provided = value as Ref<PackageSettingsWebData>;
|
|
779
734
|
return () => {};
|
|
780
735
|
},
|
|
781
736
|
});
|
|
782
737
|
if (!provided) throw new Error("shell data was not provided");
|
|
783
738
|
provided.value.activeBotId = bot.botId;
|
|
784
739
|
provided.value.botSettings = bot;
|
|
785
|
-
provided.value.userSettings = user;
|
|
786
|
-
provided.value.pluginCatalog = [
|
|
787
|
-
{
|
|
788
|
-
packageId: "provider-ollama-cloud",
|
|
789
|
-
displayName: "Ollama Cloud",
|
|
790
|
-
version: "0.0.1",
|
|
791
|
-
capabilities: [
|
|
792
|
-
{
|
|
793
|
-
id: "ollama-cloud-models",
|
|
794
|
-
kind: "model",
|
|
795
|
-
connectionTypes: ["ollama-cloud-account"],
|
|
796
|
-
},
|
|
797
|
-
],
|
|
798
|
-
connectionTypes: [
|
|
799
|
-
{
|
|
800
|
-
id: "ollama-cloud-account",
|
|
801
|
-
displayName: "Ollama Cloud account",
|
|
802
|
-
allowMultiple: true,
|
|
803
|
-
authorizationKind: "api-key",
|
|
804
|
-
capabilities: ["ollama-cloud-models"],
|
|
805
|
-
},
|
|
806
|
-
],
|
|
807
|
-
},
|
|
808
|
-
];
|
|
809
740
|
|
|
810
|
-
await provided.value.
|
|
811
|
-
connectionId: "ollama-work",
|
|
812
|
-
providerModelId: "glm-5.3-flash:cloud",
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
expect(commands).toEqual([
|
|
816
|
-
{ type: "bot/assign-capability", expectedRevision: 0 },
|
|
817
|
-
]);
|
|
818
|
-
expect(bot).toMatchObject({
|
|
819
|
-
revision: 1,
|
|
741
|
+
await provided.value.saveBotPackageSettings("custom-models", {
|
|
820
742
|
model: {
|
|
821
|
-
connectionId: "
|
|
822
|
-
providerModelId: "
|
|
743
|
+
connectionId: "model-connection",
|
|
744
|
+
providerModelId: "model-id",
|
|
823
745
|
},
|
|
824
|
-
assignments: [
|
|
825
|
-
{
|
|
826
|
-
packageId: "provider-ollama-cloud",
|
|
827
|
-
capabilityId: "ollama-cloud-models",
|
|
828
|
-
connectionId: "ollama-work",
|
|
829
|
-
state: "enabled",
|
|
830
|
-
},
|
|
831
|
-
],
|
|
832
746
|
});
|
|
833
747
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
});
|
|
849
|
-
expect(bot).toMatchObject({
|
|
850
|
-
revision: 2,
|
|
851
|
-
model: undefined,
|
|
852
|
-
assignments: [{ state: "unavailable" }],
|
|
853
|
-
});
|
|
748
|
+
expect(commands).toMatchObject([
|
|
749
|
+
{
|
|
750
|
+
type: "bot/set-package-settings",
|
|
751
|
+
botId: "package-bot",
|
|
752
|
+
packageId: "custom-models",
|
|
753
|
+
expectedRevision: 0,
|
|
754
|
+
values: {
|
|
755
|
+
model: {
|
|
756
|
+
connectionId: "model-connection",
|
|
757
|
+
providerModelId: "model-id",
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
]);
|
|
854
762
|
});
|
|
855
763
|
|
|
856
|
-
test("
|
|
764
|
+
test("shows the backend reason when Package enablement is refused", async () => {
|
|
857
765
|
let provided: Ref<FrockBotWebData> | undefined;
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
model: {
|
|
861
|
-
connectionId: "revoked-connection",
|
|
862
|
-
providerModelId: "glm-5.3-flash:cloud",
|
|
863
|
-
},
|
|
864
|
-
assignments: [],
|
|
865
|
-
};
|
|
866
|
-
let executed = false;
|
|
766
|
+
const failure =
|
|
767
|
+
'Enable dependency "custom-models" before enabling "provider-ollama-cloud"';
|
|
867
768
|
await shellClientPlugin({
|
|
868
769
|
transport: {
|
|
869
770
|
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
870
|
-
executeConfiguration: () =>
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
771
|
+
executeConfiguration: (command) =>
|
|
772
|
+
Promise.resolve({
|
|
773
|
+
schemaVersion: 1,
|
|
774
|
+
commandId: command.commandId,
|
|
775
|
+
revision: command.expectedRevision,
|
|
776
|
+
status: "rejected",
|
|
777
|
+
failure,
|
|
778
|
+
}),
|
|
874
779
|
},
|
|
875
780
|
slot: () => () => {},
|
|
876
781
|
inject: () => {
|
|
@@ -882,19 +787,24 @@ describe("Bot selection", () => {
|
|
|
882
787
|
},
|
|
883
788
|
});
|
|
884
789
|
if (!provided) throw new Error("shell data was not provided");
|
|
885
|
-
provided.value.activeBotId = bot.botId;
|
|
886
|
-
provided.value.botSettings = bot;
|
|
887
790
|
provided.value.userSettings = {
|
|
888
791
|
schemaVersion: 1,
|
|
889
|
-
revision:
|
|
792
|
+
revision: 2,
|
|
890
793
|
profile: { name: "User" },
|
|
891
|
-
packages: [
|
|
794
|
+
packages: [
|
|
795
|
+
{
|
|
796
|
+
packageId: "provider-ollama-cloud",
|
|
797
|
+
version: "0.0.1",
|
|
798
|
+
state: "disabled",
|
|
799
|
+
},
|
|
800
|
+
],
|
|
892
801
|
connections: [],
|
|
893
802
|
};
|
|
894
803
|
|
|
895
|
-
await
|
|
896
|
-
|
|
897
|
-
|
|
804
|
+
await expect(
|
|
805
|
+
provided.value.setPackageEnabled("provider-ollama-cloud", true),
|
|
806
|
+
).rejects.toThrow(failure);
|
|
807
|
+
expect(provided.value.settingsError).toBe(failure);
|
|
898
808
|
});
|
|
899
809
|
});
|
|
900
810
|
|
|
@@ -984,7 +894,7 @@ describe("detached Turn projection", () => {
|
|
|
984
894
|
input: "Keep working",
|
|
985
895
|
events: [],
|
|
986
896
|
status: "completed",
|
|
987
|
-
responseText: "Finished
|
|
897
|
+
responseText: "Finished successfully.",
|
|
988
898
|
},
|
|
989
899
|
],
|
|
990
900
|
);
|
|
@@ -992,7 +902,7 @@ describe("detached Turn projection", () => {
|
|
|
992
902
|
expect(messages).toHaveLength(2);
|
|
993
903
|
expect(messages[1]).toMatchObject({
|
|
994
904
|
role: "assistant",
|
|
995
|
-
text: "Finished
|
|
905
|
+
text: "Finished successfully.",
|
|
996
906
|
status: "completed",
|
|
997
907
|
});
|
|
998
908
|
});
|
|
@@ -1423,8 +1333,18 @@ describe("active durable Turn projection", () => {
|
|
|
1423
1333
|
await shellClientPlugin({
|
|
1424
1334
|
transport: {
|
|
1425
1335
|
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
1426
|
-
readConfiguration: () =>
|
|
1427
|
-
Promise.resolve(
|
|
1336
|
+
readConfiguration: (query) =>
|
|
1337
|
+
Promise.resolve(
|
|
1338
|
+
query.type === "bot/get"
|
|
1339
|
+
? initializeBotSettingsV1("default")
|
|
1340
|
+
: {
|
|
1341
|
+
schemaVersion: 1 as const,
|
|
1342
|
+
revision: 0,
|
|
1343
|
+
profile: { name: "User" },
|
|
1344
|
+
packages: [],
|
|
1345
|
+
connections: [],
|
|
1346
|
+
},
|
|
1347
|
+
),
|
|
1428
1348
|
listRuns: () =>
|
|
1429
1349
|
Promise.resolve([
|
|
1430
1350
|
{
|
|
@@ -1583,8 +1503,18 @@ describe("hosted Stop", () => {
|
|
|
1583
1503
|
await shellClientPlugin({
|
|
1584
1504
|
transport: {
|
|
1585
1505
|
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
1586
|
-
readConfiguration: () =>
|
|
1587
|
-
Promise.resolve(
|
|
1506
|
+
readConfiguration: (query) =>
|
|
1507
|
+
Promise.resolve(
|
|
1508
|
+
query.type === "bot/get"
|
|
1509
|
+
? initializeBotSettingsV1("default")
|
|
1510
|
+
: {
|
|
1511
|
+
schemaVersion: 1 as const,
|
|
1512
|
+
revision: 0,
|
|
1513
|
+
profile: { name: "User" },
|
|
1514
|
+
packages: [],
|
|
1515
|
+
connections: [],
|
|
1516
|
+
},
|
|
1517
|
+
),
|
|
1588
1518
|
listRuns: () =>
|
|
1589
1519
|
Promise.resolve([
|
|
1590
1520
|
{
|
|
@@ -1619,7 +1549,7 @@ describe("hosted Stop", () => {
|
|
|
1619
1549
|
expect(provided.value.activeRun).toMatchObject({
|
|
1620
1550
|
runId: "run-1",
|
|
1621
1551
|
status: "running",
|
|
1622
|
-
message: "Stop
|
|
1552
|
+
message: "Stop requested; finishing up.",
|
|
1623
1553
|
canResume: false,
|
|
1624
1554
|
});
|
|
1625
1555
|
|
|
@@ -1715,8 +1645,18 @@ describe("hosted Stop", () => {
|
|
|
1715
1645
|
await shellClientPlugin({
|
|
1716
1646
|
transport: {
|
|
1717
1647
|
turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
|
|
1718
|
-
readConfiguration: () =>
|
|
1719
|
-
Promise.resolve(
|
|
1648
|
+
readConfiguration: (query) =>
|
|
1649
|
+
Promise.resolve(
|
|
1650
|
+
query.type === "bot/get"
|
|
1651
|
+
? initializeBotSettingsV1("other")
|
|
1652
|
+
: {
|
|
1653
|
+
schemaVersion: 1 as const,
|
|
1654
|
+
revision: 0,
|
|
1655
|
+
profile: { name: "User" },
|
|
1656
|
+
packages: [],
|
|
1657
|
+
connections: [],
|
|
1658
|
+
},
|
|
1659
|
+
),
|
|
1720
1660
|
listRuns: () => Promise.resolve([]),
|
|
1721
1661
|
listNotifications: () => Promise.resolve([]),
|
|
1722
1662
|
stopRun: () => {
|
|
@@ -1788,7 +1728,7 @@ describe("uncertain Turn admission", () => {
|
|
|
1788
1728
|
input: "continue",
|
|
1789
1729
|
status: lookups === 2 ? "running" : "completed",
|
|
1790
1730
|
events: [],
|
|
1791
|
-
...(lookups === 2 ? {} : { responseText: "Done
|
|
1731
|
+
...(lookups === 2 ? {} : { responseText: "Done successfully" }),
|
|
1792
1732
|
});
|
|
1793
1733
|
},
|
|
1794
1734
|
fenceRunAdmission: () =>
|
|
@@ -1822,7 +1762,7 @@ describe("uncertain Turn admission", () => {
|
|
|
1822
1762
|
expect(provided.value.settingsError).toBeUndefined();
|
|
1823
1763
|
expect(outstandingAbortListeners).toBe(0);
|
|
1824
1764
|
expect(provided.value.messages.at(-1)).toMatchObject({
|
|
1825
|
-
text: "Done
|
|
1765
|
+
text: "Done successfully",
|
|
1826
1766
|
status: "completed",
|
|
1827
1767
|
});
|
|
1828
1768
|
});
|