@frockbot/plugin-shell 0.1.3 → 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/frockbot.json +4 -0
- package/package.json +30 -28
- package/src/agent.ts +10 -13
- package/src/backend-authoring.test.ts +356 -2
- package/src/backend-authoring.ts +585 -20
- package/src/backend-composition-input.test.ts +65 -0
- package/src/backend-composition-input.ts +58 -0
- package/src/backend-composition.ts +23 -5
- package/src/backend-configuration.test.ts +549 -1468
- package/src/backend-contracts.test.ts +28 -0
- package/src/backend-contracts.ts +3 -1
- package/src/backend-execution.ts +0 -4
- package/src/backend-iframe-ui.test.ts +131 -0
- package/src/backend-image.test.ts +8 -8
- package/src/backend-image.ts +13 -13
- package/src/backend-isolate.test.ts +230 -89
- package/src/backend-isolate.ts +103 -200
- package/src/backend-package-catalog.test.ts +451 -0
- package/src/backend-package-catalog.ts +923 -0
- package/src/backend-recovery-integration.test.ts +17 -67
- package/src/backend-routines.ts +1 -1
- package/src/backend-runner-iframe.test.ts +74 -0
- package/src/backend-runner.ts +192 -0
- package/src/backend.ts +1198 -1781
- package/src/client/FrockBotApp.vue +44 -73
- package/src/client/PackageIframeHost.vue +218 -0
- package/src/client/PackageIframeSettings.vue +52 -0
- package/src/client/SendPayloadView.vue +0 -60
- package/src/client/index.test.ts +439 -380
- package/src/client/index.ts +163 -257
- package/src/client/model-presentation.test.ts +21 -8
- package/src/client/model-presentation.ts +14 -7
- package/src/client/package-iframe-host-message.test.ts +41 -0
- package/src/client/package-iframe-host-message.ts +27 -0
- package/src/client/styles.css +0 -27
- package/src/composition-views.ts +54 -0
- package/src/settings-links.test.ts +2 -10
- package/src/settings-links.ts +1 -13
- package/src/shared.ts +10 -13
- package/src/backend-assignment.test.ts +0 -161
- package/src/backend-assignment.ts +0 -274
|
@@ -20,6 +20,34 @@ function storedRun(): StoredRun {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
describe("StoredRun durable contract", () => {
|
|
23
|
+
test("migrates a historical configuration snapshot before strict decoding", () => {
|
|
24
|
+
// Literal Bot settings shape from eb0283edcce5daea976a21a9f6a6414bedc6e2bc.
|
|
25
|
+
const decoded = requireStoredRunV1({
|
|
26
|
+
...storedRun(),
|
|
27
|
+
configurationSnapshot: {
|
|
28
|
+
schemaVersion: 1,
|
|
29
|
+
botId: "primary",
|
|
30
|
+
revision: 4,
|
|
31
|
+
profile: { name: "Primary" },
|
|
32
|
+
notifications: { enabled: true },
|
|
33
|
+
assignments: [],
|
|
34
|
+
assignmentOperations: [],
|
|
35
|
+
model: {
|
|
36
|
+
connectionId: "ollama-1",
|
|
37
|
+
providerModelId: "glm-5.3-flash:cloud",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
expect(decoded.configurationSnapshot).toEqual({
|
|
42
|
+
schemaVersion: 1,
|
|
43
|
+
botId: "primary",
|
|
44
|
+
revision: 4,
|
|
45
|
+
profile: { name: "Primary", description: undefined },
|
|
46
|
+
notifications: { enabled: true },
|
|
47
|
+
packageValues: {},
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
23
51
|
test("uses the public run identifier grammar", () => {
|
|
24
52
|
expect(() =>
|
|
25
53
|
requireStoredRunV1({ ...storedRun(), runId: "run:1" }),
|
package/src/backend-contracts.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
decodeBotSettingsViewV1,
|
|
10
10
|
isPublicIdentifier,
|
|
11
|
+
migrateStoredBotSettingsV1,
|
|
11
12
|
type BotSettingsViewV1,
|
|
12
13
|
} from "@frockbot/configuration-core";
|
|
13
14
|
|
|
@@ -36,7 +37,8 @@ export function decodeRunIdV1(value: unknown): string {
|
|
|
36
37
|
export const storedRunCodecV1: StoredRunCodecV1<BotSettingsViewV1> =
|
|
37
38
|
createStoredRunCodecV1<BotSettingsViewV1>({
|
|
38
39
|
decodeRunId: decodeRunIdV1,
|
|
39
|
-
decodeConfigurationSnapshot:
|
|
40
|
+
decodeConfigurationSnapshot: (stored) =>
|
|
41
|
+
decodeBotSettingsViewV1(migrateStoredBotSettingsV1(stored)),
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
export function requireStoredRunV1(input: unknown): StoredRun {
|
package/src/backend-execution.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
import type {
|
|
7
7
|
BotExecutionPlanV1,
|
|
8
8
|
BotSettingsViewV1,
|
|
9
|
-
ConnectionView,
|
|
10
9
|
} from "@frockbot/configuration-core";
|
|
11
10
|
import type { BotTurnCommand, BotTurnCompletion } from "./backend-contracts.js";
|
|
12
11
|
|
|
@@ -17,9 +16,6 @@ export interface BotResidentProjection {
|
|
|
17
16
|
settings: BotSettingsViewV1;
|
|
18
17
|
executionPlan: BotExecutionPlanV1;
|
|
19
18
|
systemPromptSection: string;
|
|
20
|
-
authorizeConnection(
|
|
21
|
-
assignment: BotSettingsViewV1["assignments"][number],
|
|
22
|
-
): Promise<ConnectionView>;
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
export interface BotResidentTurnExecution {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { PackageIframeCompositionV1 } from "@frockbot/kernel-contracts";
|
|
3
|
+
import type { FrockBotManifest } from "@frockbot/kernel-composition";
|
|
4
|
+
import type { CompositionGenerationV1 } from "@frockbot/kernel-composition/generation";
|
|
5
|
+
import { requirePackageUiToolDeclarationV1 } from "./backend.js";
|
|
6
|
+
import { projectPackageIframeCompositionV1 } from "./composition-views.js";
|
|
7
|
+
|
|
8
|
+
describe("Package iframe server admission", () => {
|
|
9
|
+
const catalog: PackageIframeCompositionV1 = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
botId: "bot",
|
|
12
|
+
generationId: "generation-1",
|
|
13
|
+
contributions: [
|
|
14
|
+
{
|
|
15
|
+
packageId: "weather-page",
|
|
16
|
+
displayName: "Weather page",
|
|
17
|
+
provenance: "Bot-authored",
|
|
18
|
+
artifact: {
|
|
19
|
+
contentHash: "a".repeat(64),
|
|
20
|
+
size: 123,
|
|
21
|
+
mediaType: "text/html",
|
|
22
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
23
|
+
},
|
|
24
|
+
mounts: [{ slot: "frockbot.tool-result:weather_lookup" }],
|
|
25
|
+
declaredTools: ["weather_lookup"],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
test("refuses an undeclared tool before admitting a durable Turn", () => {
|
|
31
|
+
expect(() =>
|
|
32
|
+
requirePackageUiToolDeclarationV1(catalog, {
|
|
33
|
+
generationId: "generation-1",
|
|
34
|
+
packageId: "weather-page",
|
|
35
|
+
name: "package_author",
|
|
36
|
+
}),
|
|
37
|
+
).toThrow('did not declare tool "package_author"');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("refuses a stale page after its Composition generation changed", () => {
|
|
41
|
+
expect(() =>
|
|
42
|
+
requirePackageUiToolDeclarationV1(catalog, {
|
|
43
|
+
generationId: "generation-old",
|
|
44
|
+
packageId: "weather-page",
|
|
45
|
+
name: "weather_lookup",
|
|
46
|
+
}),
|
|
47
|
+
).toThrow('generation "generation-old"');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("projects a Catalog member through the same manifest reader", async () => {
|
|
51
|
+
const manifestHash = "c".repeat(64);
|
|
52
|
+
const uiArtifact = {
|
|
53
|
+
contentHash: "d".repeat(64),
|
|
54
|
+
size: 42,
|
|
55
|
+
mediaType: "text/html" as const,
|
|
56
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
57
|
+
};
|
|
58
|
+
const manifest: FrockBotManifest = {
|
|
59
|
+
schemaVersion: 3,
|
|
60
|
+
id: "weather-page",
|
|
61
|
+
displayName: "Weather page",
|
|
62
|
+
version: "0.0.1",
|
|
63
|
+
compatibility: { frockbot: "*" },
|
|
64
|
+
dependencies: {},
|
|
65
|
+
contributions: {
|
|
66
|
+
runtime: { entry: "./package.js", host: "bot-isolate" },
|
|
67
|
+
client: {
|
|
68
|
+
kind: "iframe",
|
|
69
|
+
artifact: uiArtifact,
|
|
70
|
+
mounts: [{ slot: "frockbot.tool-result:weather_lookup" }],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
tools: [
|
|
74
|
+
{ name: "weather_lookup", description: "Looks up", inputSchema: {} },
|
|
75
|
+
],
|
|
76
|
+
permissions: [],
|
|
77
|
+
};
|
|
78
|
+
const generation: CompositionGenerationV1 = {
|
|
79
|
+
schemaVersion: 1,
|
|
80
|
+
generationId: "generation-1",
|
|
81
|
+
artifactSetHash: "a".repeat(64),
|
|
82
|
+
createdAt: "2026-09-02T00:00:00.000Z",
|
|
83
|
+
origin: { kind: "bootstrap" },
|
|
84
|
+
members: [
|
|
85
|
+
{
|
|
86
|
+
packageId: "weather-page",
|
|
87
|
+
specifier: "catalog:weather-page",
|
|
88
|
+
version: "0.0.1",
|
|
89
|
+
manifestHash,
|
|
90
|
+
provenance: {
|
|
91
|
+
kind: "catalog",
|
|
92
|
+
packageId: "weather-page",
|
|
93
|
+
version: "0.0.1",
|
|
94
|
+
catalogId: "weather-page",
|
|
95
|
+
catalogGeneration: "catalog-1",
|
|
96
|
+
contentHash: "b".repeat(64),
|
|
97
|
+
},
|
|
98
|
+
artifact: {
|
|
99
|
+
contentHash: "b".repeat(64),
|
|
100
|
+
size: 512,
|
|
101
|
+
mediaType: "application/javascript",
|
|
102
|
+
bundlerVersion: "catalog-test@1",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
status: "active",
|
|
107
|
+
};
|
|
108
|
+
const requested: string[] = [];
|
|
109
|
+
|
|
110
|
+
const projected = await projectPackageIframeCompositionV1({
|
|
111
|
+
botId: "bot",
|
|
112
|
+
generation,
|
|
113
|
+
readMemberManifest: (member) => {
|
|
114
|
+
requested.push(member.manifestHash);
|
|
115
|
+
return Promise.resolve(manifest);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(requested).toEqual([manifestHash]);
|
|
120
|
+
expect(projected.contributions).toEqual([
|
|
121
|
+
{
|
|
122
|
+
packageId: "weather-page",
|
|
123
|
+
displayName: "Weather page",
|
|
124
|
+
provenance: "User-installed",
|
|
125
|
+
artifact: uiArtifact,
|
|
126
|
+
mounts: [{ slot: "frockbot.tool-result:weather_lookup" }],
|
|
127
|
+
declaredTools: ["weather_lookup"],
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
} from "@frockbot/plugin-image/testing";
|
|
6
6
|
import {
|
|
7
7
|
createBotImageHost,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
createNativeAiImageModelV1,
|
|
9
|
+
decodeNativeAiImageV1,
|
|
10
10
|
} from "./backend-image.ts";
|
|
11
11
|
|
|
12
12
|
const IDENTITY = { userId: "user-1", botId: "bot-1" };
|
|
@@ -56,7 +56,7 @@ describe("normalizing what Workers AI answered", () => {
|
|
|
56
56
|
const png = fakePngBytesV1(64, 32);
|
|
57
57
|
|
|
58
58
|
test("accepts the base64 envelope the FLUX models answer", async () => {
|
|
59
|
-
const buffer = await
|
|
59
|
+
const buffer = await decodeNativeAiImageV1({ image: base64(png) });
|
|
60
60
|
expect([...new Uint8Array(buffer)]).toEqual([...png]);
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -68,23 +68,23 @@ describe("normalizing what Workers AI answered", () => {
|
|
|
68
68
|
controller.close();
|
|
69
69
|
},
|
|
70
70
|
});
|
|
71
|
-
expect([...new Uint8Array(await
|
|
71
|
+
expect([...new Uint8Array(await decodeNativeAiImageV1(stream))]).toEqual([
|
|
72
72
|
...png,
|
|
73
73
|
]);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
test("accepts a raw buffer or view", async () => {
|
|
77
77
|
expect([
|
|
78
|
-
...new Uint8Array(await
|
|
78
|
+
...new Uint8Array(await decodeNativeAiImageV1(png.slice().buffer)),
|
|
79
79
|
]).toEqual([...png]);
|
|
80
|
-
expect([...new Uint8Array(await
|
|
80
|
+
expect([...new Uint8Array(await decodeNativeAiImageV1(png))]).toEqual([
|
|
81
81
|
...png,
|
|
82
82
|
]);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
test("refuses anything else rather than storing it", async () => {
|
|
86
86
|
for (const answer of [undefined, null, 7, "hello", { image: 3 }, {}]) {
|
|
87
|
-
await expect(
|
|
87
|
+
await expect(decodeNativeAiImageV1(answer)).rejects.toThrow(
|
|
88
88
|
"not an image",
|
|
89
89
|
);
|
|
90
90
|
}
|
|
@@ -92,7 +92,7 @@ describe("normalizing what Workers AI answered", () => {
|
|
|
92
92
|
|
|
93
93
|
test("passes the requested size through to the binding", async () => {
|
|
94
94
|
const calls: Array<[string, Record<string, unknown>]> = [];
|
|
95
|
-
const model =
|
|
95
|
+
const model = createNativeAiImageModelV1({
|
|
96
96
|
run: (name, input) => {
|
|
97
97
|
calls.push([name, input]);
|
|
98
98
|
return Promise.resolve({ image: base64(png) });
|
package/src/backend-image.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// The Bot Durable Object's half of the image-generation seam.
|
|
2
2
|
//
|
|
3
3
|
// The Image Package consumes a narrow `ImageModelV1` — one method, answering
|
|
4
|
-
// raw image bytes — and knows nothing about
|
|
4
|
+
// raw image bytes — and knows nothing about Cloudflare's native AI binding. This module is the
|
|
5
5
|
// adapter: it takes the `AI` binding off the Durable Object's environment,
|
|
6
|
-
// normalizes the three shapes a
|
|
6
|
+
// normalizes the three shapes a native text-to-image model answers in, and
|
|
7
7
|
// hands the Package a seam with no platform vocabulary in it. "Electron,
|
|
8
8
|
// Cloudflare, provider SDK, and Computer implementation types remain inside
|
|
9
9
|
// their adapters."
|
|
10
10
|
//
|
|
11
11
|
// The binding is optional, exactly as `PACKAGE_BUNDLER` is. A deployment with
|
|
12
|
-
// no
|
|
12
|
+
// no native AI binding still mounts the Package, and `generate_image` then
|
|
13
13
|
// refuses visibly on the Turn that calls it — which is a better answer than a
|
|
14
14
|
// tool that silently vanishes from the catalog, and a far better one than a
|
|
15
15
|
// `TypeError` inside the Agent loop.
|
|
@@ -38,11 +38,11 @@ export interface BotImageTurn {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* The
|
|
41
|
+
* The native AI binding, declared structurally so this module names no
|
|
42
42
|
* Cloudflare type it does not have to. `run` is the whole of what an image
|
|
43
43
|
* model needs from it.
|
|
44
44
|
*/
|
|
45
|
-
export interface
|
|
45
|
+
export interface NativeAiBindingV1 {
|
|
46
46
|
run(model: string, input: Record<string, unknown>): Promise<unknown>;
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -51,7 +51,7 @@ export interface WorkersAiBindingV1 {
|
|
|
51
51
|
* as its own type so the binding's absence is a typed state, not a cast.
|
|
52
52
|
*/
|
|
53
53
|
export interface BotImageEnv {
|
|
54
|
-
AI?:
|
|
54
|
+
AI?: NativeAiBindingV1;
|
|
55
55
|
WORKSPACE_FILES?: WorkspaceFilesV1;
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -88,7 +88,7 @@ async function readStream(
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* The bytes inside whatever a
|
|
91
|
+
* The bytes inside whatever a native text-to-image model answered.
|
|
92
92
|
*
|
|
93
93
|
* The catalog does not agree with itself: `flux-1-schnell` and the FLUX.2
|
|
94
94
|
* models answer `{ image: "<base64>" }`, the Stable Diffusion models answer a
|
|
@@ -96,7 +96,7 @@ async function readStream(
|
|
|
96
96
|
* directly. Every one of those is an image; none of them is the Package's
|
|
97
97
|
* problem.
|
|
98
98
|
*/
|
|
99
|
-
export async function
|
|
99
|
+
export async function decodeNativeAiImageV1(
|
|
100
100
|
answer: unknown,
|
|
101
101
|
): Promise<ArrayBuffer> {
|
|
102
102
|
if (answer instanceof ArrayBuffer) return answer;
|
|
@@ -123,15 +123,15 @@ export async function decodeWorkersAiImageV1(
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
* The
|
|
126
|
+
* The native AI binding as an {@link ImageModelV1}.
|
|
127
127
|
*
|
|
128
128
|
* `width` and `height` are passed through: the Stable Diffusion models accept
|
|
129
129
|
* them, and the FLUX models ignore unknown fields rather than refusing. The
|
|
130
130
|
* Package never trusts them to have been honoured — it reads the dimensions
|
|
131
131
|
* back out of the bytes.
|
|
132
132
|
*/
|
|
133
|
-
export function
|
|
134
|
-
binding:
|
|
133
|
+
export function createNativeAiImageModelV1(
|
|
134
|
+
binding: NativeAiBindingV1,
|
|
135
135
|
): ImageModelV1 {
|
|
136
136
|
return {
|
|
137
137
|
async run(model: string, input: ImageModelInputV1): Promise<ArrayBuffer> {
|
|
@@ -140,7 +140,7 @@ export function createWorkersAiImageModelV1(
|
|
|
140
140
|
width: input.width,
|
|
141
141
|
height: input.height,
|
|
142
142
|
});
|
|
143
|
-
return await
|
|
143
|
+
return await decodeNativeAiImageV1(answer);
|
|
144
144
|
},
|
|
145
145
|
};
|
|
146
146
|
}
|
|
@@ -174,7 +174,7 @@ export function createBotImageHost(
|
|
|
174
174
|
runId: turn.runId,
|
|
175
175
|
},
|
|
176
176
|
files,
|
|
177
|
-
...(bindings.AI ? { model:
|
|
177
|
+
...(bindings.AI ? { model: createNativeAiImageModelV1(bindings.AI) } : {}),
|
|
178
178
|
...(modelId ? { modelId } : {}),
|
|
179
179
|
};
|
|
180
180
|
}
|