@fungi.computer/computer 0.3.1 → 0.4.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.
@@ -10280,6 +10280,7 @@ var ImportTableEntry = class {
10280
10280
  this.activePull.reject(error);
10281
10281
  this.activePull = void 0;
10282
10282
  }
10283
+ if (this.onBrokenRegistrations) for (let i of this.onBrokenRegistrations) delete this.session.onBrokenCallbacks[i];
10283
10284
  this.onBrokenRegistrations = void 0;
10284
10285
  }
10285
10286
  }
@@ -10437,6 +10438,7 @@ var RpcSessionImpl = class {
10437
10438
  refcount: 1
10438
10439
  };
10439
10440
  this.reverseExports.set(hook, exportId);
10441
+ this.watchExport(exportId, hook);
10440
10442
  return exportId;
10441
10443
  }
10442
10444
  }
@@ -10454,6 +10456,22 @@ var RpcSessionImpl = class {
10454
10456
  unexport(ids) {
10455
10457
  for (let id of ids) this.releaseExport(id, 1);
10456
10458
  }
10459
+ watchExport(exportId, hook) {
10460
+ hook.onBroken((error) => {
10461
+ queueMicrotask(() => {
10462
+ if (this.abortReason !== void 0 || this.exports[exportId]?.hook !== hook) return;
10463
+ try {
10464
+ this.send([
10465
+ "reject",
10466
+ exportId,
10467
+ Devaluator.devaluate(error, void 0, this, void 0, this.encodingLevel)
10468
+ ]);
10469
+ } catch (err) {
10470
+ this.abort(err);
10471
+ }
10472
+ });
10473
+ });
10474
+ }
10457
10475
  releaseExport(exportId, refcount) {
10458
10476
  let entry = this.exports[exportId];
10459
10477
  if (!entry) throw new Error(`no such export ID: ${exportId}`);
@@ -10720,8 +10738,8 @@ var RpcSessionImpl = class {
10720
10738
  } catch (err) {
10721
10739
  Promise.resolve(err);
10722
10740
  }
10723
- for (let i in this.onBrokenCallbacks) try {
10724
- this.onBrokenCallbacks[i](error);
10741
+ for (let callback of Object.values(this.onBrokenCallbacks)) try {
10742
+ callback(error);
10725
10743
  } catch (err) {
10726
10744
  Promise.resolve(err);
10727
10745
  }
@@ -10772,11 +10790,13 @@ var RpcSessionImpl = class {
10772
10790
  case "pipe": {
10773
10791
  let { readable, writable } = new TransformStream();
10774
10792
  let hook = streamImpl.createWritableStreamHook(writable);
10793
+ let exportId = this.exports.length;
10775
10794
  this.exports.push({
10776
10795
  hook,
10777
10796
  refcount: 1,
10778
10797
  pipeReadable: readable
10779
10798
  });
10799
+ this.watchExport(exportId, hook);
10780
10800
  continue;
10781
10801
  }
10782
10802
  case "pull": {
@@ -11279,6 +11299,7 @@ var WritableStreamStubHook = class WritableStreamStubHook2 extends StubHook {
11279
11299
  }
11280
11300
  }
11281
11301
  onBroken(callback) {
11302
+ this.getState().writer.closed.then(void 0, callback);
11282
11303
  }
11283
11304
  };
11284
11305
  var INITIAL_WINDOW = 262144;
@@ -11373,6 +11394,20 @@ function createWritableStreamFromHook(hook) {
11373
11394
  }
11374
11395
  };
11375
11396
  return new WritableStream({
11397
+ start(controller) {
11398
+ hook.onBroken((err) => {
11399
+ if (pendingError === void 0) {
11400
+ pendingError = err;
11401
+ controller.error(err);
11402
+ disposeHook();
11403
+ }
11404
+ if (windowReject) {
11405
+ windowReject(err);
11406
+ windowResolve = void 0;
11407
+ windowReject = void 0;
11408
+ }
11409
+ });
11410
+ },
11376
11411
  write(chunk, controller) {
11377
11412
  if (pendingError !== void 0) throw pendingError;
11378
11413
  const payload = RpcPayload.fromAppParams([chunk]);
@@ -1,3 +1,3 @@
1
- import type { WorkspaceRuntimeExecHandle } from "@fungi.computer/cloudflare-computer";
1
+ import type { WorkspaceRuntimeExecHandle, WorkspaceRuntimeResultOptions } from "@fungi.computer/cloudflare-computer";
2
2
  /** A host deadline cancels the owned execution, rather than merely abandoning its result. */
3
- export declare function computerExecutionResult(run: WorkspaceRuntimeExecHandle<"utf8">, signal?: AbortSignal): Promise<import("@fungi.computer/cloudflare-computer").WorkspaceRuntimeResult<"utf8">>;
3
+ export declare function computerExecutionResult(run: WorkspaceRuntimeExecHandle<"utf8">, signal?: AbortSignal, options?: WorkspaceRuntimeResultOptions): Promise<import("@fungi.computer/cloudflare-computer").WorkspaceRuntimeResult<"utf8">>;
@@ -1,7 +1,7 @@
1
1
  /** A host deadline cancels the owned execution, rather than merely abandoning its result. */
2
- export async function computerExecutionResult(run, signal) {
2
+ export async function computerExecutionResult(run, signal, options) {
3
3
  if (signal === undefined)
4
- return await run.result();
4
+ return await run.result(options);
5
5
  let interrupt = () => undefined;
6
6
  const interrupted = new Promise((resolve) => {
7
7
  interrupt = () => {
@@ -13,7 +13,7 @@ export async function computerExecutionResult(run, signal) {
13
13
  });
14
14
  try {
15
15
  const result = await Promise.race([
16
- run.result().then((value) => ({ kind: "result", value })),
16
+ run.result(options).then((value) => ({ kind: "result", value })),
17
17
  interrupted,
18
18
  ]);
19
19
  if (result.kind === "result")
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ export type { WorkspaceRuntimeResultOptions } from "@fungi.computer/cloudflare-computer";
1
2
  export { createLocalBackendReplayStore } from "./local-backend-replay.js";
2
- import { Workspace, WorkspaceProxy, WorkspaceServiceProxy, type WorkspaceStub } from "@fungi.computer/cloudflare-computer";
3
+ import { Workspace, WorkspaceProxy, WorkspaceServiceProxy, type WorkspaceStub, type WorkspaceRuntimeResultOptions } from "@fungi.computer/cloudflare-computer";
3
4
  import { type WorkerShellBackendOptions } from "@fungi.computer/cloudflare-computer/backends/worker-shell";
4
5
  import { type IWorkspaceContainerAPI } from "@fungi.computer/cloudflare-computer/backends/container";
5
6
  import { DurableObject } from "cloudflare:workers";
@@ -250,7 +251,7 @@ export declare class ComputerDO extends ContainerComputer implements ComputerFil
250
251
  * or the first available in the default order. Host-only `hosted` execution
251
252
  * excludes local backends, including an explicitly named local backend.
252
253
  */
253
- protected executeOperationAfterAcceptance(input: ComputerExecRequest, accept: () => void, kind: "exec" | "bash" | "hosted", budget?: ComputerExecutionBudget, signal?: AbortSignal, executionContext?: ComputerValue): Promise<ComputerOperationResult>;
254
+ protected executeOperationAfterAcceptance(input: ComputerExecRequest, accept: () => void, kind: "exec" | "bash" | "hosted", budget?: ComputerExecutionBudget, signal?: AbortSignal, executionContext?: ComputerValue, resultOptions?: WorkspaceRuntimeResultOptions): Promise<ComputerOperationResult>;
254
255
  /**
255
256
  * Run one command on a routed backend. Owners admit and meter hosted
256
257
  * backends here; a member's own machine is neither reserved nor metered.
package/dist/index.js CHANGED
@@ -131,7 +131,7 @@ async function write(workspace, input) {
131
131
  bytes: utf8.encode(content).byteLength,
132
132
  });
133
133
  }
134
- async function exec(workspace, input, backend, signal, execution) {
134
+ async function exec(workspace, input, backend, signal, execution, resultOptions) {
135
135
  const env_1 = { stack: [], error: void 0, hasError: false };
136
136
  try {
137
137
  // The host request has already been decoded under its admitted budget.
@@ -149,7 +149,7 @@ async function exec(workspace, input, backend, signal, execution) {
149
149
  execution?.dispatch();
150
150
  const run = __addDisposableResource(env_1, await workspace.runtime.exec(command, nativeOptions), false);
151
151
  execution?.opened();
152
- const result = await computerExecutionResult(run, signal);
152
+ const result = await computerExecutionResult(run, signal, resultOptions);
153
153
  const synchronizationFailure = result.sync.status === "pending"
154
154
  ? `Computer command filesystem synchronization failed: ${result.sync.error}`
155
155
  : null;
@@ -1009,7 +1009,7 @@ export class ComputerDO extends ContainerComputer {
1009
1009
  * or the first available in the default order. Host-only `hosted` execution
1010
1010
  * excludes local backends, including an explicitly named local backend.
1011
1011
  */
1012
- async executeOperationAfterAcceptance(input, accept, kind, budget = genericComputerExecutionBudget, signal, executionContext) {
1012
+ async executeOperationAfterAcceptance(input, accept, kind, budget = genericComputerExecutionBudget, signal, executionContext, resultOptions) {
1013
1013
  const { requestId, ...command } = computerExecutionRequestSchema(budget).parse(input);
1014
1014
  const route = kind === "exec" && command.backend === undefined
1015
1015
  ? undefined
@@ -1067,7 +1067,7 @@ export class ComputerDO extends ContainerComputer {
1067
1067
  },
1068
1068
  };
1069
1069
  }
1070
- const output = await exec(workspace, command, route?.id, signal, execution);
1070
+ const output = await exec(workspace, command, route?.id, signal, execution, resultOptions);
1071
1071
  completed = true;
1072
1072
  operations.settle(requestId, output);
1073
1073
  }
@@ -19518,6 +19518,11 @@ var appBackendBindingKinds = {
19518
19518
  requires: ["workflow.run", "workflow.arm"],
19519
19519
  effect: "write"
19520
19520
  },
19521
+ pauseSchedule: {
19522
+ cap: "app.workflow.run",
19523
+ requires: ["workflow.run", "workflow.arm"],
19524
+ effect: "write"
19525
+ },
19521
19526
  schedules: {
19522
19527
  cap: "app.workflow.read",
19523
19528
  requires: ["workflow.run"],
@@ -19659,7 +19664,22 @@ var appBackendBindingKinds = {
19659
19664
  name: "AGENTS",
19660
19665
  operations: {
19661
19666
  list: { cap: "agent.list", requires: ["agent.list"], effect: "read" },
19662
- send: { cap: "agent.send", requires: ["agent.send"], effect: "write" }
19667
+ send: { cap: "agent.send", requires: ["agent.send"], effect: "write" },
19668
+ schedules: {
19669
+ cap: "agent.read",
19670
+ requires: ["agent.read"],
19671
+ effect: "read"
19672
+ },
19673
+ pauseSchedule: {
19674
+ cap: "agent.manage",
19675
+ requires: ["agent.manage"],
19676
+ effect: "write"
19677
+ },
19678
+ deleteSchedule: {
19679
+ cap: "agent.manage",
19680
+ requires: ["agent.manage"],
19681
+ effect: "write"
19682
+ }
19663
19683
  }
19664
19684
  }
19665
19685
  };
@@ -19714,13 +19734,17 @@ var emptyAppDeclaration = Object.freeze({
19714
19734
 
19715
19735
  // ../puffball/dist/app-api.js
19716
19736
  var appApiMethodNameSchema = String4.check(makeFilter2(isAppApiMethodName));
19737
+ function isJsonContainer(value) {
19738
+ return value !== null && typeof value === "object";
19739
+ }
19717
19740
  function withinDepth(value, depth = 0) {
19718
19741
  if (depth > 16)
19719
19742
  return false;
19720
- if (is2(ArraySchema(Json2))(value))
19721
- return value.every((child) => withinDepth(child, depth + 1));
19722
- if (is2(JsonObject)(value))
19723
- return Object.values(value).every((child) => withinDepth(child, depth + 1));
19743
+ if (isJsonContainer(value)) {
19744
+ for (const child of Object.values(value))
19745
+ if (!withinDepth(child, depth + 1))
19746
+ return false;
19747
+ }
19724
19748
  return true;
19725
19749
  }
19726
19750
  var projection = JsonObject.pipe(check(makeFilter2((value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= 2e4 && withinDepth(value), { message: "App API schema exceeds its byte or depth bound" })));
@@ -19731,15 +19755,12 @@ var method = Struct({
19731
19755
  output: projection,
19732
19756
  pending: optional2(Literal2(true)),
19733
19757
  hidden: optional2(Literal2(true)),
19734
- anonymous: optional2(Literal2(true)),
19735
19758
  present: optional2(Struct({
19736
19759
  verb: String4.check(isMinLength(1), isMaxLength(32))
19737
19760
  })),
19738
19761
  effect: Literals(["read", "write", "external"]),
19739
19762
  requires: ArraySchema(Literals(appCapabilityIds)).pipe(check(makeFilter2((values) => new Set(values).size === values.length)))
19740
- }).check(makeFilter2((value) => value.anonymous !== true || value.effect === "read" && value.requires.length === 0, {
19741
- message: "Anonymous methods must be reads with no required capabilities"
19742
- }));
19763
+ });
19743
19764
  var appApiSchema = Struct({
19744
19765
  methods: Record(String4, method).pipe(check(makeFilter2((methods) => Object.keys(methods).every(is2(appApiMethodNameSchema)), { message: "Invalid App API method name" })), check(makeFilter2((methods) => Object.keys(methods).length <= 64)), check(makeFilter2((methods) => {
19745
19766
  const names = Object.keys(methods);
@@ -21745,6 +21766,12 @@ var BirdDogSystemIdentitySchema = Struct({
21745
21766
  systemId: Literal2("hub")
21746
21767
  });
21747
21768
  var BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
21769
+ var BIRD_DOG_WAITING_MAX_ITEMS = 8;
21770
+ var BirdDogWaitingItemSchema = Struct({
21771
+ itemId: Identifier,
21772
+ kind: String4.check(isMinLength(1), isMaxLength(64)),
21773
+ summary: String4.check(isMinLength(1), isMaxLength(200))
21774
+ });
21748
21775
  var SettledBirdDogReplySchema = Struct({
21749
21776
  commandId: Identifier,
21750
21777
  outcome: Literals([
@@ -21754,7 +21781,8 @@ var SettledBirdDogReplySchema = Struct({
21754
21781
  "cancelled",
21755
21782
  "outcome_unknown"
21756
21783
  ]),
21757
- diagnostic: optionalKey2(String4.check(isMinLength(1), isMaxLength(BIRD_DOG_DIAGNOSTIC_MAX_LENGTH)))
21784
+ diagnostic: optionalKey2(String4.check(isMinLength(1), isMaxLength(BIRD_DOG_DIAGNOSTIC_MAX_LENGTH))),
21785
+ waitingOnYou: optionalKey2(ArraySchema(BirdDogWaitingItemSchema).check(isMinLength(1), isMaxLength(BIRD_DOG_WAITING_MAX_ITEMS)))
21758
21786
  });
21759
21787
  var BirdDogUndeliverableReasonSchema = Literals([
21760
21788
  "recipient_offline",
@@ -21835,6 +21863,7 @@ var BirdDogAgentJobPayloadSchema = Struct({
21835
21863
  });
21836
21864
  var BirdDogAgentJobInputSchema = Struct({
21837
21865
  work: BirdDogAgentWorkSchema,
21866
+ delivery: Literals(["steer", "followUp"]),
21838
21867
  mail: optional2(BirdDogMailSchema)
21839
21868
  });
21840
21869
 
@@ -35634,7 +35663,7 @@ async function stopHtmlGroup(child) {
35634
35663
  }
35635
35664
  throw new Error("HTML process group survived its five-second cleanup bound");
35636
35665
  }
35637
- function runHtmlCommand(args2, cwd, env, command = "pnpm") {
35666
+ function runHtmlCommand(args2, cwd, env, command = "pnpm", onOutput) {
35638
35667
  return acquireUseRelease2(
35639
35668
  sync2(() => {
35640
35669
  if (process.platform === "win32")
@@ -35649,11 +35678,13 @@ function runHtmlCommand(args2, cwd, env, command = "pnpm") {
35649
35678
  let stdout = "";
35650
35679
  let stderr = "";
35651
35680
  child.stdout.on("data", (chunk) => {
35681
+ onOutput?.("stdout", chunk);
35652
35682
  stdout += chunk.toString();
35653
35683
  if (Buffer2.byteLength(stdout) > 1024 * 1024)
35654
35684
  reject(new Error("HTML command output exceeded its bound"));
35655
35685
  });
35656
35686
  child.stderr.on("data", (chunk) => {
35687
+ onOutput?.("stderr", chunk);
35657
35688
  stderr += chunk.toString();
35658
35689
  if (Buffer2.byteLength(stderr) > 1024 * 1024)
35659
35690
  reject(new Error("HTML command diagnostics exceeded their bound"));
@@ -35694,10 +35725,10 @@ function snapshotBuildEnvironment(sourceSnapshotPath) {
35694
35725
  }
35695
35726
  function rebuildViteSnapshot(sourceSnapshotPath, recipe, options) {
35696
35727
  if (recipe.kind === "source-html")
35697
- return rebuildNpmSnapshot(sourceSnapshotPath, recipe);
35728
+ return rebuildNpmSnapshot(sourceSnapshotPath, recipe, options);
35698
35729
  return gen2(function* () {
35699
35730
  const env = snapshotBuildEnvironment(sourceSnapshotPath);
35700
- const run2 = (args2, cwd = sourceSnapshotPath) => runHtmlCommand(args2, cwd, env);
35731
+ const run2 = (args2, cwd = sourceSnapshotPath) => runHtmlCommand(args2, cwd, env, "pnpm", options.onOutput);
35701
35732
  if ((yield* run2(["--version"])).trim() !== "11.24.0") {
35702
35733
  return yield* fail5(new Error("Publisher requires pnpm 11.24.0"));
35703
35734
  }
@@ -35915,7 +35946,7 @@ function readHtmlBuildOutput(sourceSnapshotPath, recipe) {
35915
35946
  return Object.freeze({ kind: "bytes", bytes });
35916
35947
  });
35917
35948
  }
35918
- function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35949
+ function rebuildNpmSnapshot(sourceSnapshotPath, recipe, options) {
35919
35950
  return gen2(function* () {
35920
35951
  const env = snapshotBuildEnvironment(sourceSnapshotPath);
35921
35952
  const home = join5(sourceSnapshotPath, ".publisher-home");
@@ -35932,7 +35963,8 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35932
35963
  ["--version"],
35933
35964
  sourceSnapshotPath,
35934
35965
  env,
35935
- "npm"
35966
+ "npm",
35967
+ options.onOutput
35936
35968
  )).trim() !== recipe.toolchain.npm)
35937
35969
  return yield* fail5(
35938
35970
  new Error("Captured npm version differs from the recipe")
@@ -35941,7 +35973,8 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35941
35973
  ["ci", "--include=dev", "--ignore-scripts", "--no-audit", "--no-fund"],
35942
35974
  sourceSnapshotPath,
35943
35975
  env,
35944
- "npm"
35976
+ "npm",
35977
+ options.onOutput
35945
35978
  );
35946
35979
  const program = join5(sourceSnapshotPath, ".publisher-build.mjs");
35947
35980
  yield* tryPromise2(async () => {
@@ -35962,7 +35995,8 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35962
35995
  ],
35963
35996
  sourceSnapshotPath,
35964
35997
  env,
35965
- "node"
35998
+ "node",
35999
+ options.onOutput
35966
36000
  );
35967
36001
  return yield* readHtmlBuildOutput(sourceSnapshotPath, recipe);
35968
36002
  });
@@ -36103,7 +36137,9 @@ function createLocalBuildExecutor(options = {}) {
36103
36137
  if ((yield* runHtmlCommand(
36104
36138
  ["--version"],
36105
36139
  source.snapshotPath,
36106
- environment
36140
+ environment,
36141
+ "pnpm",
36142
+ options.onOutput
36107
36143
  )).trim() !== "11.24.0")
36108
36144
  return yield* fail5(
36109
36145
  new Error("Publisher requires pnpm 11.24.0")
@@ -36114,7 +36150,9 @@ function createLocalBuildExecutor(options = {}) {
36114
36150
  options.packageCacheDirectory
36115
36151
  ),
36116
36152
  source.snapshotPath,
36117
- environment
36153
+ environment,
36154
+ "pnpm",
36155
+ options.onOutput
36118
36156
  );
36119
36157
  }
36120
36158
  }
@@ -36265,6 +36303,9 @@ async function buildTeamCandidate(input) {
36265
36303
  const onPhase = (phase, milliseconds) => {
36266
36304
  phases[phase] += milliseconds;
36267
36305
  };
36306
+ const onOutput = (stream, bytes2) => {
36307
+ input.onOutput?.(stream, bytes2);
36308
+ };
36268
36309
  const prepareStarted = performance.now();
36269
36310
  const candidate = await runPromise2(
36270
36311
  createCapturedCandidatePreparer(config2, {
@@ -36273,10 +36314,11 @@ async function buildTeamCandidate(input) {
36273
36314
  config2.sourceRevision
36274
36315
  ),
36275
36316
  buildExecutor: createLocalBuildExecutor(
36276
- input.packageStoreDirectory === void 0 ? { packageScripts: "run", onPhase } : {
36317
+ input.packageStoreDirectory === void 0 ? { packageScripts: "run", onPhase, onOutput } : {
36277
36318
  packageScripts: "run",
36278
36319
  packageStoreDirectory: input.packageStoreDirectory,
36279
- onPhase
36320
+ onPhase,
36321
+ onOutput
36280
36322
  }
36281
36323
  )
36282
36324
  }).prepareCandidate(parseReleaseId("rel_build"))
@@ -36347,7 +36389,10 @@ try {
36347
36389
  repositoryPath,
36348
36390
  outputPath,
36349
36391
  // Computer scratch survives captured snapshot cleanup, without a global or durable Workspace store.
36350
- packageStoreDirectory: "/var/tmp/fungi-app-package-store"
36392
+ packageStoreDirectory: "/var/tmp/fungi-app-package-store",
36393
+ onOutput: (stream, bytes) => {
36394
+ process[stream].write(bytes);
36395
+ }
36351
36396
  });
36352
36397
  const reportUrl = process.env.TEAM_BUILD_REPORT;
36353
36398
  if (!reportUrl) throw new TeamCandidateBuildError("report_failed");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/computer",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Cloudflare Computer host for one durable Fungi Computer",
6
6
  "type": "module",
@@ -60,10 +60,10 @@
60
60
  "@fungi.computer/capnweb": "0.12.0",
61
61
  "@fungi.computer/cloudflare-computer": "0.2.1-fungi.2",
62
62
  "@fungi.computer/cordyceps": "0.4.0",
63
- "@fungi.computer/knapsack": "0.3.1",
63
+ "@fungi.computer/knapsack": "0.4.1",
64
64
  "@fungi.computer/seam": "0.2.0",
65
65
  "@fungi.computer/watchdog": "0.1.0",
66
- "@fungi.computer/computer-contract": "0.0.4",
66
+ "@fungi.computer/computer-contract": "0.0.6",
67
67
  "effect": "4.0.2",
68
68
  "just-bash": "3.4.2",
69
69
  "zod": "4.4.3"
@@ -118,7 +118,7 @@
118
118
  "test:nightly": "bash ../../.agents/skills/orchestrate-multi-lane-work/scripts/run-proof.sh node test/egress-proof.mjs",
119
119
  "test:app-method-execution": "node test/app-method-execution-proof.mjs",
120
120
  "test:execution-lifecycle": "node test/execution-lifecycle-proof.mjs",
121
- "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",
121
+ "test:unit": "node --test --test-concurrency=${BOTANICAL_TEST_WORKERS:-4} test/egress-policy.test.mjs test/preview.test.mjs test/fleet.test.mjs test/proof-worker.test.mjs test/workspace-sync.test.mjs test/runtime-observer.test.mjs test/files-patch.test.mjs test/exec-output.test.mjs && node --test --test-concurrency=${BOTANICAL_TEST_WORKERS:-4} 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",
122
122
  "test:fleet-native": "node test/fleet.native.mjs",
123
123
  "test:light-shell:native": "node --import tsx test/light-shell.native.mjs --state",
124
124
  "assemble:image": "node scripts/assemble-image.mjs"