@frockbot/kernel-composition 0.3.1 → 0.3.3
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 +2 -2
- package/src/compiler.test.ts +50 -0
- package/src/compiler.ts +23 -0
- package/src/generation.ts +194 -8
- package/src/index.test.ts +358 -4
- package/src/isolate-host.test.ts +24 -1
- package/src/isolate-host.ts +9 -1
- package/src/isolate-wrapper.ts +28 -1
- package/src/manifest.ts +442 -85
package/src/index.test.ts
CHANGED
|
@@ -125,6 +125,63 @@ describe("PackageCatalog", () => {
|
|
|
125
125
|
).toThrow('manifest v2 desktop execution must be "sandboxed-renderer"');
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
// Constitution — Computer and Workspace: "durable roots, declared by the
|
|
129
|
+
// Computer Package's Workspace layout **and by Package manifests**". This is
|
|
130
|
+
// the manifest half; `image` and `applets` are its two callers today.
|
|
131
|
+
test("decodes the durable roots a Package declares, and refuses a malformed one", () => {
|
|
132
|
+
const base = {
|
|
133
|
+
schemaVersion: 4 as const,
|
|
134
|
+
id: "image",
|
|
135
|
+
displayName: "Image generation",
|
|
136
|
+
version: "1.0.0",
|
|
137
|
+
compatibility: { frockbot: "*" },
|
|
138
|
+
contributions: { runtime: { entry: "./agent" } },
|
|
139
|
+
permissions: [],
|
|
140
|
+
};
|
|
141
|
+
expect(
|
|
142
|
+
decodeFrockBotManifest({
|
|
143
|
+
...base,
|
|
144
|
+
roots: [{ id: "generated", scope: "user" }],
|
|
145
|
+
}).roots,
|
|
146
|
+
).toEqual([{ id: "generated", scope: "user" }]);
|
|
147
|
+
// Absent stays absent: a Package that declares no root has none, and the
|
|
148
|
+
// sync must not invent one.
|
|
149
|
+
expect(decodeFrockBotManifest(base).roots).toBeUndefined();
|
|
150
|
+
for (const [roots, message] of [
|
|
151
|
+
[[], "non-empty bounded array"],
|
|
152
|
+
[[{ id: "Generated", scope: "user" }], "id is invalid"],
|
|
153
|
+
[[{ id: "-bad", scope: "user" }], "id is invalid"],
|
|
154
|
+
// A `package-declared` root names no Bot, so a Bot-scoped one would be a
|
|
155
|
+
// root `WorkspaceRootV1` cannot address.
|
|
156
|
+
[[{ id: "generated", scope: "bot" }], 'scope must be "user"'],
|
|
157
|
+
[[{ id: "generated" }], 'scope must be "user"'],
|
|
158
|
+
[
|
|
159
|
+
[
|
|
160
|
+
{ id: "generated", scope: "user" },
|
|
161
|
+
{ id: "generated", scope: "user" },
|
|
162
|
+
],
|
|
163
|
+
"duplicate ids",
|
|
164
|
+
],
|
|
165
|
+
] as const) {
|
|
166
|
+
expect(() => decodeFrockBotManifest({ ...base, roots })).toThrow(message);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("keeps a declared root out of reach of a v2 manifest", () => {
|
|
171
|
+
expect(() =>
|
|
172
|
+
decodeFrockBotManifest({
|
|
173
|
+
schemaVersion: 2,
|
|
174
|
+
id: "legacy",
|
|
175
|
+
displayName: "Legacy",
|
|
176
|
+
version: "1.0.0",
|
|
177
|
+
compatibility: { frockbot: "*" },
|
|
178
|
+
contributions: { runtime: { entry: "./agent" } },
|
|
179
|
+
permissions: [],
|
|
180
|
+
roots: [{ id: "generated", scope: "user" }],
|
|
181
|
+
}),
|
|
182
|
+
).toThrow('manifest has unknown field "roots"');
|
|
183
|
+
});
|
|
184
|
+
|
|
128
185
|
test("keeps backend Contributions unavailable to v2 manifests", () => {
|
|
129
186
|
expect(() =>
|
|
130
187
|
decodeFrockBotManifest({
|
|
@@ -328,9 +385,22 @@ describe("decodeFrockBotManifest", () => {
|
|
|
328
385
|
const decoded = decodeFrockBotManifest(manifest);
|
|
329
386
|
const client = decoded.contributions.client;
|
|
330
387
|
expect(client && "kind" in client ? client.kind : undefined).toBe("iframe");
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
388
|
+
// Migration: the v3 single-page record decodes to the one v5 shape.
|
|
389
|
+
expect(client && "pages" in client ? client.pages : undefined).toEqual([
|
|
390
|
+
{
|
|
391
|
+
id: "main",
|
|
392
|
+
artifact: {
|
|
393
|
+
contentHash: "a".repeat(64),
|
|
394
|
+
size: 123,
|
|
395
|
+
mediaType: "text/html",
|
|
396
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
397
|
+
},
|
|
398
|
+
mounts: [
|
|
399
|
+
{ slot: "frockbot.tool-result:weather_lookup" },
|
|
400
|
+
{ slot: "frockbot.bot-settings-sections", order: 10 },
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
]);
|
|
334
404
|
expect(decoded.hooks).toEqual(["agent/tool-exposure"]);
|
|
335
405
|
expect(() =>
|
|
336
406
|
decodeFrockBotManifest({
|
|
@@ -373,6 +443,290 @@ describe("decodeFrockBotManifest", () => {
|
|
|
373
443
|
).toThrow("256 KB quota");
|
|
374
444
|
});
|
|
375
445
|
|
|
446
|
+
// The literal previous released shape of a stored iframe Contribution. It
|
|
447
|
+
// is written out rather than derived so a change to the v5 decoder that
|
|
448
|
+
// silently stopped migrating it would fail here.
|
|
449
|
+
const releasedV4IframeManifest = () => ({
|
|
450
|
+
schemaVersion: 4,
|
|
451
|
+
id: "weather-page",
|
|
452
|
+
displayName: "Weather page",
|
|
453
|
+
version: "0.0.1",
|
|
454
|
+
compatibility: { frockbot: ">=0.0.1" },
|
|
455
|
+
dependencies: {},
|
|
456
|
+
contributions: {
|
|
457
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
458
|
+
client: {
|
|
459
|
+
kind: "iframe",
|
|
460
|
+
artifact: {
|
|
461
|
+
contentHash: "b".repeat(64),
|
|
462
|
+
size: 42,
|
|
463
|
+
mediaType: "text/html",
|
|
464
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
465
|
+
},
|
|
466
|
+
mounts: [{ slot: "frockbot.tool-result:weather_lookup", order: 3 }],
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
tools: [
|
|
470
|
+
{ name: "weather_lookup", description: "Weather", inputSchema: {} },
|
|
471
|
+
],
|
|
472
|
+
permissions: [],
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("migrates a released v4 single-page iframe record to v5 pages", () => {
|
|
476
|
+
const decoded = decodeFrockBotManifest(releasedV4IframeManifest());
|
|
477
|
+
const client = decoded.contributions.client;
|
|
478
|
+
expect(client && "pages" in client ? client.pages : undefined).toEqual([
|
|
479
|
+
{
|
|
480
|
+
id: "main",
|
|
481
|
+
artifact: {
|
|
482
|
+
contentHash: "b".repeat(64),
|
|
483
|
+
size: 42,
|
|
484
|
+
mediaType: "text/html",
|
|
485
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
486
|
+
},
|
|
487
|
+
mounts: [{ slot: "frockbot.tool-result:weather_lookup", order: 3 }],
|
|
488
|
+
},
|
|
489
|
+
]);
|
|
490
|
+
expect(client && "entries" in client ? client.entries : undefined).toBe(
|
|
491
|
+
undefined,
|
|
492
|
+
);
|
|
493
|
+
// One in-memory shape: the migrated record round-trips as v5.
|
|
494
|
+
const roundTripped = decodeFrockBotManifest({
|
|
495
|
+
...releasedV4IframeManifest(),
|
|
496
|
+
schemaVersion: 5,
|
|
497
|
+
contributions: {
|
|
498
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
499
|
+
client: {
|
|
500
|
+
kind: "iframe",
|
|
501
|
+
pages: client && "pages" in client ? client.pages : [],
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
expect(roundTripped.contributions.client).toEqual(
|
|
506
|
+
decoded.contributions.client!,
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("refuses a v4 record that declares v5 pages or an instance", () => {
|
|
511
|
+
expect(() =>
|
|
512
|
+
decodeFrockBotManifest({
|
|
513
|
+
...releasedV4IframeManifest(),
|
|
514
|
+
contributions: {
|
|
515
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
516
|
+
client: { kind: "iframe", pages: [] },
|
|
517
|
+
},
|
|
518
|
+
}),
|
|
519
|
+
).toThrow("unknown field");
|
|
520
|
+
expect(() =>
|
|
521
|
+
decodeFrockBotManifest({
|
|
522
|
+
...releasedV4IframeManifest(),
|
|
523
|
+
contributions: {
|
|
524
|
+
...releasedV4IframeManifest().contributions,
|
|
525
|
+
instance: {
|
|
526
|
+
contract: 1,
|
|
527
|
+
server: {
|
|
528
|
+
contentHash: "c".repeat(64),
|
|
529
|
+
size: 10,
|
|
530
|
+
mediaType: "application/javascript",
|
|
531
|
+
bundlerVersion: "frockbot-esbuild@1",
|
|
532
|
+
},
|
|
533
|
+
ui: {
|
|
534
|
+
contentHash: "d".repeat(64),
|
|
535
|
+
size: 10,
|
|
536
|
+
mediaType: "text/html",
|
|
537
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
538
|
+
},
|
|
539
|
+
tools: [{ name: "add_todo", description: "Add", inputSchema: {} }],
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
}),
|
|
543
|
+
).toThrow("schema version 5");
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const v5Manifest = () => ({
|
|
547
|
+
schemaVersion: 5,
|
|
548
|
+
id: "applets",
|
|
549
|
+
displayName: "Applets",
|
|
550
|
+
version: "0.0.1",
|
|
551
|
+
compatibility: { frockbot: ">=0.0.1" },
|
|
552
|
+
dependencies: {},
|
|
553
|
+
contributions: {
|
|
554
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
555
|
+
client: {
|
|
556
|
+
kind: "iframe",
|
|
557
|
+
pages: [
|
|
558
|
+
{
|
|
559
|
+
id: "list",
|
|
560
|
+
artifact: {
|
|
561
|
+
contentHash: "a".repeat(64),
|
|
562
|
+
size: 10,
|
|
563
|
+
mediaType: "text/html",
|
|
564
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
565
|
+
},
|
|
566
|
+
mounts: [{ slot: "frockbot.surface:list" }],
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
id: "canvas",
|
|
570
|
+
artifact: {
|
|
571
|
+
contentHash: "b".repeat(64),
|
|
572
|
+
size: 10,
|
|
573
|
+
mediaType: "text/html",
|
|
574
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
575
|
+
},
|
|
576
|
+
mounts: [{ slot: "frockbot.right-panel" }],
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
entries: [
|
|
580
|
+
{
|
|
581
|
+
id: "open",
|
|
582
|
+
slot: "frockbot.sidebar-actions",
|
|
583
|
+
order: 5,
|
|
584
|
+
label: "Applets",
|
|
585
|
+
icon: "applets",
|
|
586
|
+
opens: { kind: "surface", page: "list" },
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
},
|
|
590
|
+
instance: {
|
|
591
|
+
contract: 1,
|
|
592
|
+
server: {
|
|
593
|
+
contentHash: "c".repeat(64),
|
|
594
|
+
size: 10,
|
|
595
|
+
mediaType: "application/javascript",
|
|
596
|
+
bundlerVersion: "frockbot-esbuild@1",
|
|
597
|
+
},
|
|
598
|
+
ui: {
|
|
599
|
+
contentHash: "d".repeat(64),
|
|
600
|
+
size: 10,
|
|
601
|
+
mediaType: "text/html",
|
|
602
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
603
|
+
},
|
|
604
|
+
tools: [{ name: "add_todo", description: "Add", inputSchema: {} }],
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
tools: [{ name: "applet_list", description: "List", inputSchema: {} }],
|
|
608
|
+
permissions: [],
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test("decodes a v5 multi-page client, entries, and Instance Contribution", () => {
|
|
612
|
+
const decoded = decodeFrockBotManifest(v5Manifest());
|
|
613
|
+
expect(decoded.schemaVersion).toBe(5);
|
|
614
|
+
const client = decoded.contributions.client;
|
|
615
|
+
expect(
|
|
616
|
+
client && "pages" in client ? client.pages.map((p) => p.id) : [],
|
|
617
|
+
).toEqual(["list", "canvas"]);
|
|
618
|
+
expect(client && "entries" in client ? client.entries : undefined).toEqual([
|
|
619
|
+
{
|
|
620
|
+
id: "open",
|
|
621
|
+
slot: "frockbot.sidebar-actions",
|
|
622
|
+
order: 5,
|
|
623
|
+
label: "Applets",
|
|
624
|
+
icon: "applets",
|
|
625
|
+
opens: { kind: "surface", page: "list" },
|
|
626
|
+
},
|
|
627
|
+
]);
|
|
628
|
+
expect(decoded.contributions.instance?.contract).toBe(1);
|
|
629
|
+
expect(decoded.contributions.instance?.tools.map((t) => t.name)).toEqual([
|
|
630
|
+
"add_todo",
|
|
631
|
+
]);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test("refuses v5 pages and entries that break their bounds", () => {
|
|
635
|
+
const base = v5Manifest();
|
|
636
|
+
const withPages = (pages: unknown) => ({
|
|
637
|
+
...base,
|
|
638
|
+
contributions: {
|
|
639
|
+
...base.contributions,
|
|
640
|
+
client: { ...base.contributions.client, pages },
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
expect(() => decodeFrockBotManifest(withPages([]))).toThrow(
|
|
644
|
+
"non-empty bounded array",
|
|
645
|
+
);
|
|
646
|
+
expect(() =>
|
|
647
|
+
decodeFrockBotManifest(
|
|
648
|
+
withPages(
|
|
649
|
+
Array.from({ length: 9 }, (_unused, index) => ({
|
|
650
|
+
...base.contributions.client.pages[0]!,
|
|
651
|
+
id: `page-${index}`,
|
|
652
|
+
mounts: [{ slot: "frockbot.right-panel" }],
|
|
653
|
+
})),
|
|
654
|
+
),
|
|
655
|
+
),
|
|
656
|
+
).toThrow("non-empty bounded array");
|
|
657
|
+
expect(() =>
|
|
658
|
+
decodeFrockBotManifest(
|
|
659
|
+
withPages([
|
|
660
|
+
base.contributions.client.pages[0]!,
|
|
661
|
+
{ ...base.contributions.client.pages[1]!, id: "list" },
|
|
662
|
+
]),
|
|
663
|
+
),
|
|
664
|
+
).toThrow("duplicate ids");
|
|
665
|
+
expect(() =>
|
|
666
|
+
decodeFrockBotManifest(
|
|
667
|
+
withPages([
|
|
668
|
+
{ ...base.contributions.client.pages[0]!, id: "List" },
|
|
669
|
+
base.contributions.client.pages[1]!,
|
|
670
|
+
]),
|
|
671
|
+
),
|
|
672
|
+
).toThrow("id is invalid");
|
|
673
|
+
expect(() =>
|
|
674
|
+
decodeFrockBotManifest(
|
|
675
|
+
withPages([
|
|
676
|
+
{
|
|
677
|
+
...base.contributions.client.pages[0]!,
|
|
678
|
+
mounts: [{ slot: "frockbot.surface:missing" }],
|
|
679
|
+
},
|
|
680
|
+
base.contributions.client.pages[1]!,
|
|
681
|
+
]),
|
|
682
|
+
),
|
|
683
|
+
).toThrow("not iframe-safe");
|
|
684
|
+
const withEntries = (entries: unknown) => ({
|
|
685
|
+
...base,
|
|
686
|
+
contributions: {
|
|
687
|
+
...base.contributions,
|
|
688
|
+
client: { ...base.contributions.client, entries },
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
expect(() =>
|
|
692
|
+
decodeFrockBotManifest(
|
|
693
|
+
withEntries([
|
|
694
|
+
{
|
|
695
|
+
...base.contributions.client.entries[0]!,
|
|
696
|
+
opens: { kind: "surface", page: "canvas" },
|
|
697
|
+
},
|
|
698
|
+
]),
|
|
699
|
+
),
|
|
700
|
+
).toThrow("declares no surface mount");
|
|
701
|
+
expect(() =>
|
|
702
|
+
decodeFrockBotManifest(
|
|
703
|
+
withEntries([
|
|
704
|
+
{
|
|
705
|
+
...base.contributions.client.entries[0]!,
|
|
706
|
+
label: "x".repeat(33),
|
|
707
|
+
},
|
|
708
|
+
]),
|
|
709
|
+
),
|
|
710
|
+
).toThrow("label is too long");
|
|
711
|
+
expect(() =>
|
|
712
|
+
decodeFrockBotManifest(
|
|
713
|
+
withEntries([
|
|
714
|
+
{ ...base.contributions.client.entries[0]!, slot: "frockbot.root" },
|
|
715
|
+
]),
|
|
716
|
+
),
|
|
717
|
+
).toThrow("slot is invalid");
|
|
718
|
+
expect(() =>
|
|
719
|
+
decodeFrockBotManifest(
|
|
720
|
+
withEntries(
|
|
721
|
+
Array.from({ length: 5 }, (_unused, index) => ({
|
|
722
|
+
...base.contributions.client.entries[0]!,
|
|
723
|
+
id: `open-${index}`,
|
|
724
|
+
})),
|
|
725
|
+
),
|
|
726
|
+
),
|
|
727
|
+
).toThrow("bounded array");
|
|
728
|
+
});
|
|
729
|
+
|
|
376
730
|
test("keeps trusted Electron main execution exclusive to manifest v3", () => {
|
|
377
731
|
const contribution = {
|
|
378
732
|
desktop: {
|
|
@@ -761,7 +1115,7 @@ describe("decodeFrockBotManifest", () => {
|
|
|
761
1115
|
test("rejects an unsupported manifest version", () => {
|
|
762
1116
|
expect(() =>
|
|
763
1117
|
decodeFrockBotManifest({
|
|
764
|
-
schemaVersion:
|
|
1118
|
+
schemaVersion: 6,
|
|
765
1119
|
id: "future",
|
|
766
1120
|
displayName: "Future",
|
|
767
1121
|
version: "1.0.0",
|
package/src/isolate-host.test.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
IsolateToolInvocationV1,
|
|
11
11
|
ToolDefinition,
|
|
12
12
|
ToolExecutionContext,
|
|
13
|
+
ToolNamespaceRegistration,
|
|
13
14
|
} from "@frockbot/kernel-contracts";
|
|
14
15
|
import type { PackageDescriptor } from "./index.ts";
|
|
15
16
|
import {
|
|
@@ -119,6 +120,7 @@ function host(
|
|
|
119
120
|
) {
|
|
120
121
|
const loads: RecordedLoad[] = [];
|
|
121
122
|
const registered: ToolDefinition[] = [];
|
|
123
|
+
const namespaces: ToolNamespaceRegistration[] = [];
|
|
122
124
|
const ceilings: (readonly TurnTypeV1[] | undefined)[] = [];
|
|
123
125
|
const { entrypoint, ...rest } = overrides;
|
|
124
126
|
const options: BotIsolateHostOptions = {
|
|
@@ -130,6 +132,13 @@ function host(
|
|
|
130
132
|
loadPackageArtifact: () => Promise.resolve("export const tools = [];"),
|
|
131
133
|
},
|
|
132
134
|
tools: {
|
|
135
|
+
registerNamespace: (namespace) => {
|
|
136
|
+
namespaces.push(namespace);
|
|
137
|
+
return () => {
|
|
138
|
+
const index = namespaces.indexOf(namespace);
|
|
139
|
+
if (index >= 0) namespaces.splice(index, 1);
|
|
140
|
+
};
|
|
141
|
+
},
|
|
133
142
|
register: (definition, registration) => {
|
|
134
143
|
registered.push(definition);
|
|
135
144
|
ceilings.push(registration?.admissionCeiling);
|
|
@@ -157,6 +166,7 @@ function host(
|
|
|
157
166
|
host: new BotIsolateContributionHost(options),
|
|
158
167
|
loads,
|
|
159
168
|
registered,
|
|
169
|
+
namespaces,
|
|
160
170
|
ceilings,
|
|
161
171
|
};
|
|
162
172
|
}
|
|
@@ -562,7 +572,11 @@ describe("Bot isolate contribution host", () => {
|
|
|
562
572
|
|
|
563
573
|
test("registers one tool per health entry and executes it over RPC", async () => {
|
|
564
574
|
let seen: IsolateToolInvocationV1 | undefined;
|
|
565
|
-
const {
|
|
575
|
+
const {
|
|
576
|
+
host: subject,
|
|
577
|
+
registered,
|
|
578
|
+
namespaces,
|
|
579
|
+
} = host({
|
|
566
580
|
entrypoint: {
|
|
567
581
|
health: () => Promise.resolve(healthy()),
|
|
568
582
|
execute: (invocation) => {
|
|
@@ -579,7 +593,15 @@ describe("Bot isolate contribution host", () => {
|
|
|
579
593
|
const active = await prepared!.commit();
|
|
580
594
|
expect(registered).toHaveLength(1);
|
|
581
595
|
expect(registered[0]!.name).toBe("reverse_text");
|
|
596
|
+
expect(registered[0]!.namespace).toBe("bot-authored");
|
|
582
597
|
expect(registered[0]!.idempotent).toBe(true);
|
|
598
|
+
expect(namespaces).toEqual([
|
|
599
|
+
{
|
|
600
|
+
name: "bot-authored",
|
|
601
|
+
external: true,
|
|
602
|
+
status: "ready",
|
|
603
|
+
},
|
|
604
|
+
]);
|
|
583
605
|
|
|
584
606
|
const result = await registered[0]!.execute(
|
|
585
607
|
{ text: "ab" },
|
|
@@ -591,6 +613,7 @@ describe("Bot isolate contribution host", () => {
|
|
|
591
613
|
|
|
592
614
|
await active.dispose();
|
|
593
615
|
expect(registered).toHaveLength(0);
|
|
616
|
+
expect(namespaces).toHaveLength(0);
|
|
594
617
|
});
|
|
595
618
|
|
|
596
619
|
test("an undecodable isolate result is a tool error, not a throw", async () => {
|
package/src/isolate-host.ts
CHANGED
|
@@ -91,7 +91,7 @@ export interface BotIsolateHostOptions {
|
|
|
91
91
|
loader: BotIsolateLoader;
|
|
92
92
|
artifacts: BotIsolateArtifactStore;
|
|
93
93
|
/** Where the isolate's tools are registered — the kernel's tool surface. */
|
|
94
|
-
tools: Pick<ToolRegistration, "register">;
|
|
94
|
+
tools: Pick<ToolRegistration, "register" | "registerNamespace">;
|
|
95
95
|
/** The mounted Bot/generation root. Isolate hook listeners live only here. */
|
|
96
96
|
loop: Context;
|
|
97
97
|
userId: string;
|
|
@@ -370,6 +370,13 @@ export class BotIsolateContributionHost implements ContributionHost {
|
|
|
370
370
|
...(subagentRoleCeiling ? { subagentRoleCeiling } : {}),
|
|
371
371
|
}
|
|
372
372
|
: undefined;
|
|
373
|
+
registered.push(
|
|
374
|
+
this.options.tools.registerNamespace({
|
|
375
|
+
name: packageId,
|
|
376
|
+
external: true,
|
|
377
|
+
status: "ready",
|
|
378
|
+
}),
|
|
379
|
+
);
|
|
373
380
|
for (const descriptor of health.tools) {
|
|
374
381
|
registered.push(
|
|
375
382
|
this.options.tools.register(
|
|
@@ -663,6 +670,7 @@ export class BotIsolateContributionHost implements ContributionHost {
|
|
|
663
670
|
const options = this.options;
|
|
664
671
|
return {
|
|
665
672
|
...isolateToolSchemaV1(descriptor),
|
|
673
|
+
namespace: packageId,
|
|
666
674
|
idempotent: descriptor.idempotent,
|
|
667
675
|
// A contract v1 isolate declares none, and its tools stay on every turn.
|
|
668
676
|
...(descriptor.admission ? { admission: descriptor.admission } : {}),
|
package/src/isolate-wrapper.ts
CHANGED
|
@@ -200,6 +200,33 @@ const BOT_ISOLATE_CONTEXT_PROPERTY_SOURCE_V1 = {
|
|
|
200
200
|
return capabilities.workspaceDelete(request);
|
|
201
201
|
},
|
|
202
202
|
}`,
|
|
203
|
+
applets: `{
|
|
204
|
+
list: function () {
|
|
205
|
+
return capabilities.applets({ op: "list" });
|
|
206
|
+
},
|
|
207
|
+
create: function (input) {
|
|
208
|
+
return capabilities.applets({ op: "create", displayName: input.displayName });
|
|
209
|
+
},
|
|
210
|
+
publish: function (input) {
|
|
211
|
+
return capabilities.applets({ op: "publish", appletId: input.appletId });
|
|
212
|
+
},
|
|
213
|
+
revert: function (input) {
|
|
214
|
+
return capabilities.applets({
|
|
215
|
+
op: "revert",
|
|
216
|
+
appletId: input.appletId,
|
|
217
|
+
generationId: input.generationId,
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
delete: function (input) {
|
|
221
|
+
return capabilities.applets({ op: "delete", appletId: input.appletId });
|
|
222
|
+
},
|
|
223
|
+
focus: function (input) {
|
|
224
|
+
return capabilities.applets({ op: "focus", appletId: input.appletId });
|
|
225
|
+
},
|
|
226
|
+
generations: function (input) {
|
|
227
|
+
return capabilities.applets({ op: "generations", appletId: input.appletId });
|
|
228
|
+
},
|
|
229
|
+
}`,
|
|
203
230
|
connection:
|
|
204
231
|
"function (connectionId) { return capabilities.connection(connectionId); }",
|
|
205
232
|
notify: "function (request) { return capabilities.notify(request); }",
|
|
@@ -354,7 +381,7 @@ export default class extends WorkerEntrypoint {
|
|
|
354
381
|
`;
|
|
355
382
|
|
|
356
383
|
/** Bumped with any change to the wrapper text; folded into the loader id. */
|
|
357
|
-
export const BOT_ISOLATE_WRAPPER_VERSION = "wrapper-
|
|
384
|
+
export const BOT_ISOLATE_WRAPPER_VERSION = "wrapper-v5";
|
|
358
385
|
|
|
359
386
|
export const BOT_ISOLATE_MAIN_MODULE = "index.js";
|
|
360
387
|
export const BOT_ISOLATE_PACKAGE_MODULE = "package.js";
|