@frockbot/plugin-shell 0.3.21 → 0.3.22
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 -32
- package/src/backend-applets.test.ts +38 -0
- package/src/backend-applets.ts +32 -2
- package/src/backend.ts +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/plugin-shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -52,42 +52,42 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@cordisjs/plugin-webui": "0.8.2",
|
|
55
|
-
"@frockbot/agent-runtime": "0.3.
|
|
56
|
-
"@frockbot/application-foundation": "0.3.
|
|
57
|
-
"@frockbot/catalog-core": "0.3.
|
|
58
|
-
"@frockbot/client-core": "0.3.
|
|
59
|
-
"@frockbot/client-ui": "0.3.
|
|
60
|
-
"@frockbot/computer-core": "0.3.
|
|
61
|
-
"@frockbot/computer-host-protocol": "0.3.
|
|
62
|
-
"@frockbot/configuration-core": "0.3.
|
|
63
|
-
"@frockbot/connection-core": "0.3.
|
|
64
|
-
"@frockbot/kernel-agent-loop": "0.3.
|
|
65
|
-
"@frockbot/kernel-composition": "0.3.
|
|
66
|
-
"@frockbot/kernel-contracts": "0.3.
|
|
67
|
-
"@frockbot/kernel-do": "0.3.
|
|
68
|
-
"@frockbot/machine-protocol": "0.3.
|
|
69
|
-
"@frockbot/plugin-applets": "0.3.
|
|
70
|
-
"@frockbot/plugin-authoring": "0.3.
|
|
71
|
-
"@frockbot/plugin-bot-template": "0.3.
|
|
72
|
-
"@frockbot/plugin-computer": "0.3.
|
|
73
|
-
"@frockbot/plugin-flock": "0.3.
|
|
74
|
-
"@frockbot/plugin-image": "0.3.
|
|
75
|
-
"@frockbot/plugin-machine-messages": "0.3.
|
|
76
|
-
"@frockbot/plugin-mcp": "0.3.
|
|
77
|
-
"@frockbot/plugin-memory": "0.3.
|
|
78
|
-
"@frockbot/plugin-package-catalog": "0.3.
|
|
79
|
-
"@frockbot/plugin-routines": "0.3.
|
|
80
|
-
"@frockbot/plugin-skills": "0.3.
|
|
81
|
-
"@frockbot/plugin-subagents": "0.3.
|
|
82
|
-
"@frockbot/plugin-user-machine": "0.3.
|
|
83
|
-
"@frockbot/protocol": "0.3.
|
|
55
|
+
"@frockbot/agent-runtime": "0.3.22",
|
|
56
|
+
"@frockbot/application-foundation": "0.3.22",
|
|
57
|
+
"@frockbot/catalog-core": "0.3.22",
|
|
58
|
+
"@frockbot/client-core": "0.3.22",
|
|
59
|
+
"@frockbot/client-ui": "0.3.22",
|
|
60
|
+
"@frockbot/computer-core": "0.3.22",
|
|
61
|
+
"@frockbot/computer-host-protocol": "0.3.22",
|
|
62
|
+
"@frockbot/configuration-core": "0.3.22",
|
|
63
|
+
"@frockbot/connection-core": "0.3.22",
|
|
64
|
+
"@frockbot/kernel-agent-loop": "0.3.22",
|
|
65
|
+
"@frockbot/kernel-composition": "0.3.22",
|
|
66
|
+
"@frockbot/kernel-contracts": "0.3.22",
|
|
67
|
+
"@frockbot/kernel-do": "0.3.22",
|
|
68
|
+
"@frockbot/machine-protocol": "0.3.22",
|
|
69
|
+
"@frockbot/plugin-applets": "0.3.22",
|
|
70
|
+
"@frockbot/plugin-authoring": "0.3.22",
|
|
71
|
+
"@frockbot/plugin-bot-template": "0.3.22",
|
|
72
|
+
"@frockbot/plugin-computer": "0.3.22",
|
|
73
|
+
"@frockbot/plugin-flock": "0.3.22",
|
|
74
|
+
"@frockbot/plugin-image": "0.3.22",
|
|
75
|
+
"@frockbot/plugin-machine-messages": "0.3.22",
|
|
76
|
+
"@frockbot/plugin-mcp": "0.3.22",
|
|
77
|
+
"@frockbot/plugin-memory": "0.3.22",
|
|
78
|
+
"@frockbot/plugin-package-catalog": "0.3.22",
|
|
79
|
+
"@frockbot/plugin-routines": "0.3.22",
|
|
80
|
+
"@frockbot/plugin-skills": "0.3.22",
|
|
81
|
+
"@frockbot/plugin-subagents": "0.3.22",
|
|
82
|
+
"@frockbot/plugin-user-machine": "0.3.22",
|
|
83
|
+
"@frockbot/protocol": "0.3.22",
|
|
84
84
|
"cordis": "4.0.0-rc.8",
|
|
85
85
|
"vue": "3.5.41"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@cloudflare/workers-types": "latest",
|
|
89
|
-
"@frockbot/plugin-prompt": "0.3.
|
|
90
|
-
"@frockbot/plugin-tools": "0.3.
|
|
89
|
+
"@frockbot/plugin-prompt": "0.3.22",
|
|
90
|
+
"@frockbot/plugin-tools": "0.3.22",
|
|
91
91
|
"@types/bun": "1.3.6",
|
|
92
92
|
"@types/node": "26.2.0",
|
|
93
93
|
"@vitejs/plugin-vue": "6.0.8",
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveAppletCompositionV1,
|
|
9
9
|
APPLET_DIRECTORY_REVISION_SEEN_KEY,
|
|
10
10
|
type AppletInstanceBindingV1,
|
|
11
|
+
type AppletSourceSyncOutcomeV1,
|
|
11
12
|
type AppletUserDirectoryV1,
|
|
12
13
|
} from "./backend-applets.js";
|
|
13
14
|
import {
|
|
@@ -344,6 +345,7 @@ describe("ctx.applets", () => {
|
|
|
344
345
|
directory?: Partial<AppletUserDirectoryV1>;
|
|
345
346
|
instance?: Partial<AppletInstanceBindingV1>;
|
|
346
347
|
storage?: ReturnType<typeof memoryStorage>;
|
|
348
|
+
synced?: AppletSourceSyncOutcomeV1;
|
|
347
349
|
}) {
|
|
348
350
|
const storage = options.storage ?? memoryStorage();
|
|
349
351
|
const recorded: unknown[] = [];
|
|
@@ -455,6 +457,9 @@ describe("ctx.applets", () => {
|
|
|
455
457
|
stat: () =>
|
|
456
458
|
Promise.resolve({ status: "not-found", reason: "" }) as never,
|
|
457
459
|
},
|
|
460
|
+
...(options.synced
|
|
461
|
+
? { syncSourceRootNow: () => Promise.resolve(options.synced!) }
|
|
462
|
+
: {}),
|
|
458
463
|
composition: {
|
|
459
464
|
current: () => bootstrap(),
|
|
460
465
|
lastKnownGood: () => bootstrap(),
|
|
@@ -488,6 +493,39 @@ describe("ctx.applets", () => {
|
|
|
488
493
|
);
|
|
489
494
|
});
|
|
490
495
|
|
|
496
|
+
// Production, 2026-09-04: a 470 KB `dist/ui.html` the Bot had just built was
|
|
497
|
+
// reported as `not-found: run \`applet build\` … first`, and the Bot ran the
|
|
498
|
+
// build again, three times. The build was fine; the sync could not carry the
|
|
499
|
+
// file. The publish says which of the two it was now.
|
|
500
|
+
test("publish says a built file did not reach the Workspace rather than blaming the build", async () => {
|
|
501
|
+
const { host: capability } = host({
|
|
502
|
+
files: {},
|
|
503
|
+
synced: {
|
|
504
|
+
status: "degraded",
|
|
505
|
+
detail:
|
|
506
|
+
'1 sync operation failed at "dist/ui.html": unavailable: the Computer could not answer.',
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
const outcome = await capability.publish({ appletId: APPLET }, scope);
|
|
510
|
+
expect(outcome.status).toBe("failed");
|
|
511
|
+
const reason = outcome.status === "failed" ? outcome.reason : "";
|
|
512
|
+
expect(reason).toBe(
|
|
513
|
+
'"dist/server.js" is on the Computer but did not reach the Workspace: 1 sync operation failed at "dist/ui.html": unavailable: the Computer could not answer.',
|
|
514
|
+
);
|
|
515
|
+
expect(reason).not.toMatch(/applet build/);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("publish still blames the build when the pull of the source root was clean", async () => {
|
|
519
|
+
const { host: capability } = host({
|
|
520
|
+
files: {},
|
|
521
|
+
synced: { status: "ok", detail: "" },
|
|
522
|
+
});
|
|
523
|
+
const outcome = await capability.publish({ appletId: APPLET }, scope);
|
|
524
|
+
expect(outcome.status === "failed" && outcome.reason).toMatch(
|
|
525
|
+
/applet build/,
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
491
529
|
test("publish refuses a manifest whose hashes do not match the bytes", async () => {
|
|
492
530
|
const { host: capability } = host({
|
|
493
531
|
files: {
|
package/src/backend-applets.ts
CHANGED
|
@@ -58,6 +58,20 @@ export const APPLET_PUBLISH_EFFECT_PREFIX = "applets:publish-effect:";
|
|
|
58
58
|
*/
|
|
59
59
|
export const APPLET_DIRECTORY_REVISION_SEEN_KEY = "applets:directory-revision";
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* How the pre-publish pull of the Applet's source root went.
|
|
63
|
+
*
|
|
64
|
+
* `ok` and `skipped` mean the store now holds what the Computer holds. Anything
|
|
65
|
+
* else means the two may differ, and a `dist/` file the publish cannot find is
|
|
66
|
+
* then far more likely to be one the sync could not carry than one `applet
|
|
67
|
+
* build` never wrote.
|
|
68
|
+
*/
|
|
69
|
+
export interface AppletSourceSyncOutcomeV1 {
|
|
70
|
+
status: "ok" | "degraded" | "unavailable" | "refused" | "skipped";
|
|
71
|
+
/** What the sync said went wrong, empty when it had nothing to say. */
|
|
72
|
+
detail: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
/** The three files `applet build` writes, read from the durable root. */
|
|
62
76
|
export const APPLET_DIST_FILES_V1 = [
|
|
63
77
|
"dist/server.js",
|
|
@@ -293,8 +307,12 @@ export interface AppletCapabilityHostOptionsV1 {
|
|
|
293
307
|
* store is read as it stands — correct but possibly stale — and the Bot is
|
|
294
308
|
* told so in the failure when the files are missing, rather than the publish
|
|
295
309
|
* silently using old bytes.
|
|
310
|
+
*
|
|
311
|
+
* What it answers is how that pull went. A build that is on the Computer but
|
|
312
|
+
* did not reach the store is a different failure from a build that was never
|
|
313
|
+
* run, and the publish can only say which when it is told.
|
|
296
314
|
*/
|
|
297
|
-
syncSourceRootNow?(appletId: string): Promise<
|
|
315
|
+
syncSourceRootNow?(appletId: string): Promise<AppletSourceSyncOutcomeV1>;
|
|
298
316
|
composition: Pick<CompositionStore, "current" | "lastKnownGood" | "propose">;
|
|
299
317
|
now?(): Date;
|
|
300
318
|
}
|
|
@@ -594,11 +612,20 @@ export function createAppletCapabilityHostV1(
|
|
|
594
612
|
appletId: string,
|
|
595
613
|
file: string,
|
|
596
614
|
maximum: number,
|
|
615
|
+
synced?: AppletSourceSyncOutcomeV1,
|
|
597
616
|
): Promise<{ text: string } | { failure: string }> {
|
|
598
617
|
const outcome = await options.workspace.read(
|
|
599
618
|
appletDistPathV1(options.userId, appletId, file),
|
|
600
619
|
);
|
|
601
620
|
if (outcome.status !== "ok") {
|
|
621
|
+
// A pull that did not finish is the likelier explanation than an
|
|
622
|
+
// unbuilt Applet, and telling the Bot to build again would send it
|
|
623
|
+
// round a loop that cannot end.
|
|
624
|
+
if (synced && synced.status !== "ok" && synced.status !== "skipped") {
|
|
625
|
+
return {
|
|
626
|
+
failure: `"${file}" is on the Computer but did not reach the Workspace: ${synced.detail || "the sync could not carry it"}`,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
602
629
|
return {
|
|
603
630
|
failure: `"${file}" is ${outcome.status}: run \`applet build\` in applets/${appletId} on the Computer first`,
|
|
604
631
|
};
|
|
@@ -731,12 +758,13 @@ export function createAppletCapabilityHostV1(
|
|
|
731
758
|
|
|
732
759
|
// Force a pull of the source root so the publish sees what the Bot just
|
|
733
760
|
// built, not the last synced copy. See the seam note on the option.
|
|
734
|
-
await options.syncSourceRootNow?.(input.appletId);
|
|
761
|
+
const synced = await options.syncSourceRootNow?.(input.appletId);
|
|
735
762
|
|
|
736
763
|
const server = await readFile(
|
|
737
764
|
input.appletId,
|
|
738
765
|
"dist/server.js",
|
|
739
766
|
APPLET_MAX_SERVER_BYTES_V1,
|
|
767
|
+
synced,
|
|
740
768
|
);
|
|
741
769
|
if ("failure" in server) {
|
|
742
770
|
return settle(failed(input.appletId, "unbuilt", server.failure));
|
|
@@ -745,6 +773,7 @@ export function createAppletCapabilityHostV1(
|
|
|
745
773
|
input.appletId,
|
|
746
774
|
"dist/ui.html",
|
|
747
775
|
APPLET_MAX_UI_BYTES_V1,
|
|
776
|
+
synced,
|
|
748
777
|
);
|
|
749
778
|
if ("failure" in ui) {
|
|
750
779
|
return settle(failed(input.appletId, "unbuilt", ui.failure));
|
|
@@ -753,6 +782,7 @@ export function createAppletCapabilityHostV1(
|
|
|
753
782
|
input.appletId,
|
|
754
783
|
"dist/manifest.json",
|
|
755
784
|
APPLET_MAX_MANIFEST_BYTES_V1,
|
|
785
|
+
synced,
|
|
756
786
|
);
|
|
757
787
|
if ("failure" in manifestFile) {
|
|
758
788
|
return settle(failed(input.appletId, "unbuilt", manifestFile.failure));
|
package/src/backend.ts
CHANGED
|
@@ -2492,7 +2492,9 @@ export class ShellBotBackendContribution {
|
|
|
2492
2492
|
sessions: typeof active.mounted.runtime.root.sessions;
|
|
2493
2493
|
};
|
|
2494
2494
|
const computerIdentity = { userId: identity.userId };
|
|
2495
|
-
if (!root.computers?.assignment(computerIdentity))
|
|
2495
|
+
if (!root.computers?.assignment(computerIdentity)) {
|
|
2496
|
+
return { status: "skipped", detail: "" } as const;
|
|
2497
|
+
}
|
|
2496
2498
|
const session = root.sessions.get(active.sessionId);
|
|
2497
2499
|
const started = session?.events.findLast(
|
|
2498
2500
|
(event) => event.type === "step/start",
|
|
@@ -2503,7 +2505,7 @@ export class ShellBotBackendContribution {
|
|
|
2503
2505
|
{ botId: identity.botId },
|
|
2504
2506
|
{ signal: active.signal },
|
|
2505
2507
|
);
|
|
2506
|
-
await syncWorkspaceRootNowV1({
|
|
2508
|
+
const summary = await syncWorkspaceRootNowV1({
|
|
2507
2509
|
computer,
|
|
2508
2510
|
sessions: root.sessions,
|
|
2509
2511
|
sessionId: active.sessionId,
|
|
@@ -2514,6 +2516,7 @@ export class ShellBotBackendContribution {
|
|
|
2514
2516
|
),
|
|
2515
2517
|
signal: active.signal,
|
|
2516
2518
|
});
|
|
2519
|
+
return { status: summary.status, detail: summary.detail };
|
|
2517
2520
|
}
|
|
2518
2521
|
: undefined,
|
|
2519
2522
|
composition: {
|