@frockbot/kernel-contracts 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 +1 -1
- package/src/authoring.ts +64 -0
- package/src/catalog-change.test.ts +56 -0
- package/src/iframe-ui.test.ts +99 -0
- package/src/iframe-ui.ts +400 -0
- package/src/index.ts +3 -0
- package/src/isolate-context-catalog.generated.ts +25 -0
- package/src/isolate.test.ts +156 -40
- package/src/isolate.ts +685 -94
- package/src/loop-events.test.ts +80 -0
- package/src/loop-events.ts +711 -0
- package/src/send-to-user.ts +7 -56
- package/src/session.test.ts +16 -0
- package/src/tool-execution.ts +16 -0
- package/src/types.ts +229 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Generated by scripts/generate-isolate-context-catalog.ts. Do not edit.
|
|
2
|
+
export const BOT_ISOLATE_CONTEXT_DTS_V1 =
|
|
3
|
+
"/**\n * The common exact `ctx` passed to a Bot-authored Package's tool and hook.\n * The model-facing declarations are generated from these interfaces, and the\n * wrapper's implementation is compile- and test-checked against the same\n * keys.\n */\ninterface BotPackageContextV1 {\n readonly tool?: string;\n readonly event?: BotIsolateHookEventNameV1;\n readonly botId: string;\n readonly sessionId: string;\n readonly runId: string;\n readonly turnId: string;\n readonly generationId: string;\n readonly packageId: string;\n readonly deadlineMs: number;\n readonly bindings: string[];\n readonly capabilities: {\n list(): Promise<IsolateCapabilityListOutcomeV1>;\n };\n readonly model: {\n invoke(request: NormalizedModelRequest): Promise<BotPackageModelOutcomeV1>;\n };\n readonly tools: {\n /** Runs through the trusted registry's active-Composition and deny guards. */\n invoke(request: IsolateToolRequestV1): Promise<IsolateToolOutcomeV1>;\n };\n readonly memory: {\n read(request: IsolateMemoryReadRequestV1): Promise<IsolateMemoryOutcomeV1>;\n write(request: IsolateMemoryWriteRequestV1): Promise<IsolateMemoryOutcomeV1>;\n forget(request: IsolateMemoryWriteRequestV1): Promise<IsolateMemoryOutcomeV1>;\n };\n readonly workspace: {\n read(path: IsolateWorkspacePathV1): Promise<IsolateWorkspaceOutcomeV1>;\n list(request: IsolateWorkspaceListRequestV1): Promise<IsolateWorkspaceOutcomeV1>;\n stat(path: IsolateWorkspacePathV1): Promise<IsolateWorkspaceOutcomeV1>;\n write(request: IsolateWorkspaceWriteRequestV1): Promise<IsolateWorkspaceOutcomeV1>;\n delete(request: IsolateWorkspaceDeleteRequestV1): Promise<IsolateWorkspaceOutcomeV1>;\n };\n connection(connectionId: string): Promise<IsolateConnectionOutcomeV1>;\n notify(request: IsolateNotificationRequestV1): Promise<IsolateNotificationOutcomeV1>;\n schedule(request: IsolateScheduleRequestV1): Promise<IsolateScheduleOutcomeV1>;\n}\n\ninterface BotPackageExecutionContextV1 extends BotPackageContextV1 {\n readonly tool: string;\n readonly event?: never;\n}\n\ninterface BotPackageHookContextV1 extends BotPackageContextV1 {\n readonly tool?: never;\n readonly event: BotIsolateHookEventNameV1;\n}";
|
|
4
|
+
export const BOT_ISOLATE_CONTEXT_KEYS_V1 = [
|
|
5
|
+
"tool",
|
|
6
|
+
"event",
|
|
7
|
+
"botId",
|
|
8
|
+
"sessionId",
|
|
9
|
+
"runId",
|
|
10
|
+
"turnId",
|
|
11
|
+
"generationId",
|
|
12
|
+
"packageId",
|
|
13
|
+
"deadlineMs",
|
|
14
|
+
"bindings",
|
|
15
|
+
"capabilities",
|
|
16
|
+
"model",
|
|
17
|
+
"tools",
|
|
18
|
+
"memory",
|
|
19
|
+
"workspace",
|
|
20
|
+
"connection",
|
|
21
|
+
"notify",
|
|
22
|
+
"schedule",
|
|
23
|
+
] as const;
|
|
24
|
+
export const BOT_ISOLATE_CONTEXT_SUMMARY_V1 =
|
|
25
|
+
"Package execute and hook ctx share: readonly tool?: string; readonly event?: BotIsolateHookEventNameV1; readonly botId: string; readonly sessionId: string; readonly runId: string; readonly turnId: string; readonly generationId: string; readonly packageId: string; readonly deadlineMs: number; readonly bindings: string[]; readonly capabilities: { list(): Promise<IsolateCapabilityListOutcomeV1>; }; readonly model: { invoke(request: NormalizedModelRequest): Promise<BotPackageModelOutcomeV1>; }; readonly tools: { /** Runs through the trusted registry's active-Composition and deny guards. */ invoke(request: IsolateToolRequestV1): Promise<IsolateToolOutcomeV1>; }; readonly memory: { read(request: IsolateMemoryReadRequestV1): Promise<IsolateMemoryOutcomeV1>; write(request: IsolateMemoryWriteRequestV1): Promise<IsolateMemoryOutcomeV1>; forget(request: IsolateMemoryWriteRequestV1): Promise<IsolateMemoryOutcomeV1>; }; readonly workspace: { read(path: IsolateWorkspacePathV1): Promise<IsolateWorkspaceOutcomeV1>; list(request: IsolateWorkspaceListRequestV1): Promise<IsolateWorkspaceOutcomeV1>; stat(path: IsolateWorkspacePathV1): Promise<IsolateWorkspaceOutcomeV1>; write(request: IsolateWorkspaceWriteRequestV1): Promise<IsolateWorkspaceOutcomeV1>; delete(request: IsolateWorkspaceDeleteRequestV1): Promise<IsolateWorkspaceOutcomeV1>; }; connection(connectionId: string): Promise<IsolateConnectionOutcomeV1>; notify(request: IsolateNotificationRequestV1): Promise<IsolateNotificationOutcomeV1>; schedule(request: IsolateScheduleRequestV1): Promise<IsolateScheduleOutcomeV1>;";
|
package/src/isolate.test.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
-
decodeIsolateAuthorityRequestV1,
|
|
4
3
|
decodeIsolateCapabilityFailureV1,
|
|
5
4
|
decodeIsolateCapabilityListV1,
|
|
6
5
|
decodeIsolateHealthV1,
|
|
6
|
+
decodeIsolateHookInvocationV1,
|
|
7
|
+
decodeIsolateHookResultV1,
|
|
7
8
|
decodeIsolateIdentityV1,
|
|
8
9
|
decodeIsolateModelEventV1,
|
|
9
10
|
decodeIsolateModelInvocationV1,
|
|
10
|
-
|
|
11
|
+
decodeIsolateScheduleRequestV1,
|
|
11
12
|
decodeIsolateToolDescriptorV1,
|
|
12
13
|
decodeIsolateToolInvocationV1,
|
|
13
14
|
decodeIsolateToolResultV1,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
isolateLoaderIdV1,
|
|
16
17
|
isolateToolSchemaV1,
|
|
17
18
|
ISOLATE_MAX_DEADLINE_MS,
|
|
19
|
+
type IsolateHookInvocationV1,
|
|
18
20
|
} from "./isolate.js";
|
|
19
21
|
|
|
20
22
|
function descriptor(overrides: Record<string, unknown> = {}) {
|
|
@@ -161,7 +163,7 @@ describe("isolate health v1", () => {
|
|
|
161
163
|
|
|
162
164
|
test("rejects an unsupported contract version", () => {
|
|
163
165
|
expect(() =>
|
|
164
|
-
decodeIsolateHealthV1({ ...health, contractVersion:
|
|
166
|
+
decodeIsolateHealthV1({ ...health, contractVersion: 4 }),
|
|
165
167
|
).toThrow(/contractVersion is unsupported/);
|
|
166
168
|
expect(() =>
|
|
167
169
|
decodeIsolateHealthV1({ ...health, contractVersion: 0 }),
|
|
@@ -196,6 +198,26 @@ describe("isolate health v1", () => {
|
|
|
196
198
|
).toThrow(/invalid fields/);
|
|
197
199
|
});
|
|
198
200
|
|
|
201
|
+
test("requires and decodes declared hooks on contract v3", () => {
|
|
202
|
+
expect(
|
|
203
|
+
decodeIsolateHealthV1({
|
|
204
|
+
...health,
|
|
205
|
+
contractVersion: 3,
|
|
206
|
+
hooks: ["agent/tool-exposure", "tools/post-execute"],
|
|
207
|
+
}).hooks,
|
|
208
|
+
).toEqual(["agent/tool-exposure", "tools/post-execute"]);
|
|
209
|
+
expect(() =>
|
|
210
|
+
decodeIsolateHealthV1({ ...health, contractVersion: 3 }),
|
|
211
|
+
).toThrow(/invalid fields/);
|
|
212
|
+
expect(() =>
|
|
213
|
+
decodeIsolateHealthV1({
|
|
214
|
+
...health,
|
|
215
|
+
contractVersion: 3,
|
|
216
|
+
hooks: ["agent/request"],
|
|
217
|
+
}),
|
|
218
|
+
).toThrow(/hooks\[0\] is invalid/);
|
|
219
|
+
});
|
|
220
|
+
|
|
199
221
|
test("rejects an unknown turn type in a v2 descriptor", () => {
|
|
200
222
|
expect(() =>
|
|
201
223
|
decodeIsolateHealthV1({
|
|
@@ -220,6 +242,61 @@ describe("isolate health v1", () => {
|
|
|
220
242
|
});
|
|
221
243
|
});
|
|
222
244
|
|
|
245
|
+
describe("isolate hook v1", () => {
|
|
246
|
+
const hookInvocation: IsolateHookInvocationV1<"agent/tool-exposure"> = {
|
|
247
|
+
schemaVersion: 1,
|
|
248
|
+
event: "agent/tool-exposure",
|
|
249
|
+
payload: {
|
|
250
|
+
step: {
|
|
251
|
+
botId: "bot-1",
|
|
252
|
+
agentId: "bot-1",
|
|
253
|
+
sessionId: "session-1",
|
|
254
|
+
status: "running",
|
|
255
|
+
compositionGenerationId: "gen-1",
|
|
256
|
+
turn: 1,
|
|
257
|
+
step: 1,
|
|
258
|
+
turnType: "chat",
|
|
259
|
+
},
|
|
260
|
+
tools: [],
|
|
261
|
+
},
|
|
262
|
+
botId: "bot-1",
|
|
263
|
+
sessionId: "session-1",
|
|
264
|
+
runId: "run-1",
|
|
265
|
+
turnId: "turn-1",
|
|
266
|
+
generationId: "gen-1",
|
|
267
|
+
deadlineMs: 1_000,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
test("decodes an exact hook invocation and result envelope", () => {
|
|
271
|
+
expect(decodeIsolateHookInvocationV1(hookInvocation)).toEqual(
|
|
272
|
+
hookInvocation,
|
|
273
|
+
);
|
|
274
|
+
expect(
|
|
275
|
+
decodeIsolateHookResultV1({
|
|
276
|
+
schemaVersion: 1,
|
|
277
|
+
status: "replaced",
|
|
278
|
+
replacement: [],
|
|
279
|
+
}),
|
|
280
|
+
).toEqual({ schemaVersion: 1, status: "replaced", replacement: [] });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("refuses undeclared events and inexact result envelopes", () => {
|
|
284
|
+
expect(() =>
|
|
285
|
+
decodeIsolateHookInvocationV1({
|
|
286
|
+
...hookInvocation,
|
|
287
|
+
event: "agent/request",
|
|
288
|
+
}),
|
|
289
|
+
).toThrow(/event is invalid/);
|
|
290
|
+
expect(() =>
|
|
291
|
+
decodeIsolateHookResultV1({
|
|
292
|
+
schemaVersion: 1,
|
|
293
|
+
status: "unchanged",
|
|
294
|
+
replacement: [],
|
|
295
|
+
}),
|
|
296
|
+
).toThrow(/invalid fields/);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
223
300
|
describe("isolate identity and capabilities", () => {
|
|
224
301
|
test("decodes the identity binding", () => {
|
|
225
302
|
expect(
|
|
@@ -233,42 +310,79 @@ describe("isolate identity and capabilities", () => {
|
|
|
233
310
|
|
|
234
311
|
test("decodes a capability list", () => {
|
|
235
312
|
expect(
|
|
236
|
-
decodeIsolateCapabilityListV1(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
313
|
+
decodeIsolateCapabilityListV1({
|
|
314
|
+
status: "available",
|
|
315
|
+
connections: [
|
|
316
|
+
{
|
|
317
|
+
connectionId: "connection-1",
|
|
318
|
+
packageId: "provider",
|
|
319
|
+
connectionTypeId: "account",
|
|
320
|
+
displayName: "Account",
|
|
321
|
+
generation: "generation-1",
|
|
322
|
+
safeMetadata: { region: "au" },
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
model: {
|
|
326
|
+
connectionId: "connection-1",
|
|
327
|
+
packageId: "provider",
|
|
328
|
+
provider: "provider",
|
|
329
|
+
providerModelId: "model-1",
|
|
330
|
+
connectionGeneration: "generation-1",
|
|
331
|
+
},
|
|
332
|
+
tools: true,
|
|
333
|
+
memory: true,
|
|
334
|
+
workspace: false,
|
|
335
|
+
notify: true,
|
|
336
|
+
schedule: true,
|
|
337
|
+
}),
|
|
338
|
+
).toMatchObject({
|
|
339
|
+
status: "available",
|
|
340
|
+
connections: [{ connectionId: "connection-1" }],
|
|
341
|
+
tools: true,
|
|
342
|
+
memory: true,
|
|
343
|
+
workspace: false,
|
|
344
|
+
notify: true,
|
|
345
|
+
schedule: true,
|
|
346
|
+
});
|
|
240
347
|
});
|
|
241
348
|
|
|
242
|
-
test("rejects
|
|
349
|
+
test("rejects authority fields outside the Bot authority contract", () => {
|
|
243
350
|
expect(() =>
|
|
244
|
-
decodeIsolateCapabilityListV1(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
351
|
+
decodeIsolateCapabilityListV1({
|
|
352
|
+
status: "available",
|
|
353
|
+
connections: [],
|
|
354
|
+
tools: true,
|
|
355
|
+
memory: true,
|
|
356
|
+
workspace: true,
|
|
357
|
+
notify: true,
|
|
358
|
+
schedule: true,
|
|
359
|
+
packageId: "package-local-authority",
|
|
360
|
+
}),
|
|
361
|
+
).toThrow(/invalid fields/);
|
|
248
362
|
});
|
|
363
|
+
});
|
|
249
364
|
|
|
250
|
-
|
|
365
|
+
describe("isolate schedule request v1", () => {
|
|
366
|
+
test("decodes the durable Routine input and call id", () => {
|
|
251
367
|
expect(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
368
|
+
decodeIsolateScheduleRequestV1({
|
|
369
|
+
callId: "schedule-1",
|
|
370
|
+
input: { action: "create", schedule: "@daily" },
|
|
255
371
|
}),
|
|
256
|
-
).toEqual({
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
decisionId: "decision-1",
|
|
261
|
-
}).decisionId,
|
|
262
|
-
).toBe("decision-1");
|
|
372
|
+
).toEqual({
|
|
373
|
+
callId: "schedule-1",
|
|
374
|
+
input: { action: "create", schedule: "@daily" },
|
|
375
|
+
});
|
|
263
376
|
});
|
|
264
377
|
|
|
265
|
-
test("
|
|
378
|
+
test("rejects an undeclared field", () => {
|
|
266
379
|
expect(() =>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
380
|
+
decodeIsolateScheduleRequestV1({
|
|
381
|
+
callId: "schedule-1",
|
|
382
|
+
input: {},
|
|
383
|
+
packageId: "package-local-authority",
|
|
270
384
|
}),
|
|
271
|
-
).toThrow(/
|
|
385
|
+
).toThrow(/invalid fields/);
|
|
272
386
|
});
|
|
273
387
|
});
|
|
274
388
|
|
|
@@ -314,13 +428,16 @@ describe("isolate model invocation v1", () => {
|
|
|
314
428
|
if (outcome.status === "streaming") expect(outcome.events).toBe(events);
|
|
315
429
|
});
|
|
316
430
|
|
|
317
|
-
test("decodes
|
|
431
|
+
test("decodes an unavailable outcome when no model is configured", () => {
|
|
318
432
|
expect(
|
|
319
433
|
decodeIsolateModelInvocationV1({
|
|
320
|
-
status: "
|
|
321
|
-
|
|
434
|
+
status: "unavailable",
|
|
435
|
+
reason: "this Bot has no configured model",
|
|
322
436
|
}),
|
|
323
|
-
).toEqual({
|
|
437
|
+
).toEqual({
|
|
438
|
+
status: "unavailable",
|
|
439
|
+
reason: "this Bot has no configured model",
|
|
440
|
+
});
|
|
324
441
|
});
|
|
325
442
|
|
|
326
443
|
test("rejects a streaming outcome without a stream", () => {
|
|
@@ -368,7 +485,7 @@ describe("isolate capability failure v1", () => {
|
|
|
368
485
|
});
|
|
369
486
|
|
|
370
487
|
describe("isolate loader identity", () => {
|
|
371
|
-
test("is the User and
|
|
488
|
+
test("is the User and binding-addressed module set — nothing else", () => {
|
|
372
489
|
expect(
|
|
373
490
|
isolateLoaderIdV1({
|
|
374
491
|
userId: "user-1",
|
|
@@ -377,25 +494,24 @@ describe("isolate loader identity", () => {
|
|
|
377
494
|
).toBe(`bot-package:user-1:${"a".repeat(64)}`);
|
|
378
495
|
});
|
|
379
496
|
|
|
380
|
-
test("
|
|
381
|
-
const hash = "b".repeat(64);
|
|
497
|
+
test("different binding digests produce different ids", () => {
|
|
382
498
|
expect(
|
|
383
499
|
isolateLoaderIdV1({
|
|
384
500
|
userId: "user-1",
|
|
385
|
-
artifactSetHash:
|
|
501
|
+
artifactSetHash: "b".repeat(64),
|
|
386
502
|
}),
|
|
387
503
|
).not.toBe(
|
|
388
504
|
isolateLoaderIdV1({
|
|
389
|
-
userId: "user-
|
|
390
|
-
artifactSetHash:
|
|
505
|
+
userId: "user-1",
|
|
506
|
+
artifactSetHash: "c".repeat(64),
|
|
391
507
|
}),
|
|
392
508
|
);
|
|
393
509
|
});
|
|
394
510
|
|
|
395
|
-
test("rejects a component that could forge another
|
|
511
|
+
test("rejects a component that could forge another Bot's id", () => {
|
|
396
512
|
expect(() =>
|
|
397
513
|
isolateLoaderIdV1({
|
|
398
|
-
userId: "user-1:
|
|
514
|
+
userId: "user-1:bot-2",
|
|
399
515
|
artifactSetHash: "c".repeat(64),
|
|
400
516
|
}),
|
|
401
517
|
).toThrow(/components are invalid/);
|