@fungi.computer/mycelium 0.0.0-stage → 0.0.2
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/LICENSE +21 -0
- package/README.md +303 -2
- package/THIRD_PARTY_NOTICES +2 -0
- package/dist/access.d.ts +129 -0
- package/dist/access.js +166 -0
- package/dist/capability-source.d.ts +44 -0
- package/dist/capability-source.js +1 -0
- package/dist/compiler.d.ts +28 -0
- package/dist/compiler.js +221 -0
- package/dist/define.d.ts +8 -0
- package/dist/define.js +7 -0
- package/dist/effect.d.ts +89 -0
- package/dist/effect.js +42 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.js +62 -0
- package/dist/execute-tool.d.ts +22 -0
- package/dist/execute-tool.js +163 -0
- package/dist/image-collector.d.ts +47 -0
- package/dist/image-collector.js +39 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +22 -0
- package/dist/internal/build-identity.d.ts +1 -0
- package/dist/internal/build-identity.js +15 -0
- package/dist/internal/build-identity.node.d.ts +1 -0
- package/dist/internal/build-identity.node.js +42 -0
- package/dist/internal/execution-lifecycle.d.ts +44 -0
- package/dist/internal/execution-lifecycle.js +328 -0
- package/dist/internal/mcp-principal.d.ts +14 -0
- package/dist/internal/mcp-principal.js +17 -0
- package/dist/mcp-catalog.d.ts +23 -0
- package/dist/mcp-catalog.js +110 -0
- package/dist/mcp.d.ts +92 -0
- package/dist/mcp.js +250 -0
- package/dist/operation-schema.d.ts +47 -0
- package/dist/operation-schema.js +50 -0
- package/dist/promise-facade.d.ts +3 -0
- package/dist/promise-facade.js +82 -0
- package/dist/registration.d.ts +74 -0
- package/dist/registration.js +157 -0
- package/dist/remote.d.ts +33 -0
- package/dist/remote.js +126 -0
- package/dist/runtime.d.ts +51 -0
- package/dist/runtime.js +159 -0
- package/dist/sandbox-boundary.d.ts +44 -0
- package/dist/sandbox-boundary.js +325 -0
- package/dist/sandbox-failure-event.d.ts +15 -0
- package/dist/sandbox-failure-event.js +34 -0
- package/dist/sandbox-failure.d.ts +128 -0
- package/dist/sandbox-failure.js +161 -0
- package/dist/sandbox-guest.d.ts +34 -0
- package/dist/sandbox-guest.js +131 -0
- package/dist/sandbox-wire.d.ts +76 -0
- package/dist/sandbox-wire.js +190 -0
- package/dist/sandbox.d.ts +41 -0
- package/dist/sandbox.js +1 -0
- package/dist/semaphore-transition.d.ts +7 -0
- package/dist/semaphore-transition.js +9 -0
- package/dist/source-runtime.d.ts +28 -0
- package/dist/source-runtime.js +162 -0
- package/dist/testing.d.ts +11 -0
- package/dist/testing.js +19 -0
- package/dist/workers/index.d.ts +9 -0
- package/dist/workers/index.js +90 -0
- package/docs/api/README.md +25 -0
- package/docs/api/define.md +20 -0
- package/docs/api/effect.md +245 -0
- package/docs/api/index.md +1081 -0
- package/docs/api/mcp.md +239 -0
- package/docs/api/remote.md +108 -0
- package/docs/api/sandbox-boundary.md +325 -0
- package/docs/api/sandbox-failure.md +1133 -0
- package/docs/api/sandbox-guest.md +191 -0
- package/docs/api/sandbox-wire.md +117 -0
- package/docs/api/sandbox.md +81 -0
- package/docs/api/semaphore-transition.md +40 -0
- package/docs/api/testing.md +72 -0
- package/docs/api/workers.md +55 -0
- package/package.json +132 -3
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { isSandboxFailure } from "./sandbox-boundary.js";
|
|
2
|
+
import { ResultTooLarge, sandboxFailureMessage } from "./sandbox-failure.js";
|
|
3
|
+
import { nanoid } from "@fungi.computer/cairn";
|
|
4
|
+
import { forageDescribeInput, forageSearchInput, forageListInput, } from "@fungi.computer/forage";
|
|
5
|
+
import { Type, } from "@fungi.computer/mule/effect";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Schema from "effect/Schema";
|
|
8
|
+
import * as Stream from "effect/Stream";
|
|
9
|
+
import { checkAccess } from "./access.js";
|
|
10
|
+
import { normalizeExecutionPolicy, runExecution, } from "./internal/execution-lifecycle.js";
|
|
11
|
+
import { ImageCollector } from "./image-collector.js";
|
|
12
|
+
import { InvalidOperationInput } from "./errors.js";
|
|
13
|
+
/** The one outer Mule input authority for the Mycelium execute door. */
|
|
14
|
+
const ExecuteInputSchema = Schema.Struct({ code: Schema.String });
|
|
15
|
+
const executeDescription = "Run async JavaScript in the sandbox. To enumerate all available operations, use forage.list({limit:50}) and follow nextCursor until null; optionally filter by namespace. Use await forage.search({ query: ... }) with short operation keywords, such as read or shell. Search matches operation names and descriptions; if no results match, retry with one relevant keyword. Then use await forage.describe({ path: ... }) to inspect the input before calling an operation. Describe known paths directly. For multiline shell commands, put the command in a JavaScript template literal, for example const command = `pwd\\nprintf done`; a physical newline inside a double-quoted JavaScript string is invalid source. Code is evaluated as JavaScript before shell commands run: if a shell command needs a backslash, write it doubled in the JavaScript string (for example, `\\\\(` passes `\\(` to the shell; `\\(` becomes `(`). For file edits, prefer Files operations when available. Explicitly return the final value. For shell or process results, inspect status, exitCode, and stderr when present before treating the operation as successful. Each execute call runs in a fresh sandbox: no variable, function, or other global survives from a previous call, so redefine any helpers you need every time, or persist state to a file and read it back instead.";
|
|
16
|
+
/** One bound operation: its labels are checked against the host's grants first. */
|
|
17
|
+
function guardedInvoke(entry, context, access, sessionId) {
|
|
18
|
+
const label = {
|
|
19
|
+
module: entry.grantKey,
|
|
20
|
+
operation: entry.name,
|
|
21
|
+
access: entry.operation.access,
|
|
22
|
+
confirm: entry.operation.confirm,
|
|
23
|
+
};
|
|
24
|
+
return async (input) => {
|
|
25
|
+
const refused = await checkAccess(access, { sessionId }, label, input, context.signal);
|
|
26
|
+
return refused ?? (await entry.operation.invoke(input, context));
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function operationBindings(operations, context, access, sessionId) {
|
|
30
|
+
const groups = new Map();
|
|
31
|
+
for (const entry of operations) {
|
|
32
|
+
const namespace = entry.namespace;
|
|
33
|
+
const operationName = entry.name;
|
|
34
|
+
const group = groups.get(namespace) ??
|
|
35
|
+
new Map();
|
|
36
|
+
group.set(operationName, guardedInvoke(entry, context, access, sessionId));
|
|
37
|
+
groups.set(namespace, group);
|
|
38
|
+
}
|
|
39
|
+
return Object.freeze(Object.fromEntries([...groups].map(([namespace, operations]) => [
|
|
40
|
+
namespace,
|
|
41
|
+
Object.freeze(Object.fromEntries(operations)),
|
|
42
|
+
])));
|
|
43
|
+
}
|
|
44
|
+
/** Parse a built-in Forage request; a bad request is the model's invalid input. */
|
|
45
|
+
function forageRequest(schema, input) {
|
|
46
|
+
const request = schema.safeParse(input);
|
|
47
|
+
if (!request.success)
|
|
48
|
+
throw new InvalidOperationInput(request.error.issues);
|
|
49
|
+
return request.data;
|
|
50
|
+
}
|
|
51
|
+
function forageBindings(catalog) {
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
list: (input) => Promise.resolve().then(() => catalog.list(forageRequest(forageListInput, input))),
|
|
54
|
+
describe: (input) => Promise.resolve().then(() => catalog.describe(forageRequest(forageDescribeInput, input))),
|
|
55
|
+
search: (input) => Promise.resolve().then(() => catalog.search(forageRequest(forageSearchInput, input))),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function bindings(operations, catalog, context, access, sessionId) {
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
...operationBindings(operations, context, access, sessionId),
|
|
61
|
+
forage: forageBindings(catalog),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Construct one execute door over a stable compiled surface and sandbox adapter. */
|
|
65
|
+
export function createExecuteDoor(operations, catalog, sandbox, backingWork, modules, namespacePaths, sessionId, access) {
|
|
66
|
+
const policy = sandbox.execution ?? normalizeExecutionPolicy(undefined);
|
|
67
|
+
const imageCollector = new ImageCollector();
|
|
68
|
+
const run = (executionId, code, signal, toolCallId) => runExecution({
|
|
69
|
+
executionId,
|
|
70
|
+
toolCallId,
|
|
71
|
+
images: Object.freeze({
|
|
72
|
+
push: (image) => imageCollector.push(executionId, image),
|
|
73
|
+
}),
|
|
74
|
+
code,
|
|
75
|
+
callerSignal: signal,
|
|
76
|
+
policy,
|
|
77
|
+
sandbox,
|
|
78
|
+
backingWork,
|
|
79
|
+
createBindings: (context) => bindings(operations, catalog, Object.freeze({ ...context, toolCallCode: code }), access, sessionId),
|
|
80
|
+
modules,
|
|
81
|
+
namespacePaths,
|
|
82
|
+
sessionId,
|
|
83
|
+
});
|
|
84
|
+
/** Run one execution, then clear whatever images it collected. */
|
|
85
|
+
const execute = (code, signal) => Effect.suspend(() => {
|
|
86
|
+
const executionId = nanoid();
|
|
87
|
+
return run(executionId, code, signal).pipe(Effect.ensuring(Effect.sync(() => {
|
|
88
|
+
imageCollector.dispose(executionId);
|
|
89
|
+
})));
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* The Mule projection: after the run, drain the collector once and append
|
|
93
|
+
* its images after the text, on the oversized-result path too.
|
|
94
|
+
*/
|
|
95
|
+
const executeTerminal = (code, toolCallId) => Effect.suspend(() => {
|
|
96
|
+
const executionId = nanoid();
|
|
97
|
+
const drain = () => imageCollector.drain(executionId);
|
|
98
|
+
return run(executionId, code, undefined, toolCallId).pipe(Effect.matchEffect({
|
|
99
|
+
onSuccess: (value) => Effect.succeed(terminal({ text: value.text, isError: false }, drain())),
|
|
100
|
+
onFailure: (error) => {
|
|
101
|
+
const images = drain();
|
|
102
|
+
return error instanceof ResultTooLarge && images.length > 0
|
|
103
|
+
? Effect.succeed(terminal({ text: sandboxFailureMessage(error), isError: true }, images))
|
|
104
|
+
: Effect.fail(toMuleError(error));
|
|
105
|
+
},
|
|
106
|
+
}), Effect.ensuring(Effect.sync(() => {
|
|
107
|
+
imageCollector.dispose(executionId);
|
|
108
|
+
})));
|
|
109
|
+
});
|
|
110
|
+
const effectTool = {
|
|
111
|
+
// Guest JavaScript can invoke mutating operations, even when some bindings
|
|
112
|
+
// declare safe reads. Mule must never blindly replay this whole program.
|
|
113
|
+
replay: "unsafe",
|
|
114
|
+
descriptor: {
|
|
115
|
+
name: "execute",
|
|
116
|
+
description: executeDescription,
|
|
117
|
+
parameters: Type.Object({ code: Type.String() }, { additionalProperties: false }),
|
|
118
|
+
},
|
|
119
|
+
prepare: (call) => Schema.decodeUnknownEffect(ExecuteInputSchema)(call.arguments).pipe(Effect.map(({ code }) => ({
|
|
120
|
+
execute: () => Stream.fromEffect(executeTerminal(code, call.id)),
|
|
121
|
+
})), Effect.mapError(() => ({ message: "Invalid execute arguments" }))),
|
|
122
|
+
};
|
|
123
|
+
return Object.freeze({ effectTool, execute });
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Project one finished run into Mule's only terminal part. Each collected
|
|
127
|
+
* image becomes an {@link ImageContent} part after the text, the same
|
|
128
|
+
* `MuleToolResult.content` every other tool result uses; its path, size and
|
|
129
|
+
* type go into `details`.
|
|
130
|
+
*/
|
|
131
|
+
function terminal(head, collected) {
|
|
132
|
+
const images = collected.map((image) => ({
|
|
133
|
+
type: "image",
|
|
134
|
+
data: image.data,
|
|
135
|
+
mimeType: image.mimeType,
|
|
136
|
+
}));
|
|
137
|
+
const content = [
|
|
138
|
+
{ type: "text", text: head.text },
|
|
139
|
+
...images,
|
|
140
|
+
];
|
|
141
|
+
if (collected.length === 0)
|
|
142
|
+
return { type: "result", result: { content, isError: head.isError } };
|
|
143
|
+
const details = {
|
|
144
|
+
images: collected.map((image) => ({
|
|
145
|
+
bytes: image.bytes,
|
|
146
|
+
mimeType: image.mimeType,
|
|
147
|
+
path: image.path,
|
|
148
|
+
})),
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
type: "result",
|
|
152
|
+
result: { content, details, isError: head.isError },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function toMuleError(error) {
|
|
156
|
+
return {
|
|
157
|
+
_tag: "MuleError",
|
|
158
|
+
phase: "tool",
|
|
159
|
+
message: isSandboxFailure(error)
|
|
160
|
+
? sandboxFailureMessage(error)
|
|
161
|
+
: error.message,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution-scoped image collector, ported from the retained
|
|
3
|
+
* `@shiit/mycelium/execute` collector.
|
|
4
|
+
*
|
|
5
|
+
* Per-execution isolation for out-of-band image content: a connector
|
|
6
|
+
* operation (for example `images.view`) pushes one downsampled image through
|
|
7
|
+
* its `InvocationContext.images`, keyed by the execution's id. After the run,
|
|
8
|
+
* the execute tool drains the collector and appends the entries as
|
|
9
|
+
* `{ type: "image" }` content after the text: the same `MuleToolResult`
|
|
10
|
+
* content any other tool result carries, never a value the guest returns.
|
|
11
|
+
*
|
|
12
|
+
* - Per-execution isolation (the runtime's own execution id).
|
|
13
|
+
* - Bounded capacity per execution ({@link MAX_EXECUTION_IMAGES}).
|
|
14
|
+
* - In memory only: drained once, and cleared on dispose.
|
|
15
|
+
*/
|
|
16
|
+
/** A single image collected during an execute run. */
|
|
17
|
+
export type CollectedImage = Readonly<{
|
|
18
|
+
/** Base64-encoded image bytes. */
|
|
19
|
+
data: string;
|
|
20
|
+
/** MIME type of the collected image. */
|
|
21
|
+
mimeType: "image/jpeg" | "image/png" | "image/webp";
|
|
22
|
+
/** Path the image was read from. */
|
|
23
|
+
path: string;
|
|
24
|
+
/** Byte length of the collected image payload. */
|
|
25
|
+
bytes: number;
|
|
26
|
+
}>;
|
|
27
|
+
/** The collector as one execution's operations see it: push only. */
|
|
28
|
+
export type ExecutionImages = Readonly<{
|
|
29
|
+
/**
|
|
30
|
+
* Push one downsampled image and return its stable reference (`img_0`,
|
|
31
|
+
* `img_1`, ...) to surface as a text handle. Throws {@link CapabilityFailed}
|
|
32
|
+
* once the execution already holds the maximum.
|
|
33
|
+
*/
|
|
34
|
+
push: (image: CollectedImage) => string;
|
|
35
|
+
}>;
|
|
36
|
+
/** Maximum number of out-of-band images retained for one execution. */
|
|
37
|
+
export declare const MAX_EXECUTION_IMAGES = 8;
|
|
38
|
+
export declare class ImageCollector {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(maxPerExecution?: number);
|
|
41
|
+
/** Push one image for `executionId` and return its reference id. */
|
|
42
|
+
push(executionId: string, image: CollectedImage): string;
|
|
43
|
+
/** Drain every image for `executionId` in push order and forget them. */
|
|
44
|
+
drain(executionId: string): readonly CollectedImage[];
|
|
45
|
+
/** Discard any pending images for `executionId`; safe to call twice. */
|
|
46
|
+
dispose(executionId: string): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CapabilityFailed } from "./sandbox-failure.js";
|
|
2
|
+
/** Maximum number of out-of-band images retained for one execution. */
|
|
3
|
+
export const MAX_EXECUTION_IMAGES = 8;
|
|
4
|
+
export class ImageCollector {
|
|
5
|
+
#byExecution = new Map();
|
|
6
|
+
#maxPerExecution;
|
|
7
|
+
constructor(maxPerExecution = MAX_EXECUTION_IMAGES) {
|
|
8
|
+
this.#maxPerExecution = maxPerExecution;
|
|
9
|
+
}
|
|
10
|
+
/** Push one image for `executionId` and return its reference id. */
|
|
11
|
+
push(executionId, image) {
|
|
12
|
+
const images = this.#byExecution.get(executionId) ?? [];
|
|
13
|
+
if (images.length >= this.#maxPerExecution)
|
|
14
|
+
throw new CapabilityFailed({
|
|
15
|
+
namespace: "execute",
|
|
16
|
+
operation: "images",
|
|
17
|
+
cause: {
|
|
18
|
+
kind: "Unavailable",
|
|
19
|
+
message: `Image collector cap reached for this execution (max ${String(this.#maxPerExecution)} images). Process images one at a time or write them to files.`,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
const ref = `img_${String(images.length)}`;
|
|
23
|
+
images.push(Object.freeze({ ...image }));
|
|
24
|
+
this.#byExecution.set(executionId, images);
|
|
25
|
+
return ref;
|
|
26
|
+
}
|
|
27
|
+
/** Drain every image for `executionId` in push order and forget them. */
|
|
28
|
+
drain(executionId) {
|
|
29
|
+
const images = this.#byExecution.get(executionId);
|
|
30
|
+
if (images === undefined)
|
|
31
|
+
return [];
|
|
32
|
+
this.#byExecution.delete(executionId);
|
|
33
|
+
return images;
|
|
34
|
+
}
|
|
35
|
+
/** Discard any pending images for `executionId`; safe to call twice. */
|
|
36
|
+
dispose(executionId) {
|
|
37
|
+
this.#byExecution.delete(executionId);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export { sandboxFailure, isSandboxFailure, sandboxOperatorValue, } from "./sandbox-boundary.js";
|
|
2
|
+
import type { ForageCatalog } from "@fungi.computer/forage";
|
|
3
|
+
import type { MuleTool } from "@fungi.computer/mule/effect";
|
|
4
|
+
import type { Sandbox, MyceliumJsonValue } from "./sandbox.js";
|
|
5
|
+
export type { MyceliumJsonValue, SandboxValue, SandboxBinding, SandboxRequest, Sandbox, } from "./sandbox.js";
|
|
6
|
+
import type * as Schema from "effect/Schema";
|
|
7
|
+
import type { OperationSchema } from "./operation-schema.js";
|
|
8
|
+
import type { CapabilitySession, CapabilitySource } from "./capability-source.js";
|
|
9
|
+
import type { SandboxFailureEvent } from "./sandbox-failure-event.js";
|
|
10
|
+
import type { ExecutionImages } from "./image-collector.js";
|
|
11
|
+
import type { ExecutionOptions } from "./execute-tool.js";
|
|
12
|
+
import type { Access, AccessOptions, LabelledTool } from "./access.js";
|
|
13
|
+
import { type ModuleRegistration, type OperationGate, type OperationRegistration } from "./registration.js";
|
|
14
|
+
export type { CapabilitySession, CapabilitySource, CapabilitySourceAcquisition, CapabilitySourceContext, CapabilitySourceSnapshot, } from "./capability-source.js";
|
|
15
|
+
export { InvalidOperationInput, InvalidOperationOutput, MyceliumClosedError, MyceliumDefinitionError, MyceliumSourceError, type MyceliumError, type MyceliumSourcePhase, } from "./errors.js";
|
|
16
|
+
export { SourceInvalid, GuestThrew, DeadlineExceeded, Cancelled, DispatchLost, SandboxCrashed, BadResponse, ResultTooLarge, ResultNotJson, CapabilityFailed, Unknown, sandboxFailureMessage, type SandboxFailure, type CapabilityDiagnostic, type CapabilityFailureFields, } from "./sandbox-failure.js";
|
|
17
|
+
export { SandboxFailureEventSchema, type SandboxFailureEvent, } from "./sandbox-failure-event.js";
|
|
18
|
+
export type { ExecutionOptions } from "./execute-tool.js";
|
|
19
|
+
export { GrantSchema, ToolAccessSchema, ToolConfirmSchema, ToolPolicySchema, } from "./access.js";
|
|
20
|
+
export type { Access, AccessContext, AccessLabel, AccessOptions, AccessRefusal, AccessRefusalReason, ConfirmRequest, Grant, Grants, LabelledTool, ToolPolicy, } from "./access.js";
|
|
21
|
+
export { MAX_EXECUTION_IMAGES, type CollectedImage, type ExecutionImages, } from "./image-collector.js";
|
|
22
|
+
export type { OperationSchema, OperationSchemaIssue, OperationSchemaResult, StandardOperationSchema, } from "./operation-schema.js";
|
|
23
|
+
export type { GateDecision, ModuleRegistration, OperationGate, OperationRegistration, } from "./registration.js";
|
|
24
|
+
/** The only context an operation callback may observe. */
|
|
25
|
+
export type InvocationContext = Readonly<{
|
|
26
|
+
/** Fresh identity for this invocation, independent from Mule's tool-call ID. */
|
|
27
|
+
executionId: string;
|
|
28
|
+
/** Enclosing model tool call; absent for direct operation/execute calls. */
|
|
29
|
+
toolCallId?: string;
|
|
30
|
+
/** Enclosing sandbox program supplied by the host; never an operation argument. */
|
|
31
|
+
toolCallCode?: string;
|
|
32
|
+
/** Absolute epoch-millisecond execute deadline; null for direct calls outside execute. */
|
|
33
|
+
deadlineAt: number | null;
|
|
34
|
+
/** Cooperative cancellation signal owned by the active Mule run. */
|
|
35
|
+
signal: AbortSignal;
|
|
36
|
+
/**
|
|
37
|
+
* This execution's image collector: an operation that lets the model look
|
|
38
|
+
* at an image pushes it here, and `execute` appends it as image content.
|
|
39
|
+
* Absent when a host invokes an operation directly, outside `execute`,
|
|
40
|
+
* where no tool result would carry the image.
|
|
41
|
+
*/
|
|
42
|
+
images?: ExecutionImages;
|
|
43
|
+
}>;
|
|
44
|
+
/** Inputs used to construct the Promise-facing runtime. */
|
|
45
|
+
export type MyceliumOptions = Readonly<{
|
|
46
|
+
/** Unordered module registrations compiled into one immutable revision. */
|
|
47
|
+
modules?: readonly ModuleRegistration[];
|
|
48
|
+
/** Versioned host projections reconciled into immutable revisions. */
|
|
49
|
+
sources?: readonly CapabilitySource[];
|
|
50
|
+
/** Session every source acquisition is made for; absent for session-free hosts. */
|
|
51
|
+
session?: CapabilitySession;
|
|
52
|
+
/** Bounded Mycelium-owned deadline and cancellation acknowledgement policy. */
|
|
53
|
+
execution?: ExecutionOptions;
|
|
54
|
+
/** Host-owned execution boundary for model-authored programs. */
|
|
55
|
+
sandbox: Sandbox;
|
|
56
|
+
/** Operator-only failure sink; never contributes a model message. */
|
|
57
|
+
onSandboxFailure?: (event: SandboxFailureEvent) => Promise<void>;
|
|
58
|
+
/** Labelled direct tools offered beside execute under the same access check. */
|
|
59
|
+
tools?: readonly LabelledTool[];
|
|
60
|
+
/** Host grants and approval, checked against each call's labels. */
|
|
61
|
+
access?: AccessOptions;
|
|
62
|
+
}>;
|
|
63
|
+
/** Cancellation options for one Promise-facing lease acquisition. */
|
|
64
|
+
export type AcquireOptions = Readonly<{
|
|
65
|
+
/** Optional cancellation signal for lease acquisition. */
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}>;
|
|
68
|
+
/** One immutable Promise-facing capability revision. */
|
|
69
|
+
export type Lease = Readonly<{
|
|
70
|
+
/** Installed capability revision captured by this immutable lease. */
|
|
71
|
+
revision: string;
|
|
72
|
+
/** Bounded discovery catalog for the captured revision. */
|
|
73
|
+
catalog: ForageCatalog;
|
|
74
|
+
/** Single Mule tool that exposes discovery and scoped execution. */
|
|
75
|
+
executeTool: MuleTool;
|
|
76
|
+
/** The runtime's labelled direct tools, each behind the access check. */
|
|
77
|
+
tools: readonly MuleTool[];
|
|
78
|
+
/** Release the lease; safe to await more than once. */
|
|
79
|
+
release: () => Promise<void>;
|
|
80
|
+
}>;
|
|
81
|
+
/** Warm Promise-facing lifecycle for acquiring immutable run leases. */
|
|
82
|
+
export type Runtime = Readonly<{
|
|
83
|
+
/** Acquire one immutable lease from the currently installed revision. */
|
|
84
|
+
acquire: (options?: AcquireOptions) => Promise<Lease>;
|
|
85
|
+
/** Stop new acquisition and await runtime-owned cleanup. */
|
|
86
|
+
close: () => Promise<void>;
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* Declaration for one operation behind the execute door. `input` and `output`
|
|
90
|
+
* may be Effect schemas (encoded as JSON) or any Standard Schema with
|
|
91
|
+
* Standard JSON Schema, such as Zod 4.
|
|
92
|
+
*/
|
|
93
|
+
export type OperationDefinition<I, O> = Readonly<{
|
|
94
|
+
/** Declaration for inventory; the enclosing model-facing execute tool remains unsafe. */
|
|
95
|
+
replay?: "safe" | "unsafe";
|
|
96
|
+
/**
|
|
97
|
+
* What this operation does: `read` observes, `write` changes something.
|
|
98
|
+
* A fixed fact the author owns; unlabelled is `write`. A host's `read`
|
|
99
|
+
* grant for this module runs only `read` operations.
|
|
100
|
+
*/
|
|
101
|
+
access?: Access;
|
|
102
|
+
/** Ask a person to approve each call first; refused when no one can be asked. */
|
|
103
|
+
confirm?: boolean;
|
|
104
|
+
/** Bounded discovery text for the model. */
|
|
105
|
+
description?: string;
|
|
106
|
+
/** Schema that decodes untrusted operation input. */
|
|
107
|
+
input: OperationSchema<I>;
|
|
108
|
+
/** Optional schema that encodes (Effect) or validates (Standard) the result. */
|
|
109
|
+
output?: OperationSchema<O>;
|
|
110
|
+
/** Host implementation invoked only after input validation. */
|
|
111
|
+
execute: (input: I, context: InvocationContext) => Promise<O>;
|
|
112
|
+
}>;
|
|
113
|
+
/** Declaration for one stable module and its related operations. */
|
|
114
|
+
export type ModuleDefinition = Readonly<{
|
|
115
|
+
/** Stable authority identity; independent from the model-facing alias. */
|
|
116
|
+
id: string;
|
|
117
|
+
/** Optional friendly name used to derive the model-facing alias. */
|
|
118
|
+
name?: string;
|
|
119
|
+
/** Parent namespaces in the guest environment, independent of authority identity. */
|
|
120
|
+
namespace?: readonly string[];
|
|
121
|
+
/** Bounded namespace instructions projected into Forage. */
|
|
122
|
+
description?: string;
|
|
123
|
+
/** Operations or child modules, keyed by a path segment or a dotted relative path such as `repos.list` (the guest calls it as `env.<module>.repos.list()`). A key that is both an operation and a namespace prefix is refused. */
|
|
124
|
+
operations: Readonly<Record<string, OperationRegistration | ModuleRegistration>>;
|
|
125
|
+
}>;
|
|
126
|
+
/** Public convenience namespace contract. */
|
|
127
|
+
export type MyceliumApi = Readonly<{
|
|
128
|
+
/** Declare one namespaced collection of related operations. */
|
|
129
|
+
module: (definition: ModuleDefinition) => ModuleRegistration;
|
|
130
|
+
/**
|
|
131
|
+
* Declare one schema-described operation behind the execute door. An Effect
|
|
132
|
+
* output schema encodes the result to JSON; otherwise the result must
|
|
133
|
+
* already be a JSON value.
|
|
134
|
+
*/
|
|
135
|
+
operation: {
|
|
136
|
+
<I, O>(definition: OperationDefinition<I, O> & {
|
|
137
|
+
readonly output: Schema.Codec<O, unknown>;
|
|
138
|
+
}): OperationRegistration<I, O>;
|
|
139
|
+
<I, O extends MyceliumJsonValue>(definition: OperationDefinition<I, O>): OperationRegistration<I, O>;
|
|
140
|
+
};
|
|
141
|
+
/** Open the Promise-shaped facade over Mycelium's Effect runtime. */
|
|
142
|
+
make: (options: MyceliumOptions) => Promise<Runtime>;
|
|
143
|
+
/** Put every operation of one module behind a host gate. */
|
|
144
|
+
gate: (module: ModuleRegistration, gate: OperationGate) => ModuleRegistration;
|
|
145
|
+
/** Look one module's grant up by `grantKey` instead of its id. */
|
|
146
|
+
grantAs: (module: ModuleRegistration, grantKey: string) => ModuleRegistration;
|
|
147
|
+
}>;
|
|
148
|
+
/** Schema-described authoring and Promise-facing runtime construction. */
|
|
149
|
+
export declare const Mycelium: MyceliumApi;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { sandboxFailure, isSandboxFailure, sandboxOperatorValue, } from "./sandbox-boundary.js";
|
|
2
|
+
import { openPromiseRuntime } from "./promise-facade.js";
|
|
3
|
+
import { defineModule, defineOperation, gateModule, grantModuleAs, } from "./registration.js";
|
|
4
|
+
export { InvalidOperationInput, InvalidOperationOutput, MyceliumClosedError, MyceliumDefinitionError, MyceliumSourceError, } from "./errors.js";
|
|
5
|
+
export { SourceInvalid, GuestThrew, DeadlineExceeded, Cancelled, DispatchLost, SandboxCrashed, BadResponse, ResultTooLarge, ResultNotJson, CapabilityFailed, Unknown, sandboxFailureMessage, } from "./sandbox-failure.js";
|
|
6
|
+
export { SandboxFailureEventSchema, } from "./sandbox-failure-event.js";
|
|
7
|
+
export { GrantSchema, ToolAccessSchema, ToolConfirmSchema, ToolPolicySchema, } from "./access.js";
|
|
8
|
+
export { MAX_EXECUTION_IMAGES, } from "./image-collector.js";
|
|
9
|
+
function operation(definition) {
|
|
10
|
+
return defineOperation(definition);
|
|
11
|
+
}
|
|
12
|
+
function module(definition) {
|
|
13
|
+
return defineModule(definition);
|
|
14
|
+
}
|
|
15
|
+
/** Schema-described authoring and Promise-facing runtime construction. */
|
|
16
|
+
export const Mycelium = Object.freeze({
|
|
17
|
+
module,
|
|
18
|
+
operation,
|
|
19
|
+
make: openPromiseRuntime,
|
|
20
|
+
gate: gateModule,
|
|
21
|
+
grantAs: grantModuleAs,
|
|
22
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sandboxBuildSha(): string | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Platform-neutral default. Browser bundles (apps/demo, esbuild
|
|
2
|
+
// `platform: "browser"`), Electron-renderer bundles
|
|
3
|
+
// (electron-local-consumer-probe) and Workers bundles (apps/hub, wrangler)
|
|
4
|
+
// all reach this module through execution-lifecycle.ts's `#build-identity`
|
|
5
|
+
// internal import (package.json `imports`) and resolve to *this* file,
|
|
6
|
+
// since none of those bundlers activate a `node` condition. This file must
|
|
7
|
+
// never import `node:*` -- the real, Git-backed implementation lives in
|
|
8
|
+
// build-identity.node.ts, which only a literal Node process (Node's own
|
|
9
|
+
// resolver always activates the `node` condition) ever loads (2026-10-07
|
|
10
|
+
// cache-hash-stability audit, S1 + the batch-368 gate failure it caused:
|
|
11
|
+
// `Could not resolve "node:child_process"` while bundling
|
|
12
|
+
// @fungi.computer/demo and electron-local-consumer-probe).
|
|
13
|
+
export function sandboxBuildSha() {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sandboxBuildSha(): string | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { URL } from "node:url";
|
|
3
|
+
// Node-only variant of build-identity.ts, selected only by the package's
|
|
4
|
+
// `#build-identity` internal import (package.json `imports`, `node`
|
|
5
|
+
// condition) -- a literal Node process (apps/exec, services/agent-host,
|
|
6
|
+
// runtimes/local's non-bundled dist, this package's own tests) always
|
|
7
|
+
// activates that condition; no bundler build target in this repo does
|
|
8
|
+
// (demo and the Electron probe build with esbuild `platform: "browser"`;
|
|
9
|
+
// Hub builds with wrangler/workerd), so this file's `node:child_process`
|
|
10
|
+
// import never reaches a bundle that can't resolve it.
|
|
11
|
+
//
|
|
12
|
+
// Until 2026-10-07 the package build stamped execution-lifecycle.ts's
|
|
13
|
+
// build-identity copy with the exact commit, and Turbo hashed that same
|
|
14
|
+
// commit into the `build` task to avoid serving a stale stamp from cache.
|
|
15
|
+
// That forced every commit to miss the cache for this package and the ~80
|
|
16
|
+
// tasks across ~25 packages that depend on it (cache-hash-stability audit,
|
|
17
|
+
// S1). The identity is read lazily instead: nothing calls this outside the
|
|
18
|
+
// rare sandbox-failure path, so it costs a `git` call only when something
|
|
19
|
+
// is actually tagging a failure, never at build, lint, typecheck or
|
|
20
|
+
// ordinary test time, and the build output no longer depends on which
|
|
21
|
+
// commit produced it. A source checkout without a reachable Git repository
|
|
22
|
+
// (or a packed/registry install, which carries no `.git` at all) answers
|
|
23
|
+
// `null`; `pnpm pack` stamps its own copy of this file with a literal
|
|
24
|
+
// identity instead (package.json's `pack` script, scripts/stamp-build.mjs),
|
|
25
|
+
// since a packed copy can't shell out to Git once installed.
|
|
26
|
+
let cached;
|
|
27
|
+
export function sandboxBuildSha() {
|
|
28
|
+
if (cached !== undefined)
|
|
29
|
+
return cached;
|
|
30
|
+
try {
|
|
31
|
+
const candidate = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
32
|
+
cwd: new URL(".", import.meta.url),
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
35
|
+
}).trim();
|
|
36
|
+
cached = /^[0-9a-f]{40}$/u.test(candidate) ? candidate : null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
cached = null;
|
|
40
|
+
}
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type SandboxFailure } from "../sandbox-failure.js";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Fiber from "effect/Fiber";
|
|
4
|
+
import type { ExecutionOptions } from "../execute-tool.js";
|
|
5
|
+
import { type MyceliumError } from "../errors.js";
|
|
6
|
+
import type { InvocationContext, SandboxValue } from "../index.js";
|
|
7
|
+
import type { ExecutionImages } from "../image-collector.js";
|
|
8
|
+
import type { EffectSandbox } from "../runtime.js";
|
|
9
|
+
export type ExecutionPolicy = Readonly<{
|
|
10
|
+
readonly timeoutMs: number;
|
|
11
|
+
readonly abortGraceMs: number;
|
|
12
|
+
}>;
|
|
13
|
+
export type BackingWorkGroup = Readonly<{
|
|
14
|
+
readonly start: <A, E>(effect: Effect.Effect<A, E>) => Effect.Effect<Fiber.Fiber<A, E>, SandboxFailure>;
|
|
15
|
+
readonly awaitIdle: Effect.Effect<void>;
|
|
16
|
+
}>;
|
|
17
|
+
export type BackingWorkTracker = Readonly<{
|
|
18
|
+
readonly createGroup: () => BackingWorkGroup;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function normalizeExecutionPolicy(value: ExecutionOptions | undefined): ExecutionPolicy;
|
|
21
|
+
export declare function createBackingWorkTracker(): BackingWorkTracker;
|
|
22
|
+
export type SerializedSandboxValue = Readonly<{
|
|
23
|
+
readonly text: string;
|
|
24
|
+
}>;
|
|
25
|
+
export type RawBindings = Readonly<Record<string, Readonly<Record<string, (input: SandboxValue) => Promise<SandboxValue>>>>>;
|
|
26
|
+
export type ExecutionLifecycleRequest = Readonly<{
|
|
27
|
+
readonly toolCallId?: string | undefined;
|
|
28
|
+
/** This run's identity, minted by the execute door that drains its images. */
|
|
29
|
+
readonly executionId: string;
|
|
30
|
+
/** This run's slot in the door's collector, exposed through the context. */
|
|
31
|
+
readonly images: ExecutionImages;
|
|
32
|
+
readonly code: string;
|
|
33
|
+
readonly callerSignal: AbortSignal | undefined;
|
|
34
|
+
readonly policy: ExecutionPolicy;
|
|
35
|
+
readonly sandbox: EffectSandbox;
|
|
36
|
+
readonly backingWork: BackingWorkGroup;
|
|
37
|
+
readonly createBindings: (context: InvocationContext) => RawBindings;
|
|
38
|
+
/** Every bound execution alias mapped to its stable module id. */
|
|
39
|
+
readonly modules: Readonly<Record<string, string>>;
|
|
40
|
+
readonly namespacePaths: Readonly<Record<string, readonly string[]>>;
|
|
41
|
+
/** Stable Session identity for this run; null for session-free hosts. */
|
|
42
|
+
readonly sessionId: string | null;
|
|
43
|
+
}>;
|
|
44
|
+
export declare function runExecution(request: ExecutionLifecycleRequest): Effect.Effect<SerializedSandboxValue, MyceliumError>;
|