@fungi.computer/computer 0.1.0 → 0.2.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 (39) hide show
  1. package/README.md +38 -10
  2. package/dist/computer-backends.d.ts +0 -1
  3. package/dist/computer-backends.js +0 -3
  4. package/dist/exec-output.d.ts +13 -0
  5. package/dist/exec-output.js +24 -0
  6. package/dist/execution-lifecycle.d.ts +35 -0
  7. package/dist/execution-lifecycle.js +78 -0
  8. package/dist/files-patch.d.ts +12 -4
  9. package/dist/files-patch.js +8 -14
  10. package/dist/files.d.ts +65 -15
  11. package/dist/files.js +164 -31
  12. package/dist/fleet/index.d.ts +1 -1
  13. package/dist/fleet/index.js +1 -1
  14. package/dist/fleet/inventory-wire.d.ts +6 -2
  15. package/dist/fleet/inventory-wire.js +7 -2
  16. package/dist/fleet/lifecycle.d.ts +7 -1
  17. package/dist/fleet/lifecycle.js +22 -0
  18. package/dist/fleet/wire.d.ts +12 -12
  19. package/dist/fleet/wire.js +1 -1
  20. package/dist/index.d.ts +33 -15
  21. package/dist/index.js +422 -105
  22. package/dist/internet-access.d.ts +4 -0
  23. package/dist/internet-access.js +14 -0
  24. package/dist/local-backend-replay.d.ts +10 -0
  25. package/dist/local-backend-replay.js +43 -0
  26. package/dist/local-backend.d.ts +2 -1
  27. package/dist/local-backend.js +4 -4
  28. package/dist/on-box-vm.d.ts +11 -0
  29. package/dist/on-box-vm.js +36 -13
  30. package/dist/operation-record.d.ts +1 -1
  31. package/dist/operation-record.js +1 -1
  32. package/dist/process-inventory.d.ts +3 -1
  33. package/dist/process-inventory.js +2 -2
  34. package/dist/team-candidate-builder.mjs +8375 -334
  35. package/package.json +17 -24
  36. package/dist/light-shell.d.ts +0 -44
  37. package/dist/light-shell.js +0 -376
  38. package/dist/operation-contract.d.ts +0 -456
  39. package/dist/operation-contract.js +0 -450
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/computer",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Cloudflare Computer host for one durable Fungi Computer",
6
6
  "type": "module",
@@ -27,10 +27,6 @@
27
27
  "types": "./dist/host-config.d.ts",
28
28
  "import": "./dist/host-config.js"
29
29
  },
30
- "./operations": {
31
- "types": "./dist/operation-contract.d.ts",
32
- "import": "./dist/operation-contract.js"
33
- },
34
30
  "./container-policy": {
35
31
  "types": "./dist/container-policy.d.ts",
36
32
  "import": "./dist/container-policy.js"
@@ -46,10 +42,6 @@
46
42
  "./egress": {
47
43
  "types": "./dist/egress-policy.d.ts",
48
44
  "import": "./dist/egress-policy.js"
49
- },
50
- "./light-shell": {
51
- "types": "./dist/light-shell.d.ts",
52
- "import": "./dist/light-shell.js"
53
45
  }
54
46
  },
55
47
  "files": [
@@ -60,27 +52,28 @@
60
52
  "LICENSE"
61
53
  ],
62
54
  "dependencies": {
63
- "@fungi.computer/cairn": "0.0.1",
64
- "@fungi.computer/cloudflare-computer": "0.2.1-fungi.1",
65
- "@fungi.computer/cordyceps": "0.2.2",
66
- "@fungi.computer/knapsack": "0.1.0",
55
+ "@fungi.computer/cairn": "0.1.0",
67
56
  "@fungi.computer/capnweb": "0.12.0",
57
+ "@fungi.computer/cloudflare-computer": "0.2.1-fungi.1",
58
+ "@fungi.computer/cordyceps": "0.3.1",
59
+ "@fungi.computer/knapsack": "0.2.0",
60
+ "@fungi.computer/seam": "0.1.0",
61
+ "@fungi.computer/watchdog": "0.1.0",
62
+ "@fungi.computer/computer-contract": "0.0.1",
68
63
  "effect": "4.0.2",
69
- "zod": "4.4.3",
70
- "@fungi.computer/seam": "0.0.1",
71
- "@fungi.computer/watchdog": "0.0.1",
72
- "just-bash": "3.4.2"
64
+ "just-bash": "3.4.2",
65
+ "zod": "4.4.3"
73
66
  },
74
67
  "devDependencies": {
75
68
  "@cloudflare/workers-types": "5.20260828.1",
76
69
  "@types/node": "24.13.3",
77
70
  "esbuild": "0.28.2",
78
- "typescript": "5.9.3",
79
- "uuid": "11.1.1",
80
- "wrangler": "4.127.1",
81
71
  "eslint": "10.9.1",
82
72
  "oxlint": "1.78.0",
83
- "tsx": "4.23.12"
73
+ "tsx": "4.23.12",
74
+ "typescript": "5.9.3",
75
+ "uuid": "11.1.1",
76
+ "wrangler": "4.127.1"
84
77
  },
85
78
  "botanical": {
86
79
  "role": "runtime",
@@ -114,15 +107,15 @@
114
107
  "build": "pnpm run clean && tsc -p tsconfig.build.json && node scripts/copy-daemon.mjs",
115
108
  "build:native": "fungi-apply-patch-build --out native/bin",
116
109
  "clean": "node --input-type=module --eval \"import { rm } from 'node:fs/promises'; await rm('dist', { recursive: true, force: true });\"",
117
- "test": "pnpm run test:unit && pnpm run test:fleet-native && node test/port-proof.mjs && node test/terminal-container-proof.mjs && node test/fuse-startup-proof.mjs && node test/container-resource-proof.mjs && node test/runtime-proof.mjs && node test/process-schema-proof.mjs && node test/process-proof.mjs && node test/terminal-proof.mjs && pnpm run test:app-method-execution",
110
+ "test": "pnpm run test:unit && pnpm run test:fleet-native && node test/port-proof.mjs && node test/terminal-container-proof.mjs && node test/fuse-startup-proof.mjs && node test/container-resource-proof.mjs && node test/runtime-proof.mjs && node test/process-schema-proof.mjs && node test/process-proof.mjs && node test/terminal-proof.mjs && pnpm run test:app-method-execution && pnpm run test:execution-lifecycle",
118
111
  "format:check": "node ../../tooling/repository/scripts/format-package-check.mjs",
119
112
  "lint:anti-slop": "node ../../tooling/repository/scripts/lint-package-anti-slop.mjs",
120
113
  "test:merge": "pnpm run test",
121
114
  "test:nightly": "bash ../../.agents/skills/orchestrate-multi-lane-work/scripts/run-proof.sh node test/egress-proof.mjs",
122
115
  "test:app-method-execution": "node test/app-method-execution-proof.mjs",
123
- "test:unit": "node --test test/egress-policy.test.mjs test/preview.test.mjs test/light-shell.test.mjs test/fleet.test.mjs test/proof-worker.test.mjs test/workspace-sync.test.mjs test/files-patch.test.mjs && node test/reserved-ports.test.mjs && node --test test/seam-signature.test.mjs test/computer-backends.test.mjs test/terminal-welcome.test.mjs && node test/terminal-protocol.test.mjs && node test/container-policy.test.mjs && node test/files-adapter-proof.mjs && node test/operation-record-proof.mjs && node test/process-inventory-proof.mjs",
116
+ "test:execution-lifecycle": "node test/execution-lifecycle-proof.mjs",
117
+ "test:unit": "node --test test/egress-policy.test.mjs test/preview.test.mjs test/fleet.test.mjs test/proof-worker.test.mjs test/workspace-sync.test.mjs test/files-patch.test.mjs test/exec-output.test.mjs && node --test test/seam-signature.test.mjs test/local-backend-replay.test.mjs test/machine-link-reopen-replay.test.mjs test/computer-backends.test.mjs test/terminal-welcome.test.mjs test/on-box-vm.test.mjs && node test/terminal-protocol.test.mjs && node test/container-policy.test.mjs && node test/files-adapter-proof.mjs && node test/operation-record-proof.mjs && node test/process-inventory-proof.mjs",
124
118
  "test:fleet-native": "node test/fleet.native.mjs",
125
- "test:light-shell": "node --test test/light-shell.test.mjs",
126
119
  "test:light-shell:native": "node --import tsx test/light-shell.native.mjs --state",
127
120
  "assemble:image": "node scripts/assemble-image.mjs"
128
121
  }
@@ -1,44 +0,0 @@
1
- export type ShellRoute = Readonly<{
2
- where: "light";
3
- }> | Readonly<{
4
- where: "full";
5
- reason: string;
6
- }> | Readonly<{
7
- where: "invalid";
8
- message: string;
9
- }>;
10
- export declare const lightShellCommands: readonly string[];
11
- /** Route the maintained parser's AST before either backend executes anything. */
12
- export declare function routeShellCommand(source: string): ShellRoute;
13
- export type CommandCompletion = Readonly<{
14
- name: string;
15
- where: "light" | "full";
16
- }>;
17
- /** First-word completion reads the maintained AST; quoted/path completion is deferred. */
18
- export declare function completeShellCommand(source: string): readonly CommandCompletion[] | null | Readonly<{
19
- kind: "invalid";
20
- message: string;
21
- }>;
22
- /**
23
- * `cd`/`export` route light (they need no external program), but the exec
24
- * primitive behind a light command has no state event yet: each run is its
25
- * own process, so a bare `cd`/`export` would report success and change
26
- * nothing the next command sees. True only for a line that is exactly one
27
- * such invocation, so the caller can refuse it honestly instead of faking a
28
- * result; a `cd`/`export` combined with other work is not detected here and
29
- * still runs, silently without the navigation/export effect, as today.
30
- */
31
- export declare function bareNavigationCommand(source: string): Readonly<{
32
- name: "cd" | "export";
33
- }> | null;
34
- /** Prompt controls never become a backend command or change model shell grammar. */
35
- export declare function prepareShellPrompt(source: string): Readonly<{
36
- kind: "command";
37
- command: string;
38
- route: ShellRoute;
39
- }> | Readonly<{
40
- kind: "light" | "exit";
41
- }> | Readonly<{
42
- kind: "invalid";
43
- message: string;
44
- }>;
@@ -1,376 +0,0 @@
1
- import * as z from "zod";
2
- import { Bash, getCommandNames } from "just-bash/browser";
3
- // These optional groups are intentionally absent from the Worker shell.
4
- const deferredGroups = new Set(["sqlite3", "html-to-markdown"]);
5
- export const lightShellCommands = [
6
- ...getCommandNames().filter((name) => !deferredGroups.has(name)),
7
- "cd",
8
- "export",
9
- ];
10
- const lightCommands = new Set(lightShellCommands);
11
- // Constructed lazily: a module that only needs the command set or types
12
- // (for example, a mounting React tree before any command is typed) should
13
- // never pay for a real just-bash parser instance.
14
- let parser;
15
- function shellParser() {
16
- parser ??= new Bash({ commands: [] });
17
- return parser;
18
- }
19
- function literalWord(parts) {
20
- let value = "";
21
- for (const part of parts) {
22
- if (part.type === "DoubleQuoted") {
23
- const quoted = literalWord(part.parts);
24
- if (quoted === null)
25
- return null;
26
- value += quoted;
27
- }
28
- else if (part.type === "Literal" ||
29
- part.type === "SingleQuoted" ||
30
- part.type === "Escaped") {
31
- value += part.value;
32
- }
33
- else
34
- return null;
35
- }
36
- return value;
37
- }
38
- function routeStatements(statements) {
39
- for (const statement of statements) {
40
- if (statement.background)
41
- return { where: "full", reason: "background job" };
42
- if (statement.deferredError)
43
- return { where: "invalid", message: statement.deferredError.message };
44
- for (const pipeline of statement.pipelines) {
45
- for (const command of pipeline.commands) {
46
- const route = routeCommand(command);
47
- if (route.where !== "light")
48
- return route;
49
- }
50
- }
51
- }
52
- return { where: "light" };
53
- }
54
- function routeCommand(command) {
55
- for (const redirection of command.redirections) {
56
- if (redirection.target.type === "HereDoc" && redirection.target.quoted)
57
- continue;
58
- const word = redirection.target.type === "HereDoc"
59
- ? redirection.target.content
60
- : redirection.target;
61
- const route = routeWord(word.parts);
62
- if (route.where !== "light")
63
- return route;
64
- }
65
- if (command.type === "SimpleCommand") {
66
- const name = command.name === null ? null : literalWord(command.name.parts);
67
- if (name !== null && !lightCommands.has(name))
68
- return { where: "full", reason: name };
69
- const words = [
70
- ...(command.name === null ? [] : [command.name]),
71
- ...command.args,
72
- ...command.assignments.flatMap((assignment) => [
73
- ...(assignment.value === null ? [] : [assignment.value]),
74
- ...(assignment.array ?? []),
75
- ]),
76
- ];
77
- for (const word of words) {
78
- const route = routeWord(word.parts);
79
- if (route.where !== "light")
80
- return route;
81
- }
82
- return { where: "light" };
83
- }
84
- if (command.type === "If") {
85
- for (const clause of command.clauses) {
86
- const route = routeStatements([...clause.condition, ...clause.body]);
87
- if (route.where !== "light")
88
- return route;
89
- }
90
- return routeStatements(command.elseBody ?? []);
91
- }
92
- if (command.type === "While" || command.type === "Until")
93
- return routeStatements([...command.condition, ...command.body]);
94
- if (command.type === "Subshell" || command.type === "Group")
95
- return routeStatements(command.body);
96
- if (command.type === "FunctionDef")
97
- return routeCommand(command.body);
98
- if (command.type === "For") {
99
- for (const word of command.words ?? []) {
100
- const route = routeWord(word.parts);
101
- if (route.where !== "light")
102
- return route;
103
- }
104
- return routeStatements(command.body);
105
- }
106
- if (command.type === "Case") {
107
- const route = routeWord(command.word.parts);
108
- if (route.where !== "light")
109
- return route;
110
- for (const item of command.items) {
111
- for (const pattern of item.patterns) {
112
- const route = routeWord(pattern.parts);
113
- if (route.where !== "light")
114
- return route;
115
- }
116
- const route = routeStatements(item.body);
117
- if (route.where !== "light")
118
- return route;
119
- }
120
- return { where: "light" };
121
- }
122
- return { where: "full", reason: "shell expression" };
123
- }
124
- function routeWord(parts) {
125
- for (const part of parts) {
126
- if (part.type === "CommandSubstitution" ||
127
- part.type === "ProcessSubstitution") {
128
- const nested = routeStatements(part.body.statements);
129
- if (nested.where !== "light")
130
- return nested;
131
- }
132
- else if (part.type === "DoubleQuoted") {
133
- const nested = routeWord(part.parts);
134
- if (nested.where !== "light")
135
- return nested;
136
- }
137
- else if (part.type === "ArithmeticExpansion") {
138
- const nested = routeArithmetic(part.expression.expression);
139
- if (nested.where !== "light")
140
- return nested;
141
- }
142
- else if (part.type === "ParameterExpansion") {
143
- const nested = routeParameter(part.operation);
144
- if (nested.where !== "light")
145
- return nested;
146
- }
147
- }
148
- return { where: "light" };
149
- }
150
- function routeArithmetic(expression) {
151
- if (expression.type === "ArithCommandSubst")
152
- return routeShellCommand(expression.command);
153
- // These forms expand and reparse text at execution time. Never replay them.
154
- if (expression.type === "ArithBracedExpansion" ||
155
- expression.type === "ArithDynamicBase" ||
156
- expression.type === "ArithDynamicNumber")
157
- return { where: "full", reason: "dynamic arithmetic" };
158
- const children = [
159
- ...("left" in expression ? [expression.left] : []),
160
- ...("right" in expression ? [expression.right] : []),
161
- ...("operand" in expression ? [expression.operand] : []),
162
- ...("condition" in expression ? [expression.condition] : []),
163
- ...("consequent" in expression ? [expression.consequent] : []),
164
- ...("alternate" in expression ? [expression.alternate] : []),
165
- ...(expression.type === "ArithAssignment" ||
166
- expression.type === "ArithDynamicAssignment"
167
- ? [expression.value]
168
- : []),
169
- ...("subscript" in expression ? [expression.subscript] : []),
170
- ...("target" in expression ? [expression.target] : []),
171
- ...("nameExpr" in expression ? [expression.nameExpr] : []),
172
- ...("index" in expression ? [expression.index] : []),
173
- ...("expression" in expression ? [expression.expression] : []),
174
- ...("parts" in expression ? expression.parts : []),
175
- ];
176
- for (const child of children) {
177
- const route = routeArithmetic(child);
178
- if (route.where !== "light")
179
- return route;
180
- }
181
- return { where: "light" };
182
- }
183
- function routeParameter(operation) {
184
- if (!operation)
185
- return { where: "light" };
186
- const words = [
187
- ...("word" in operation && operation.word ? [operation.word] : []),
188
- ...("pattern" in operation && operation.pattern ? [operation.pattern] : []),
189
- ...("replacement" in operation && operation.replacement
190
- ? [operation.replacement]
191
- : []),
192
- ];
193
- for (const word of words) {
194
- const route = routeWord(word.parts);
195
- if (route.where !== "light")
196
- return route;
197
- }
198
- return "innerOp" in operation
199
- ? routeParameter(operation.innerOp)
200
- : { where: "light" };
201
- }
202
- function parseShell(source) {
203
- try {
204
- return { kind: "parsed", ast: shellParser().transform(source).ast };
205
- }
206
- catch (cause) {
207
- return {
208
- kind: "invalid",
209
- message: cause instanceof Error ? cause.message : "Invalid shell syntax",
210
- };
211
- }
212
- }
213
- /** Route the maintained parser's AST before either backend executes anything. */
214
- export function routeShellCommand(source) {
215
- const parsed = parseShell(source);
216
- return parsed.kind === "invalid"
217
- ? { where: "invalid", message: parsed.message }
218
- : routeStatements(parsed.ast.statements);
219
- }
220
- const fullCompletionCommands = [
221
- "bun",
222
- "cargo",
223
- "curl",
224
- "docker",
225
- "git",
226
- "go",
227
- "make",
228
- "nano",
229
- "node",
230
- "npm",
231
- "npx",
232
- "pnpm",
233
- "python3",
234
- "ssh",
235
- "vim",
236
- "wget",
237
- ];
238
- const completionCommands = [
239
- ...new Set([...lightShellCommands, ...fullCompletionCommands]),
240
- ].sort();
241
- /** First-word completion reads the maintained AST; quoted/path completion is deferred. */
242
- export function completeShellCommand(source) {
243
- const parsed = parseShell(source);
244
- if (parsed.kind === "invalid")
245
- return parsed;
246
- const statement = parsed.ast.statements[0];
247
- if (statement === undefined ||
248
- parsed.ast.statements.length !== 1 ||
249
- statement.background ||
250
- statement.deferredError ||
251
- statement.pipelines.length !== 1)
252
- return null;
253
- const pipeline = statement.pipelines[0];
254
- if (pipeline === undefined)
255
- return null;
256
- const command = pipeline.commands[0];
257
- if (command === undefined ||
258
- pipeline.commands.length !== 1 ||
259
- pipeline.negated ||
260
- pipeline.timed ||
261
- command.type !== "SimpleCommand" ||
262
- command.assignments.length !== 0 ||
263
- command.redirections.length !== 0 ||
264
- command.args.length !== 0 ||
265
- command.name === null)
266
- return null;
267
- const parts = command.name.parts;
268
- const part = parts[0];
269
- if (parts.length !== 1 || part?.type !== "Literal" || part.value !== source)
270
- return null;
271
- return completionCommands
272
- .filter((name) => name.startsWith(source))
273
- .map((name) => ({
274
- name,
275
- where: lightCommands.has(name) ? "light" : "full",
276
- }));
277
- }
278
- /**
279
- * `cd`/`export` route light (they need no external program), but the exec
280
- * primitive behind a light command has no state event yet: each run is its
281
- * own process, so a bare `cd`/`export` would report success and change
282
- * nothing the next command sees. True only for a line that is exactly one
283
- * such invocation, so the caller can refuse it honestly instead of faking a
284
- * result; a `cd`/`export` combined with other work is not detected here and
285
- * still runs, silently without the navigation/export effect, as today.
286
- */
287
- export function bareNavigationCommand(source) {
288
- const parsed = parseShell(source);
289
- if (parsed.kind === "invalid")
290
- return null;
291
- const statement = parsed.ast.statements[0];
292
- if (statement === undefined ||
293
- parsed.ast.statements.length !== 1 ||
294
- statement.background ||
295
- statement.deferredError ||
296
- statement.pipelines.length !== 1)
297
- return null;
298
- const pipeline = statement.pipelines[0];
299
- if (pipeline === undefined)
300
- return null;
301
- const command = pipeline.commands[0];
302
- if (command === undefined ||
303
- pipeline.commands.length !== 1 ||
304
- pipeline.negated ||
305
- pipeline.timed ||
306
- command.type !== "SimpleCommand" ||
307
- command.redirections.length !== 0)
308
- return null;
309
- const name = command.name === null ? null : literalWord(command.name.parts);
310
- return name === "cd" || name === "export" ? { name } : null;
311
- }
312
- const promptControlSchema = z.enum(["ordinary", "full", "light", "exit"]);
313
- let promptParser;
314
- function shellPromptParser() {
315
- if (promptParser !== undefined)
316
- return promptParser;
317
- const configured = new Bash({ commands: [] });
318
- configured.registerTransformPlugin({
319
- name: "fungi-shell-prompt",
320
- transform: ({ ast }) => {
321
- const statement = ast.statements[0];
322
- const pipeline = statement?.pipelines[0];
323
- const command = pipeline?.commands[0];
324
- if (command?.type !== "SimpleCommand" || command.assignments.length !== 0)
325
- return { ast, metadata: { control: "ordinary" } };
326
- const name = command.name === null ? null : literalWord(command.name.parts);
327
- const standalone = ast.statements.length === 1 &&
328
- statement?.pipelines.length === 1 &&
329
- pipeline?.commands.length === 1 &&
330
- !statement.background &&
331
- command.redirections.length === 0;
332
- if ((name === "light" || name === "exit") &&
333
- standalone &&
334
- command.args.length === 0)
335
- return { ast, metadata: { control: name } };
336
- if (name !== "full")
337
- return { ast, metadata: { control: "ordinary" } };
338
- const next = command.args.shift();
339
- if (next !== undefined) {
340
- command.name = next;
341
- return { ast, metadata: { control: "full" } };
342
- }
343
- return standalone
344
- ? { ast: { ...ast, statements: [] }, metadata: { control: "full" } }
345
- : { ast, metadata: { control: "ordinary" } };
346
- },
347
- });
348
- promptParser = configured;
349
- return configured;
350
- }
351
- /** Prompt controls never become a backend command or change model shell grammar. */
352
- export function prepareShellPrompt(source) {
353
- try {
354
- const transformed = shellPromptParser().transform(source);
355
- const control = promptControlSchema.parse(transformed.metadata.control);
356
- if (control === "light" || control === "exit")
357
- return { kind: control };
358
- const command = control === "full" ? transformed.script : source;
359
- const route = routeStatements(transformed.ast.statements);
360
- return {
361
- kind: "command",
362
- command,
363
- route: route.where === "invalid"
364
- ? route
365
- : control === "full"
366
- ? { where: "full", reason: "explicit full" }
367
- : route,
368
- };
369
- }
370
- catch (cause) {
371
- return {
372
- kind: "invalid",
373
- message: cause instanceof Error ? cause.message : "Invalid shell syntax",
374
- };
375
- }
376
- }