@fungi.computer/truffle 0.1.1 → 0.2.1
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 +282 -34
- package/dist/auth-cli.d.ts +10 -0
- package/dist/auth-cli.js +36 -14
- package/dist/fungi-account-command.d.ts +1 -1
- package/dist/fungi-account-command.js +6 -3
- package/dist/index.d.ts +10 -2
- package/dist/index.js +8 -2
- package/dist/internal/image-fit-metafile.json +106 -0
- package/dist/internal/local-image-fit-worker.mjs +960 -0
- package/dist/internal/private-workerd-sandbox-worker.mjs +11 -12
- package/dist/internal/sandbox-metafile.json +7 -7
- package/dist/local-archive-handoff.d.ts +12 -0
- package/dist/local-archive-handoff.js +21 -0
- package/dist/local-extensions.d.ts +58 -12
- package/dist/local-extensions.js +195 -55
- package/dist/local-image-fit.d.ts +30 -0
- package/dist/local-image-fit.js +105 -0
- package/dist/local-models.d.ts +4 -2
- package/dist/local-models.js +11 -6
- package/dist/local-session-archives.d.ts +8 -0
- package/dist/local-session-archives.js +152 -0
- package/dist/local-skill-source.d.ts +18 -3
- package/dist/local-skill-source.js +216 -140
- package/dist/log-event.d.ts +7 -0
- package/dist/log-event.js +8 -4
- package/dist/private-bounded-response.d.ts +2 -0
- package/dist/private-bounded-response.js +41 -0
- package/dist/private-file-credential-store.d.ts +28 -3
- package/dist/private-file-credential-store.js +57 -9
- package/dist/private-files-patch.d.ts +2 -1
- package/dist/private-files-patch.js +21 -3
- package/dist/private-fungi-account.d.ts +40 -23
- package/dist/private-fungi-account.js +201 -21
- package/dist/private-fungi-mcp-connection.d.ts +4 -0
- package/dist/private-fungi-mcp-connection.js +25 -6
- package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
- package/dist/private-fungi-mcp-selection-schema.js +15 -0
- package/dist/private-fungi-mcp-selection.d.ts +6 -6
- package/dist/private-fungi-mcp-selection.js +29 -16
- package/dist/private-fungi-model-api.js +4 -4
- package/dist/private-fungi-model-catalog.d.ts +10 -0
- package/dist/private-fungi-model-catalog.js +10 -2
- package/dist/private-fungi-model-provider.d.ts +1 -1
- package/dist/private-fungi-model-provider.js +6 -1
- package/dist/private-fungi-pending-logouts.d.ts +1 -1
- package/dist/private-fungi-pending-logouts.js +1 -2
- package/dist/private-fungi-service-logout.d.ts +44 -0
- package/dist/private-fungi-service-logout.js +59 -0
- package/dist/private-invocation-computer.d.ts +24 -3
- package/dist/private-invocation-computer.js +101 -62
- package/dist/private-invocation-files.d.ts +16 -1
- package/dist/private-invocation-files.js +154 -83
- package/dist/private-local-acp-stdio.js +3 -0
- package/dist/private-local-admission-gate.d.ts +14 -0
- package/dist/private-local-admission-gate.js +52 -0
- package/dist/private-local-agent-control.d.ts +101 -0
- package/dist/private-local-agent-control.js +30 -0
- package/dist/private-local-agent-default-model.d.ts +12 -0
- package/dist/private-local-agent-default-model.js +31 -0
- package/dist/private-local-auth-rpc.d.ts +67 -7
- package/dist/private-local-auth-rpc.js +140 -17
- package/dist/private-local-bwrap.d.ts +13 -0
- package/dist/private-local-bwrap.js +70 -0
- package/dist/private-local-client-version.d.ts +5 -0
- package/dist/private-local-client-version.js +43 -0
- package/dist/private-local-computer-attach.d.ts +3 -3
- package/dist/private-local-computer-attach.js +17 -3
- package/dist/private-local-computer-processes.d.ts +6 -3
- package/dist/private-local-computer-processes.js +55 -31
- package/dist/private-local-config-root.d.ts +21 -0
- package/dist/private-local-config-root.js +19 -0
- package/dist/private-local-containment.d.ts +51 -0
- package/dist/private-local-containment.js +161 -0
- package/dist/private-local-delegation.d.ts +10 -3
- package/dist/private-local-delegation.js +70 -15
- package/dist/private-local-effective-model.d.ts +9 -1
- package/dist/private-local-effective-model.js +3 -0
- package/dist/private-local-exec-environment.d.ts +16 -0
- package/dist/private-local-exec-environment.js +11 -0
- package/dist/private-local-linux-sandbox.d.ts +8 -0
- package/dist/private-local-linux-sandbox.js +67 -0
- package/dist/private-local-mail.d.ts +4 -2
- package/dist/private-local-mail.js +41 -33
- package/dist/private-local-mesh-enrollment.d.ts +34 -0
- package/dist/private-local-mesh-enrollment.js +188 -0
- package/dist/private-local-mesh-links.d.ts +21 -0
- package/dist/private-local-mesh-links.js +229 -0
- package/dist/private-local-mesh-record.d.ts +120 -0
- package/dist/private-local-mesh-record.js +55 -0
- package/dist/private-local-process-identity.d.ts +20 -0
- package/dist/private-local-process-identity.js +40 -0
- package/dist/private-local-runtime-host.d.ts +64 -13
- package/dist/private-local-runtime-host.js +361 -172
- package/dist/private-local-sandbox-policy.d.ts +21 -0
- package/dist/private-local-sandbox-policy.js +121 -0
- package/dist/private-local-sandbox.d.ts +37 -0
- package/dist/private-local-sandbox.js +114 -0
- package/dist/private-local-seatbelt.d.ts +24 -0
- package/dist/private-local-seatbelt.js +68 -0
- package/dist/private-local-service-contract.d.ts +91 -11
- package/dist/private-local-service-discovery.d.ts +129 -13
- package/dist/private-local-service-discovery.js +148 -47
- package/dist/private-local-service-lease.d.ts +16 -0
- package/dist/private-local-service-lease.js +105 -0
- package/dist/private-local-service-listener.d.ts +9 -2
- package/dist/private-local-service-listener.js +260 -64
- package/dist/private-local-service-log.d.ts +128 -0
- package/dist/private-local-service-log.js +321 -0
- package/dist/private-local-service-owner.d.ts +87 -1519
- package/dist/private-local-service-owner.js +1069 -71
- package/dist/private-local-service-process.js +3 -1
- package/dist/private-local-service-termination.d.ts +7 -0
- package/dist/private-local-service-termination.js +67 -0
- package/dist/private-local-service.d.ts +31 -2
- package/dist/private-local-service.js +402 -126
- package/dist/private-local-session-discovery.d.ts +52 -5
- package/dist/private-local-session-discovery.js +132 -11
- package/dist/private-local-shiitake-host.js +2 -2
- package/dist/private-local-startup-model.d.ts +51 -0
- package/dist/private-local-startup-model.js +86 -0
- package/dist/private-local-stop-all.d.ts +27 -0
- package/dist/private-local-stop-all.js +26 -0
- package/dist/private-local-ui-attachment.d.ts +19 -0
- package/dist/private-local-ui-attachment.js +71 -0
- package/dist/private-local-worker-server.d.ts +1 -0
- package/dist/private-local-worker-server.js +13 -5
- package/dist/private-mcp-form-contract.d.ts +186 -0
- package/dist/private-mcp-form-contract.js +53 -0
- package/dist/private-mcp-form-elicitation.d.ts +18 -0
- package/dist/private-mcp-form-elicitation.js +97 -0
- package/dist/private-mcp-form-session.d.ts +172 -0
- package/dist/private-mcp-form-session.js +148 -0
- package/dist/private-openai-claims.d.ts +30 -0
- package/dist/private-openai-claims.js +69 -0
- package/dist/private-oyster-mcp-auth.d.ts +13 -0
- package/dist/private-oyster-mcp-auth.js +57 -0
- package/dist/private-provider-rejection.d.ts +15 -0
- package/dist/private-provider-rejection.js +24 -0
- package/dist/private-sqlite-owner.d.ts +34 -2
- package/dist/private-sqlite-owner.js +103 -37
- package/dist/private-team-computers.d.ts +4 -4
- package/dist/private-team-computers.js +11 -2
- package/dist/seatbelt/safe-base.sbpl +79 -0
- package/dist/subscription-activation.d.ts +9 -0
- package/dist/subscription-activation.js +62 -0
- package/dist/subscription-identity.d.ts +23 -0
- package/dist/subscription-identity.js +124 -0
- package/dist/subscription-turn.d.ts +17 -0
- package/dist/subscription-turn.js +143 -0
- package/dist/team-apps.d.ts +2 -2
- package/dist/team-apps.js +7 -2
- package/native/bin/LICENSE.bubblewrap +502 -0
- package/native/bin/LICENSE.libcap +112 -0
- package/native/bin/LICENSE.sandbox-runtime +201 -0
- package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
- package/native/bin/bwrap-build-source/Dockerfile +48 -0
- package/native/bin/bwrap-build-source/README.md +96 -0
- package/native/bin/bwrap-build-source/build.mjs +52 -0
- package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
- package/native/bin/bwrap-build-source/pin.mjs +21 -0
- package/native/bin/bwrap-build-source/record.mjs +74 -0
- package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
- package/native/bin/bwrap-build-source/vendor/README.md +23 -0
- package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
- package/native/bin/bwrap-build.json +44 -0
- package/native/bin/fungi-bwrap +0 -0
- package/native/bin/fungi-bwrap.sha256 +1 -0
- package/native/bin/fungi-seccomp.bpf +0 -0
- package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
- package/package.json +44 -31
- package/dist/private-local-model-settings.d.ts +0 -5
- package/dist/private-local-model-settings.js +0 -31
- package/dist/private-local-subagents.d.ts +0 -27
- package/dist/private-local-subagents.js +0 -77
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { accessSync, constants, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const digestSchema = z
|
|
8
|
+
.string()
|
|
9
|
+
.trim()
|
|
10
|
+
.regex(/^[a-f0-9]{64}$/u);
|
|
11
|
+
export class LocalBwrapUnavailable extends Error {
|
|
12
|
+
reason;
|
|
13
|
+
name = "LocalBwrapUnavailable";
|
|
14
|
+
constructor(reason, message, options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Check both packaged artifacts on every use; never cache verification. */
|
|
20
|
+
export function verifyLocalLinuxSandboxPayload(executable) {
|
|
21
|
+
verifiedBytes(executable, "fungi-bwrap");
|
|
22
|
+
try {
|
|
23
|
+
accessSync(executable, constants.X_OK);
|
|
24
|
+
}
|
|
25
|
+
catch (cause) {
|
|
26
|
+
throw new LocalBwrapUnavailable("not-executable", "Packaged fungi-bwrap cannot be executed; reinstall Fungi.", { cause });
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
executable,
|
|
30
|
+
seccompFilter: verifiedBytes(join(dirname(executable), "fungi-seccomp.bpf"), "fungi-seccomp.bpf"),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function verifiedBytes(path, label) {
|
|
34
|
+
let bytes;
|
|
35
|
+
let digest;
|
|
36
|
+
try {
|
|
37
|
+
if (!statSync(path).isFile())
|
|
38
|
+
throw new LocalBwrapUnavailable("not-executable", `Packaged ${label} is not a regular file; reinstall Fungi.`);
|
|
39
|
+
bytes = readFileSync(path);
|
|
40
|
+
digest = readFileSync(`${path}.sha256`, "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
44
|
+
throw new LocalBwrapUnavailable("missing", `Packaged ${label} or its digest is missing; reinstall Fungi with optional dependencies enabled.`, { cause });
|
|
45
|
+
throw cause;
|
|
46
|
+
}
|
|
47
|
+
const expected = digestSchema.safeParse(digest);
|
|
48
|
+
if (!expected.success)
|
|
49
|
+
throw new LocalBwrapUnavailable("invalid-digest", `Packaged ${label} has an invalid SHA-256 record; reinstall Fungi.`);
|
|
50
|
+
if (createHash("sha256").update(bytes).digest("hex") !== expected.data)
|
|
51
|
+
throw new LocalBwrapUnavailable("digest-mismatch", `Packaged ${label} digest mismatch; reinstall Fungi. No command was started.`);
|
|
52
|
+
return bytes;
|
|
53
|
+
}
|
|
54
|
+
/** Same optional native-package lookup as apply_patch, with no PATH fallback. */
|
|
55
|
+
export function localLinuxSandboxPayload() {
|
|
56
|
+
let executable;
|
|
57
|
+
if (process.env.FUNGI_BUNDLED_CLI) {
|
|
58
|
+
const name = `@fungi.computer/spore-native-${process.platform}-${process.arch}`;
|
|
59
|
+
try {
|
|
60
|
+
executable = createRequire(process.env.FUNGI_BUNDLED_CLI).resolve(`${name}/bin/fungi-bwrap`);
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
throw new LocalBwrapUnavailable("missing", `Missing ${name}/bin/fungi-bwrap; reinstall Fungi with optional dependencies enabled.`, { cause });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
executable = fileURLToPath(new URL("../native/bin/fungi-bwrap", import.meta.url));
|
|
68
|
+
}
|
|
69
|
+
return verifyLocalLinuxSandboxPayload(executable);
|
|
70
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const execute = promisify(execFile);
|
|
8
|
+
const queryOptions = { timeout: 2_000, maxBuffer: 4_096 };
|
|
9
|
+
const buildId = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
10
|
+
function installedLauncherOnPath() {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
execFile("which", ["fungi"], queryOptions, (error, stdout) => {
|
|
13
|
+
if (error === null)
|
|
14
|
+
resolve(stdout.trim());
|
|
15
|
+
// which exits 1 when nothing named fungi is installed on PATH.
|
|
16
|
+
else if (error.code === 1)
|
|
17
|
+
resolve(undefined);
|
|
18
|
+
else
|
|
19
|
+
reject(new Error("Could not look up the installed fungi launcher", {
|
|
20
|
+
cause: error,
|
|
21
|
+
}));
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read the installed producer, never the calling window's frozen environment.
|
|
27
|
+
* Undefined means no fungi is installed on PATH, so no client is current.
|
|
28
|
+
*/
|
|
29
|
+
export async function readInstalledLocalClientBuildId(launcher) {
|
|
30
|
+
const found = launcher ?? (await installedLauncherOnPath());
|
|
31
|
+
if (found === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const installed = await realpath(found);
|
|
34
|
+
if (basename(installed) === "fungi.cjs") {
|
|
35
|
+
// npm replaces cli.js in place. This is the launcher's own identity rule.
|
|
36
|
+
return createHash("sha256")
|
|
37
|
+
.update(await readFile(join(dirname(installed), "cli.js")))
|
|
38
|
+
.digest("hex");
|
|
39
|
+
}
|
|
40
|
+
// Compiled launchers identify their embedded archive, not executable bytes.
|
|
41
|
+
const { stdout } = await execute(installed, ["--internal-build-id"], queryOptions);
|
|
42
|
+
return buildId.parse(JSON.parse(stdout));
|
|
43
|
+
}
|
|
@@ -18,7 +18,7 @@ export declare function attachPrivateLocalComputer(rawEndpoint: string | URL, in
|
|
|
18
18
|
computer: string;
|
|
19
19
|
folderPath: string;
|
|
20
20
|
hostLabel?: string;
|
|
21
|
-
}>,
|
|
21
|
+
}>, controlOptions?: ControlOptions): Promise<PrivateLocalComputerAttachResult>;
|
|
22
22
|
export type PrivateLocalComputerDetachResult = Readonly<{
|
|
23
23
|
kind: "detached" | "not-attached" | "uncertain" | "denied";
|
|
24
24
|
}>;
|
|
@@ -26,7 +26,7 @@ export type PrivateLocalComputerDetachResult = Readonly<{
|
|
|
26
26
|
* Detach a Computer's attachment on the Hub, then forget it here. A pending
|
|
27
27
|
* attach is replayed first so the Hub never keeps an attachment Local forgot.
|
|
28
28
|
*/
|
|
29
|
-
export declare function detachPrivateLocalComputer(rawEndpoint: string | URL, computer: string,
|
|
29
|
+
export declare function detachPrivateLocalComputer(rawEndpoint: string | URL, computer: string, input?: ControlOptions): Promise<PrivateLocalComputerDetachResult>;
|
|
30
30
|
/** What `fungi computer status` shows: never a key. */
|
|
31
31
|
export declare function listPrivateLocalComputers(options?: Readonly<{
|
|
32
32
|
credentialPath?: string;
|
|
@@ -46,4 +46,4 @@ export type PrivateLocalComputerRunOptions = {
|
|
|
46
46
|
* Serve one attached folder until `signal` aborts or the Hub refuses the
|
|
47
47
|
* machine (it was detached elsewhere), in which case Local forgets it.
|
|
48
48
|
*/
|
|
49
|
-
export declare function runPrivateLocalComputer(rawEndpoint: string | URL, computer: string,
|
|
49
|
+
export declare function runPrivateLocalComputer(rawEndpoint: string | URL, computer: string, input: PrivateLocalComputerRunOptions): Promise<"stopped" | "detached">;
|
|
@@ -4,6 +4,7 @@ import { hostname } from "node:os";
|
|
|
4
4
|
import { basename, resolve } from "node:path";
|
|
5
5
|
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { capturePrivateLocalConfigRoot } from "./private-local-config-root.js";
|
|
7
8
|
import { callFungiMcpTeamControl, fungiMcpTeamEndpoint, readFungiMcpLocalBackends, writeFungiMcpLocalBackends, } from "./private-fungi-account.js";
|
|
8
9
|
import { runLocalComputerBackend, startLocalComputerd, } from "./private-local-computer-backend.js";
|
|
9
10
|
/**
|
|
@@ -76,7 +77,12 @@ function deviceKey() {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
/** Attach `folderPath` to a Team Computer, or return the existing attachment. */
|
|
79
|
-
export async function attachPrivateLocalComputer(rawEndpoint, input,
|
|
80
|
+
export async function attachPrivateLocalComputer(rawEndpoint, input, controlOptions = {}) {
|
|
81
|
+
const options = {
|
|
82
|
+
...controlOptions,
|
|
83
|
+
credentialPath: controlOptions.credentialPath ??
|
|
84
|
+
capturePrivateLocalConfigRoot().credentialPath,
|
|
85
|
+
};
|
|
80
86
|
const { endpoint, teamId } = fungiMcpTeamEndpoint(rawEndpoint);
|
|
81
87
|
const rootPath = await realpath(resolve(input.folderPath));
|
|
82
88
|
if (!(await stat(rootPath)).isDirectory())
|
|
@@ -142,7 +148,11 @@ export async function attachPrivateLocalComputer(rawEndpoint, input, options = {
|
|
|
142
148
|
* Detach a Computer's attachment on the Hub, then forget it here. A pending
|
|
143
149
|
* attach is replayed first so the Hub never keeps an attachment Local forgot.
|
|
144
150
|
*/
|
|
145
|
-
export async function detachPrivateLocalComputer(rawEndpoint, computer,
|
|
151
|
+
export async function detachPrivateLocalComputer(rawEndpoint, computer, input = {}) {
|
|
152
|
+
const options = {
|
|
153
|
+
...input,
|
|
154
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
155
|
+
};
|
|
146
156
|
const { endpoint, teamId } = fungiMcpTeamEndpoint(rawEndpoint);
|
|
147
157
|
const saved = await readFungiMcpLocalBackends(options);
|
|
148
158
|
const record = saved.backends.find((candidate) => candidate.teamId === teamId && sameComputer(candidate, computer));
|
|
@@ -183,7 +193,11 @@ export async function listPrivateLocalComputers(options = {}) {
|
|
|
183
193
|
* Serve one attached folder until `signal` aborts or the Hub refuses the
|
|
184
194
|
* machine (it was detached elsewhere), in which case Local forgets it.
|
|
185
195
|
*/
|
|
186
|
-
export async function runPrivateLocalComputer(rawEndpoint, computer,
|
|
196
|
+
export async function runPrivateLocalComputer(rawEndpoint, computer, input) {
|
|
197
|
+
const options = {
|
|
198
|
+
...input,
|
|
199
|
+
credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
|
|
200
|
+
};
|
|
187
201
|
const { endpoint, teamId } = fungiMcpTeamEndpoint(rawEndpoint);
|
|
188
202
|
const saved = await readFungiMcpLocalBackends(options);
|
|
189
203
|
const record = saved.backends.find((candidate) => candidate.teamId === teamId && sameComputer(candidate, computer));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { LocalSandboxLaunch } from "./private-invocation-computer.js";
|
|
2
3
|
declare const processSchema: z.ZodObject<{
|
|
3
4
|
id: z.ZodString & z.ZodType<import("@fungi.computer/cairn").CairnId<"local-computer-process", "process", 1>, string, z.core.$ZodTypeInternals<import("@fungi.computer/cairn").CairnId<"local-computer-process", "process", 1>, string>>;
|
|
4
5
|
command: z.ZodString;
|
|
@@ -99,7 +100,7 @@ export type PrivateLocalProcessCommandSpec = Readonly<{
|
|
|
99
100
|
}>;
|
|
100
101
|
/** One Session's (or invocation's) view of the host's background processes. */
|
|
101
102
|
export type PrivateLocalProcessScope = Readonly<{
|
|
102
|
-
start: (spec: PrivateLocalProcessCommandSpec, cwd: string, executionPath: string) => Promise<PrivateLocalProcess>;
|
|
103
|
+
start: (spec: PrivateLocalProcessCommandSpec, cwd: string, executionPath: string, sandbox?: LocalSandboxLaunch) => Promise<PrivateLocalProcess>;
|
|
103
104
|
status: (id: string) => PrivateLocalProcess;
|
|
104
105
|
list: () => readonly PrivateLocalProcess[];
|
|
105
106
|
read: (id: string, offset: number, maxBytes: number) => Promise<z.output<typeof readOutput>>;
|
|
@@ -111,11 +112,13 @@ export type PrivateLocalComputerProcesses = Readonly<{
|
|
|
111
112
|
scope: (key: string) => PrivateLocalProcessScope;
|
|
112
113
|
/** Stop every process of one scope and remove its logs (Session ended). */
|
|
113
114
|
disposeScope: (key: string) => Promise<void>;
|
|
115
|
+
/** Whether any background process of any scope is still running. */
|
|
116
|
+
running: () => boolean;
|
|
114
117
|
/** Stop every process, remove every log, and refuse new starts. */
|
|
115
118
|
close: () => Promise<void>;
|
|
116
119
|
}>;
|
|
117
120
|
/** Open the background-process owner for one invocation root. */
|
|
118
|
-
export declare function createPrivateLocalComputerProcesses(root: string): PrivateLocalComputerProcesses;
|
|
121
|
+
export declare function createPrivateLocalComputerProcesses(root: string, home?: string): PrivateLocalComputerProcesses;
|
|
119
122
|
/**
|
|
120
123
|
* Background operations share the Computer's one shell/exec surface; a
|
|
121
124
|
* plain (not discriminated) union, since `start`'s shell and direct branches
|
|
@@ -195,5 +198,5 @@ export declare const backgroundExecOutput: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
195
198
|
}, z.core.$strict>>;
|
|
196
199
|
}, z.core.$strict>]>;
|
|
197
200
|
/** Reuse Session ownership and logs; a wait deadline never cancels the command. */
|
|
198
|
-
export declare function callBackgroundExec(scope: PrivateLocalProcessScope, request: z.output<typeof backgroundExecInput>, resolveCwd: (requested: string | undefined) => Promise<string>, executionPath: string, signal: AbortSignal): Promise<z.output<typeof backgroundExecOutput>>;
|
|
201
|
+
export declare function callBackgroundExec(scope: PrivateLocalProcessScope, request: z.output<typeof backgroundExecInput>, resolveCwd: (requested: string | undefined) => Promise<string>, executionPath: string, signal: AbortSignal, sandbox?: LocalSandboxLaunch): Promise<z.output<typeof backgroundExecOutput>>;
|
|
199
202
|
export {};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { spawn, } from "node:child_process";
|
|
2
2
|
import { closeSync, openSync, readSync, writeSync } from "node:fs";
|
|
3
3
|
import { mkdir, rm } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
4
|
import { join } from "node:path";
|
|
6
5
|
import process from "node:process";
|
|
7
6
|
import { setTimeout as delay } from "node:timers/promises";
|
|
8
7
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
9
8
|
import { z } from "zod";
|
|
10
9
|
import { combinedArgvBytes, launchCwdSchema, launchStdinSchema, maximumLaunchTextLength, } from "./private-local-exec-launch.js";
|
|
10
|
+
import { privateLocalExecEnvironment } from "./private-local-exec-environment.js";
|
|
11
|
+
import { capturePrivateLocalConfigRoot } from "./private-local-config-root.js";
|
|
12
|
+
import { Writable } from "node:stream";
|
|
11
13
|
/**
|
|
12
14
|
* Background processes of the local Computer: the one owner of "a command that
|
|
13
15
|
* outlives a single exec call".
|
|
@@ -214,7 +216,7 @@ class ManagedProcess {
|
|
|
214
216
|
#endedAt = null;
|
|
215
217
|
#leaderExited = false;
|
|
216
218
|
command;
|
|
217
|
-
constructor(spec, cwd, logDirectory, executionPath) {
|
|
219
|
+
constructor(spec, cwd, logDirectory, executionPath, home, launch) {
|
|
218
220
|
this.spec = spec;
|
|
219
221
|
this.cwd = cwd;
|
|
220
222
|
this.command = describeCommandSpec(spec);
|
|
@@ -222,32 +224,49 @@ class ManagedProcess {
|
|
|
222
224
|
this.#fd = openSync(this.logPath, "wx+", 0o600);
|
|
223
225
|
const closed = Promise.withResolvers();
|
|
224
226
|
this.closed = closed.promise;
|
|
225
|
-
const env =
|
|
227
|
+
const env = privateLocalExecEnvironment(executionPath, {
|
|
228
|
+
HOME: home,
|
|
229
|
+
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR,
|
|
230
|
+
DBUS_SESSION_BUS_ADDRESS: process.env.DBUS_SESSION_BUS_ADDRESS,
|
|
231
|
+
});
|
|
232
|
+
if (launch !== undefined) {
|
|
233
|
+
delete env.XDG_RUNTIME_DIR;
|
|
234
|
+
delete env.DBUS_SESSION_BUS_ADDRESS;
|
|
235
|
+
Object.assign(env, launch.environment);
|
|
236
|
+
}
|
|
226
237
|
try {
|
|
227
238
|
// Its own process group, so neither exec's finalizer nor this host's
|
|
228
239
|
// terminal signals reach it; kill() owns ending the whole group.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
240
|
+
const argv = spec.kind === "shell"
|
|
241
|
+
? ["sh", "-c", spec.command]
|
|
242
|
+
: [spec.executable, ...spec.args];
|
|
243
|
+
const stdio = [
|
|
244
|
+
spec.stdin === undefined ? "ignore" : "pipe",
|
|
245
|
+
"pipe",
|
|
246
|
+
"pipe",
|
|
247
|
+
];
|
|
248
|
+
if (launch?.seccompFilter !== undefined)
|
|
249
|
+
stdio.push("pipe");
|
|
250
|
+
this.#child = spawn(launch?.executable ?? argv[0], launch?.args ?? argv.slice(1), {
|
|
251
|
+
cwd,
|
|
252
|
+
env,
|
|
253
|
+
detached: true,
|
|
254
|
+
stdio,
|
|
255
|
+
});
|
|
256
|
+
if (launch?.seccompFilter !== undefined) {
|
|
257
|
+
const filter = this.#child.stdio[3];
|
|
258
|
+
if (!(filter instanceof Writable)) {
|
|
259
|
+
this.#child.kill("SIGKILL");
|
|
260
|
+
throw new Error("Local sandbox filter input fd3 is unavailable");
|
|
261
|
+
}
|
|
262
|
+
// A failed filter delivery cannot become a bare retry or an unhandled
|
|
263
|
+
// stream error. The existing process owner records and reaps failure.
|
|
264
|
+
filter.on("error", (error) => {
|
|
265
|
+
this.#append(Buffer.from(`\n[fungi: sandbox filter failed: ${error.message}]\n`));
|
|
266
|
+
this.killNow();
|
|
267
|
+
});
|
|
268
|
+
filter.end(launch.seccompFilter);
|
|
269
|
+
}
|
|
251
270
|
// Finite stdin, delivered once and closed; never an interactive
|
|
252
271
|
// protocol. `end()` writes the bytes and closes the write side. A
|
|
253
272
|
// child that exits or closes its own stdin before the payload drains
|
|
@@ -414,7 +433,7 @@ class ManagedProcess {
|
|
|
414
433
|
}
|
|
415
434
|
}
|
|
416
435
|
/** Open the background-process owner for one invocation root. */
|
|
417
|
-
export function createPrivateLocalComputerProcesses(root) {
|
|
436
|
+
export function createPrivateLocalComputerProcesses(root, home = capturePrivateLocalConfigRoot().home) {
|
|
418
437
|
const logDirectory = join(root, ".fungi", "processes");
|
|
419
438
|
const scopes = new Map();
|
|
420
439
|
let closed = false;
|
|
@@ -446,7 +465,7 @@ export function createPrivateLocalComputerProcesses(root) {
|
|
|
446
465
|
return record;
|
|
447
466
|
};
|
|
448
467
|
return Object.freeze({
|
|
449
|
-
start: async (spec, cwd, executionPath) => {
|
|
468
|
+
start: async (spec, cwd, executionPath, sandbox) => {
|
|
450
469
|
if (closed)
|
|
451
470
|
throw new Error("Local Computer processes are closed");
|
|
452
471
|
const records = owned();
|
|
@@ -468,7 +487,11 @@ export function createPrivateLocalComputerProcesses(root) {
|
|
|
468
487
|
process.on("exit", killAllNow);
|
|
469
488
|
exitHook = true;
|
|
470
489
|
}
|
|
471
|
-
const
|
|
490
|
+
const argv = spec.kind === "shell"
|
|
491
|
+
? ["sh", "-c", spec.command]
|
|
492
|
+
: [spec.executable, ...spec.args];
|
|
493
|
+
const launch = sandbox?.(cwd, argv);
|
|
494
|
+
const started = new ManagedProcess(spec, cwd, logDirectory, executionPath, home, launch);
|
|
472
495
|
records.set(started.id, started);
|
|
473
496
|
return started.snapshot();
|
|
474
497
|
},
|
|
@@ -509,6 +532,7 @@ export function createPrivateLocalComputerProcesses(root) {
|
|
|
509
532
|
if (owned !== undefined)
|
|
510
533
|
await dispose(owned);
|
|
511
534
|
},
|
|
535
|
+
running: () => all().some((record) => record.running),
|
|
512
536
|
close: async () => {
|
|
513
537
|
closed = true;
|
|
514
538
|
const owned = [...scopes.values()];
|
|
@@ -552,7 +576,7 @@ const backgroundResult = readOutput.extend({
|
|
|
552
576
|
});
|
|
553
577
|
export const backgroundExecOutput = z.union([backgroundResult, listOutput]);
|
|
554
578
|
/** Reuse Session ownership and logs; a wait deadline never cancels the command. */
|
|
555
|
-
export async function callBackgroundExec(scope, request, resolveCwd, executionPath, signal) {
|
|
579
|
+
export async function callBackgroundExec(scope, request, resolveCwd, executionPath, signal, sandbox) {
|
|
556
580
|
signal.throwIfAborted();
|
|
557
581
|
if (request.operation === "list")
|
|
558
582
|
return { processes: [...scope.list()] };
|
|
@@ -563,7 +587,7 @@ export async function callBackgroundExec(scope, request, resolveCwd, executionPa
|
|
|
563
587
|
case "start": {
|
|
564
588
|
const cwd = await resolveCwd(request.cwd);
|
|
565
589
|
signal.throwIfAborted();
|
|
566
|
-
id = (await scope.start(toCommandSpec(request), cwd, executionPath)).id;
|
|
590
|
+
id = (await scope.start(toCommandSpec(request), cwd, executionPath, sandbox)).id;
|
|
567
591
|
break;
|
|
568
592
|
}
|
|
569
593
|
case "wait":
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host's selected user home, not an Agent Home or an invocation Files jail.
|
|
3
|
+
* Pass these values through the existing home/path parameters. Capture once at
|
|
4
|
+
* the outer host/client boundary, before awaiting or constructing lazy owners;
|
|
5
|
+
* later opens, reloads and exec launches must not select ambient HOME again.
|
|
6
|
+
*
|
|
7
|
+
* PATH remains the existing per-client/per-Session choice. Neither this value
|
|
8
|
+
* nor an exec child inherits the rest of the host environment.
|
|
9
|
+
*/
|
|
10
|
+
export type PrivateLocalConfigRoot = Readonly<{
|
|
11
|
+
home: string;
|
|
12
|
+
credentialPath: string;
|
|
13
|
+
serviceDirectory: string;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Select paths without reading credentials, creating directories or starting a
|
|
17
|
+
* service. Keep this capture independent of service/discovery owners, which
|
|
18
|
+
* consume its paths. Explicit path overrides remain the caller's responsibility;
|
|
19
|
+
* there is no settings registry, fallback chain or alternate storage format.
|
|
20
|
+
*/
|
|
21
|
+
export declare function capturePrivateLocalConfigRoot(home?: string): PrivateLocalConfigRoot;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { resolveDefaultCredentialPath } from "./private-file-credential-store.js";
|
|
4
|
+
/**
|
|
5
|
+
* Select paths without reading credentials, creating directories or starting a
|
|
6
|
+
* service. Keep this capture independent of service/discovery owners, which
|
|
7
|
+
* consume its paths. Explicit path overrides remain the caller's responsibility;
|
|
8
|
+
* there is no settings registry, fallback chain or alternate storage format.
|
|
9
|
+
*/
|
|
10
|
+
export function capturePrivateLocalConfigRoot(home = homedir()) {
|
|
11
|
+
if (!isAbsolute(home))
|
|
12
|
+
throw new Error("Local configuration home must be absolute");
|
|
13
|
+
const selected = resolve(home);
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
home: selected,
|
|
16
|
+
credentialPath: resolveDefaultCredentialPath(selected),
|
|
17
|
+
serviceDirectory: join(selected, ".fungi", "service"),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const sandboxUnavailableReasonSchema: z.ZodObject<{
|
|
3
|
+
kind: z.ZodEnum<{
|
|
4
|
+
missing: "missing";
|
|
5
|
+
"invalid-digest": "invalid-digest";
|
|
6
|
+
"digest-mismatch": "digest-mismatch";
|
|
7
|
+
"not-executable": "not-executable";
|
|
8
|
+
"unsupported-platform": "unsupported-platform";
|
|
9
|
+
"invalid-folder": "invalid-folder";
|
|
10
|
+
"backend-refused": "backend-refused";
|
|
11
|
+
}>;
|
|
12
|
+
message: z.ZodString;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
export type SandboxUnavailableReason = z.output<typeof sandboxUnavailableReasonSchema>;
|
|
15
|
+
export declare class LocalSandboxUnavailable extends Error {
|
|
16
|
+
readonly reason: SandboxUnavailableReason;
|
|
17
|
+
readonly name = "LocalSandboxUnavailable";
|
|
18
|
+
constructor(reason: SandboxUnavailableReason);
|
|
19
|
+
}
|
|
20
|
+
export type SandboxCommand = Readonly<{
|
|
21
|
+
executable: string;
|
|
22
|
+
args: readonly string[];
|
|
23
|
+
environment: Readonly<{
|
|
24
|
+
TMPDIR: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** Linux only: feed these verified bytes to fd3; never inherit arbitrary fds. */
|
|
27
|
+
seccompFilter?: Uint8Array;
|
|
28
|
+
}>;
|
|
29
|
+
/** Existing foreground/background owners retain process and cancellation lifetime. */
|
|
30
|
+
export type LocalSandbox = Readonly<{
|
|
31
|
+
command(cwd: string, argv: readonly [string, ...string[]]): SandboxCommand;
|
|
32
|
+
close(): void;
|
|
33
|
+
}>;
|
|
34
|
+
export type SandboxAdmission = Readonly<{
|
|
35
|
+
state: "ready";
|
|
36
|
+
sandbox: LocalSandbox;
|
|
37
|
+
}> | Readonly<{
|
|
38
|
+
state: "unavailable";
|
|
39
|
+
reason: SandboxUnavailableReason;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* The caller already owns the person's auto-approve choice. This module has no
|
|
43
|
+
* mode, setting, bare-command branch, PATH fallback or permission to widen it.
|
|
44
|
+
* A ready result proves only that the backend can start, not the escape matrix.
|
|
45
|
+
*/
|
|
46
|
+
export declare function establishLocalSandbox(input: Readonly<{
|
|
47
|
+
project: string;
|
|
48
|
+
/** Absolute folder selected by the Local owner. */
|
|
49
|
+
folder: string;
|
|
50
|
+
platform?: string;
|
|
51
|
+
}>): SandboxAdmission;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { closeSync, mkdtempSync, openSync, realpathSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { LocalBwrapUnavailable, localLinuxSandboxPayload, } from "./private-local-bwrap.js";
|
|
6
|
+
import { linuxSandboxCommand } from "./private-local-linux-sandbox.js";
|
|
7
|
+
import { seatbeltCommand } from "./private-local-seatbelt.js";
|
|
8
|
+
import { sandboxPolicy } from "./private-local-sandbox-policy.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
export const sandboxUnavailableReasonSchema = z.strictObject({
|
|
11
|
+
kind: z.enum([
|
|
12
|
+
"unsupported-platform",
|
|
13
|
+
"invalid-folder",
|
|
14
|
+
"missing",
|
|
15
|
+
"invalid-digest",
|
|
16
|
+
"digest-mismatch",
|
|
17
|
+
"not-executable",
|
|
18
|
+
"backend-refused",
|
|
19
|
+
]),
|
|
20
|
+
message: z.string(),
|
|
21
|
+
});
|
|
22
|
+
export class LocalSandboxUnavailable extends Error {
|
|
23
|
+
reason;
|
|
24
|
+
name = "LocalSandboxUnavailable";
|
|
25
|
+
constructor(reason) {
|
|
26
|
+
super(`Local sandbox unavailable: ${reason.message}`);
|
|
27
|
+
this.reason = reason;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function unavailable(cause) {
|
|
31
|
+
if (cause instanceof LocalBwrapUnavailable)
|
|
32
|
+
return { kind: cause.reason, message: cause.message };
|
|
33
|
+
return {
|
|
34
|
+
kind: "backend-refused",
|
|
35
|
+
message: cause instanceof Error ? cause.message : "Sandbox preparation failed.",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The caller already owns the person's auto-approve choice. This module has no
|
|
40
|
+
* mode, setting, bare-command branch, PATH fallback or permission to widen it.
|
|
41
|
+
* A ready result proves only that the backend can start, not the escape matrix.
|
|
42
|
+
*/
|
|
43
|
+
export function establishLocalSandbox(input) {
|
|
44
|
+
try {
|
|
45
|
+
sandboxPolicy(input.project, input.folder);
|
|
46
|
+
}
|
|
47
|
+
catch (cause) {
|
|
48
|
+
return {
|
|
49
|
+
state: "unavailable",
|
|
50
|
+
reason: {
|
|
51
|
+
kind: "invalid-folder",
|
|
52
|
+
message: cause instanceof Error ? cause.message : "Invalid sandbox folder.",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const platform = input.platform ?? process.platform;
|
|
57
|
+
if (platform !== "linux" && platform !== "darwin")
|
|
58
|
+
return {
|
|
59
|
+
state: "unavailable",
|
|
60
|
+
reason: {
|
|
61
|
+
kind: "unsupported-platform",
|
|
62
|
+
message: `Local sandbox is unavailable on ${platform}.`,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
let temporaryDirectory;
|
|
66
|
+
try {
|
|
67
|
+
// A sandbox owns only its own temporary directory, never the host's HOME.
|
|
68
|
+
temporaryDirectory = realpathSync(mkdtempSync(join(tmpdir(), "fungi-sandbox-")));
|
|
69
|
+
const temporary = temporaryDirectory;
|
|
70
|
+
let closed = false;
|
|
71
|
+
const sandbox = {
|
|
72
|
+
command: (cwd, argv) => {
|
|
73
|
+
if (closed)
|
|
74
|
+
throw new LocalSandboxUnavailable({
|
|
75
|
+
kind: "backend-refused",
|
|
76
|
+
message: "Sandbox has been closed.",
|
|
77
|
+
});
|
|
78
|
+
// Failed/incomplete authority discovery is not an unavailable backend:
|
|
79
|
+
// it must not turn into an authorized unsandboxed retry.
|
|
80
|
+
const policy = sandboxPolicy(input.project, input.folder);
|
|
81
|
+
try {
|
|
82
|
+
const launch = platform === "linux"
|
|
83
|
+
? linuxSandboxCommand(policy, localLinuxSandboxPayload(), cwd, argv)
|
|
84
|
+
: seatbeltCommand({ ...policy, temporaryDirectory: temporary }, argv);
|
|
85
|
+
return {
|
|
86
|
+
...launch,
|
|
87
|
+
environment: { TMPDIR: platform === "linux" ? "/tmp" : temporary },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
throw new LocalSandboxUnavailable(unavailable(cause));
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
close: () => {
|
|
95
|
+
if (closed)
|
|
96
|
+
return;
|
|
97
|
+
closed = true;
|
|
98
|
+
rmSync(temporary, { recursive: true });
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const command = sandbox.command(realpathSync(input.folder), [
|
|
102
|
+
"/usr/bin/true",
|
|
103
|
+
]);
|
|
104
|
+
let filterDescriptor;
|
|
105
|
+
const probe = () => {
|
|
106
|
+
if (command.seccompFilter !== undefined) {
|
|
107
|
+
const path = join(temporary, "seccomp.bpf");
|
|
108
|
+
writeFileSync(path, command.seccompFilter, { mode: 0o600 });
|
|
109
|
+
filterDescriptor = openSync(path, "r");
|
|
110
|
+
}
|
|
111
|
+
return spawnSync(command.executable, [...command.args], {
|
|
112
|
+
cwd: input.folder,
|
|
113
|
+
env: { ...command.environment },
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
timeout: 5_000,
|
|
116
|
+
// Node's maintained spawn allowlist closes every other inherited fd.
|
|
117
|
+
// bwrap loads and closes fd3 before exec (seccomp_program_new).
|
|
118
|
+
stdio: [
|
|
119
|
+
"ignore",
|
|
120
|
+
"pipe",
|
|
121
|
+
"pipe",
|
|
122
|
+
...(filterDescriptor === undefined ? [] : [filterDescriptor]),
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const result = (() => {
|
|
127
|
+
try {
|
|
128
|
+
return probe();
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
if (filterDescriptor !== undefined)
|
|
132
|
+
closeSync(filterDescriptor);
|
|
133
|
+
}
|
|
134
|
+
})();
|
|
135
|
+
if (result.status !== 0) {
|
|
136
|
+
sandbox.close();
|
|
137
|
+
const stderr = result.stderr.trim();
|
|
138
|
+
return {
|
|
139
|
+
state: "unavailable",
|
|
140
|
+
reason: {
|
|
141
|
+
kind: "backend-refused",
|
|
142
|
+
message: result.error?.message ??
|
|
143
|
+
(stderr.length > 0
|
|
144
|
+
? stderr
|
|
145
|
+
: `Sandbox startup exited ${String(result.status)} (${String(result.signal)}).`),
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { state: "ready", sandbox };
|
|
150
|
+
}
|
|
151
|
+
catch (cause) {
|
|
152
|
+
if (temporaryDirectory !== undefined)
|
|
153
|
+
rmSync(temporaryDirectory, { recursive: true });
|
|
154
|
+
return {
|
|
155
|
+
state: "unavailable",
|
|
156
|
+
reason: cause instanceof LocalSandboxUnavailable
|
|
157
|
+
? cause.reason
|
|
158
|
+
: unavailable(cause),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|