@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
|
@@ -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
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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({
|