@frockbot/plugin-shell 0.3.1 → 0.3.2
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 +32 -29
- package/src/backend-applets.test.ts +581 -0
- package/src/backend-applets.ts +959 -0
- package/src/backend-authoring.test.ts +61 -19
- package/src/backend-authoring.ts +52 -26
- package/src/backend-composition.ts +64 -0
- package/src/backend-computer.test.ts +128 -0
- package/src/backend-computer.ts +81 -0
- package/src/backend-configuration.test.ts +8 -1
- package/src/backend-iframe-ui.test.ts +29 -12
- package/src/backend-isolate.ts +31 -5
- package/src/backend-package-catalog.test.ts +13 -8
- package/src/backend-package-catalog.ts +8 -6
- package/src/backend-recovery-integration.test.ts +23 -0
- package/src/backend.ts +508 -5
- package/src/client/AppletCanvas.vue +679 -0
- package/src/client/FrockBotApp.vue +169 -15
- package/src/client/PackageEntryTrigger.vue +77 -0
- package/src/client/PackageIframeHost.vue +148 -47
- package/src/client/PackageIframeSettings.vue +8 -6
- package/src/client/PackageSurfacePage.vue +39 -0
- package/src/client/applets-client.test.ts +204 -0
- package/src/client/applets-client.ts +139 -0
- package/src/client/applets-state.ts +64 -0
- package/src/client/index.test.ts +13 -7
- package/src/client/index.ts +308 -1
- package/src/client/package-iframe-entries.test.ts +122 -0
- package/src/client/package-iframe-entries.ts +112 -0
- package/src/client/package-iframe-host-message.test.ts +3 -3
- package/src/client/package-iframe-host-message.ts +3 -3
- package/src/client/styles.css +107 -1
- package/src/composition-views.ts +31 -6
- package/src/shared.ts +52 -0
package/src/backend-isolate.ts
CHANGED
|
@@ -251,6 +251,16 @@ export function isolateModelEventStreamV1(
|
|
|
251
251
|
* identity. User, Bot, the resolved model binding, and the pinned Composition
|
|
252
252
|
* generation complete the digest. Package id is deliberately absent: two
|
|
253
253
|
* Packages of one Bot receive the same authority projection.
|
|
254
|
+
*
|
|
255
|
+
* `runId` is here because `CAPABILITIES` is one of those bindings and it is
|
|
256
|
+
* scoped to one Turn: the stub carries that Turn's run, session, and turn id,
|
|
257
|
+
* and the Bot Durable Object refuses a capability call whose scope is not the
|
|
258
|
+
* Turn it is currently running. The loader hands back the *cached* worker for a
|
|
259
|
+
* repeated id, env and all, so leaving the Turn out of the digest gave a second
|
|
260
|
+
* Turn on one Composition generation an isolate holding the first Turn's stub —
|
|
261
|
+
* every `ctx.memory`, `ctx.workspace`, and `ctx.applets` call in it answered
|
|
262
|
+
* "the Package is not running in this Bot's active Composition". The digest is
|
|
263
|
+
* a statement about what is bound; a per-Turn binding belongs in it.
|
|
254
264
|
*/
|
|
255
265
|
export async function isolateBindingDigestV1(input: {
|
|
256
266
|
userId: string;
|
|
@@ -261,6 +271,8 @@ export async function isolateBindingDigestV1(input: {
|
|
|
261
271
|
>[];
|
|
262
272
|
model?: IsolateModelBindingV1;
|
|
263
273
|
compositionGenerationId: string;
|
|
274
|
+
/** The Turn whose `CAPABILITIES` stub this isolate's env carries. */
|
|
275
|
+
runId?: string;
|
|
264
276
|
}): Promise<string> {
|
|
265
277
|
const connections = [...input.connections]
|
|
266
278
|
.map(({ connectionId, generation }) => ({ connectionId, generation }))
|
|
@@ -270,6 +282,7 @@ export async function isolateBindingDigestV1(input: {
|
|
|
270
282
|
userId: input.userId,
|
|
271
283
|
botId: input.botId,
|
|
272
284
|
compositionGenerationId: input.compositionGenerationId,
|
|
285
|
+
runId: input.runId ?? null,
|
|
273
286
|
connections,
|
|
274
287
|
model: input.model ?? null,
|
|
275
288
|
}),
|
|
@@ -290,17 +303,30 @@ async function sha256Hex(value: string): Promise<string> {
|
|
|
290
303
|
* Reads a Bot Package artifact from object storage and verifies its content
|
|
291
304
|
* address before a byte of it becomes code. Artifacts are immutable content,
|
|
292
305
|
* not state; the hash is the only thing that makes them safe to mount.
|
|
306
|
+
*
|
|
307
|
+
* `bundled` is a second *place* to find the same immutable bytes, not a second
|
|
308
|
+
* kind of thing. A first-party Package that ships as an artifact-backed member
|
|
309
|
+
* (ADR 0022 decision 8) is built at build time and travels inside this bundle,
|
|
310
|
+
* so its bytes are already here and object storage never has to be seeded for
|
|
311
|
+
* a deploy to be correct. Object storage still wins when it holds the object,
|
|
312
|
+
* the digest is still verified either way, and nothing is ever *built* here —
|
|
313
|
+
* which is what "Composition consumes immutable content-addressed artifacts and
|
|
314
|
+
* never builds them" asks of this seam.
|
|
293
315
|
*/
|
|
294
316
|
export function createR2PackageArtifactStore(
|
|
295
317
|
bucket: R2Bucket,
|
|
318
|
+
bundled?: ReadonlyMap<string, string>,
|
|
296
319
|
): BotIsolateArtifactStore {
|
|
297
320
|
return {
|
|
298
321
|
async loadPackageArtifact(contentHash: string): Promise<string> {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
322
|
+
const key = `packages/${contentHash}.mjs`;
|
|
323
|
+
const object = await bucket.get(key);
|
|
324
|
+
const module = object
|
|
325
|
+
? await object.text()
|
|
326
|
+
: (bundled?.get(key) ??
|
|
327
|
+
(() => {
|
|
328
|
+
throw new Error(`package artifact "${contentHash}" is missing`);
|
|
329
|
+
})());
|
|
304
330
|
if ((await sha256Hex(module)) !== contentHash) {
|
|
305
331
|
throw new Error(
|
|
306
332
|
`package artifact "${contentHash}" failed hash verification`,
|
|
@@ -28,7 +28,7 @@ const UI_SIZE = 42;
|
|
|
28
28
|
const CREATED_AT = "2026-09-02T00:00:00.000Z";
|
|
29
29
|
|
|
30
30
|
const manifest = {
|
|
31
|
-
schemaVersion:
|
|
31
|
+
schemaVersion: 5 as const,
|
|
32
32
|
id: "parcel-tracking",
|
|
33
33
|
displayName: "Parcel tracking",
|
|
34
34
|
version: "0.0.1",
|
|
@@ -38,13 +38,18 @@ const manifest = {
|
|
|
38
38
|
runtime: { entry: "./package.js", host: "bot-isolate" as const },
|
|
39
39
|
client: {
|
|
40
40
|
kind: "iframe" as const,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
pages: [
|
|
42
|
+
{
|
|
43
|
+
id: "main",
|
|
44
|
+
artifact: {
|
|
45
|
+
contentHash: UI_CONTENT_HASH,
|
|
46
|
+
size: UI_SIZE,
|
|
47
|
+
mediaType: "text/html" as const,
|
|
48
|
+
bundlerVersion: "frockbot-inline-html@1",
|
|
49
|
+
},
|
|
50
|
+
mounts: [{ slot: "frockbot.tool-result:track_parcel" }],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
48
53
|
},
|
|
49
54
|
},
|
|
50
55
|
configuration: {
|
|
@@ -259,13 +259,15 @@ export function createPackageCatalogHost(
|
|
|
259
259
|
}
|
|
260
260
|
const client = entry.bundle.manifest.contributions.client;
|
|
261
261
|
if (client && isClientIframeContribution(client)) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (!uiArtifact || uiArtifact.size !== client.artifact.size) {
|
|
266
|
-
throw new Error(
|
|
267
|
-
`Catalog iframe artifact "${client.artifact.contentHash}" is absent or has the wrong size`,
|
|
262
|
+
for (const page of client.pages) {
|
|
263
|
+
const uiArtifact = await options.catalog.headUiArtifact(
|
|
264
|
+
page.artifact.contentHash,
|
|
268
265
|
);
|
|
266
|
+
if (!uiArtifact || uiArtifact.size !== page.artifact.size) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Catalog iframe artifact "${page.artifact.contentHash}" is absent or has the wrong size`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
269
271
|
}
|
|
270
272
|
}
|
|
271
273
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
initializeBotSettingsV1,
|
|
9
9
|
type UserSettingsViewV1,
|
|
10
10
|
} from "@frockbot/configuration-core";
|
|
11
|
+
import { compileFoundationApplication } from "@frockbot/application-foundation/runtime";
|
|
11
12
|
import { createShellBotBackendContribution } from "./backend.js";
|
|
12
13
|
import {
|
|
13
14
|
botTurnCommandFingerprintV1,
|
|
@@ -85,6 +86,27 @@ class MemoryStorage {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/*
|
|
90
|
+
* The application as a host with no Worker Loader can mount it.
|
|
91
|
+
*
|
|
92
|
+
* This suite runs under `bun test`, where there is no `BOT_PACKAGES` binding
|
|
93
|
+
* and no `BotCapabilities` loopback, so an artifact-backed member — the
|
|
94
|
+
* Applets Package, ADR 0022 decision 8 — has nowhere to load from and the
|
|
95
|
+
* Composition fails verification closed, which is correct and is not what
|
|
96
|
+
* these tests are about. Dropping those members is the honest way to say
|
|
97
|
+
* "this host cannot run isolate code"; it is not a switch over any Package's
|
|
98
|
+
* identity, and workerd's suites mount the real thing.
|
|
99
|
+
*/
|
|
100
|
+
async function compileWithoutIsolateMembers(): ReturnType<
|
|
101
|
+
typeof compileFoundationApplication
|
|
102
|
+
> {
|
|
103
|
+
const application = await compileFoundationApplication();
|
|
104
|
+
return {
|
|
105
|
+
...application,
|
|
106
|
+
packages: application.packages.filter((pkg) => pkg.artifact === undefined),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
88
110
|
describe("Bot recovery", () => {
|
|
89
111
|
test("executes and reconstructs an Ollama-bound Bot without Foundation fallback", async () => {
|
|
90
112
|
const storage = new MemoryStorage();
|
|
@@ -216,6 +238,7 @@ describe("Bot recovery", () => {
|
|
|
216
238
|
typeof createShellBotBackendContribution
|
|
217
239
|
>[0]["env"],
|
|
218
240
|
outboundFetch,
|
|
241
|
+
compileApplication: compileWithoutIsolateMembers,
|
|
219
242
|
});
|
|
220
243
|
|
|
221
244
|
const configured = host();
|