@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.
- package/frockbot.json +68 -0
- package/package.json +87 -6
- package/src/agent.test.ts +372 -0
- package/src/agent.ts +335 -0
- package/src/approvals.test.ts +224 -0
- package/src/approvals.ts +530 -0
- package/src/backend-assignment.test.ts +161 -0
- package/src/backend-assignment.ts +274 -0
- package/src/backend-authoring.test.ts +518 -0
- package/src/backend-authoring.ts +531 -0
- package/src/backend-bot-identity.test.ts +215 -0
- package/src/backend-completion.test.ts +289 -0
- package/src/backend-completion.ts +95 -0
- package/src/backend-composition.ts +242 -0
- package/src/backend-computer.ts +76 -0
- package/src/backend-configuration.test.ts +1757 -0
- package/src/backend-contracts.test.ts +189 -0
- package/src/backend-contracts.ts +44 -0
- package/src/backend-debug.test.ts +202 -0
- package/src/backend-execution.ts +55 -0
- package/src/backend-flock.ts +96 -0
- package/src/backend-image.test.ts +115 -0
- package/src/backend-image.ts +180 -0
- package/src/backend-isolate.test.ts +238 -0
- package/src/backend-isolate.ts +409 -0
- package/src/backend-machine.ts +144 -0
- package/src/backend-memory.ts +89 -0
- package/src/backend-recovery-integration.test.ts +1575 -0
- package/src/backend-recovery.ts +106 -0
- package/src/backend-routines.ts +375 -0
- package/src/backend-runner.ts +251 -0
- package/src/backend-skills.test.ts +126 -0
- package/src/backend-skills.ts +198 -0
- package/src/backend-stop.test.ts +356 -0
- package/src/backend-subagents.ts +459 -0
- package/src/backend.ts +6035 -0
- package/src/client/FrockBotApp.vue +1026 -0
- package/src/client/SendPayloadView.vue +337 -0
- package/src/client/composer-draft.test.ts +31 -0
- package/src/client/composer-draft.ts +35 -0
- package/src/client/cordis-client-shim.d.ts +15 -0
- package/src/client/index.test.ts +2548 -0
- package/src/client/index.ts +2346 -0
- package/src/client/model-presentation.test.ts +35 -0
- package/src/client/model-presentation.ts +19 -0
- package/src/client/notify.test.ts +89 -0
- package/src/client/notify.ts +101 -0
- package/src/client/skill-invocation.test.ts +143 -0
- package/src/client/skill-invocation.ts +175 -0
- package/src/client/styles.css +1043 -0
- package/src/composition-views.ts +118 -0
- package/src/debug-protocol.test.ts +80 -0
- package/src/debug-protocol.ts +165 -0
- package/src/env.d.ts +10 -0
- package/src/history.test.ts +163 -0
- package/src/history.ts +108 -0
- package/src/host.ts +20 -0
- package/src/index.ts +2 -0
- package/src/manifest.ts +3 -0
- package/src/run-cursor.ts +28 -0
- package/src/run-protocol.test.ts +1281 -0
- package/src/run-protocol.ts +1417 -0
- package/src/settings-links.test.ts +106 -0
- package/src/settings-links.ts +289 -0
- package/src/shared.ts +338 -0
- package/src/skill-protocol.ts +117 -0
- package/src/terminal-records.test.ts +217 -0
- package/src/terminal-records.ts +150 -0
- package/src/unread.test.ts +362 -0
- package/src/unread.ts +675 -0
- package/tsconfig.json +18 -0
- package/vite.config.ts +32 -0
- 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
|
+
}
|