@frockbot/plugin-shell 0.0.0 → 0.1.1

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 (73) hide show
  1. package/frockbot.json +68 -0
  2. package/package.json +87 -6
  3. package/src/agent.test.ts +372 -0
  4. package/src/agent.ts +335 -0
  5. package/src/approvals.test.ts +224 -0
  6. package/src/approvals.ts +530 -0
  7. package/src/backend-assignment.test.ts +161 -0
  8. package/src/backend-assignment.ts +274 -0
  9. package/src/backend-authoring.test.ts +518 -0
  10. package/src/backend-authoring.ts +531 -0
  11. package/src/backend-bot-identity.test.ts +215 -0
  12. package/src/backend-completion.test.ts +289 -0
  13. package/src/backend-completion.ts +95 -0
  14. package/src/backend-composition.ts +242 -0
  15. package/src/backend-computer.ts +76 -0
  16. package/src/backend-configuration.test.ts +1757 -0
  17. package/src/backend-contracts.test.ts +189 -0
  18. package/src/backend-contracts.ts +44 -0
  19. package/src/backend-debug.test.ts +202 -0
  20. package/src/backend-execution.ts +55 -0
  21. package/src/backend-flock.ts +96 -0
  22. package/src/backend-image.test.ts +115 -0
  23. package/src/backend-image.ts +180 -0
  24. package/src/backend-isolate.test.ts +238 -0
  25. package/src/backend-isolate.ts +409 -0
  26. package/src/backend-machine.ts +144 -0
  27. package/src/backend-memory.ts +89 -0
  28. package/src/backend-recovery-integration.test.ts +1575 -0
  29. package/src/backend-recovery.ts +106 -0
  30. package/src/backend-routines.ts +375 -0
  31. package/src/backend-runner.ts +251 -0
  32. package/src/backend-skills.test.ts +126 -0
  33. package/src/backend-skills.ts +198 -0
  34. package/src/backend-stop.test.ts +356 -0
  35. package/src/backend-subagents.ts +459 -0
  36. package/src/backend.ts +6035 -0
  37. package/src/client/FrockBotApp.vue +1026 -0
  38. package/src/client/SendPayloadView.vue +337 -0
  39. package/src/client/composer-draft.test.ts +31 -0
  40. package/src/client/composer-draft.ts +35 -0
  41. package/src/client/cordis-client-shim.d.ts +15 -0
  42. package/src/client/index.test.ts +2548 -0
  43. package/src/client/index.ts +2346 -0
  44. package/src/client/model-presentation.test.ts +35 -0
  45. package/src/client/model-presentation.ts +19 -0
  46. package/src/client/notify.test.ts +89 -0
  47. package/src/client/notify.ts +101 -0
  48. package/src/client/skill-invocation.test.ts +143 -0
  49. package/src/client/skill-invocation.ts +175 -0
  50. package/src/client/styles.css +1043 -0
  51. package/src/composition-views.ts +118 -0
  52. package/src/debug-protocol.test.ts +80 -0
  53. package/src/debug-protocol.ts +165 -0
  54. package/src/env.d.ts +10 -0
  55. package/src/history.test.ts +163 -0
  56. package/src/history.ts +108 -0
  57. package/src/host.ts +20 -0
  58. package/src/index.ts +2 -0
  59. package/src/manifest.ts +3 -0
  60. package/src/run-cursor.ts +28 -0
  61. package/src/run-protocol.test.ts +1281 -0
  62. package/src/run-protocol.ts +1417 -0
  63. package/src/settings-links.test.ts +106 -0
  64. package/src/settings-links.ts +289 -0
  65. package/src/shared.ts +338 -0
  66. package/src/skill-protocol.ts +117 -0
  67. package/src/terminal-records.test.ts +217 -0
  68. package/src/terminal-records.ts +150 -0
  69. package/src/unread.test.ts +362 -0
  70. package/src/unread.ts +675 -0
  71. package/tsconfig.json +18 -0
  72. package/vite.config.ts +32 -0
  73. package/README.md +0 -3
@@ -0,0 +1,242 @@
1
+ // The Shell Package owns the Composition a Turn runs on. First-party members
2
+ // are the compiled Foundation runtime, mounted in the kernel isolate; members
3
+ // carrying an immutable artifact are Bot isolate members, mounted through the
4
+ // kernel's `BotIsolateContributionHost` as a loaded Dynamic Worker with
5
+ // `globalOutbound` disabled.
6
+ import {
7
+ createFoundationRuntime,
8
+ type FoundationAgentPackage,
9
+ type FoundationRuntime,
10
+ type RuntimeModelSelection,
11
+ } from "@frockbot/agent-runtime/runtime";
12
+ import { createFoundationRuntimeApplication } from "@frockbot/application-foundation/runtime";
13
+ import type { AgentEffectAdmission } from "@frockbot/kernel-agent-loop/agent";
14
+ import {
15
+ CompositionMountFailureError,
16
+ type CompositionFailurePhaseV1,
17
+ } from "@frockbot/kernel-composition/activation";
18
+ import type { ApplicationPlan } from "@frockbot/kernel-composition/compiler";
19
+ import {
20
+ bootstrapGeneration,
21
+ type CompositionGenerationV1,
22
+ type CompositionHost,
23
+ type CompositionMemberV1,
24
+ type MountedComposition,
25
+ } from "@frockbot/kernel-composition/generation";
26
+ import {
27
+ BotIsolateContributionHost,
28
+ botIsolatePackageDescriptorV1,
29
+ type BotIsolateArtifactStore,
30
+ type BotIsolateLimits,
31
+ type BotIsolateLoader,
32
+ } from "@frockbot/kernel-composition/isolate";
33
+ import type { ActiveContribution } from "@frockbot/kernel-composition";
34
+ import {
35
+ type BotCapabilitiesStub,
36
+ type PersistSessionEvents,
37
+ type SessionEvent,
38
+ type TurnTypeV1,
39
+ } from "@frockbot/kernel-contracts";
40
+
41
+ /** The bootstrap generation for a compiled first-party application. */
42
+ export function bootstrapCompositionGeneration(
43
+ plan: ApplicationPlan,
44
+ createdAt: string,
45
+ ): Promise<CompositionGenerationV1> {
46
+ return bootstrapGeneration(
47
+ plan.packages.map((pkg) => ({
48
+ packageId: pkg.id,
49
+ specifier: pkg.specifier,
50
+ version: pkg.version,
51
+ manifest: pkg.manifest,
52
+ })),
53
+ { createdAt },
54
+ );
55
+ }
56
+
57
+ export interface ShellMountedComposition extends MountedComposition {
58
+ readonly runtime: FoundationRuntime;
59
+ }
60
+
61
+ /** Everything the Bot Durable Object supplies for isolate members. */
62
+ export interface ShellIsolateMountOptions {
63
+ userId: string;
64
+ runId: string;
65
+ turnId: string;
66
+ loader: BotIsolateLoader;
67
+ artifacts: BotIsolateArtifactStore;
68
+ /**
69
+ * Mints the loopback `CAPABILITIES` service binding for one Package —
70
+ * `ctx.exports.BotCapabilities({ props })` in the Durable Object.
71
+ */
72
+ capabilitiesFor(member: CompositionMemberV1): BotCapabilitiesStub;
73
+ /**
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.
78
+ */
79
+ bindingDigest: string;
80
+ compatibilityDate: string;
81
+ limits?: BotIsolateLimits;
82
+ deadlineMs?: number;
83
+ }
84
+
85
+ export interface ShellCompositionMountOptions {
86
+ botId: string;
87
+ sessionId: string;
88
+ sessionEvents: readonly SessionEvent[];
89
+ persistSessionEvents?: PersistSessionEvents;
90
+ agentPackages?: readonly FoundationAgentPackage[];
91
+ modelSelection?: RuntimeModelSelection;
92
+ systemPromptSection?: string;
93
+ /**
94
+ * Durably linearizes each provider or tool effect against Stop immediately
95
+ * before it is used. The Bot Durable Object owns the transaction; the mounted
96
+ * runtime only presents the exact effect identity.
97
+ */
98
+ admitEffect(effect: AgentEffectAdmission): Promise<boolean>;
99
+ /**
100
+ * The turn type the admitted Turn runs on; the mounted Agent trims its tool
101
+ * catalog to it. Absent ⇒ `chat`.
102
+ */
103
+ turnType?: TurnTypeV1;
104
+ /**
105
+ * The subagent role the admitted Turn runs under; the mounted Agent trims
106
+ * its catalog to it as well. Absent ⇒ no role narrowing.
107
+ */
108
+ subagentRole?: string;
109
+ /** Absent when the host cannot load isolates; isolate members then fail verify. */
110
+ isolate?: ShellIsolateMountOptions;
111
+ }
112
+
113
+ export interface ShellCompositionHost extends CompositionHost {
114
+ mount(
115
+ generation: CompositionGenerationV1,
116
+ signal: AbortSignal,
117
+ ): Promise<ShellMountedComposition>;
118
+ }
119
+
120
+ interface MemberVerificationFailure {
121
+ phase: CompositionFailurePhaseV1;
122
+ message: string;
123
+ }
124
+
125
+ function memberFailure(error: unknown): MemberVerificationFailure {
126
+ if (error instanceof CompositionMountFailureError) {
127
+ return { phase: error.phase, message: error.message };
128
+ }
129
+ return {
130
+ phase: "mount",
131
+ message: error instanceof Error ? error.message : String(error),
132
+ };
133
+ }
134
+
135
+ /** Mounts one pinned generation as the Cordis root a single Turn runs on. */
136
+ export function createShellCompositionHost(
137
+ options: ShellCompositionMountOptions,
138
+ ): ShellCompositionHost {
139
+ return {
140
+ async mount(generation, signal) {
141
+ signal.throwIfAborted();
142
+ const runtime = await createFoundationRuntime(undefined, {
143
+ agentId: options.botId,
144
+ sessionId: options.sessionId,
145
+ sessionEvents: options.sessionEvents,
146
+ application: await createFoundationRuntimeApplication(),
147
+ composition: {
148
+ generationId: generation.generationId,
149
+ artifactSetHash: generation.artifactSetHash,
150
+ },
151
+ persistSessionEvents: options.persistSessionEvents,
152
+ admitEffect: options.admitEffect,
153
+ agentPackages: options.agentPackages,
154
+ modelSelection: options.modelSelection,
155
+ systemPromptSection: options.systemPromptSection,
156
+ ...(options.turnType ? { turnType: options.turnType } : {}),
157
+ ...(options.subagentRole ? { subagentRole: options.subagentRole } : {}),
158
+ });
159
+
160
+ const isolateMembers = generation.members.filter(
161
+ (member) => member.artifact !== undefined,
162
+ );
163
+ const active: ActiveContribution[] = [];
164
+ const failures: MemberVerificationFailure[] = [];
165
+ for (const member of isolateMembers) {
166
+ if (!options.isolate) {
167
+ failures.push({
168
+ phase: "mount",
169
+ message: `package "${member.packageId}" needs a Bot isolate and this host has no loader`,
170
+ });
171
+ continue;
172
+ }
173
+ const isolate = options.isolate;
174
+ try {
175
+ signal.throwIfAborted();
176
+ const host = new BotIsolateContributionHost({
177
+ loader: isolate.loader,
178
+ artifacts: isolate.artifacts,
179
+ tools: runtime.root.tools,
180
+ userId: isolate.userId,
181
+ botId: options.botId,
182
+ sessionId: options.sessionId,
183
+ runId: isolate.runId,
184
+ turnId: isolate.turnId,
185
+ generationId: generation.generationId,
186
+ capabilities: isolate.capabilitiesFor(member),
187
+ compatibilityDate: isolate.compatibilityDate,
188
+ bindingDigest: isolate.bindingDigest,
189
+ ...(isolate.limits ? { limits: isolate.limits } : {}),
190
+ ...(isolate.deadlineMs === undefined
191
+ ? {}
192
+ : { deadlineMs: isolate.deadlineMs }),
193
+ });
194
+ // Mount and health-check are one guarded phase (Worker Loader spike).
195
+ const prepared = await host.prepare(
196
+ botIsolatePackageDescriptorV1(member),
197
+ );
198
+ if (!prepared) {
199
+ failures.push({
200
+ phase: "resolve",
201
+ message: `package "${member.packageId}" declared no Bot isolate contribution`,
202
+ });
203
+ continue;
204
+ }
205
+ active.push(await prepared.commit());
206
+ } catch (error) {
207
+ failures.push(memberFailure(error));
208
+ }
209
+ }
210
+
211
+ const dispose = async () => {
212
+ for (const contribution of active.toReversed()) {
213
+ await contribution.dispose();
214
+ }
215
+ await runtime.dispose();
216
+ };
217
+
218
+ return {
219
+ generation,
220
+ root: runtime.root,
221
+ runtime,
222
+ // First-party members run in the kernel isolate and have nothing to
223
+ // health-check; an isolate member that failed to resolve, mount, or
224
+ // answer `health()` surfaces here, carrying the load site it failed at
225
+ // so `activateCompositionV1` records the phase rather than guessing it.
226
+ verify: () => {
227
+ if (failures.length === 0) return Promise.resolve();
228
+ return Promise.reject(
229
+ new CompositionMountFailureError(
230
+ failures[0]!.phase,
231
+ `Composition generation "${generation.generationId}" failed verification: ${failures
232
+ .map((failure) => failure.message)
233
+ .join("; ")}`,
234
+ failures.map((failure) => `${failure.phase}: ${failure.message}`),
235
+ ),
236
+ );
237
+ },
238
+ dispose,
239
+ };
240
+ },
241
+ };
242
+ }
@@ -0,0 +1,76 @@
1
+ // The Bot Durable Object's half of the Computer sync seam (ADR 0013).
2
+ //
3
+ // The durable-root sync reconciles two halves: the Workspace on the Computer,
4
+ // which the Computer provider Package owns, and object storage, whose
5
+ // authority is this Durable Object. This module supplies the second half for
6
+ // one admitted Turn — the store surface, the effect records a push writes
7
+ // before it runs, and the generation ledger a removal's writer is recovered
8
+ // from. It runs no sync and implements no interface; the provider Package does
9
+ // both.
10
+ //
11
+ // ATTRIBUTION. Nothing here names a writer for what the sync finds. "A file
12
+ // that reaches a durable root without passing through the Workspace file
13
+ // surface (a shell write on the Computer) is mirrored to object storage by the
14
+ // sync with an unattributed writer": one Computer serves all of a User's Bots,
15
+ // so this object cannot know which Bot's process wrote a file, and the Turn
16
+ // that happens to be running is not evidence. A Bot that means to author a
17
+ // Skill writes it through the Workspace file surface, which records real
18
+ // provenance.
19
+ //
20
+ // HIBERNATION. Nothing here reaches a Computer. It is a description of what a
21
+ // sync may use if one happens, handed to the provider Package; whether a
22
+ // Computer is awake is decided by the Bot using it and by nothing on this
23
+ // path. "The Computer wakes only when a Bot uses it."
24
+ //
25
+ // SEAM. `WORKSPACE_SYNC_FILES` and `WORKSPACE_SYNC_EFFECTS` are constructed
26
+ // onto the Durable Object environment by `apps/cloudflare/src/bot-state.ts`,
27
+ // the same way `WORKSPACE_FILES` is: neither is a Worker binding, and a host
28
+ // that binds neither simply has no sync — the durable roots then live on the
29
+ // Computer alone, visibly, rather than syncing to a store this module invented.
30
+ import type {
31
+ WorkspaceFilesV1,
32
+ WorkspaceGenerationsV1,
33
+ WorkspaceSyncEffectsV1,
34
+ } from "@frockbot/kernel-contracts";
35
+ import type { ComputerSyncHostV1 } from "@frockbot/computer-core";
36
+
37
+ /**
38
+ * The narrow slice of the Durable Object environment this module reads. Named
39
+ * as its own type so each binding's absence is a typed state, not a cast.
40
+ */
41
+ export interface BotComputerSyncEnv {
42
+ /** The durable roots in object storage, built with the `sync` surface. */
43
+ WORKSPACE_SYNC_FILES?: WorkspaceFilesV1;
44
+ /** Where a push records its intent, in this Bot's Durable Object. */
45
+ WORKSPACE_SYNC_EFFECTS?: WorkspaceSyncEffectsV1;
46
+ /** This object's generation ledger, read to recover a removal's writer. */
47
+ WORKSPACE_SYNC_GENERATIONS?: WorkspaceGenerationsV1;
48
+ }
49
+
50
+ /**
51
+ * The Computer sync seam one admitted Turn runs under, or `undefined` when the
52
+ * object-storage side is unavailable.
53
+ *
54
+ * It takes no identity and no Turn, and that is the point: see ATTRIBUTION
55
+ * above. What a sync finds on the Computer is attributed to nobody, so knowing
56
+ * which Bot, Session, and Turn asked for the sync would only be an invitation
57
+ * to record a writer this seam cannot support.
58
+ */
59
+ export function createBotComputerSyncHost(
60
+ env: object,
61
+ ): ComputerSyncHostV1 | undefined {
62
+ // SAFETY: these surfaces are constructed onto the Durable Object environment
63
+ // rather than declared in the generated `Env`; they are not Worker bindings.
64
+ const bound = env as BotComputerSyncEnv;
65
+ const store = bound.WORKSPACE_SYNC_FILES;
66
+ if (!store) return undefined;
67
+ return {
68
+ store,
69
+ ...(bound.WORKSPACE_SYNC_EFFECTS
70
+ ? { effects: bound.WORKSPACE_SYNC_EFFECTS }
71
+ : {}),
72
+ ...(bound.WORKSPACE_SYNC_GENERATIONS
73
+ ? { generations: bound.WORKSPACE_SYNC_GENERATIONS }
74
+ : {}),
75
+ };
76
+ }