@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
package/dist/remote.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { InvocationContext, MyceliumJsonValue, SandboxValue } from "./index.js";
|
|
2
|
+
import { type ModuleRegistration } from "./registration.js";
|
|
3
|
+
/** One operation's model-facing contract with its schemas as JSON Schema. */
|
|
4
|
+
export type RemoteOperationDescriptor = Readonly<{
|
|
5
|
+
replay: "safe" | "unsafe";
|
|
6
|
+
description?: string | undefined;
|
|
7
|
+
input: MyceliumJsonValue;
|
|
8
|
+
/** Absent when the operation declares no output schema. */
|
|
9
|
+
output: MyceliumJsonValue | null;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* A module's operations keyed by their dotted path below the module, for a
|
|
13
|
+
* host that runs the module in another isolate. Identity, alias and placement
|
|
14
|
+
* stay the host's decision; this carries only what the model may call.
|
|
15
|
+
*/
|
|
16
|
+
export type RemoteModuleDescriptor = Readonly<{
|
|
17
|
+
description?: string | undefined;
|
|
18
|
+
operations: Readonly<Record<string, RemoteOperationDescriptor>>;
|
|
19
|
+
}>;
|
|
20
|
+
/** Project one module's callable surface; unrepresentable schemas throw. */
|
|
21
|
+
export declare function describeModule(registration: ModuleRegistration): RemoteModuleDescriptor;
|
|
22
|
+
/** Run one described operation through its own input and output schemas. */
|
|
23
|
+
export declare function invokeModuleOperation(registration: ModuleRegistration, path: string, input: SandboxValue, context: InvocationContext): Promise<MyceliumJsonValue>;
|
|
24
|
+
/**
|
|
25
|
+
* Rebuild a described module on the host side. Each operation parses its input
|
|
26
|
+
* and output against the described JSON Schema, then forwards to `invoke`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function remoteModule(options: Readonly<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
descriptor: RemoteModuleDescriptor;
|
|
32
|
+
invoke: (path: string, input: MyceliumJsonValue, context: InvocationContext) => Promise<MyceliumJsonValue>;
|
|
33
|
+
}>): ModuleRegistration;
|
package/dist/remote.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsonSchemaToZod } from "@fungi.computer/forage";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { MyceliumDefinitionError } from "./errors.js";
|
|
4
|
+
import { defineModule, defineOperation, isModuleRegistration, readModule, readOperation, } from "./registration.js";
|
|
5
|
+
function operationsOf(registration) {
|
|
6
|
+
const stored = readModule(registration);
|
|
7
|
+
if (stored === undefined)
|
|
8
|
+
throw new MyceliumDefinitionError("Unknown module registration");
|
|
9
|
+
const operations = new Map();
|
|
10
|
+
const visit = (handles, prefix) => {
|
|
11
|
+
for (const [name, handle] of Object.entries(handles)) {
|
|
12
|
+
if (isModuleRegistration(handle)) {
|
|
13
|
+
const child = readModule(handle);
|
|
14
|
+
if (child === undefined)
|
|
15
|
+
throw new MyceliumDefinitionError("Unknown child module registration");
|
|
16
|
+
visit(child.operations, [...prefix, name, ...child.namespace]);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const operation = readOperation(handle);
|
|
20
|
+
if (operation === undefined)
|
|
21
|
+
throw new MyceliumDefinitionError(`Unknown operation registration: ${name}`);
|
|
22
|
+
operations.set([...prefix, name].join("."), operation);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
visit(stored.operations, []);
|
|
26
|
+
return operations;
|
|
27
|
+
}
|
|
28
|
+
/** Project one module's callable surface; unrepresentable schemas throw. */
|
|
29
|
+
export function describeModule(registration) {
|
|
30
|
+
const stored = readModule(registration);
|
|
31
|
+
if (stored === undefined)
|
|
32
|
+
throw new MyceliumDefinitionError("Unknown module registration");
|
|
33
|
+
const operations = {};
|
|
34
|
+
for (const [path, operation] of operationsOf(registration)) {
|
|
35
|
+
const input = z.json().parse(operation.input["~standard"].jsonSchema.input({
|
|
36
|
+
target: "draft-2020-12",
|
|
37
|
+
}));
|
|
38
|
+
const output = operation.output === undefined
|
|
39
|
+
? null
|
|
40
|
+
: z.json().parse(operation.output["~standard"].jsonSchema.output({
|
|
41
|
+
target: "draft-2020-12",
|
|
42
|
+
}));
|
|
43
|
+
operations[path] =
|
|
44
|
+
operation.description === undefined
|
|
45
|
+
? { replay: operation.replay, input, output }
|
|
46
|
+
: {
|
|
47
|
+
replay: operation.replay,
|
|
48
|
+
description: operation.description,
|
|
49
|
+
input,
|
|
50
|
+
output,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return stored.description === undefined
|
|
54
|
+
? { operations }
|
|
55
|
+
: { description: stored.description, operations };
|
|
56
|
+
}
|
|
57
|
+
/** Run one described operation through its own input and output schemas. */
|
|
58
|
+
export function invokeModuleOperation(registration, path, input, context) {
|
|
59
|
+
const operation = operationsOf(registration).get(path);
|
|
60
|
+
if (operation === undefined)
|
|
61
|
+
return Promise.reject(new MyceliumDefinitionError(`Unknown remote operation: ${path}`));
|
|
62
|
+
return operation
|
|
63
|
+
.invoke(input, context)
|
|
64
|
+
.then((value) => z.json().parse(value));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Rebuild a described module on the host side. Each operation parses its input
|
|
68
|
+
* and output against the described JSON Schema, then forwards to `invoke`.
|
|
69
|
+
*/
|
|
70
|
+
export function remoteModule(options) {
|
|
71
|
+
const root = new Map();
|
|
72
|
+
for (const [path, operation] of Object.entries(options.descriptor.operations)) {
|
|
73
|
+
const segments = path.split(".");
|
|
74
|
+
const leaf = segments.pop();
|
|
75
|
+
if (leaf === undefined)
|
|
76
|
+
throw new MyceliumDefinitionError("Empty remote operation path");
|
|
77
|
+
let node = root;
|
|
78
|
+
for (const segment of segments) {
|
|
79
|
+
const next = node.get(segment) ??
|
|
80
|
+
new Map();
|
|
81
|
+
if (!(next instanceof Map))
|
|
82
|
+
throw new MyceliumDefinitionError(`Remote operation path collides with an operation: ${path}`);
|
|
83
|
+
node.set(segment, next);
|
|
84
|
+
node = next;
|
|
85
|
+
}
|
|
86
|
+
if (node.has(leaf))
|
|
87
|
+
throw new MyceliumDefinitionError(`Duplicate remote operation path: ${path}`);
|
|
88
|
+
node.set(leaf, operation);
|
|
89
|
+
}
|
|
90
|
+
const build = (tree, prefix) => Object.fromEntries([...tree].map(([name, entry]) => {
|
|
91
|
+
const path = [...prefix, name];
|
|
92
|
+
if (entry instanceof Map)
|
|
93
|
+
return [
|
|
94
|
+
name,
|
|
95
|
+
defineModule({
|
|
96
|
+
id: `${options.id}.${path.join(".")}`,
|
|
97
|
+
name,
|
|
98
|
+
operations: build(entry, path),
|
|
99
|
+
}),
|
|
100
|
+
];
|
|
101
|
+
const output = entry.output === null
|
|
102
|
+
? undefined
|
|
103
|
+
: z.intersection(jsonSchemaToZod(entry.output), z.json());
|
|
104
|
+
const forward = (value, context) => options.invoke(path.join("."), value, context);
|
|
105
|
+
const base = {
|
|
106
|
+
replay: entry.replay,
|
|
107
|
+
input: z.intersection(jsonSchemaToZod(entry.input), z.json()),
|
|
108
|
+
execute: forward,
|
|
109
|
+
};
|
|
110
|
+
const described = entry.description === undefined
|
|
111
|
+
? base
|
|
112
|
+
: { ...base, description: entry.description };
|
|
113
|
+
return [
|
|
114
|
+
name,
|
|
115
|
+
defineOperation(output === undefined ? described : { ...described, output }),
|
|
116
|
+
];
|
|
117
|
+
}));
|
|
118
|
+
return defineModule(options.descriptor.description === undefined
|
|
119
|
+
? { id: options.id, name: options.name, operations: build(root, []) }
|
|
120
|
+
: {
|
|
121
|
+
id: options.id,
|
|
122
|
+
name: options.name,
|
|
123
|
+
description: options.descriptor.description,
|
|
124
|
+
operations: build(root, []),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SandboxFailureEvent } from "./sandbox-failure-event.js";
|
|
2
|
+
import type { SandboxFailure } from "./sandbox-failure.js";
|
|
3
|
+
import type { MuleTool } from "@fungi.computer/mule/effect";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Scope from "effect/Scope";
|
|
6
|
+
import type { CapabilitySession, CapabilitySource } from "./capability-source.js";
|
|
7
|
+
import { type CompiledSurface } from "./compiler.js";
|
|
8
|
+
import { type MyceliumError } from "./errors.js";
|
|
9
|
+
import type { ExecutionOptions } from "./execute-tool.js";
|
|
10
|
+
import type { ExecuteDoor } from "./execute-tool.js";
|
|
11
|
+
import type { ExecutionPolicy } from "./internal/execution-lifecycle.js";
|
|
12
|
+
import type { SandboxRequest, SandboxValue } from "./index.js";
|
|
13
|
+
import type { ModuleRegistration } from "./registration.js";
|
|
14
|
+
import { type AccessOptions, type LabelledTool } from "./access.js";
|
|
15
|
+
/** Effect-native host seam for sandbox execution. */
|
|
16
|
+
export type EffectSandbox = Readonly<{
|
|
17
|
+
readonly adapter?: string | undefined;
|
|
18
|
+
readonly buildSha?: string | undefined;
|
|
19
|
+
readonly onFailure?: ((event: SandboxFailureEvent) => Effect.Effect<void>) | undefined;
|
|
20
|
+
readonly execute: (request: SandboxRequest, signal: AbortSignal) => Effect.Effect<SandboxValue, SandboxFailure>;
|
|
21
|
+
/** Effective policy injected by the canonical runtime before compilation. */
|
|
22
|
+
readonly execution?: ExecutionPolicy;
|
|
23
|
+
}>;
|
|
24
|
+
/** Inputs consumed by the canonical runtime core. */
|
|
25
|
+
export type CoreOptions = Readonly<{
|
|
26
|
+
readonly modules: readonly ModuleRegistration[];
|
|
27
|
+
readonly sources: readonly CapabilitySource[];
|
|
28
|
+
readonly session?: CapabilitySession | undefined;
|
|
29
|
+
readonly execution?: ExecutionOptions | undefined;
|
|
30
|
+
readonly sandbox: EffectSandbox;
|
|
31
|
+
readonly onSandboxFailure?: ((event: SandboxFailureEvent) => Effect.Effect<void>) | undefined;
|
|
32
|
+
readonly tools: readonly LabelledTool[];
|
|
33
|
+
readonly access: AccessOptions;
|
|
34
|
+
}>;
|
|
35
|
+
/** Internal lease carrying the stable surface and its Effect execute door. */
|
|
36
|
+
export type CoreLease = Readonly<{
|
|
37
|
+
readonly revision: string;
|
|
38
|
+
readonly catalog: CompiledSurface["catalog"];
|
|
39
|
+
readonly executeDoor: ExecuteDoor;
|
|
40
|
+
readonly effectExecuteTool: MuleTool;
|
|
41
|
+
/** Labelled direct tools, each behind the runtime's access check. */
|
|
42
|
+
readonly tools: readonly MuleTool[];
|
|
43
|
+
readonly release: () => Effect.Effect<void, MyceliumError>;
|
|
44
|
+
}>;
|
|
45
|
+
/** Canonical scoped runtime used by both public lifecycle projections. */
|
|
46
|
+
export type CoreRuntime = Readonly<{
|
|
47
|
+
readonly acquire: () => Effect.Effect<CoreLease, MyceliumError, Scope.Scope>;
|
|
48
|
+
readonly close: () => Effect.Effect<void, MyceliumError>;
|
|
49
|
+
}>;
|
|
50
|
+
/** Construct the single Effect-owned lifecycle implementation. */
|
|
51
|
+
export declare function openCoreRuntime(options: CoreOptions): Effect.Effect<CoreRuntime, MyceliumError, Scope.Scope>;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as Deferred from "effect/Deferred";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Semaphore from "effect/Semaphore";
|
|
4
|
+
import * as Exit from "effect/Exit";
|
|
5
|
+
import * as Scope from "effect/Scope";
|
|
6
|
+
import { compileSurface } from "./compiler.js";
|
|
7
|
+
import { MyceliumClosedError, MyceliumDefinitionError, } from "./errors.js";
|
|
8
|
+
import { createBackingWorkTracker, normalizeExecutionPolicy, } from "./internal/execution-lifecycle.js";
|
|
9
|
+
import { guardTool } from "./access.js";
|
|
10
|
+
import { acquireSourceCandidate, } from "./source-runtime.js";
|
|
11
|
+
import { claimThenSettle } from "./semaphore-transition.js";
|
|
12
|
+
function finishCleanup(installed, liveRevisions) {
|
|
13
|
+
// A failed revision stays live, so a repeated close reports its retained
|
|
14
|
+
// cleanup failure instead of a fresh success.
|
|
15
|
+
return Effect.uninterruptible(installed.candidate.close().pipe(Effect.matchEffect({
|
|
16
|
+
onFailure: (failure) => Deferred.fail(installed.settled, failure).pipe(Effect.andThen(Effect.fail(failure))),
|
|
17
|
+
onSuccess: () => Deferred.succeed(installed.settled, undefined).pipe(Effect.andThen(Effect.sync(() => {
|
|
18
|
+
liveRevisions.delete(installed);
|
|
19
|
+
}))),
|
|
20
|
+
})));
|
|
21
|
+
}
|
|
22
|
+
function settleClosePlan(plan, liveRevisions) {
|
|
23
|
+
return plan.runCleanup
|
|
24
|
+
? finishCleanup(plan.installed, liveRevisions)
|
|
25
|
+
: Deferred.await(plan.installed.settled);
|
|
26
|
+
}
|
|
27
|
+
function settleClosePlans(plans, liveRevisions) {
|
|
28
|
+
return Effect.all(plans.map((plan) => settleClosePlan(plan, liveRevisions).pipe(Effect.exit)), { concurrency: "unbounded" }).pipe(Effect.flatMap((exits) => {
|
|
29
|
+
const failure = exits.find(Exit.isFailure);
|
|
30
|
+
return failure === undefined
|
|
31
|
+
? Effect.void
|
|
32
|
+
: Effect.failCause(failure.cause);
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
/** Construct the single Effect-owned lifecycle implementation. */
|
|
36
|
+
export function openCoreRuntime(options) {
|
|
37
|
+
return Effect.gen(function* () {
|
|
38
|
+
const gate = yield* Semaphore.make(1);
|
|
39
|
+
const backingWork = createBackingWorkTracker();
|
|
40
|
+
const execution = yield* Effect.try({
|
|
41
|
+
try: () => normalizeExecutionPolicy(options.execution),
|
|
42
|
+
catch: (cause) => cause instanceof MyceliumDefinitionError
|
|
43
|
+
? cause
|
|
44
|
+
: new MyceliumDefinitionError("Execution policy is invalid"),
|
|
45
|
+
});
|
|
46
|
+
const sandbox = Object.freeze({
|
|
47
|
+
execute: options.sandbox.execute,
|
|
48
|
+
adapter: options.sandbox.adapter,
|
|
49
|
+
buildSha: options.sandbox.buildSha,
|
|
50
|
+
onFailure: options.onSandboxFailure,
|
|
51
|
+
execution,
|
|
52
|
+
});
|
|
53
|
+
const staticSurface = yield* Effect.try({
|
|
54
|
+
try: () => compileSurface(options.modules),
|
|
55
|
+
catch: (cause) => cause instanceof MyceliumDefinitionError
|
|
56
|
+
? cause
|
|
57
|
+
: new MyceliumDefinitionError("Capability compilation failed"),
|
|
58
|
+
});
|
|
59
|
+
const tools = yield* Effect.try({
|
|
60
|
+
try: () => Object.freeze(options.tools.map((tool) => guardTool(tool, options.access, {
|
|
61
|
+
sessionId: options.session?.sessionId ?? null,
|
|
62
|
+
}))),
|
|
63
|
+
catch: (cause) => cause instanceof MyceliumDefinitionError
|
|
64
|
+
? cause
|
|
65
|
+
: new MyceliumDefinitionError("Direct tool labels are invalid"),
|
|
66
|
+
});
|
|
67
|
+
let installed;
|
|
68
|
+
const liveRevisions = new Set();
|
|
69
|
+
let closed = false;
|
|
70
|
+
if (options.sources.length === 0) {
|
|
71
|
+
const candidate = yield* acquireSourceCandidate({
|
|
72
|
+
...options,
|
|
73
|
+
sandbox,
|
|
74
|
+
backingWork,
|
|
75
|
+
precompiled: staticSurface,
|
|
76
|
+
});
|
|
77
|
+
const initial = {
|
|
78
|
+
candidate,
|
|
79
|
+
settled: yield* Deferred.make(),
|
|
80
|
+
leases: 0,
|
|
81
|
+
retired: false,
|
|
82
|
+
closing: false,
|
|
83
|
+
};
|
|
84
|
+
installed = initial;
|
|
85
|
+
liveRevisions.add(initial);
|
|
86
|
+
}
|
|
87
|
+
const close = () => claimThenSettle(gate, () => {
|
|
88
|
+
closed = true;
|
|
89
|
+
return [...liveRevisions].map((revision) => {
|
|
90
|
+
revision.retired = true;
|
|
91
|
+
const runCleanup = revision.leases === 0 && !revision.closing;
|
|
92
|
+
if (runCleanup)
|
|
93
|
+
revision.closing = true;
|
|
94
|
+
return { installed: revision, runCleanup };
|
|
95
|
+
});
|
|
96
|
+
}, (plans) => settleClosePlans(plans, liveRevisions));
|
|
97
|
+
yield* Effect.addFinalizer(() => close().pipe(Effect.orDie));
|
|
98
|
+
const acquire = () => gate.withPermits(1)(Effect.gen(function* () {
|
|
99
|
+
if (closed) {
|
|
100
|
+
return yield* Effect.fail(new MyceliumClosedError());
|
|
101
|
+
}
|
|
102
|
+
if (installed === undefined || installed.candidate.isStale()) {
|
|
103
|
+
const candidate = yield* acquireSourceCandidate({
|
|
104
|
+
...options,
|
|
105
|
+
sandbox,
|
|
106
|
+
backingWork,
|
|
107
|
+
});
|
|
108
|
+
const previous = installed;
|
|
109
|
+
const replacement = {
|
|
110
|
+
candidate,
|
|
111
|
+
settled: yield* Deferred.make(),
|
|
112
|
+
leases: 0,
|
|
113
|
+
retired: false,
|
|
114
|
+
closing: false,
|
|
115
|
+
};
|
|
116
|
+
installed = replacement;
|
|
117
|
+
liveRevisions.add(replacement);
|
|
118
|
+
if (previous !== undefined) {
|
|
119
|
+
previous.retired = true;
|
|
120
|
+
if (previous.leases === 0 && !previous.closing) {
|
|
121
|
+
previous.closing = true;
|
|
122
|
+
yield* finishCleanup(previous, liveRevisions);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const owned = installed;
|
|
127
|
+
owned.leases += 1;
|
|
128
|
+
// Undefined while held; afterwards, whether this release began cleanup.
|
|
129
|
+
let releaseCleanup;
|
|
130
|
+
const release = () => claimThenSettle(gate, () => {
|
|
131
|
+
// A retry awaits the cleanup this lease began, if any.
|
|
132
|
+
if (releaseCleanup !== undefined) {
|
|
133
|
+
return releaseCleanup
|
|
134
|
+
? { installed: owned, runCleanup: false }
|
|
135
|
+
: undefined;
|
|
136
|
+
}
|
|
137
|
+
owned.leases -= 1;
|
|
138
|
+
releaseCleanup =
|
|
139
|
+
owned.retired && owned.leases === 0 && !owned.closing;
|
|
140
|
+
if (!releaseCleanup)
|
|
141
|
+
return undefined;
|
|
142
|
+
owned.closing = true;
|
|
143
|
+
return { installed: owned, runCleanup: true };
|
|
144
|
+
}, (plan) => plan === undefined
|
|
145
|
+
? Effect.void
|
|
146
|
+
: settleClosePlan(plan, liveRevisions));
|
|
147
|
+
yield* Effect.addFinalizer(() => release().pipe(Effect.orDie));
|
|
148
|
+
return Object.freeze({
|
|
149
|
+
revision: owned.candidate.compiled.revision,
|
|
150
|
+
catalog: owned.candidate.compiled.catalog,
|
|
151
|
+
executeDoor: owned.candidate.executeDoor,
|
|
152
|
+
effectExecuteTool: owned.candidate.executeDoor.effectTool,
|
|
153
|
+
tools,
|
|
154
|
+
release,
|
|
155
|
+
});
|
|
156
|
+
}));
|
|
157
|
+
return Object.freeze({ acquire, close });
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { MyceliumJsonValue, SandboxRequest, SandboxValue } from "./sandbox.js";
|
|
3
|
+
import { GuestThrew, type SandboxFailure, type CapabilityDiagnostic } from "./sandbox-failure.js";
|
|
4
|
+
/** Admit only errors constructed by the sandbox owner, using the maintained decoder. */
|
|
5
|
+
export declare function isSandboxFailure(cause: unknown): cause is SandboxFailure;
|
|
6
|
+
/** Foreign throws stay private until an owner admits a diagnostic. */
|
|
7
|
+
export declare function sandboxFailure(cause: unknown): SandboxFailure;
|
|
8
|
+
/** Label a rejected JSON value without caller-owned reflection. */
|
|
9
|
+
export declare function sandboxValueKind(value: SandboxValue): string;
|
|
10
|
+
/** JSON argument or the SDK's explicit absence of an argument. */
|
|
11
|
+
export type SandboxBindingInput = MyceliumJsonValue | undefined;
|
|
12
|
+
/** Decode the RPC argument before any capability callback observes it. */
|
|
13
|
+
export declare function sandboxBindingInput(value: SandboxValue): SandboxBindingInput;
|
|
14
|
+
/** Full operator representation, including nested errors, aggregates and cycles. */
|
|
15
|
+
export declare function sandboxOperatorValue(cause: unknown): z.core.util.JSONType;
|
|
16
|
+
/** Decode the engine's guest-owned diagnostic once; renderers receive typed fields. */
|
|
17
|
+
export declare function sandboxGuestFailure(cause: unknown, bindings?: SandboxRequest["bindings"]): GuestThrew;
|
|
18
|
+
/** Error.message is the SDK's wire carrier; only this safe, bounded envelope crosses it. */
|
|
19
|
+
export declare function sandboxTransportMessage(failure: SandboxFailure, ref: string): string;
|
|
20
|
+
/** Recovery uses the decoded identity, never a match against diagnostic text. */
|
|
21
|
+
export declare function recoverSandboxFailure(failure: SandboxFailure, failures: ReadonlyMap<string, SandboxFailure>): SandboxFailure;
|
|
22
|
+
/** Decode Codemode's documented error carrier before adapters choose a failure. */
|
|
23
|
+
export declare function sandboxExecutorFailure(cause: unknown, limitMs: number, elapsedMs: number): SandboxFailure;
|
|
24
|
+
/** Decode a transport-owned errno without reflecting on a foreign throw in adapters. */
|
|
25
|
+
export declare function sandboxTransportErrno(cause: unknown): string | undefined;
|
|
26
|
+
/** Admit Acorn's documented offset from a syntax exception at this boundary. */
|
|
27
|
+
export declare function sandboxSourceError(cause: unknown): Readonly<{
|
|
28
|
+
pos: number;
|
|
29
|
+
}> | undefined;
|
|
30
|
+
/** Capability owners supply an admission schema; decoding and foreign fallback stay here. */
|
|
31
|
+
export declare function sandboxCapabilityFailure(cause: unknown, namespace: string, operation: string, admitted?: z.ZodType<CapabilityDiagnostic>): SandboxFailure;
|
|
32
|
+
/** Host operation callbacks after decoding JSON or an absent RPC argument. */
|
|
33
|
+
export type SandboxOperationBindings = Readonly<Record<string, Readonly<Record<string, (input: SandboxBindingInput) => Promise<MyceliumJsonValue>>>>>;
|
|
34
|
+
/** One boundary owns RPC decoding, safe transport and invocation-scoped recovery. */
|
|
35
|
+
export declare function sandboxBindingBoundary(bindings: SandboxOperationBindings): Readonly<{
|
|
36
|
+
/** Guest-visible callbacks with validated inputs and safe error carriers. */
|
|
37
|
+
bindings: SandboxRequest["bindings"];
|
|
38
|
+
/** Retain one typed provider refusal and return its safe guest carrier. */
|
|
39
|
+
capture: (failure: SandboxFailure) => string;
|
|
40
|
+
/** Restore an invocation-owned typed error from its exact wire envelope. */
|
|
41
|
+
recover: (failure: SandboxFailure) => SandboxFailure;
|
|
42
|
+
/** Decode a provider error deliberately returned by guest code. */
|
|
43
|
+
returnedFailure: (value: SandboxValue) => SandboxFailure | undefined;
|
|
44
|
+
}>;
|