@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.
- package/README.md +70 -7
- package/dist/access.d.ts +2 -7
- package/dist/access.js +0 -3
- package/dist/execute-tool.d.ts +3 -2
- package/dist/execute-tool.js +40 -11
- package/dist/image-collector.d.ts +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +2 -1
- package/dist/internal/build-identity.node.js +1 -1
- package/dist/internal/execution-lifecycle.d.ts +4 -2
- package/dist/internal/execution-lifecycle.js +94 -46
- package/dist/internal/host-cores.d.ts +2 -0
- package/dist/internal/host-cores.js +4 -0
- package/dist/internal/host-cores.node.d.ts +2 -0
- package/dist/internal/host-cores.node.js +5 -0
- package/dist/mcp-catalog.d.ts +0 -6
- package/dist/mcp-catalog.js +0 -1
- package/dist/mcp.d.ts +0 -1
- package/dist/mcp.js +0 -1
- package/dist/output-collector.d.ts +46 -0
- package/dist/output-collector.js +145 -0
- package/dist/runtime.js +1 -1
- package/dist/sandbox-admission.d.ts +19 -0
- package/dist/sandbox-admission.js +21 -0
- package/dist/sandbox-boundary.js +3 -1
- package/dist/sandbox-failure-event.d.ts +1 -1
- package/dist/sandbox-failure-event.js +1 -0
- package/dist/sandbox-failure.d.ts +12 -1
- package/dist/sandbox-failure.js +14 -0
- package/dist/sandbox-wire.d.ts +5 -0
- package/dist/sandbox-wire.js +20 -1
- package/docs/api/index.md +68 -5
- package/docs/api/mcp.md +0 -2
- package/docs/api/sandbox-failure.md +90 -0
- package/docs/api/sandbox-guest.md +36 -30
- package/docs/api/sandbox-wire.md +3 -3
- package/package.json +9 -5
package/dist/mcp-catalog.d.ts
CHANGED
|
@@ -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.
|
package/dist/mcp-catalog.js
CHANGED
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
|
@@ -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
|
+
}
|
package/dist/sandbox-boundary.js
CHANGED
|
@@ -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
|
}>;
|
|
@@ -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. */
|
package/dist/sandbox-failure.js
CHANGED
|
@@ -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
|
package/dist/sandbox-wire.d.ts
CHANGED
|
@@ -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;
|
package/dist/sandbox-wire.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
}>;
|