@frockbot/plugin-shell 0.3.26 → 0.3.28

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-shell",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
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.26",
56
- "@frockbot/application-foundation": "0.3.26",
57
- "@frockbot/catalog-core": "0.3.26",
58
- "@frockbot/client-core": "0.3.26",
59
- "@frockbot/client-ui": "0.3.26",
60
- "@frockbot/computer-core": "0.3.26",
61
- "@frockbot/computer-host-protocol": "0.3.26",
62
- "@frockbot/configuration-core": "0.3.26",
63
- "@frockbot/connection-core": "0.3.26",
64
- "@frockbot/kernel-agent-loop": "0.3.26",
65
- "@frockbot/kernel-composition": "0.3.26",
66
- "@frockbot/kernel-contracts": "0.3.26",
67
- "@frockbot/kernel-do": "0.3.26",
68
- "@frockbot/machine-protocol": "0.3.26",
69
- "@frockbot/plugin-applets": "0.3.26",
70
- "@frockbot/plugin-authoring": "0.3.26",
71
- "@frockbot/plugin-bot-template": "0.3.26",
72
- "@frockbot/plugin-computer": "0.3.26",
73
- "@frockbot/plugin-flock": "0.3.26",
74
- "@frockbot/plugin-image": "0.3.26",
75
- "@frockbot/plugin-machine-messages": "0.3.26",
76
- "@frockbot/plugin-mcp": "0.3.26",
77
- "@frockbot/plugin-memory": "0.3.26",
78
- "@frockbot/plugin-package-catalog": "0.3.26",
79
- "@frockbot/plugin-routines": "0.3.26",
80
- "@frockbot/plugin-skills": "0.3.26",
81
- "@frockbot/plugin-subagents": "0.3.26",
82
- "@frockbot/plugin-user-machine": "0.3.26",
83
- "@frockbot/protocol": "0.3.26",
55
+ "@frockbot/agent-runtime": "0.3.28",
56
+ "@frockbot/application-foundation": "0.3.28",
57
+ "@frockbot/catalog-core": "0.3.28",
58
+ "@frockbot/client-core": "0.3.28",
59
+ "@frockbot/client-ui": "0.3.28",
60
+ "@frockbot/computer-core": "0.3.28",
61
+ "@frockbot/computer-host-protocol": "0.3.28",
62
+ "@frockbot/configuration-core": "0.3.28",
63
+ "@frockbot/connection-core": "0.3.28",
64
+ "@frockbot/kernel-agent-loop": "0.3.28",
65
+ "@frockbot/kernel-composition": "0.3.28",
66
+ "@frockbot/kernel-contracts": "0.3.28",
67
+ "@frockbot/kernel-do": "0.3.28",
68
+ "@frockbot/machine-protocol": "0.3.28",
69
+ "@frockbot/plugin-applets": "0.3.28",
70
+ "@frockbot/plugin-authoring": "0.3.28",
71
+ "@frockbot/plugin-bot-template": "0.3.28",
72
+ "@frockbot/plugin-computer": "0.3.28",
73
+ "@frockbot/plugin-flock": "0.3.28",
74
+ "@frockbot/plugin-image": "0.3.28",
75
+ "@frockbot/plugin-machine-messages": "0.3.28",
76
+ "@frockbot/plugin-mcp": "0.3.28",
77
+ "@frockbot/plugin-memory": "0.3.28",
78
+ "@frockbot/plugin-package-catalog": "0.3.28",
79
+ "@frockbot/plugin-routines": "0.3.28",
80
+ "@frockbot/plugin-skills": "0.3.28",
81
+ "@frockbot/plugin-subagents": "0.3.28",
82
+ "@frockbot/plugin-user-machine": "0.3.28",
83
+ "@frockbot/protocol": "0.3.28",
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.26",
90
- "@frockbot/plugin-tools": "0.3.26",
89
+ "@frockbot/plugin-prompt": "0.3.28",
90
+ "@frockbot/plugin-tools": "0.3.28",
91
91
  "@types/bun": "1.3.6",
92
92
  "@types/node": "26.2.0",
93
93
  "@vitejs/plugin-vue": "6.0.8",
@@ -526,6 +526,65 @@ describe("ctx.applets", () => {
526
526
  );
527
527
  });
528
528
 
529
+ // Production, 2026-09-04, run 76b4f8d9: `applet build` printed
530
+ // `dist/manifest.json 2 tool(s)`, the publish sync reported `ok` and moved
531
+ // nothing, and the publish then said "run `applet build` … first" about a
532
+ // file that was sitting on the Computer. A sync that reported `ok` is not a
533
+ // reason to blame the build when the sync itself says it saw the file.
534
+ test("publish names the hash the Computer held when the Workspace answers not-found", async () => {
535
+ const { host: capability } = host({
536
+ files: {
537
+ [`${APPLET}/dist/server.js`]: "export class Applet {}",
538
+ [`${APPLET}/dist/ui.html`]: "<h1>hi</h1>",
539
+ },
540
+ synced: {
541
+ status: "ok",
542
+ detail: "",
543
+ required: [
544
+ {
545
+ path: `${APPLET}/dist/server.js`,
546
+ contentHash: "a".repeat(64),
547
+ durable: true,
548
+ },
549
+ {
550
+ path: `${APPLET}/dist/ui.html`,
551
+ contentHash: "b".repeat(64),
552
+ durable: true,
553
+ },
554
+ {
555
+ path: `${APPLET}/dist/manifest.json`,
556
+ contentHash: "e0e3ce78fabf".padEnd(64, "0"),
557
+ durable: false,
558
+ },
559
+ ],
560
+ },
561
+ });
562
+
563
+ const outcome = await capability.publish({ appletId: APPLET }, scope);
564
+
565
+ expect(outcome.status).toBe("failed");
566
+ const reason = outcome.status === "failed" ? outcome.reason : "";
567
+ expect(reason).toBe(
568
+ '"dist/manifest.json" is on the Computer (sha256 e0e3ce78fabf) but the Workspace answered not-found: the publish sync reported ok and did not carry it',
569
+ );
570
+ expect(reason).not.toMatch(/applet build/);
571
+ });
572
+
573
+ test("publish still blames the build when the sync saw no such file", async () => {
574
+ const { host: capability } = host({
575
+ files: {},
576
+ synced: {
577
+ status: "ok",
578
+ detail: "",
579
+ required: [{ path: `${APPLET}/dist/server.js`, durable: false }],
580
+ },
581
+ });
582
+ const outcome = await capability.publish({ appletId: APPLET }, scope);
583
+ expect(outcome.status === "failed" && outcome.reason).toMatch(
584
+ /applet build/,
585
+ );
586
+ });
587
+
529
588
  test("publish refuses a manifest whose hashes do not match the bytes", async () => {
530
589
  const { host: capability } = host({
531
590
  files: {
@@ -70,6 +70,19 @@ export interface AppletSourceSyncOutcomeV1 {
70
70
  status: "ok" | "degraded" | "unavailable" | "refused" | "skipped";
71
71
  /** What the sync said went wrong, empty when it had nothing to say. */
72
72
  detail: string;
73
+ /**
74
+ * What the sync saw for each `dist/` file the publish named required: the
75
+ * hash of the bytes on the Computer, and whether the store ended up holding
76
+ * them. It is the evidence behind the publish's own failure sentence — a
77
+ * build output that demonstrably exists is never answered with "run `applet
78
+ * build` first" (production, 2026-09-04).
79
+ */
80
+ required?: readonly {
81
+ /** The source-root-relative path, `<appletId>/dist/…`. */
82
+ path: string;
83
+ contentHash?: string;
84
+ durable: boolean;
85
+ }[];
73
86
  }
74
87
 
75
88
  /** The three files `applet build` writes, read from the durable root. */
@@ -627,6 +640,18 @@ export function createAppletCapabilityHostV1(
627
640
  appletDistPathV1(options.userId, appletId, file),
628
641
  );
629
642
  if (outcome.status !== "ok") {
643
+ // Verify before reporting. The sync was asked for these exact paths, so
644
+ // it can say whether the Computer held the file and whether the store
645
+ // took it. A build output that is on disk is never answered with "run
646
+ // `applet build` first": that sends a Bot round a loop that cannot end.
647
+ const seen = synced?.required?.find(
648
+ (entry) => entry.path === `${appletId}/${file}`,
649
+ );
650
+ if (seen?.contentHash !== undefined) {
651
+ return {
652
+ failure: `"${file}" is on the Computer (sha256 ${seen.contentHash.slice(0, 12)}) but the Workspace answered ${outcome.status}: the publish sync reported ${synced?.status ?? "nothing"} and did not carry it${synced?.detail ? ` — ${synced.detail}` : ""}`,
653
+ };
654
+ }
630
655
  // A pull that did not finish is the likelier explanation than an
631
656
  // unbuilt Applet, and telling the Bot to build again would send it
632
657
  // round a loop that cannot end.
package/src/backend.ts CHANGED
@@ -2521,7 +2521,11 @@ export class ShellBotBackendContribution {
2521
2521
  ),
2522
2522
  signal: active.signal,
2523
2523
  });
2524
- return { status: summary.status, detail: summary.detail };
2524
+ return {
2525
+ status: summary.status,
2526
+ detail: summary.detail,
2527
+ ...(summary.required ? { required: summary.required } : {}),
2528
+ };
2525
2529
  }
2526
2530
  : undefined,
2527
2531
  composition: {