@fungi.computer/mycelium 0.1.0 → 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.
@@ -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";
7
+ import { SourceInvalid, GuestThrew, DeadlineExceeded, SandboxBusy, Cancelled, DispatchLost, SandboxCrashed, BadResponse, ResultTooLarge, ResultNotJson, CapabilityFailed, Unknown, referenceSandboxFailure, sandboxFailureMessage, } from "./sandbox-failure.js";
8
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),
@@ -175,6 +176,7 @@ const transportEnvelopeSchema = z.lazy(() => z.strictObject({
175
176
  "SourceInvalid",
176
177
  "GuestThrew",
177
178
  "DeadlineExceeded",
179
+ "SandboxBusy",
178
180
  "Cancelled",
179
181
  "DispatchLost",
180
182
  "SandboxCrashed",
@@ -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
@@ -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;
@@ -1,6 +1,6 @@
1
1
  import { sandboxOperatorValue, sandboxGuestFailure, } from "./sandbox-boundary.js";
2
2
  import { z } from "zod";
3
- import { SourceInvalid, DeadlineExceeded, ResultNotJson, SandboxCrashed, BadResponse, DispatchLost, Cancelled, ResultTooLarge, CapabilityFailed, Unknown, } from "./sandbox-failure.js";
3
+ import { SourceInvalid, DeadlineExceeded, SandboxBusy, ResultNotJson, SandboxCrashed, BadResponse, DispatchLost, Cancelled, ResultTooLarge, CapabilityFailed, Unknown, } from "./sandbox-failure.js";
4
4
  /** The host-to-host result contract; raw diagnostic fields never go to guest code. */
5
5
  export const sandboxWireSchema = z.discriminatedUnion("ok", [
6
6
  z.strictObject({ ok: z.literal(true), value: z.json() }),
@@ -24,6 +24,12 @@ export const sandboxWireSchema = z.discriminatedUnion("ok", [
24
24
  limitMs: z.number().int().positive(),
25
25
  elapsedMs: z.number().nonnegative(),
26
26
  }),
27
+ z.strictObject({
28
+ kind: z.literal("sandbox_busy"),
29
+ code: z.literal("sandbox-busy"),
30
+ capacity: z.int().positive(),
31
+ waitedMs: z.number().nonnegative(),
32
+ }),
27
33
  z.strictObject({
28
34
  kind: z.literal("result_not_json"),
29
35
  valueKind: z.string().max(80),
@@ -82,6 +88,11 @@ export function sandboxWireFailure(error) {
82
88
  return sandboxGuestFailure(error);
83
89
  case "deadline_exceeded":
84
90
  return new DeadlineExceeded(error);
91
+ case "sandbox_busy":
92
+ return new SandboxBusy({
93
+ capacity: error.capacity,
94
+ waitedMs: error.waitedMs,
95
+ });
85
96
  case "result_not_json":
86
97
  return new ResultNotJson({
87
98
  kind: error.valueKind,
@@ -131,6 +142,14 @@ export function sandboxFailureWire(failure) {
131
142
  elapsedMs: failure.elapsedMs,
132
143
  };
133
144
  break;
145
+ case "SandboxBusy":
146
+ error = {
147
+ kind: "sandbox_busy",
148
+ code: failure.code,
149
+ capacity: failure.capacity,
150
+ waitedMs: failure.waitedMs,
151
+ };
152
+ break;
134
153
  case "Cancelled":
135
154
  error = { kind: "cancelled", by: failure.by };
136
155
  break;
package/docs/api/index.md CHANGED
@@ -378,7 +378,6 @@ type AccessOptions = Readonly<{
378
378
  sessionGrants?: (context) => Grants;
379
379
  beforeCall?: (signal) => Promise<void>;
380
380
  confirmWrites?: boolean;
381
- approvedWrites?: (context) => readonly string[];
382
381
  confirm?: (request, signal) => Promise<boolean>;
383
382
  onRefused?: (refusal, context) => void;
384
383
  }>;
@@ -387,7 +386,8 @@ type AccessOptions = Readonly<{
387
386
  Host-supplied access data and the one way to ask a person. Every field is
388
387
  optional: hosted defaults allow unnamed modules, and no `confirm` means an
389
388
  operation requiring approval is refused, never run unasked. MCP catalogs
390
- explicitly default unnamed modules to none and require approval for writes.
389
+ explicitly default unnamed modules to none. Native hosts own durable approval
390
+ policy for admitted writes; explicit author confirmation still uses this port.
391
391
 
392
392
  ---
393
393
 
@@ -503,10 +503,11 @@ Closed failure vocabulary for the first Mycelium runtime slice.
503
503
  type ExecutionOptions = Readonly<{
504
504
  timeoutMs?: number;
505
505
  abortGraceMs?: number;
506
- }>;
506
+ }> &
507
+ SandboxAdmissionOptions;
507
508
  ```
508
509
 
509
- Host-selectable execution limits; neither budget can be disabled.
510
+ Host execution limits plus the reserved, host-only admission contract.
510
511
 
511
512
  ---
512
513
 
@@ -515,7 +516,7 @@ Host-selectable execution limits; neither budget can be disabled.
515
516
  ```ts
516
517
  type CollectedImage = Readonly<{
517
518
  data: string;
518
- mimeType: "image/jpeg" | "image/png" | "image/webp";
519
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
519
520
  path: string;
520
521
  bytes: number;
521
522
  }>;
@@ -543,10 +544,14 @@ The collector as one execution's operations see it: push only.
543
544
  type InvocationContext = Readonly<{
544
545
  executionId: string;
545
546
  toolCallId?: string;
547
+ operationOrdinal?: number;
546
548
  toolCallCode?: string;
547
549
  deadlineAt: number | null;
548
550
  signal: AbortSignal;
549
551
  images?: ExecutionImages;
552
+ output?: Readonly<{
553
+ write: (stream, bytes) => void;
554
+ }>;
550
555
  }>;
551
556
  ```
552
557
 
@@ -835,6 +840,32 @@ Asked before every call of a gated module's operations.
835
840
 
836
841
  ---
837
842
 
843
+ ### SandboxAdmissionOptions
844
+
845
+ ```ts
846
+ type SandboxAdmissionOptions = Readonly<{
847
+ sandboxCapacity?: number;
848
+ admissionWaitMs?: number;
849
+ }>;
850
+ ```
851
+
852
+ Host-owned admission inputs; never part of guest execute arguments.
853
+
854
+ ---
855
+
856
+ ### SandboxAdmissionPolicy
857
+
858
+ ```ts
859
+ type SandboxAdmissionPolicy = Readonly<{
860
+ capacity: number;
861
+ waitMs: number;
862
+ }>;
863
+ ```
864
+
865
+ Captured once per runtime and shared across backing-work groups.
866
+
867
+ ---
868
+
838
869
  ### SandboxFailureEvent
839
870
 
840
871
  ```ts
@@ -925,6 +956,7 @@ const SandboxFailureEventSchema: Struct<{
925
956
  "SourceInvalid",
926
957
  "GuestThrew",
927
958
  "DeadlineExceeded",
959
+ "SandboxBusy",
928
960
  "Cancelled",
929
961
  "DispatchLost",
930
962
  "SandboxCrashed",
@@ -942,6 +974,31 @@ const SandboxFailureEventSchema: Struct<{
942
974
 
943
975
  Shared structural grammar for durable operator facts and client inspection.
944
976
 
977
+ ## Functions
978
+
979
+ ### normalizeSandboxAdmissionPolicy()
980
+
981
+ ```ts
982
+ function normalizeSandboxAdmissionPolicy(
983
+ options,
984
+ availableCores?,
985
+ ): SandboxAdmissionPolicy;
986
+ ```
987
+
988
+ Validate host policy and resolve defaults from a platform-owned core
989
+ observation.
990
+
991
+ #### Parameters
992
+
993
+ | Parameter | Type |
994
+ | ----------------- | ------------------------------------------------------------------------------------------------- |
995
+ | `options` | \| `Readonly`\<\{ `sandboxCapacity?`: `number`; `admissionWaitMs?`: `number`; \}\> \| `undefined` |
996
+ | `availableCores?` | `number` |
997
+
998
+ #### Returns
999
+
1000
+ [`SandboxAdmissionPolicy`](#sandboxadmissionpolicy)
1001
+
945
1002
  ## References
946
1003
 
947
1004
  ### sandboxFailure
@@ -1010,6 +1067,12 @@ Re-exports [DeadlineExceeded](sandbox-failure.md#deadlineexceeded)
1010
1067
 
1011
1068
  ---
1012
1069
 
1070
+ ### SandboxBusy
1071
+
1072
+ Re-exports [SandboxBusy](sandbox-failure.md#sandboxbusy)
1073
+
1074
+ ---
1075
+
1013
1076
  ### Cancelled
1014
1077
 
1015
1078
  Re-exports [Cancelled](sandbox-failure.md#cancelled)
package/docs/api/mcp.md CHANGED
@@ -44,7 +44,6 @@ type McpCatalogSnapshot = Readonly<{
44
44
  instructions: string;
45
45
  sources: readonly CapabilitySource[];
46
46
  grants: Grants;
47
- approvedWrites?: readonly string[];
48
47
  }>;
49
48
  ```
50
49
 
@@ -93,7 +92,6 @@ type McpExecutionContext = Readonly<{
93
92
  onPart?: (part) => Promise<void>;
94
93
  access: Pick<AccessOptions, "confirm"> &
95
94
  Readonly<{
96
- confirmWrites: true;
97
95
  beforeCall: (signal) => Promise<void>;
98
96
  }>;
99
97
  }>;