@fungi.computer/mycelium 0.0.2 → 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.
Files changed (40) hide show
  1. package/README.md +70 -7
  2. package/dist/access.d.ts +2 -7
  3. package/dist/access.js +0 -3
  4. package/dist/execute-tool.d.ts +3 -2
  5. package/dist/execute-tool.js +40 -11
  6. package/dist/image-collector.d.ts +1 -1
  7. package/dist/index.d.ts +14 -1
  8. package/dist/index.js +2 -1
  9. package/dist/internal/build-identity.node.js +1 -1
  10. package/dist/internal/execution-lifecycle.d.ts +4 -2
  11. package/dist/internal/execution-lifecycle.js +97 -54
  12. package/dist/internal/host-cores.d.ts +2 -0
  13. package/dist/internal/host-cores.js +4 -0
  14. package/dist/internal/host-cores.node.d.ts +2 -0
  15. package/dist/internal/host-cores.node.js +5 -0
  16. package/dist/mcp-catalog.d.ts +0 -6
  17. package/dist/mcp-catalog.js +0 -1
  18. package/dist/mcp.d.ts +0 -1
  19. package/dist/mcp.js +0 -1
  20. package/dist/output-collector.d.ts +46 -0
  21. package/dist/output-collector.js +145 -0
  22. package/dist/runtime.js +1 -1
  23. package/dist/sandbox-admission.d.ts +19 -0
  24. package/dist/sandbox-admission.js +21 -0
  25. package/dist/sandbox-boundary.js +32 -23
  26. package/dist/sandbox-failure-event.d.ts +1 -1
  27. package/dist/sandbox-failure-event.js +1 -0
  28. package/dist/sandbox-failure.d.ts +12 -1
  29. package/dist/sandbox-failure.js +14 -0
  30. package/dist/sandbox-guest.d.ts +6 -6
  31. package/dist/sandbox-guest.js +5 -7
  32. package/dist/sandbox-wire.d.ts +5 -0
  33. package/dist/sandbox-wire.js +20 -1
  34. package/dist/source-runtime.js +3 -3
  35. package/docs/api/index.md +68 -5
  36. package/docs/api/mcp.md +0 -2
  37. package/docs/api/sandbox-failure.md +90 -0
  38. package/docs/api/sandbox-guest.md +36 -30
  39. package/docs/api/sandbox-wire.md +3 -3
  40. package/package.json +9 -5
@@ -0,0 +1,2 @@
1
+ /** Node's maintained, affinity-aware observation; captured by runtime policy. */
2
+ export declare function availableHostCores(): number;
@@ -0,0 +1,5 @@
1
+ import { availableParallelism } from "node:os";
2
+ /** Node's maintained, affinity-aware observation; captured by runtime policy. */
3
+ export function availableHostCores() {
4
+ return availableParallelism();
5
+ }
@@ -6,12 +6,6 @@ export type McpCatalogSnapshot = Readonly<{
6
6
  sources: readonly CapabilitySource[];
7
7
  /** Every permitted module is named; an omitted module is refused. */
8
8
  grants: Grants;
9
- /**
10
- * Granted modules whose writes a person already approved for this principal,
11
- * such as an owner's stored approval of a key's App tools. Their writes run
12
- * without a per-call elicitation; every other write still asks.
13
- */
14
- approvedWrites?: readonly string[];
15
9
  }>;
16
10
  /**
17
11
  * Adapt principal-scoped capability sources and a sandbox to the shared MCP server.
@@ -41,7 +41,6 @@ export function createMcpCatalog(options) {
41
41
  ...access,
42
42
  defaultGrant: "none",
43
43
  sessionGrants: () => snapshot?.grants ?? {},
44
- approvedWrites: () => snapshot?.approvedWrites ?? [],
45
44
  },
46
45
  });
47
46
  try {
package/dist/mcp.d.ts CHANGED
@@ -28,7 +28,6 @@ export type McpExecutionContext = Readonly<{
28
28
  }>) => Promise<void>;
29
29
  /** Apply to Mycelium access options for this call only; never persist callbacks. */
30
30
  access: Pick<AccessOptions, "confirm"> & Readonly<{
31
- confirmWrites: true;
32
31
  /** Recheck the open connection and refresh authority before each operation. */
33
32
  beforeCall: (signal: AbortSignal) => Promise<void>;
34
33
  }>;
package/dist/mcp.js CHANGED
@@ -73,7 +73,6 @@ export function createMcp(options) {
73
73
  }
74
74
  : undefined;
75
75
  const access = {
76
- confirmWrites: true,
77
76
  confirm,
78
77
  beforeCall: async (signal) => {
79
78
  signal.throwIfAborted();
@@ -0,0 +1,46 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Stream from "effect/Stream";
3
+ /** Complete decoded text, in completion order across stdout/stderr, not byte order. */
4
+ export declare class OutputCollector {
5
+ #private;
6
+ private readonly wake;
7
+ private constructor();
8
+ static make(): Effect.Effect<OutputCollector, never, never>;
9
+ /** Never waits for a reader. An incomplete scalar stays with its producer. */
10
+ write(stream: "stdout" | "stderr", bytes: Uint8Array): void;
11
+ snapshot(): {
12
+ tail: string;
13
+ droppedBytes: number;
14
+ totalBytes: number;
15
+ };
16
+ /** EOF flush order is stdout then stderr; the final update precedes terminal. */
17
+ finish(): {
18
+ type: "update";
19
+ result: {
20
+ isError: false;
21
+ content: {
22
+ type: "text";
23
+ text: string;
24
+ }[];
25
+ data: {
26
+ droppedBytes: number;
27
+ totalBytes: number;
28
+ };
29
+ };
30
+ } | undefined;
31
+ get updates(): Stream.Stream<{
32
+ type: "update";
33
+ result: {
34
+ isError: false;
35
+ content: {
36
+ type: "text";
37
+ text: string;
38
+ }[];
39
+ data: {
40
+ droppedBytes: number;
41
+ totalBytes: number;
42
+ };
43
+ };
44
+ }, never, never>;
45
+ dispose(): void;
46
+ }
@@ -0,0 +1,145 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Queue from "effect/Queue";
3
+ import * as Stream from "effect/Stream";
4
+ const capacity = 8192;
5
+ const encoder = new TextEncoder();
6
+ // fatal: false is the existing default; Cloudflare's type requires the field.
7
+ const decoder = () => new TextDecoder("utf-8", { ignoreBOM: true, fatal: false });
8
+ /** Complete decoded text, in completion order across stdout/stderr, not byte order. */
9
+ export class OutputCollector {
10
+ wake;
11
+ #producers = {
12
+ stdout: { decoder: decoder(), pendingBytes: 0 },
13
+ stderr: { decoder: decoder(), pendingBytes: 0 },
14
+ };
15
+ #probe = decoder();
16
+ // One bounded ring of complete decoder emissions. No UTF-8 slicing/parser.
17
+ #parts = Array.from({ length: capacity }, () => undefined);
18
+ #head = 0;
19
+ #size = 0;
20
+ #textBytes = 0;
21
+ #newlines = 0;
22
+ #droppedBytes = 0;
23
+ #totalBytes = 0;
24
+ #dirty = false;
25
+ #closed = false;
26
+ constructor(wake) {
27
+ this.wake = wake;
28
+ }
29
+ static make() {
30
+ return Queue.make({ capacity: 1, strategy: "dropping" }).pipe(Effect.map((wake) => new OutputCollector(wake)));
31
+ }
32
+ /** Never waits for a reader. An incomplete scalar stays with its producer. */
33
+ write(stream, bytes) {
34
+ if (this.#closed || bytes.length === 0)
35
+ return;
36
+ const producer = this.#producers[stream];
37
+ this.#totalBytes += bytes.length;
38
+ // Byte-sized decoder inputs let each retained emission carry its exact raw
39
+ // cost, even when malformed input expands to replacement characters.
40
+ for (let i = 0; i < bytes.length; i++) {
41
+ const byte = bytes.subarray(i, i + 1);
42
+ producer.pendingBytes++;
43
+ const text = producer.decoder.decode(byte, { stream: true });
44
+ if (text.length === 0)
45
+ continue;
46
+ // A decoder may emit a replacement for a prior incomplete sequence while
47
+ // buffering the current byte as the start of the next scalar. Ask the
48
+ // maintained decoder, rather than duplicating UTF-8 byte grammar.
49
+ const pending = producer.pendingBytes > 1 &&
50
+ this.#probe.decode(byte, { stream: true }).length === 0
51
+ ? 1
52
+ : 0;
53
+ this.#probe.decode();
54
+ this.#append(text, producer.pendingBytes - pending);
55
+ producer.pendingBytes = pending;
56
+ }
57
+ this.#dirty = true;
58
+ Queue.offerUnsafe(this.wake, undefined);
59
+ }
60
+ #evict() {
61
+ const part = this.#parts[this.#head];
62
+ if (part === undefined)
63
+ throw new Error("Missing output ring entry");
64
+ this.#parts[this.#head] = undefined;
65
+ this.#head = (this.#head + 1) % capacity;
66
+ this.#size--;
67
+ this.#textBytes -= part.textBytes;
68
+ this.#newlines -= part.newlines;
69
+ this.#droppedBytes += part.rawBytes;
70
+ }
71
+ #append(text, rawBytes) {
72
+ if (text.length === 0)
73
+ return;
74
+ const part = {
75
+ text,
76
+ rawBytes,
77
+ textBytes: encoder.encode(text).length,
78
+ newlines: text.split("\n").length - 1,
79
+ };
80
+ if (this.#size === capacity)
81
+ this.#evict();
82
+ this.#parts[(this.#head + this.#size) % capacity] = part;
83
+ this.#size++;
84
+ this.#textBytes += part.textBytes;
85
+ this.#newlines += part.newlines;
86
+ while (this.#textBytes > capacity || this.#newlines >= 200)
87
+ this.#evict();
88
+ }
89
+ snapshot() {
90
+ let tail = "";
91
+ for (let i = 0; i < this.#size; i++) {
92
+ const part = this.#parts[(this.#head + i) % capacity];
93
+ if (part === undefined)
94
+ throw new Error("Missing output ring entry");
95
+ tail += part.text;
96
+ }
97
+ return {
98
+ tail,
99
+ droppedBytes: this.#droppedBytes,
100
+ totalBytes: this.#totalBytes,
101
+ };
102
+ }
103
+ #update() {
104
+ if (!this.#dirty)
105
+ return undefined;
106
+ this.#dirty = false;
107
+ const { tail, ...data } = this.snapshot();
108
+ return {
109
+ type: "update",
110
+ result: {
111
+ isError: false,
112
+ content: [{ type: "text", text: tail }],
113
+ data,
114
+ },
115
+ };
116
+ }
117
+ /** EOF flush order is stdout then stderr; the final update precedes terminal. */
118
+ finish() {
119
+ if (this.#closed)
120
+ return undefined;
121
+ this.#closed = true;
122
+ for (const producer of [this.#producers.stdout, this.#producers.stderr]) {
123
+ const text = producer.decoder.decode();
124
+ this.#append(text, producer.pendingBytes);
125
+ producer.pendingBytes = 0;
126
+ if (text.length > 0)
127
+ this.#dirty = true;
128
+ }
129
+ return this.#update();
130
+ }
131
+ get updates() {
132
+ return Stream.fromQueue(this.wake).pipe(Stream.mapEffect(() => Effect.sleep("100 millis").pipe(Effect.map(() => (this.#closed ? undefined : this.#update())))), Stream.filter((part) => part !== undefined));
133
+ }
134
+ dispose() {
135
+ this.#closed = true;
136
+ this.#parts.fill(undefined);
137
+ this.#head = this.#size = this.#textBytes = this.#newlines = 0;
138
+ this.#totalBytes = this.#droppedBytes = 0;
139
+ this.#dirty = false;
140
+ for (const producer of [this.#producers.stdout, this.#producers.stderr]) {
141
+ producer.decoder.decode();
142
+ producer.pendingBytes = 0;
143
+ }
144
+ }
145
+ }
package/dist/runtime.js CHANGED
@@ -36,13 +36,13 @@ function settleClosePlans(plans, liveRevisions) {
36
36
  export function openCoreRuntime(options) {
37
37
  return Effect.gen(function* () {
38
38
  const gate = yield* Semaphore.make(1);
39
- const backingWork = createBackingWorkTracker();
40
39
  const execution = yield* Effect.try({
41
40
  try: () => normalizeExecutionPolicy(options.execution),
42
41
  catch: (cause) => cause instanceof MyceliumDefinitionError
43
42
  ? cause
44
43
  : new MyceliumDefinitionError("Execution policy is invalid"),
45
44
  });
45
+ const backingWork = yield* createBackingWorkTracker(execution.capacity);
46
46
  const sandbox = Object.freeze({
47
47
  execute: options.sandbox.execute,
48
48
  adapter: options.sandbox.adapter,
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ export declare const sandboxAdmissionOptionsSchema: z.ZodObject<{
3
+ sandboxCapacity: z.ZodOptional<z.ZodInt>;
4
+ admissionWaitMs: z.ZodOptional<z.ZodInt>;
5
+ }, z.core.$strict>;
6
+ /** Host-owned admission inputs; never part of guest execute arguments. */
7
+ export type SandboxAdmissionOptions = Readonly<{
8
+ /** Positive host-selected capacity; omission derives the host default. */
9
+ sandboxCapacity?: number | undefined;
10
+ /** Maximum admission wait in milliseconds, from 1 through 900,000. */
11
+ admissionWaitMs?: number | undefined;
12
+ }>;
13
+ /** Captured once per runtime and shared across backing-work groups. */
14
+ export type SandboxAdmissionPolicy = Readonly<{
15
+ capacity: number;
16
+ waitMs: number;
17
+ }>;
18
+ /** Validate host policy and resolve defaults from a platform-owned core observation. */
19
+ export declare function normalizeSandboxAdmissionPolicy(options: SandboxAdmissionOptions | undefined, availableCores?: number): SandboxAdmissionPolicy;
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { MyceliumDefinitionError } from "./errors.js";
3
+ export const sandboxAdmissionOptionsSchema = z.strictObject({
4
+ sandboxCapacity: z.int().positive().optional(),
5
+ admissionWaitMs: z.int().min(1).max(900_000).optional(),
6
+ });
7
+ /** Validate host policy and resolve defaults from a platform-owned core observation. */
8
+ export function normalizeSandboxAdmissionPolicy(options, availableCores) {
9
+ const parsed = sandboxAdmissionOptionsSchema.safeParse(options ?? {});
10
+ const cores = z
11
+ .int()
12
+ .positive()
13
+ .safeParse(availableCores ?? 2);
14
+ if (!parsed.success || !cores.success) {
15
+ throw new MyceliumDefinitionError("Sandbox admission policy is invalid");
16
+ }
17
+ return Object.freeze({
18
+ capacity: parsed.data.sandboxCapacity ?? Math.min(32, Math.max(2, cores.data)),
19
+ waitMs: parsed.data.admissionWaitMs ?? 5_000,
20
+ });
21
+ }
@@ -4,11 +4,12 @@ import * as Result from "effect/Result";
4
4
  import * as Schema from "effect/Schema";
5
5
  import { z } from "zod";
6
6
  import { InvalidOperationInput } from "./errors.js";
7
- import { SourceInvalid, GuestThrew, DeadlineExceeded, Cancelled, DispatchLost, SandboxCrashed, BadResponse, ResultTooLarge, ResultNotJson, CapabilityFailed, Unknown, referenceSandboxFailure, sandboxFailureMessage, } from "./sandbox-failure.js";
8
- const ownedFailureSchema = z.union([
7
+ import { SourceInvalid, GuestThrew, DeadlineExceeded, SandboxBusy, Cancelled, DispatchLost, SandboxCrashed, BadResponse, ResultTooLarge, ResultNotJson, CapabilityFailed, Unknown, referenceSandboxFailure, sandboxFailureMessage, } from "./sandbox-failure.js";
8
+ const ownedFailureSchema = z.lazy(() => z.union([
9
9
  z.instanceof(SourceInvalid),
10
10
  z.instanceof(GuestThrew),
11
11
  z.instanceof(DeadlineExceeded),
12
+ z.instanceof(SandboxBusy),
12
13
  z.instanceof(Cancelled),
13
14
  z.instanceof(DispatchLost),
14
15
  z.instanceof(SandboxCrashed),
@@ -17,9 +18,9 @@ const ownedFailureSchema = z.union([
17
18
  z.instanceof(ResultNotJson),
18
19
  z.instanceof(CapabilityFailed),
19
20
  z.instanceof(Unknown),
20
- ]);
21
+ ]));
21
22
  /** The one interpretation boundary for foreign in-process values and rejections. */
22
- const foreignValueSchema = z
23
+ const foreignValueSchema = z.lazy(() => z
23
24
  .unknown()
24
25
  .pipe(z.union([
25
26
  z
@@ -32,7 +33,12 @@ const foreignValueSchema = z
32
33
  z.string().transform((value) => ({ kind: "string", value })),
33
34
  z.boolean().transform((value) => ({ kind: "boolean", value })),
34
35
  z
35
- .union([z.number(), z.nan(), z.literal(Infinity), z.literal(-Infinity)])
36
+ .union([
37
+ z.number(),
38
+ z.nan(),
39
+ z.literal(Infinity),
40
+ z.literal(-Infinity),
41
+ ])
36
42
  .transform((value) => ({ kind: "number", value })),
37
43
  z.bigint().transform((value) => ({ kind: "bigint", value })),
38
44
  z.symbol().transform((value) => ({ kind: "symbol", value })),
@@ -46,7 +52,7 @@ const foreignValueSchema = z
46
52
  .union([z.instanceof(Object), z.record(z.string(), z.unknown())])
47
53
  .transform((value) => ({ kind: "object", value })),
48
54
  z.unknown().transform(() => ({ kind: "opaque" })),
49
- ]));
55
+ ])));
50
56
  /** Admit only errors constructed by the sandbox owner, using the maintained decoder. */
51
57
  export function isSandboxFailure(cause) {
52
58
  return ownedFailureSchema.safeParse(cause).success;
@@ -68,7 +74,7 @@ export function sandboxValueKind(value) {
68
74
  return decoded.kind;
69
75
  }
70
76
  }
71
- const bindingInputSchema = z.json().optional();
77
+ const bindingInputSchema = z.lazy(() => z.json().optional());
72
78
  /** Decode the RPC argument before any capability callback observes it. */
73
79
  export function sandboxBindingInput(value) {
74
80
  return bindingInputSchema.parse(value);
@@ -132,7 +138,7 @@ function operatorValue(cause, seen) {
132
138
  }
133
139
  }
134
140
  }
135
- const missingVariable = z
141
+ const missingVariable = z.lazy(() => z
136
142
  .object({
137
143
  name: z.literal("ReferenceError"),
138
144
  message: z
@@ -142,10 +148,13 @@ const missingVariable = z
142
148
  .transform((value) => ({
143
149
  ...value,
144
150
  missingVariable: value.message.slice(0, -" is not defined".length),
145
- }));
146
- const guestDiagnosticSchema = z
147
- .union([missingVariable, z.object({ name: z.string(), message: z.string() })])
148
- .and(z.object({ transportMessage: z.string().max(512).optional() }));
151
+ })));
152
+ const guestDiagnosticSchema = z.lazy(() => z
153
+ .union([
154
+ missingVariable,
155
+ z.object({ name: z.string(), message: z.string() }),
156
+ ])
157
+ .and(z.object({ transportMessage: z.string().max(512).optional() })));
149
158
  /** Decode the engine's guest-owned diagnostic once; renderers receive typed fields. */
150
159
  export function sandboxGuestFailure(cause, bindings = {}) {
151
160
  const diagnostic = guestDiagnosticSchema.parse(cause);
@@ -160,13 +169,14 @@ export function sandboxGuestFailure(cause, bindings = {}) {
160
169
  }
161
170
  return new GuestThrew(diagnostic);
162
171
  }
163
- const transportEnvelopeSchema = z.strictObject({
172
+ const transportEnvelopeSchema = z.lazy(() => z.strictObject({
164
173
  v: z.literal(1),
165
174
  kind: z.literal("sandbox_failure"),
166
175
  tag: z.enum([
167
176
  "SourceInvalid",
168
177
  "GuestThrew",
169
178
  "DeadlineExceeded",
179
+ "SandboxBusy",
170
180
  "Cancelled",
171
181
  "DispatchLost",
172
182
  "SandboxCrashed",
@@ -178,9 +188,9 @@ const transportEnvelopeSchema = z.strictObject({
178
188
  ]),
179
189
  ref: z.string().min(1).max(32),
180
190
  message: z.string().max(160),
181
- });
191
+ }));
182
192
  const decodeJsonText = Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Unknown));
183
- const jsonTextSchema = z.string().transform((text, context) => {
193
+ const jsonTextSchema = z.lazy(() => z.string().transform((text, context) => {
184
194
  const decoded = decodeJsonText(text);
185
195
  if (Result.isFailure(decoded)) {
186
196
  context.addIssue({
@@ -190,7 +200,7 @@ const jsonTextSchema = z.string().transform((text, context) => {
190
200
  return z.NEVER;
191
201
  }
192
202
  return decoded.success;
193
- });
203
+ }));
194
204
  const transportMessageSchema = jsonTextSchema.pipe(transportEnvelopeSchema);
195
205
  const returnedOperationFailure = z.strictObject({
196
206
  error: transportMessageSchema,
@@ -218,16 +228,15 @@ export function recoverSandboxFailure(failure, failures) {
218
228
  const original = failures.get(decoded.data.ref);
219
229
  return original?._tag === decoded.data.tag ? original : failure;
220
230
  }
221
- const executorFailureSchema = z
222
- .unknown()
223
- .pipe(z.union([
231
+ const executorFailureSchema = z.lazy(() => z.unknown().pipe(z.union([
224
232
  z
225
233
  .literal("Execution timed out")
226
234
  .transform(() => ({ kind: "deadline" })),
227
- z
228
- .unknown()
229
- .transform((rawCause) => ({ kind: "bad_response", rawCause })),
230
- ]));
235
+ z.unknown().transform((rawCause) => ({
236
+ kind: "bad_response",
237
+ rawCause,
238
+ })),
239
+ ])));
231
240
  /** Decode Codemode's documented error carrier before adapters choose a failure. */
232
241
  export function sandboxExecutorFailure(cause, limitMs, elapsedMs) {
233
242
  const decoded = executorFailureSchema.parse(cause);
@@ -7,7 +7,7 @@ export declare const SandboxFailureEventSchema: Schema.Struct<{
7
7
  readonly executionId: Schema.String;
8
8
  readonly adapter: Schema.String;
9
9
  readonly buildSha: Schema.NullOr<Schema.String>;
10
- readonly tag: Schema.Literals<readonly ["SourceInvalid", "GuestThrew", "DeadlineExceeded", "Cancelled", "DispatchLost", "SandboxCrashed", "BadResponse", "ResultTooLarge", "ResultNotJson", "CapabilityFailed", "Unknown"]>;
10
+ readonly tag: Schema.Literals<readonly ["SourceInvalid", "GuestThrew", "DeadlineExceeded", "SandboxBusy", "Cancelled", "DispatchLost", "SandboxCrashed", "BadResponse", "ResultTooLarge", "ResultNotJson", "CapabilityFailed", "Unknown"]>;
11
11
  readonly fields: Schema.$Record<Schema.String, Schema.Codec<z.core.util.JSONType, z.core.util.JSONType, never, never>>;
12
12
  readonly healthAlert: Schema.Boolean;
13
13
  }>;
@@ -20,6 +20,7 @@ export const SandboxFailureEventSchema = Schema.Struct({
20
20
  "SourceInvalid",
21
21
  "GuestThrew",
22
22
  "DeadlineExceeded",
23
+ "SandboxBusy",
23
24
  "Cancelled",
24
25
  "DispatchLost",
25
26
  "SandboxCrashed",
@@ -34,6 +34,17 @@ export declare class DeadlineExceeded extends DeadlineExceeded_base<SandboxFailu
34
34
  readonly acknowledged?: boolean | undefined;
35
35
  }> {
36
36
  }
37
+ declare const SandboxBusy_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
38
+ readonly _tag: "SandboxBusy";
39
+ } & Readonly<A>;
40
+ /** Admission expired before any sandbox work started. */
41
+ export declare class SandboxBusy extends SandboxBusy_base<SandboxFailureReference & {
42
+ readonly capacity: number;
43
+ readonly waitedMs: number;
44
+ }> {
45
+ /** Stable contention code, not a sandbox health failure. */
46
+ readonly code = "sandbox-busy";
47
+ }
37
48
  declare const Cancelled_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
38
49
  readonly _tag: "Cancelled";
39
50
  } & Readonly<A>;
@@ -120,7 +131,7 @@ export declare class Unknown extends Unknown_base<SandboxFailureReference & {
120
131
  }> {
121
132
  }
122
133
  /** Closed error channel shared by sandbox adapters and the invocation owner. */
123
- export type SandboxFailure = SourceInvalid | GuestThrew | DeadlineExceeded | Cancelled | DispatchLost | SandboxCrashed | BadResponse | ResultTooLarge | ResultNotJson | CapabilityFailed | Unknown;
134
+ export type SandboxFailure = SourceInvalid | GuestThrew | DeadlineExceeded | SandboxBusy | Cancelled | DispatchLost | SandboxCrashed | BadResponse | ResultTooLarge | ResultNotJson | CapabilityFailed | Unknown;
124
135
  /** Copy the failure to correlate one execution without mutating shared host errors. */
125
136
  export declare function referenceSandboxFailure(failure: SandboxFailure, ref: string): SandboxFailure;
126
137
  /** The sole model projection. Transport, parser and raw cause fields stay private. */
@@ -8,6 +8,11 @@ export class GuestThrew extends Data.TaggedError("GuestThrew") {
8
8
  /** The invocation owner decided its execution budget was spent. */
9
9
  export class DeadlineExceeded extends Data.TaggedError("DeadlineExceeded") {
10
10
  }
11
+ /** Admission expired before any sandbox work started. */
12
+ export class SandboxBusy extends Data.TaggedError("SandboxBusy") {
13
+ /** Stable contention code, not a sandbox health failure. */
14
+ code = "sandbox-busy";
15
+ }
11
16
  /** The invocation owner accepted cancellation. */
12
17
  export class Cancelled extends Data.TaggedError("Cancelled") {
13
18
  }
@@ -62,6 +67,12 @@ export function referenceSandboxFailure(failure, ref) {
62
67
  acknowledged: failure.acknowledged,
63
68
  ref,
64
69
  });
70
+ case "SandboxBusy":
71
+ return new SandboxBusy({
72
+ capacity: failure.capacity,
73
+ waitedMs: failure.waitedMs,
74
+ ref,
75
+ });
65
76
  case "Cancelled":
66
77
  return new Cancelled({
67
78
  by: failure.by,
@@ -126,6 +137,9 @@ export function sandboxFailureMessage(failure, includeReference = true) {
126
137
  case "DeadlineExceeded":
127
138
  text = `execute hit its ${String(failure.limitMs)} ms deadline after ${String(failure.elapsedMs)} ms; split the work or persist progress${failure.acknowledged === false ? "; sandbox did not acknowledge cancellation" : ""}`;
128
139
  break;
140
+ case "SandboxBusy":
141
+ text = `sandbox-busy: all ${String(failure.capacity)} sandbox slots remained occupied after waiting ${String(failure.waitedMs)} ms. Nothing ran; retry this call after capacity becomes available.`;
142
+ break;
129
143
  case "Cancelled":
130
144
  text =
131
145
  failure.acknowledged === false
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  /** Closed guest envelope: guest exceptions cannot masquerade as host failures. */
3
- export declare const sandboxGuestResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ export declare const sandboxGuestResultSchema: z.ZodLazy<z.ZodDiscriminatedUnion<[z.ZodObject<{
4
4
  ok: z.ZodLiteral<true>;
5
5
  value: z.ZodUnknown;
6
6
  }, z.core.$strict>, z.ZodObject<{
@@ -11,7 +11,7 @@ export declare const sandboxGuestResultSchema: z.ZodDiscriminatedUnion<[z.ZodObj
11
11
  message: z.ZodString;
12
12
  transportMessage: z.ZodOptional<z.ZodString>;
13
13
  }, z.core.$strict>;
14
- }, z.core.$strict>], "ok">;
14
+ }, z.core.$strict>], "ok">>;
15
15
  /** Locate syntax in the submitted code, accounting for Codemode's normalization. */
16
16
  export declare function submittedSourceLocation(normalized: string, submitted: string): Readonly<{
17
17
  line: number;
@@ -19,13 +19,13 @@ export declare function submittedSourceLocation(normalized: string, submitted: s
19
19
  excerpt: string;
20
20
  }> | null;
21
21
  /** Parsed guest namespace segments; callbacks never cross this wire. */
22
- export declare const sandboxNamespaceSegmentSchema: z.ZodString;
22
+ export declare const sandboxNamespaceSegmentSchema: z.ZodLazy<z.ZodString>;
23
23
  /** A transport alias is interpolated as a strict JavaScript binding identifier. */
24
- export declare const sandboxTransportAliasSchema: z.ZodString;
24
+ export declare const sandboxTransportAliasSchema: z.ZodLazy<z.ZodString>;
25
25
  /** Transport aliases mapped to their logical namespace paths. */
26
- export declare const sandboxNamespacePathsSchema: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
26
+ export declare const sandboxNamespacePathsSchema: z.ZodLazy<z.ZodRecord<z.ZodLazy<z.ZodString>, z.ZodArray<z.ZodLazy<z.ZodString>>>>;
27
27
  /** Serializable flat transport table; operation keys may have any namespace depth. */
28
- export declare const sandboxBindingNamesSchema: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
28
+ export declare const sandboxBindingNamesSchema: z.ZodLazy<z.ZodRecord<z.ZodLazy<z.ZodString>, z.ZodArray<z.ZodString>>>;
29
29
  /** Serializable operation names; callbacks stay in the host's existing binding table. */
30
30
  export declare function sandboxBindingNames<A>(bindings: Readonly<Record<string, Readonly<Record<string, A>>>>): Readonly<Record<string, readonly string[]>>;
31
31
  /** Injective provider identifier: Codemode sanitizes dotted function names. */
@@ -2,7 +2,7 @@ import { parse } from "acorn";
2
2
  import { z } from "zod";
3
3
  import { sandboxSourceError } from "./sandbox-boundary.js";
4
4
  /** Closed guest envelope: guest exceptions cannot masquerade as host failures. */
5
- export const sandboxGuestResultSchema = z.discriminatedUnion("ok", [
5
+ export const sandboxGuestResultSchema = z.lazy(() => z.discriminatedUnion("ok", [
6
6
  z.strictObject({ ok: z.literal(true), value: z.unknown() }),
7
7
  z.strictObject({
8
8
  ok: z.literal(false),
@@ -13,7 +13,7 @@ export const sandboxGuestResultSchema = z.discriminatedUnion("ok", [
13
13
  transportMessage: z.string().max(512).optional(),
14
14
  }),
15
15
  }),
16
- ]);
16
+ ]));
17
17
  /** Locate syntax in the submitted code, accounting for Codemode's normalization. */
18
18
  export function submittedSourceLocation(normalized, submitted) {
19
19
  try {
@@ -45,9 +45,7 @@ export function submittedSourceLocation(normalized, submitted) {
45
45
  }
46
46
  }
47
47
  /** Parsed guest namespace segments; callbacks never cross this wire. */
48
- export const sandboxNamespaceSegmentSchema = z
49
- .string()
50
- .regex(/^[A-Za-z][A-Za-z0-9_]*$/u);
48
+ export const sandboxNamespaceSegmentSchema = z.lazy(() => z.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/u));
51
49
  /** A transport alias is interpolated as a strict JavaScript binding identifier. */
52
50
  export const sandboxTransportAliasSchema = sandboxNamespaceSegmentSchema.refine((alias) => {
53
51
  if (alias === "env")
@@ -63,9 +61,9 @@ export const sandboxTransportAliasSchema = sandboxNamespaceSegmentSchema.refine(
63
61
  }
64
62
  }, "Invalid sandbox transport alias");
65
63
  /** Transport aliases mapped to their logical namespace paths. */
66
- export const sandboxNamespacePathsSchema = z.record(sandboxTransportAliasSchema, z.array(sandboxNamespaceSegmentSchema).min(1));
64
+ export const sandboxNamespacePathsSchema = z.lazy(() => z.record(sandboxTransportAliasSchema, z.array(sandboxNamespaceSegmentSchema).min(1)));
67
65
  /** Serializable flat transport table; operation keys may have any namespace depth. */
68
- export const sandboxBindingNamesSchema = z.record(sandboxTransportAliasSchema, z.array(z.string().regex(/^[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)*$/u)));
66
+ export const sandboxBindingNamesSchema = z.lazy(() => z.record(sandboxTransportAliasSchema, z.array(z.string().regex(/^[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)*$/u))));
69
67
  /** Serializable operation names; callbacks stay in the host's existing binding table. */
70
68
  export function sandboxBindingNames(bindings) {
71
69
  return Object.fromEntries(Object.entries(bindings).map(([namespace, operations]) => [
@@ -20,6 +20,11 @@ export declare const sandboxWireSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
20
20
  kind: z.ZodLiteral<"deadline_exceeded">;
21
21
  limitMs: z.ZodNumber;
22
22
  elapsedMs: z.ZodNumber;
23
+ }, z.core.$strict>, z.ZodObject<{
24
+ kind: z.ZodLiteral<"sandbox_busy">;
25
+ code: z.ZodLiteral<"sandbox-busy">;
26
+ capacity: z.ZodInt;
27
+ waitedMs: z.ZodNumber;
23
28
  }, z.core.$strict>, z.ZodObject<{
24
29
  kind: z.ZodLiteral<"result_not_json">;
25
30
  valueKind: z.ZodString;