@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.
Files changed (41) hide show
  1. package/frockbot.json +4 -0
  2. package/package.json +30 -28
  3. package/src/agent.ts +10 -13
  4. package/src/backend-authoring.test.ts +356 -2
  5. package/src/backend-authoring.ts +585 -20
  6. package/src/backend-composition-input.test.ts +65 -0
  7. package/src/backend-composition-input.ts +58 -0
  8. package/src/backend-composition.ts +23 -5
  9. package/src/backend-configuration.test.ts +549 -1468
  10. package/src/backend-contracts.test.ts +28 -0
  11. package/src/backend-contracts.ts +3 -1
  12. package/src/backend-execution.ts +0 -4
  13. package/src/backend-iframe-ui.test.ts +131 -0
  14. package/src/backend-image.test.ts +8 -8
  15. package/src/backend-image.ts +13 -13
  16. package/src/backend-isolate.test.ts +230 -89
  17. package/src/backend-isolate.ts +103 -200
  18. package/src/backend-package-catalog.test.ts +451 -0
  19. package/src/backend-package-catalog.ts +923 -0
  20. package/src/backend-recovery-integration.test.ts +17 -67
  21. package/src/backend-routines.ts +1 -1
  22. package/src/backend-runner-iframe.test.ts +74 -0
  23. package/src/backend-runner.ts +192 -0
  24. package/src/backend.ts +1198 -1781
  25. package/src/client/FrockBotApp.vue +44 -73
  26. package/src/client/PackageIframeHost.vue +218 -0
  27. package/src/client/PackageIframeSettings.vue +52 -0
  28. package/src/client/SendPayloadView.vue +0 -60
  29. package/src/client/index.test.ts +439 -380
  30. package/src/client/index.ts +163 -257
  31. package/src/client/model-presentation.test.ts +21 -8
  32. package/src/client/model-presentation.ts +14 -7
  33. package/src/client/package-iframe-host-message.test.ts +41 -0
  34. package/src/client/package-iframe-host-message.ts +27 -0
  35. package/src/client/styles.css +0 -27
  36. package/src/composition-views.ts +54 -0
  37. package/src/settings-links.test.ts +2 -10
  38. package/src/settings-links.ts +1 -13
  39. package/src/shared.ts +10 -13
  40. package/src/backend-assignment.test.ts +0 -161
  41. package/src/backend-assignment.ts +0 -274
@@ -0,0 +1,65 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ compositionFailureDurableInputV1,
4
+ compositionFailureTurnTextV1,
5
+ } from "./backend-composition-input.ts";
6
+
7
+ describe("Composition failure durable Bot input", () => {
8
+ test("includes the authored Package, phase, diagnostics, and quarantine", () => {
9
+ const text = compositionFailureDurableInputV1({
10
+ attemptedGenerationId: "generation-broken",
11
+ generation: {
12
+ schemaVersion: 1,
13
+ generationId: "generation-broken",
14
+ artifactSetHash: "a".repeat(64),
15
+ createdAt: "2026-09-02T00:00:00.000Z",
16
+ origin: {
17
+ kind: "bot-authored",
18
+ runId: "run-1",
19
+ sessionId: "session-1",
20
+ turnId: "turn-1",
21
+ },
22
+ members: [
23
+ {
24
+ packageId: "weather-lookup",
25
+ specifier: "bot-authored:weather-lookup",
26
+ version: "0.0.1",
27
+ manifestHash: "b".repeat(64),
28
+ provenance: {
29
+ kind: "bot",
30
+ packageId: "weather-lookup",
31
+ version: "0.0.1",
32
+ botId: "bot-1",
33
+ sessionId: "session-1",
34
+ turnId: "turn-1",
35
+ runId: "run-1",
36
+ authoredAt: "2026-09-02T00:00:00.000Z",
37
+ },
38
+ },
39
+ ],
40
+ status: "quarantined",
41
+ },
42
+ failure: {
43
+ generationId: "generation-broken",
44
+ attempt: 3,
45
+ at: "2026-09-02T00:01:00.000Z",
46
+ phase: "health",
47
+ message: "declared tools do not match",
48
+ diagnostics: ["declared=weather_lookup", "reported=other"],
49
+ },
50
+ quarantined: true,
51
+ });
52
+
53
+ expect(text).toContain("Generation: generation-broken");
54
+ expect(text).toContain("Package: weather-lookup");
55
+ expect(text).toContain("Phase: health");
56
+ expect(text).toContain("- reported=other");
57
+ expect(text).toContain("Status: quarantined");
58
+ expect(
59
+ compositionFailureTurnTextV1("please continue", {
60
+ attemptedGenerationId: "generation-broken",
61
+ quarantined: false,
62
+ }),
63
+ ).toEndWith("\n\nplease continue");
64
+ });
65
+ });
@@ -0,0 +1,58 @@
1
+ import type { CompositionFailureV1 } from "@frockbot/kernel-composition/activation";
2
+ import type { CompositionGenerationV1 } from "@frockbot/kernel-composition/generation";
3
+
4
+ /**
5
+ * Model-visible durable input for the Turn that fell back from a broken
6
+ * Composition. The caller places this exact text in `user/message`, from
7
+ * which the exact recorded `model/request` is reconstructed.
8
+ */
9
+ export function compositionFailureDurableInputV1(input: {
10
+ attemptedGenerationId: string;
11
+ generation?: CompositionGenerationV1;
12
+ failure?: CompositionFailureV1;
13
+ quarantined: boolean;
14
+ }): string {
15
+ const origin = input.generation?.origin;
16
+ const authoredPackageId =
17
+ origin?.kind === "bot-authored"
18
+ ? input.generation?.members.find(
19
+ (member) =>
20
+ member.provenance.kind === "bot" &&
21
+ member.provenance.runId === origin.runId,
22
+ )?.packageId
23
+ : undefined;
24
+ const failure = input.failure;
25
+ const packageId =
26
+ authoredPackageId ??
27
+ failure?.message.match(/package "([^"]+)"/i)?.[1] ??
28
+ (input.generation?.members.filter(
29
+ (member) => member.provenance.kind === "bot",
30
+ ).length === 1
31
+ ? input.generation.members.find(
32
+ (member) => member.provenance.kind === "bot",
33
+ )?.packageId
34
+ : undefined);
35
+ const diagnostics = failure?.diagnostics.length
36
+ ? failure.diagnostics.map((entry) => `- ${entry}`).join("\n")
37
+ : "- none";
38
+ return [
39
+ "[Durable Package activation failure]",
40
+ `Generation: ${input.attemptedGenerationId}`,
41
+ `Package: ${packageId ?? "unknown"}`,
42
+ `Phase: ${failure?.phase ?? "resolve"}`,
43
+ `Message: ${failure?.message ?? "This quarantined generation remains unavailable."}`,
44
+ "Diagnostics:",
45
+ diagnostics,
46
+ input.quarantined
47
+ ? "Status: quarantined after repeated activation failures; the last working Package setup was mounted. Author a repair or use package_undo."
48
+ : "Status: activation failed; the last working Package setup was mounted for this Turn. Author a repair or use package_undo.",
49
+ ].join("\n");
50
+ }
51
+
52
+ /** The exact command text handed to the Agent loop on a fallback Turn. */
53
+ export function compositionFailureTurnTextV1(
54
+ ordinaryInput: string,
55
+ failure: Parameters<typeof compositionFailureDurableInputV1>[0],
56
+ ): string {
57
+ return `${compositionFailureDurableInputV1(failure)}\n\n${ordinaryInput}`;
58
+ }
@@ -65,16 +65,18 @@ export interface ShellIsolateMountOptions {
65
65
  turnId: string;
66
66
  loader: BotIsolateLoader;
67
67
  artifacts: BotIsolateArtifactStore;
68
+ /** Reads the exact manifest whose hash the member records. */
69
+ manifestFor(member: CompositionMemberV1): Promise<unknown>;
68
70
  /**
69
71
  * Mints the loopback `CAPABILITIES` service binding for one Package —
70
72
  * `ctx.exports.BotCapabilities({ props })` in the Durable Object.
71
73
  */
72
74
  capabilitiesFor(member: CompositionMemberV1): BotCapabilitiesStub;
73
75
  /**
74
- * Content address of the Assignment-derived bindings this isolate is granted
75
- * the Assignments *and* the Composition generation whose `CAPABILITIES`
76
- * stub is baked into its `env`. Required: it is what keeps a cached isolate
77
- * from answering under a stale authority.
76
+ * Content address of the User-enabled bindings this isolate is granted — the
77
+ * enabled set *and* the Composition generation whose `CAPABILITIES` stub is
78
+ * baked into its `env`. Required: it is what keeps a cached isolate from
79
+ * answering under stale authority (ADR 0019).
78
80
  */
79
81
  bindingDigest: string;
80
82
  compatibilityDate: string;
@@ -177,12 +179,27 @@ export function createShellCompositionHost(
177
179
  loader: isolate.loader,
178
180
  artifacts: isolate.artifacts,
179
181
  tools: runtime.root.tools,
182
+ loop: runtime.root,
180
183
  userId: isolate.userId,
181
184
  botId: options.botId,
182
185
  sessionId: options.sessionId,
183
186
  runId: isolate.runId,
184
187
  turnId: isolate.turnId,
185
188
  generationId: generation.generationId,
189
+ turnType: options.turnType ?? "chat",
190
+ ...(options.subagentRole === undefined
191
+ ? {}
192
+ : { subagentRole: options.subagentRole }),
193
+ recordHookFailure: async (failure) => {
194
+ const session = runtime.root.sessions.get(options.sessionId);
195
+ if (!session) {
196
+ throw new Error(
197
+ `session "${options.sessionId}" is unavailable for hook failure recording`,
198
+ );
199
+ }
200
+ session.append({ type: "package/hook-failed", ...failure });
201
+ await session.flush();
202
+ },
186
203
  capabilities: isolate.capabilitiesFor(member),
187
204
  compatibilityDate: isolate.compatibilityDate,
188
205
  bindingDigest: isolate.bindingDigest,
@@ -192,8 +209,9 @@ export function createShellCompositionHost(
192
209
  : { deadlineMs: isolate.deadlineMs }),
193
210
  });
194
211
  // Mount and health-check are one guarded phase (Worker Loader spike).
212
+ const storedManifest = await isolate.manifestFor(member);
195
213
  const prepared = await host.prepare(
196
- botIsolatePackageDescriptorV1(member),
214
+ await botIsolatePackageDescriptorV1(member, storedManifest),
197
215
  );
198
216
  if (!prepared) {
199
217
  failures.push({