@frockbot/kernel-composition 0.1.4 → 0.2.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/package.json +2 -2
- package/src/compiler.ts +8 -2
- package/src/generation.test.ts +55 -0
- package/src/generation.ts +111 -7
- package/src/index.test.ts +142 -0
- package/src/isolate-host.test.ts +341 -53
- package/src/isolate-host.ts +308 -26
- package/src/isolate-wrapper.test.ts +35 -0
- package/src/isolate-wrapper.ts +155 -32
- package/src/manifest.ts +271 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/kernel-composition",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"cordis": "4.0.0-rc.8",
|
|
22
22
|
"semver": "7.8.5",
|
|
23
|
-
"@frockbot/kernel-contracts": "0.
|
|
23
|
+
"@frockbot/kernel-contracts": "0.2.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "1.4.0",
|
package/src/compiler.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
decodeFrockBotManifest,
|
|
3
|
+
isClientIframeContribution,
|
|
4
|
+
type FrockBotManifest,
|
|
5
|
+
} from "./manifest.ts";
|
|
2
6
|
import { satisfies, valid } from "semver";
|
|
3
7
|
|
|
4
8
|
export type JsonValue =
|
|
@@ -166,7 +170,9 @@ function orderedPackages(
|
|
|
166
170
|
function validateClientComposition(packages: readonly CompiledPackage[]): void {
|
|
167
171
|
const clients = packages.flatMap((pkg) => {
|
|
168
172
|
const client = pkg.manifest.contributions.client;
|
|
169
|
-
return client
|
|
173
|
+
return client && !isClientIframeContribution(client)
|
|
174
|
+
? [{ id: pkg.id, client }]
|
|
175
|
+
: [];
|
|
170
176
|
});
|
|
171
177
|
const roots = clients.flatMap(({ id, client }) =>
|
|
172
178
|
client.mounts.filter((mount) => mount.slot === "root").map(() => id),
|
package/src/generation.test.ts
CHANGED
|
@@ -200,4 +200,59 @@ describe("Composition generation v1", () => {
|
|
|
200
200
|
}),
|
|
201
201
|
).toThrow("mediaType is invalid");
|
|
202
202
|
});
|
|
203
|
+
|
|
204
|
+
test("decodes a Catalog isolate member and its plain-language generation summary", async () => {
|
|
205
|
+
const generation = await bootstrap();
|
|
206
|
+
const member = {
|
|
207
|
+
packageId: "parcel-tracking",
|
|
208
|
+
specifier: "catalog:parcel-tracking",
|
|
209
|
+
version: "0.0.1",
|
|
210
|
+
manifestHash: "c".repeat(64),
|
|
211
|
+
provenance: {
|
|
212
|
+
kind: "catalog" as const,
|
|
213
|
+
packageId: "parcel-tracking",
|
|
214
|
+
version: "0.0.1",
|
|
215
|
+
catalogId: "parcel-tracking",
|
|
216
|
+
catalogGeneration: "catalog-1",
|
|
217
|
+
contentHash: "d".repeat(64),
|
|
218
|
+
},
|
|
219
|
+
artifact: {
|
|
220
|
+
contentHash: "d".repeat(64),
|
|
221
|
+
size: 512,
|
|
222
|
+
mediaType: "application/javascript" as const,
|
|
223
|
+
bundlerVersion: "catalog-test@1",
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
const decoded = decodeCompositionGenerationV1({
|
|
227
|
+
...generation,
|
|
228
|
+
members: [member],
|
|
229
|
+
artifactSetHash: await compositionArtifactSetHashV1([member]),
|
|
230
|
+
origin: {
|
|
231
|
+
kind: "bot-catalog",
|
|
232
|
+
action: "install",
|
|
233
|
+
packageId: "parcel-tracking",
|
|
234
|
+
catalogId: "parcel-tracking",
|
|
235
|
+
botId: "primary",
|
|
236
|
+
runId: "run-1",
|
|
237
|
+
sessionId: "user-1:primary",
|
|
238
|
+
turnId: "turn-1",
|
|
239
|
+
},
|
|
240
|
+
summary: "Added parcel tracking",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(decoded.summary).toBe("Added parcel tracking");
|
|
244
|
+
expect(decoded.members[0]?.provenance.kind).toBe("catalog");
|
|
245
|
+
expect(() =>
|
|
246
|
+
decodeCompositionGenerationV1({
|
|
247
|
+
...decoded,
|
|
248
|
+
members: [{ ...member, artifact: undefined }],
|
|
249
|
+
}),
|
|
250
|
+
).toThrow("must match its Bot-isolate artifact");
|
|
251
|
+
expect(() =>
|
|
252
|
+
decodeCompositionGenerationV1({
|
|
253
|
+
...decoded,
|
|
254
|
+
summary: "Added\nparcel tracking",
|
|
255
|
+
}),
|
|
256
|
+
).toThrow("must be one trimmed line");
|
|
257
|
+
});
|
|
203
258
|
});
|
package/src/generation.ts
CHANGED
|
@@ -7,6 +7,14 @@ import { canonicalJson, sha256 } from "./compiler.ts";
|
|
|
7
7
|
|
|
8
8
|
export type PackageProvenanceV1 =
|
|
9
9
|
| { kind: "first-party"; packageId: string; version: string }
|
|
10
|
+
| {
|
|
11
|
+
kind: "catalog";
|
|
12
|
+
packageId: string;
|
|
13
|
+
version: string;
|
|
14
|
+
catalogId: string;
|
|
15
|
+
catalogGeneration: string;
|
|
16
|
+
contentHash: string;
|
|
17
|
+
}
|
|
10
18
|
| {
|
|
11
19
|
kind: "user";
|
|
12
20
|
packageId: string;
|
|
@@ -46,8 +54,25 @@ export interface CompositionMemberV1 {
|
|
|
46
54
|
export type CompositionOriginV1 =
|
|
47
55
|
| { kind: "bootstrap" }
|
|
48
56
|
| { kind: "bot-authored"; runId: string; sessionId: string; turnId: string }
|
|
57
|
+
| {
|
|
58
|
+
kind: "bot-catalog";
|
|
59
|
+
action: "install" | "update" | "remove";
|
|
60
|
+
packageId: string;
|
|
61
|
+
catalogId: string;
|
|
62
|
+
botId: string;
|
|
63
|
+
runId: string;
|
|
64
|
+
sessionId: string;
|
|
65
|
+
turnId: string;
|
|
66
|
+
}
|
|
49
67
|
| { kind: "user-install"; userId: string }
|
|
50
|
-
| { kind: "revert"; revertsTo: string; userId: string }
|
|
68
|
+
| { kind: "revert"; revertsTo: string; userId: string }
|
|
69
|
+
| {
|
|
70
|
+
kind: "revert";
|
|
71
|
+
revertsTo: string;
|
|
72
|
+
botId: string;
|
|
73
|
+
runId: string;
|
|
74
|
+
turnId: string;
|
|
75
|
+
};
|
|
51
76
|
|
|
52
77
|
export type CompositionGenerationStatusV1 =
|
|
53
78
|
"pending" | "active" | "superseded" | "failed" | "quarantined";
|
|
@@ -59,6 +84,8 @@ export interface CompositionGenerationV1 {
|
|
|
59
84
|
/** sha-256 over the canonical member list — the loader identity. */
|
|
60
85
|
artifactSetHash: string;
|
|
61
86
|
parentGenerationId?: string;
|
|
87
|
+
/** Bot-written one-line audit copy for a setup change. */
|
|
88
|
+
summary?: string;
|
|
62
89
|
createdAt: string;
|
|
63
90
|
origin: CompositionOriginV1;
|
|
64
91
|
members: CompositionMemberV1[];
|
|
@@ -83,6 +110,7 @@ export interface CompositionStore {
|
|
|
83
110
|
revert(
|
|
84
111
|
toGenerationId: string,
|
|
85
112
|
origin: Extract<CompositionOriginV1, { kind: "revert" }>,
|
|
113
|
+
options?: { createdAt?: string },
|
|
86
114
|
): Promise<CompositionGenerationV1>;
|
|
87
115
|
list(query: {
|
|
88
116
|
limit: number;
|
|
@@ -115,7 +143,7 @@ const GENERATION_REQUIRED_KEYS = [
|
|
|
115
143
|
"members",
|
|
116
144
|
"status",
|
|
117
145
|
] as const;
|
|
118
|
-
const GENERATION_OPTIONAL_KEYS = ["parentGenerationId"] as const;
|
|
146
|
+
const GENERATION_OPTIONAL_KEYS = ["parentGenerationId", "summary"] as const;
|
|
119
147
|
const MEMBER_REQUIRED_KEYS = [
|
|
120
148
|
"packageId",
|
|
121
149
|
"specifier",
|
|
@@ -132,6 +160,7 @@ const ARTIFACT_KEYS = [
|
|
|
132
160
|
] as const;
|
|
133
161
|
const MAX_COMPOSITION_MEMBERS = 512;
|
|
134
162
|
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
163
|
+
export const MAX_COMPOSITION_SUMMARY_V1 = 160;
|
|
135
164
|
|
|
136
165
|
function record(value: unknown, label: string): Record<string, unknown> {
|
|
137
166
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -195,6 +224,17 @@ function decodePackageProvenanceV1(
|
|
|
195
224
|
if (kind === "first-party") {
|
|
196
225
|
exactKeys(value, common, [], label);
|
|
197
226
|
identity();
|
|
227
|
+
} else if (kind === "catalog") {
|
|
228
|
+
exactKeys(
|
|
229
|
+
value,
|
|
230
|
+
[...common, "catalogId", "catalogGeneration", "contentHash"],
|
|
231
|
+
[],
|
|
232
|
+
label,
|
|
233
|
+
);
|
|
234
|
+
identity();
|
|
235
|
+
boundedString(value.catalogId, `${label}.catalogId`, 64);
|
|
236
|
+
boundedString(value.catalogGeneration, `${label}.catalogGeneration`, 64);
|
|
237
|
+
hashString(value.contentHash, `${label}.contentHash`);
|
|
198
238
|
} else if (kind === "user") {
|
|
199
239
|
exactKeys(value, [...common, "userId", "authoredAt"], [], label);
|
|
200
240
|
identity();
|
|
@@ -251,15 +291,25 @@ function decodeCompositionMemberV1(
|
|
|
251
291
|
if (provenance.packageId !== packageId || provenance.version !== version) {
|
|
252
292
|
throw new Error(`${label}.provenance does not match its member`);
|
|
253
293
|
}
|
|
294
|
+
const artifact =
|
|
295
|
+
value.artifact === undefined
|
|
296
|
+
? undefined
|
|
297
|
+
: decodeArtifactRefV1(value.artifact, `${label}.artifact`);
|
|
298
|
+
if (
|
|
299
|
+
provenance.kind === "catalog" &&
|
|
300
|
+
(!artifact || artifact.contentHash !== provenance.contentHash)
|
|
301
|
+
) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
`${label}.catalog provenance must match its Bot-isolate artifact`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
254
306
|
return {
|
|
255
307
|
packageId,
|
|
256
308
|
specifier,
|
|
257
309
|
version,
|
|
258
310
|
manifestHash,
|
|
259
311
|
provenance,
|
|
260
|
-
...(
|
|
261
|
-
? {}
|
|
262
|
-
: { artifact: decodeArtifactRefV1(value.artifact, `${label}.artifact`) }),
|
|
312
|
+
...(artifact === undefined ? {} : { artifact }),
|
|
263
313
|
};
|
|
264
314
|
}
|
|
265
315
|
|
|
@@ -276,13 +326,54 @@ function decodeCompositionOriginV1(
|
|
|
276
326
|
boundedString(value.runId, `${label}.runId`, 128);
|
|
277
327
|
boundedString(value.sessionId, `${label}.sessionId`, 257);
|
|
278
328
|
boundedString(value.turnId, `${label}.turnId`, 128);
|
|
329
|
+
} else if (kind === "bot-catalog") {
|
|
330
|
+
exactKeys(
|
|
331
|
+
value,
|
|
332
|
+
[
|
|
333
|
+
"kind",
|
|
334
|
+
"action",
|
|
335
|
+
"packageId",
|
|
336
|
+
"catalogId",
|
|
337
|
+
"botId",
|
|
338
|
+
"runId",
|
|
339
|
+
"sessionId",
|
|
340
|
+
"turnId",
|
|
341
|
+
],
|
|
342
|
+
[],
|
|
343
|
+
label,
|
|
344
|
+
);
|
|
345
|
+
if (
|
|
346
|
+
value.action !== "install" &&
|
|
347
|
+
value.action !== "update" &&
|
|
348
|
+
value.action !== "remove"
|
|
349
|
+
) {
|
|
350
|
+
throw new Error(`${label}.action is invalid`);
|
|
351
|
+
}
|
|
352
|
+
boundedString(value.packageId, `${label}.packageId`, 128);
|
|
353
|
+
boundedString(value.catalogId, `${label}.catalogId`, 64);
|
|
354
|
+
boundedString(value.botId, `${label}.botId`, 256);
|
|
355
|
+
boundedString(value.runId, `${label}.runId`, 128);
|
|
356
|
+
boundedString(value.sessionId, `${label}.sessionId`, 257);
|
|
357
|
+
boundedString(value.turnId, `${label}.turnId`, 128);
|
|
279
358
|
} else if (kind === "user-install") {
|
|
280
359
|
exactKeys(value, ["kind", "userId"], [], label);
|
|
281
360
|
boundedString(value.userId, `${label}.userId`, 256);
|
|
282
361
|
} else if (kind === "revert") {
|
|
283
|
-
exactKeys(value, ["kind", "revertsTo", "userId"], [], label);
|
|
284
362
|
boundedString(value.revertsTo, `${label}.revertsTo`, 256);
|
|
285
|
-
|
|
363
|
+
if (Object.hasOwn(value, "userId")) {
|
|
364
|
+
exactKeys(value, ["kind", "revertsTo", "userId"], [], label);
|
|
365
|
+
boundedString(value.userId, `${label}.userId`, 256);
|
|
366
|
+
} else {
|
|
367
|
+
exactKeys(
|
|
368
|
+
value,
|
|
369
|
+
["kind", "revertsTo", "botId", "runId", "turnId"],
|
|
370
|
+
[],
|
|
371
|
+
label,
|
|
372
|
+
);
|
|
373
|
+
boundedString(value.botId, `${label}.botId`, 256);
|
|
374
|
+
boundedString(value.runId, `${label}.runId`, 128);
|
|
375
|
+
boundedString(value.turnId, `${label}.turnId`, 128);
|
|
376
|
+
}
|
|
286
377
|
} else {
|
|
287
378
|
throw new Error(`${label}.kind is invalid`);
|
|
288
379
|
}
|
|
@@ -331,6 +422,16 @@ export function decodeCompositionGenerationV1(
|
|
|
331
422
|
if (value.parentGenerationId !== undefined) {
|
|
332
423
|
boundedString(value.parentGenerationId, `${label}.parentGenerationId`, 256);
|
|
333
424
|
}
|
|
425
|
+
if (value.summary !== undefined) {
|
|
426
|
+
const summary = boundedString(
|
|
427
|
+
value.summary,
|
|
428
|
+
`${label}.summary`,
|
|
429
|
+
MAX_COMPOSITION_SUMMARY_V1,
|
|
430
|
+
);
|
|
431
|
+
if (summary.trim() !== summary || /[\r\n]/u.test(summary)) {
|
|
432
|
+
throw new Error(`${label}.summary must be one trimmed line`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
334
435
|
return {
|
|
335
436
|
schemaVersion: 1,
|
|
336
437
|
generationId,
|
|
@@ -342,6 +443,9 @@ export function decodeCompositionGenerationV1(
|
|
|
342
443
|
...(value.parentGenerationId === undefined
|
|
343
444
|
? {}
|
|
344
445
|
: { parentGenerationId: value.parentGenerationId as string }),
|
|
446
|
+
...(value.summary === undefined
|
|
447
|
+
? {}
|
|
448
|
+
: { summary: value.summary as string }),
|
|
345
449
|
};
|
|
346
450
|
}
|
|
347
451
|
|
package/src/index.test.ts
CHANGED
|
@@ -295,6 +295,84 @@ describe("PackageCatalog", () => {
|
|
|
295
295
|
});
|
|
296
296
|
|
|
297
297
|
describe("decodeFrockBotManifest", () => {
|
|
298
|
+
test("accepts iframe UI only in settings or its own declared tool-result slots", () => {
|
|
299
|
+
const manifest = {
|
|
300
|
+
schemaVersion: 3,
|
|
301
|
+
id: "weather-page",
|
|
302
|
+
displayName: "Weather page",
|
|
303
|
+
version: "0.0.1",
|
|
304
|
+
compatibility: { frockbot: ">=0.0.1" },
|
|
305
|
+
dependencies: {},
|
|
306
|
+
contributions: {
|
|
307
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
308
|
+
client: {
|
|
309
|
+
kind: "iframe",
|
|
310
|
+
artifact: {
|
|
311
|
+
contentHash: "a".repeat(64),
|
|
312
|
+
size: 123,
|
|
313
|
+
mediaType: "text/html",
|
|
314
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
315
|
+
},
|
|
316
|
+
mounts: [
|
|
317
|
+
{ slot: "frockbot.tool-result:weather_lookup" },
|
|
318
|
+
{ slot: "frockbot.bot-settings-sections", order: 10 },
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
tools: [
|
|
323
|
+
{ name: "weather_lookup", description: "Weather", inputSchema: {} },
|
|
324
|
+
],
|
|
325
|
+
hooks: ["agent/tool-exposure"],
|
|
326
|
+
permissions: [],
|
|
327
|
+
};
|
|
328
|
+
const decoded = decodeFrockBotManifest(manifest);
|
|
329
|
+
const client = decoded.contributions.client;
|
|
330
|
+
expect(client && "kind" in client ? client.kind : undefined).toBe("iframe");
|
|
331
|
+
expect(client?.mounts[0]).toEqual({
|
|
332
|
+
slot: "frockbot.tool-result:weather_lookup",
|
|
333
|
+
});
|
|
334
|
+
expect(decoded.hooks).toEqual(["agent/tool-exposure"]);
|
|
335
|
+
expect(() =>
|
|
336
|
+
decodeFrockBotManifest({
|
|
337
|
+
...manifest,
|
|
338
|
+
contributions: {
|
|
339
|
+
...manifest.contributions,
|
|
340
|
+
client: {
|
|
341
|
+
...manifest.contributions.client,
|
|
342
|
+
mounts: [{ slot: "root" }],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
).toThrow("not iframe-safe");
|
|
347
|
+
expect(() =>
|
|
348
|
+
decodeFrockBotManifest({
|
|
349
|
+
...manifest,
|
|
350
|
+
contributions: {
|
|
351
|
+
...manifest.contributions,
|
|
352
|
+
client: {
|
|
353
|
+
...manifest.contributions.client,
|
|
354
|
+
mounts: [{ slot: "frockbot.tool-result:package_author" }],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
}),
|
|
358
|
+
).toThrow("undeclared tool");
|
|
359
|
+
expect(() =>
|
|
360
|
+
decodeFrockBotManifest({
|
|
361
|
+
...manifest,
|
|
362
|
+
contributions: {
|
|
363
|
+
...manifest.contributions,
|
|
364
|
+
client: {
|
|
365
|
+
...manifest.contributions.client,
|
|
366
|
+
artifact: {
|
|
367
|
+
...manifest.contributions.client.artifact,
|
|
368
|
+
size: 256 * 1024 + 1,
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
373
|
+
).toThrow("256 KB quota");
|
|
374
|
+
});
|
|
375
|
+
|
|
298
376
|
test("keeps trusted Electron main execution exclusive to manifest v3", () => {
|
|
299
377
|
const contribution = {
|
|
300
378
|
desktop: {
|
|
@@ -1127,6 +1205,70 @@ describe("decodeFrockBotManifest", () => {
|
|
|
1127
1205
|
).toThrow("manifest setting schema is too large");
|
|
1128
1206
|
});
|
|
1129
1207
|
|
|
1208
|
+
test("decodes the exact Bot isolate runtime and tool declaration", () => {
|
|
1209
|
+
const decoded = decodeFrockBotManifest({
|
|
1210
|
+
schemaVersion: 3,
|
|
1211
|
+
id: "authored",
|
|
1212
|
+
displayName: "Authored",
|
|
1213
|
+
version: "0.0.1",
|
|
1214
|
+
compatibility: { frockbot: ">=0.0.1" },
|
|
1215
|
+
dependencies: {},
|
|
1216
|
+
contributions: {
|
|
1217
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
1218
|
+
},
|
|
1219
|
+
tools: [
|
|
1220
|
+
{
|
|
1221
|
+
name: "look_up",
|
|
1222
|
+
description: "Looks up a value",
|
|
1223
|
+
inputSchema: { type: "object" },
|
|
1224
|
+
},
|
|
1225
|
+
],
|
|
1226
|
+
hooks: ["agent/tool-exposure", "tools/post-execute"],
|
|
1227
|
+
permissions: [],
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
expect(decoded.contributions.runtime?.host).toBe("bot-isolate");
|
|
1231
|
+
expect(decoded.tools?.map((tool) => tool.name)).toEqual(["look_up"]);
|
|
1232
|
+
expect(decoded.hooks).toEqual([
|
|
1233
|
+
"agent/tool-exposure",
|
|
1234
|
+
"tools/post-execute",
|
|
1235
|
+
]);
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
test("requires Bot isolate runtime and tools declarations together", () => {
|
|
1239
|
+
const base = {
|
|
1240
|
+
schemaVersion: 3,
|
|
1241
|
+
id: "authored",
|
|
1242
|
+
displayName: "Authored",
|
|
1243
|
+
version: "0.0.1",
|
|
1244
|
+
compatibility: { frockbot: ">=0.0.1" },
|
|
1245
|
+
dependencies: {},
|
|
1246
|
+
permissions: [],
|
|
1247
|
+
};
|
|
1248
|
+
expect(() =>
|
|
1249
|
+
decodeFrockBotManifest({
|
|
1250
|
+
...base,
|
|
1251
|
+
contributions: {
|
|
1252
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
1253
|
+
},
|
|
1254
|
+
}),
|
|
1255
|
+
).toThrow(/must appear together/);
|
|
1256
|
+
expect(() =>
|
|
1257
|
+
decodeFrockBotManifest({
|
|
1258
|
+
...base,
|
|
1259
|
+
contributions: { runtime: { entry: "./package.js" } },
|
|
1260
|
+
hooks: ["agent/tool-exposure"],
|
|
1261
|
+
}),
|
|
1262
|
+
).toThrow(/hooks require a bot-isolate/);
|
|
1263
|
+
expect(() =>
|
|
1264
|
+
decodeFrockBotManifest({
|
|
1265
|
+
...base,
|
|
1266
|
+
contributions: { runtime: { entry: "./package.js" } },
|
|
1267
|
+
tools: [{ name: "look_up", description: "Looks", inputSchema: {} }],
|
|
1268
|
+
}),
|
|
1269
|
+
).toThrow(/must appear together/);
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1130
1272
|
test("orders normalized contribution kinds", () => {
|
|
1131
1273
|
const decoded = decodeFrockBotManifest({
|
|
1132
1274
|
schemaVersion: 3,
|