@fungi.computer/truffle 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/private-files-patch.js +3 -17
- package/dist/private-invocation-computer.d.ts +224 -12
- package/dist/private-invocation-computer.js +162 -22
- package/dist/private-invocation-files.d.ts +3 -2
- package/dist/private-invocation-files.js +22 -3
- package/dist/private-local-agent-control.d.ts +2 -2
- package/dist/private-local-bwrap.d.ts +9 -7
- package/dist/private-local-bwrap.js +26 -21
- package/dist/private-local-computer-processes.d.ts +15 -0
- package/dist/private-local-computer-processes.js +13 -0
- package/dist/private-local-containment.d.ts +15 -13
- package/dist/private-local-containment.js +49 -65
- package/dist/private-local-delegation.d.ts +3 -3
- package/dist/private-local-delegation.js +32 -19
- package/dist/private-local-exec-review.d.ts +10 -0
- package/dist/private-local-exec-review.js +50 -0
- package/dist/private-local-exec-rules.d.ts +14 -0
- package/dist/private-local-exec-rules.js +420 -0
- package/dist/private-local-git-exclude.js +25 -15
- package/dist/private-local-linux-sandbox.d.ts +5 -5
- package/dist/private-local-linux-sandbox.js +63 -30
- package/dist/private-local-mail.d.ts +2 -2
- package/dist/private-local-mesh-links.d.ts +4 -1
- package/dist/private-local-mesh-links.js +50 -3
- package/dist/private-local-native-digests.js +1 -1
- package/dist/private-local-native-install-error.d.ts +2 -0
- package/dist/private-local-native-install-error.js +10 -0
- package/dist/private-local-native-staging.d.ts +7 -8
- package/dist/private-local-native-staging.js +132 -43
- package/dist/private-local-permission-mode.d.ts +10 -0
- package/dist/private-local-permission-mode.js +18 -0
- package/dist/private-local-runtime-host.d.ts +19 -5
- package/dist/private-local-runtime-host.js +378 -65
- package/dist/private-local-sandbox-policy.d.ts +30 -11
- package/dist/private-local-sandbox-policy.js +223 -373
- package/dist/private-local-sandbox.d.ts +30 -25
- package/dist/private-local-sandbox.js +190 -100
- package/dist/private-local-seatbelt.d.ts +7 -13
- package/dist/private-local-seatbelt.js +55 -32
- package/dist/private-local-service-contract.d.ts +5 -4
- package/dist/private-local-service-discovery.d.ts +19 -7
- package/dist/private-local-service-discovery.js +4 -3
- package/dist/private-local-service-listener.js +40 -2
- package/dist/private-local-service-log.d.ts +8 -0
- package/dist/private-local-service-log.js +3 -0
- package/dist/private-local-service-owner.d.ts +18 -10
- package/dist/private-local-service-owner.js +117 -25
- package/dist/private-local-service.js +30 -3
- package/dist/private-local-session-discovery.d.ts +2 -1
- package/dist/private-local-session-discovery.js +18 -6
- package/dist/private-mcp-form-contract.d.ts +8 -1
- package/dist/private-mcp-form-contract.js +8 -1
- package/dist/private-mcp-form-session.d.ts +33 -3
- package/dist/private-mcp-form-session.js +28 -5
- package/dist/private-oyster-mcp-auth.d.ts +1 -1
- package/dist/private-oyster-mcp-auth.js +139 -7
- package/dist/private-provider-rejection.js +1 -1
- package/dist/seatbelt/safe-base.sbpl +2 -2
- package/native/bin/bwrap-build-source/Dockerfile +2 -0
- package/native/bin/bwrap-build-source/README.md +6 -6
- package/native/bin/bwrap-build-source/pin.mjs +2 -2
- package/native/bin/bwrap-build-source/record.mjs +12 -1
- package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +10 -7
- package/native/bin/bwrap-build.json +3 -2
- package/native/bin/fungi-apply-patch +0 -0
- package/native/bin/fungi-seccomp-workspace.bpf +0 -0
- package/native/bin/fungi-seccomp-workspace.bpf.sha256 +1 -0
- package/package.json +23 -17
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import { type LocalSandboxPolicy } from "./private-local-sandbox-policy.js";
|
|
2
1
|
export declare class LocalBwrapUnavailable extends Error {
|
|
3
|
-
readonly reason: "
|
|
2
|
+
readonly reason: "install";
|
|
4
3
|
readonly name = "LocalBwrapUnavailable";
|
|
5
|
-
constructor(reason: "
|
|
4
|
+
constructor(reason: "install", message: string, options?: ErrorOptions);
|
|
6
5
|
}
|
|
7
6
|
export type LinuxSandboxPayload = {
|
|
7
|
+
readonly sourcePath: string;
|
|
8
8
|
readonly executableBytes: Uint8Array;
|
|
9
|
-
readonly
|
|
9
|
+
readonly seccomp: Uint8Array;
|
|
10
|
+
readonly seccompWorkspace: Uint8Array;
|
|
10
11
|
};
|
|
11
12
|
export type LinuxSandboxDigests = Readonly<{
|
|
12
13
|
bwrap: string;
|
|
13
14
|
seccomp: string;
|
|
15
|
+
seccompWorkspace: string;
|
|
14
16
|
}>;
|
|
15
17
|
/** Packaged patch has no sidecar; the built module owns its digest. */
|
|
16
18
|
export type NativeSourceReader = (path: string) => Buffer;
|
|
17
19
|
export declare function verifiedNativePatchHelper(path: string, project: string, readNativeSource?: NativeSourceReader): Uint8Array;
|
|
18
|
-
/**
|
|
19
|
-
export declare function verifyLocalLinuxSandboxPayload(executable: string,
|
|
20
|
+
/** Verify the three packaged Linux files once before copying into service-owned staging. */
|
|
21
|
+
export declare function verifyLocalLinuxSandboxPayload(executable: string, project: string, expected: LinuxSandboxDigests, readNativeSource?: NativeSourceReader): LinuxSandboxPayload;
|
|
20
22
|
/** Same optional native-package lookup as apply_patch, with no PATH fallback. */
|
|
21
|
-
export declare function localLinuxSandboxPayload(
|
|
23
|
+
export declare function localLinuxSandboxPayload(project: string, readNativeSource?: NativeSourceReader): LinuxSandboxPayload;
|
|
@@ -16,15 +16,17 @@ export class LocalBwrapUnavailable extends Error {
|
|
|
16
16
|
export function verifiedNativePatchHelper(path, project, readNativeSource = readFileSync) {
|
|
17
17
|
const expected = nativeDigests[`${process.platform}-${process.arch}`]?.patch;
|
|
18
18
|
if (expected === undefined)
|
|
19
|
-
throw new LocalBwrapUnavailable("
|
|
19
|
+
throw new LocalBwrapUnavailable("install", "No built native patch digest for this platform; reinstall Fungi.");
|
|
20
20
|
return verifiedBytes(path, "fungi-apply-patch", project, expected, readNativeSource);
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
export function verifyLocalLinuxSandboxPayload(executable,
|
|
24
|
-
const executableBytes = verifiedBytes(executable, "fungi-bwrap",
|
|
22
|
+
/** Verify the three packaged Linux files once before copying into service-owned staging. */
|
|
23
|
+
export function verifyLocalLinuxSandboxPayload(executable, project, expected, readNativeSource = readFileSync) {
|
|
24
|
+
const executableBytes = verifiedBytes(executable, "fungi-bwrap", project, expected.bwrap, readNativeSource);
|
|
25
25
|
return {
|
|
26
|
+
sourcePath: executable,
|
|
26
27
|
executableBytes,
|
|
27
|
-
|
|
28
|
+
seccomp: verifiedBytes(join(dirname(executable), "fungi-seccomp.bpf"), "fungi-seccomp.bpf", project, expected.seccomp, readNativeSource),
|
|
29
|
+
seccompWorkspace: verifiedBytes(join(dirname(executable), "fungi-seccomp-workspace.bpf"), "fungi-seccomp-workspace.bpf", project, expected.seccompWorkspace, readNativeSource),
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
32
|
function verifiedBytes(path, label, project, expected, readNativeSource = readFileSync) {
|
|
@@ -32,26 +34,28 @@ function verifiedBytes(path, label, project, expected, readNativeSource = readFi
|
|
|
32
34
|
try {
|
|
33
35
|
assertHostOwnedNativeFile(project, path);
|
|
34
36
|
if (!statSync(path).isFile())
|
|
35
|
-
throw new LocalBwrapUnavailable("
|
|
37
|
+
throw new LocalBwrapUnavailable("install", `Packaged ${label} is not a regular file; reinstall Fungi.`);
|
|
36
38
|
bytes = readNativeSource(path);
|
|
37
39
|
}
|
|
38
40
|
catch (cause) {
|
|
39
41
|
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
40
|
-
throw new LocalBwrapUnavailable("
|
|
42
|
+
throw new LocalBwrapUnavailable("install", `Packaged ${label} is missing; reinstall Fungi with optional dependencies enabled.`, { cause });
|
|
41
43
|
if (cause instanceof UnsafeNativePayload ||
|
|
42
44
|
cause instanceof LocalBwrapUnavailable)
|
|
43
45
|
throw cause;
|
|
44
|
-
throw new LocalBwrapUnavailable("
|
|
46
|
+
throw new LocalBwrapUnavailable("install", `Packaged ${label} cannot be read; reinstall Fungi. No command was started.`, { cause });
|
|
45
47
|
}
|
|
46
48
|
if (createHash("sha256").update(bytes).digest("hex") !== expected)
|
|
47
|
-
throw new LocalBwrapUnavailable("
|
|
49
|
+
throw new LocalBwrapUnavailable("install", `Packaged ${label} digest mismatch; reinstall Fungi. No command was started.`);
|
|
48
50
|
return bytes;
|
|
49
51
|
}
|
|
50
52
|
/** Same optional native-package lookup as apply_patch, with no PATH fallback. */
|
|
51
|
-
export function localLinuxSandboxPayload(
|
|
53
|
+
export function localLinuxSandboxPayload(project, readNativeSource = readFileSync) {
|
|
52
54
|
const digest = nativeDigests[`${process.platform}-${process.arch}`];
|
|
53
|
-
if (digest?.bwrap === undefined ||
|
|
54
|
-
|
|
55
|
+
if (digest?.bwrap === undefined ||
|
|
56
|
+
digest.seccomp === undefined ||
|
|
57
|
+
digest.seccompWorkspace === undefined)
|
|
58
|
+
throw new LocalBwrapUnavailable("install", "No built native Linux payload digest for this platform; reinstall Fungi.");
|
|
55
59
|
let executable;
|
|
56
60
|
if (process.env.FUNGI_BUNDLED_CLI) {
|
|
57
61
|
const name = `@fungi.computer/spore-native-${process.platform}-${process.arch}`;
|
|
@@ -61,10 +65,10 @@ export function localLinuxSandboxPayload(policy) {
|
|
|
61
65
|
directories = createRequire(process.env.FUNGI_BUNDLED_CLI).resolve.paths(name);
|
|
62
66
|
}
|
|
63
67
|
catch (cause) {
|
|
64
|
-
throw new LocalBwrapUnavailable("
|
|
68
|
+
throw new LocalBwrapUnavailable("install", message, { cause });
|
|
65
69
|
}
|
|
66
70
|
if (directories === null)
|
|
67
|
-
throw new LocalBwrapUnavailable("
|
|
71
|
+
throw new LocalBwrapUnavailable("install", message);
|
|
68
72
|
let packageRoot;
|
|
69
73
|
for (const directory of directories) {
|
|
70
74
|
const candidate = join(directory, name);
|
|
@@ -75,31 +79,32 @@ export function localLinuxSandboxPayload(policy) {
|
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
catch (cause) {
|
|
78
|
-
throw new LocalBwrapUnavailable("
|
|
82
|
+
throw new LocalBwrapUnavailable("install", message, { cause });
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
if (packageRoot === undefined)
|
|
82
|
-
throw new LocalBwrapUnavailable("
|
|
86
|
+
throw new LocalBwrapUnavailable("install", `Missing ${name}; reinstall Fungi with optional dependencies enabled.`);
|
|
83
87
|
// Package metadata (including exports) is not an authority for whether
|
|
84
88
|
// native bytes are absent. Check the package's location before its bin.
|
|
85
89
|
try {
|
|
86
|
-
assertHostOwnedNativeFile(
|
|
90
|
+
assertHostOwnedNativeFile(project, packageRoot);
|
|
87
91
|
if (!statSync(packageRoot).isDirectory())
|
|
88
|
-
throw new LocalBwrapUnavailable("
|
|
92
|
+
throw new LocalBwrapUnavailable("install", message);
|
|
89
93
|
}
|
|
90
94
|
catch (cause) {
|
|
91
95
|
if (cause instanceof UnsafeNativePayload ||
|
|
92
96
|
cause instanceof LocalBwrapUnavailable)
|
|
93
97
|
throw cause;
|
|
94
|
-
throw new LocalBwrapUnavailable("
|
|
98
|
+
throw new LocalBwrapUnavailable("install", message, { cause });
|
|
95
99
|
}
|
|
96
100
|
executable = join(packageRoot, "bin", "fungi-bwrap");
|
|
97
101
|
}
|
|
98
102
|
else {
|
|
99
103
|
executable = fileURLToPath(new URL("../native/bin/fungi-bwrap", import.meta.url));
|
|
100
104
|
}
|
|
101
|
-
return verifyLocalLinuxSandboxPayload(executable,
|
|
105
|
+
return verifyLocalLinuxSandboxPayload(executable, project, {
|
|
102
106
|
bwrap: digest.bwrap,
|
|
103
107
|
seccomp: digest.seccomp,
|
|
104
|
-
|
|
108
|
+
seccompWorkspace: digest.seccompWorkspace,
|
|
109
|
+
}, readNativeSource);
|
|
105
110
|
}
|
|
@@ -128,11 +128,17 @@ export declare function createPrivateLocalComputerProcesses(root: string, home?:
|
|
|
128
128
|
*/
|
|
129
129
|
export declare const backgroundExecInput: z.ZodUnion<readonly [z.ZodObject<{
|
|
130
130
|
operation: z.ZodLiteral<"start">;
|
|
131
|
+
escape: z.ZodOptional<z.ZodObject<{
|
|
132
|
+
justification: z.ZodString;
|
|
133
|
+
}, z.core.$strict>>;
|
|
131
134
|
command: z.ZodString;
|
|
132
135
|
stdin: z.ZodOptional<z.ZodString>;
|
|
133
136
|
cwd: z.ZodOptional<z.ZodString>;
|
|
134
137
|
}, z.core.$strict>, z.ZodObject<{
|
|
135
138
|
operation: z.ZodLiteral<"start">;
|
|
139
|
+
escape: z.ZodOptional<z.ZodObject<{
|
|
140
|
+
justification: z.ZodString;
|
|
141
|
+
}, z.core.$strict>>;
|
|
136
142
|
executable: z.ZodString;
|
|
137
143
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
138
144
|
stdin: z.ZodOptional<z.ZodString>;
|
|
@@ -178,6 +184,15 @@ export declare const backgroundExecOutput: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
178
184
|
complete: z.ZodBoolean;
|
|
179
185
|
timedOut: z.ZodBoolean;
|
|
180
186
|
signalled: z.ZodOptional<z.ZodBoolean>;
|
|
187
|
+
sandbox: z.ZodOptional<z.ZodObject<{
|
|
188
|
+
mode: z.ZodEnum<{
|
|
189
|
+
"read-only": "read-only";
|
|
190
|
+
workspace: "workspace";
|
|
191
|
+
"full-access": "full-access";
|
|
192
|
+
}>;
|
|
193
|
+
likelyDenied: z.ZodBoolean;
|
|
194
|
+
ranOutsideSandbox: z.ZodBoolean;
|
|
195
|
+
}, z.core.$strict>>;
|
|
181
196
|
}, z.core.$strict>, z.ZodObject<{
|
|
182
197
|
processes: z.ZodArray<z.ZodObject<{
|
|
183
198
|
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>>;
|
|
@@ -65,6 +65,9 @@ const processSchema = z.strictObject({
|
|
|
65
65
|
/** The intentional shell form of background `start`. */
|
|
66
66
|
const shellStartInput = z.strictObject({
|
|
67
67
|
operation: z.literal("start"),
|
|
68
|
+
escape: z
|
|
69
|
+
.strictObject({ justification: z.string().min(1).max(2048) })
|
|
70
|
+
.optional(),
|
|
68
71
|
command: z.string().min(1).max(maximumLaunchTextLength),
|
|
69
72
|
stdin: launchStdinSchema.optional(),
|
|
70
73
|
cwd: launchCwdSchema.optional(),
|
|
@@ -73,6 +76,9 @@ const shellStartInput = z.strictObject({
|
|
|
73
76
|
const directStartInput = z
|
|
74
77
|
.strictObject({
|
|
75
78
|
operation: z.literal("start"),
|
|
79
|
+
escape: z
|
|
80
|
+
.strictObject({ justification: z.string().min(1).max(2048) })
|
|
81
|
+
.optional(),
|
|
76
82
|
executable: z.string().min(1),
|
|
77
83
|
args: z.array(z.string()).default([]),
|
|
78
84
|
stdin: launchStdinSchema.optional(),
|
|
@@ -573,6 +579,13 @@ export const backgroundExecInput = z.union([
|
|
|
573
579
|
const backgroundResult = readOutput.extend({
|
|
574
580
|
timedOut: z.boolean(),
|
|
575
581
|
signalled: z.boolean().optional(),
|
|
582
|
+
sandbox: z
|
|
583
|
+
.strictObject({
|
|
584
|
+
mode: z.enum(["full-access", "workspace", "read-only"]),
|
|
585
|
+
likelyDenied: z.boolean(),
|
|
586
|
+
ranOutsideSandbox: z.boolean(),
|
|
587
|
+
})
|
|
588
|
+
.optional(),
|
|
576
589
|
});
|
|
577
590
|
export const backgroundExecOutput = z.union([backgroundResult, listOutput]);
|
|
578
591
|
/** Reuse Session ownership and logs; a wait deadline never cancels the command. */
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import { type NativePayloadStaging } from "./private-local-native-staging.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import type { LocalPermissionMode } from "./private-local-permission-mode.js";
|
|
3
4
|
export declare const sandboxUnavailableReasonSchema: z.ZodObject<{
|
|
4
5
|
kind: z.ZodEnum<{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"digest-mismatch": "digest-mismatch";
|
|
9
|
-
unreadable: "unreadable";
|
|
10
|
-
"not-executable": "not-executable";
|
|
11
|
-
"staging-failed": "staging-failed";
|
|
12
|
-
"unsupported-platform": "unsupported-platform";
|
|
13
|
-
"invalid-folder": "invalid-folder";
|
|
14
|
-
"unsafe-native-payload": "unsafe-native-payload";
|
|
15
|
-
"unsafe-host-code": "unsafe-host-code";
|
|
6
|
+
install: "install";
|
|
7
|
+
unsupported: "unsupported";
|
|
8
|
+
folder: "folder";
|
|
16
9
|
"startup-refused": "startup-refused";
|
|
17
|
-
"backend-refused": "backend-refused";
|
|
18
10
|
}>;
|
|
19
11
|
message: z.ZodString;
|
|
20
12
|
}, z.core.$strict>;
|
|
@@ -29,6 +21,7 @@ export type SandboxCommand = Readonly<{
|
|
|
29
21
|
args: readonly string[];
|
|
30
22
|
environment: Readonly<{
|
|
31
23
|
TMPDIR: string;
|
|
24
|
+
SSH_AUTH_SOCK?: string;
|
|
32
25
|
}>;
|
|
33
26
|
/** Linux only: feed these verified bytes to fd3; never inherit arbitrary fds. */
|
|
34
27
|
seccompFilter?: Uint8Array;
|
|
@@ -55,5 +48,14 @@ export declare function establishLocalSandbox(input: Readonly<{
|
|
|
55
48
|
project: string;
|
|
56
49
|
/** Absolute folder selected by the Local owner. */
|
|
57
50
|
folder: string;
|
|
51
|
+
mode: Exclude<LocalPermissionMode, "full-access">;
|
|
52
|
+
temporaryDirectory: string;
|
|
58
53
|
platform?: string;
|
|
59
|
-
}>, staging?: NativePayloadStaging
|
|
54
|
+
}>, staging?: NativePayloadStaging,
|
|
55
|
+
/** Host startup probe; a Linux command-generation test can supply its own runner. */
|
|
56
|
+
runProbe?: (command: SandboxCommand) => Readonly<{
|
|
57
|
+
status: number | null;
|
|
58
|
+
stderr: string;
|
|
59
|
+
signal: string | null;
|
|
60
|
+
error?: Error;
|
|
61
|
+
}>): SandboxAdmission;
|
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { closeSync,
|
|
3
|
-
import {
|
|
4
|
-
import { join } from "node:path";
|
|
2
|
+
import { closeSync, openSync, realpathSync } from "node:fs";
|
|
3
|
+
import { userInfo } from "node:os";
|
|
5
4
|
import { LocalBwrapUnavailable } from "./private-local-bwrap.js";
|
|
6
5
|
import { createNativePayloadStaging, } from "./private-local-native-staging.js";
|
|
7
6
|
import { linuxSandboxCommand } from "./private-local-linux-sandbox.js";
|
|
8
|
-
import { seatbeltCommand } from "./private-local-seatbelt.js";
|
|
9
|
-
import { sandboxPolicy, UnsafeHostCode, UnsafeNativePayload, } from "./private-local-sandbox-policy.js";
|
|
7
|
+
import { seatbeltCommand, UnsafeSeatbeltFolder, } from "./private-local-seatbelt.js";
|
|
8
|
+
import { sandboxPolicy, linuxSandboxProfile, UnsafeGitMarker, UnsafeHostCode, UnsafeNativePayload, } from "./private-local-sandbox-policy.js";
|
|
10
9
|
import { z } from "zod";
|
|
11
10
|
export const sandboxUnavailableReasonSchema = z.strictObject({
|
|
12
|
-
kind: z.enum([
|
|
13
|
-
"unsupported-platform",
|
|
14
|
-
"invalid-folder",
|
|
15
|
-
"missing",
|
|
16
|
-
"missing-artifact",
|
|
17
|
-
"invalid-digest",
|
|
18
|
-
"digest-mismatch",
|
|
19
|
-
"unreadable",
|
|
20
|
-
"not-executable",
|
|
21
|
-
"staging-failed",
|
|
22
|
-
"unsafe-native-payload",
|
|
23
|
-
"unsafe-host-code",
|
|
24
|
-
"startup-refused",
|
|
25
|
-
"backend-refused",
|
|
26
|
-
]),
|
|
11
|
+
kind: z.enum(["unsupported", "install", "folder", "startup-refused"]),
|
|
27
12
|
message: z.string(),
|
|
28
13
|
});
|
|
29
14
|
export class LocalSandboxUnavailable extends Error {
|
|
@@ -35,14 +20,18 @@ export class LocalSandboxUnavailable extends Error {
|
|
|
35
20
|
}
|
|
36
21
|
}
|
|
37
22
|
export function sandboxUnavailableReason(cause) {
|
|
23
|
+
if (cause instanceof UnsafeGitMarker)
|
|
24
|
+
return { kind: "folder", message: cause.message };
|
|
25
|
+
if (cause instanceof UnsafeSeatbeltFolder)
|
|
26
|
+
return { kind: "folder", message: cause.message };
|
|
38
27
|
if (cause instanceof UnsafeHostCode)
|
|
39
|
-
return { kind: "
|
|
28
|
+
return { kind: "install", message: cause.message };
|
|
40
29
|
if (cause instanceof UnsafeNativePayload)
|
|
41
|
-
return { kind: "
|
|
30
|
+
return { kind: "install", message: cause.message };
|
|
42
31
|
if (cause instanceof LocalBwrapUnavailable)
|
|
43
32
|
return { kind: cause.reason, message: cause.message };
|
|
44
33
|
return {
|
|
45
|
-
kind: "
|
|
34
|
+
kind: "startup-refused",
|
|
46
35
|
message: cause instanceof Error ? cause.message : "Sandbox preparation failed.",
|
|
47
36
|
};
|
|
48
37
|
}
|
|
@@ -51,7 +40,9 @@ export function sandboxUnavailableReason(cause) {
|
|
|
51
40
|
* mode, setting, bare-command branch, PATH fallback or permission to widen it.
|
|
52
41
|
* A ready result proves only that the backend can start, not the escape matrix.
|
|
53
42
|
*/
|
|
54
|
-
export function establishLocalSandbox(input, staging
|
|
43
|
+
export function establishLocalSandbox(input, staging,
|
|
44
|
+
/** Host startup probe; a Linux command-generation test can supply its own runner. */
|
|
45
|
+
runProbe) {
|
|
55
46
|
try {
|
|
56
47
|
sandboxPolicy(input.project, input.folder);
|
|
57
48
|
}
|
|
@@ -59,7 +50,7 @@ export function establishLocalSandbox(input, staging) {
|
|
|
59
50
|
return {
|
|
60
51
|
state: "unavailable",
|
|
61
52
|
reason: {
|
|
62
|
-
kind: "
|
|
53
|
+
kind: "folder",
|
|
63
54
|
message: cause instanceof Error ? cause.message : "Invalid sandbox folder.",
|
|
64
55
|
},
|
|
65
56
|
};
|
|
@@ -69,34 +60,35 @@ export function establishLocalSandbox(input, staging) {
|
|
|
69
60
|
return {
|
|
70
61
|
state: "unavailable",
|
|
71
62
|
reason: {
|
|
72
|
-
kind: "unsupported
|
|
73
|
-
message:
|
|
63
|
+
kind: "unsupported",
|
|
64
|
+
message: `${input.mode === "workspace" ? "Workspace" : "Read-only"} on ${platform} needs a supported OS sandbox; use --yolo or run on macOS/Linux.`,
|
|
74
65
|
},
|
|
75
66
|
};
|
|
76
|
-
|
|
77
|
-
let agentTemporaryDirectory;
|
|
67
|
+
let native;
|
|
78
68
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
native =
|
|
70
|
+
staging ??
|
|
71
|
+
(platform === "linux"
|
|
72
|
+
? createNativePayloadStaging(input.project)
|
|
73
|
+
: undefined);
|
|
74
|
+
const environmentTmp = platform === "linux" ? "/tmp" : input.temporaryDirectory;
|
|
84
75
|
let closed = false;
|
|
85
76
|
const sandbox = {
|
|
86
77
|
command: (cwd, argv, readOnlyHelperFile) => {
|
|
87
78
|
if (closed)
|
|
88
79
|
throw new LocalSandboxUnavailable({
|
|
89
|
-
kind: "
|
|
80
|
+
kind: "startup-refused",
|
|
90
81
|
message: "Sandbox has been closed.",
|
|
91
82
|
});
|
|
92
83
|
// Failed/incomplete authority discovery is not an unavailable backend:
|
|
93
84
|
// it must not turn into an authorized unsandboxed retry.
|
|
94
85
|
const policy = sandboxPolicy(input.project, input.folder);
|
|
95
86
|
try {
|
|
96
|
-
const
|
|
87
|
+
const profile = linuxSandboxProfile(policy, input.mode, input.temporaryDirectory, process.env, userInfo().homedir, platform);
|
|
88
|
+
const linuxPayload = platform === "linux" ? native?.linux(profile) : undefined;
|
|
97
89
|
const stagedHelper = readOnlyHelperFile === undefined
|
|
98
90
|
? undefined
|
|
99
|
-
: native
|
|
91
|
+
: native?.patch(readOnlyHelperFile);
|
|
100
92
|
const stagedArgv = stagedHelper === undefined
|
|
101
93
|
? argv
|
|
102
94
|
: [
|
|
@@ -106,15 +98,17 @@ export function establishLocalSandbox(input, staging) {
|
|
|
106
98
|
.map((argument) => argument === readOnlyHelperFile ? stagedHelper : argument),
|
|
107
99
|
];
|
|
108
100
|
const launch = linuxPayload !== undefined
|
|
109
|
-
? linuxSandboxCommand(
|
|
110
|
-
: seatbeltCommand(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}, stagedArgv);
|
|
114
|
-
return {
|
|
115
|
-
...launch,
|
|
116
|
-
environment: { TMPDIR: environmentTmp },
|
|
101
|
+
? linuxSandboxCommand(profile, linuxPayload, cwd, stagedArgv, undefined, stagedHelper)
|
|
102
|
+
: seatbeltCommand(profile, stagedArgv);
|
|
103
|
+
let environment = {
|
|
104
|
+
TMPDIR: environmentTmp,
|
|
117
105
|
};
|
|
106
|
+
if (platform === "linux" && profile.sshAgentSocket !== undefined)
|
|
107
|
+
environment = {
|
|
108
|
+
TMPDIR: environmentTmp,
|
|
109
|
+
SSH_AUTH_SOCK: profile.sshAgentSocket,
|
|
110
|
+
};
|
|
111
|
+
return { ...launch, environment };
|
|
118
112
|
}
|
|
119
113
|
catch (cause) {
|
|
120
114
|
throw new LocalSandboxUnavailable(sandboxUnavailableReason(cause));
|
|
@@ -124,14 +118,8 @@ export function establishLocalSandbox(input, staging) {
|
|
|
124
118
|
if (closed)
|
|
125
119
|
return;
|
|
126
120
|
closed = true;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
native.close();
|
|
130
|
-
}
|
|
131
|
-
finally {
|
|
132
|
-
if (agentTemporary !== undefined)
|
|
133
|
-
rmSync(agentTemporary, { recursive: true });
|
|
134
|
-
}
|
|
121
|
+
if (staging === undefined)
|
|
122
|
+
native?.close();
|
|
135
123
|
},
|
|
136
124
|
};
|
|
137
125
|
const command = sandbox.command(realpathSync(input.folder), [
|
|
@@ -140,7 +128,9 @@ export function establishLocalSandbox(input, staging) {
|
|
|
140
128
|
let filterDescriptor;
|
|
141
129
|
const probe = () => {
|
|
142
130
|
if (command.seccompFilter !== undefined) {
|
|
143
|
-
const path = native
|
|
131
|
+
const path = native?.probeFilter(command.seccompFilter);
|
|
132
|
+
if (path === undefined)
|
|
133
|
+
throw new Error("No Linux filter staging owner");
|
|
144
134
|
filterDescriptor = openSync(path, "r");
|
|
145
135
|
}
|
|
146
136
|
return spawnSync(command.executable, [...command.args], {
|
|
@@ -160,7 +150,7 @@ export function establishLocalSandbox(input, staging) {
|
|
|
160
150
|
};
|
|
161
151
|
const result = (() => {
|
|
162
152
|
try {
|
|
163
|
-
return probe();
|
|
153
|
+
return runProbe?.(command) ?? probe();
|
|
164
154
|
}
|
|
165
155
|
finally {
|
|
166
156
|
if (filterDescriptor !== undefined)
|
|
@@ -174,24 +164,18 @@ export function establishLocalSandbox(input, staging) {
|
|
|
174
164
|
state: "unavailable",
|
|
175
165
|
reason: {
|
|
176
166
|
kind: "startup-refused",
|
|
177
|
-
message: result.error?.message ??
|
|
167
|
+
message: `${input.mode === "workspace" ? "Workspace" : "Read-only"} on this ${platform === "darwin" ? "Mac" : "Linux host"} needs a working ${platform === "darwin" ? "sandbox-exec profile probe" : "bwrap installation"}; use --yolo or ${platform === "darwin" ? "check the failed Seatbelt profile probe" : "fix the sandbox installation"}. ${result.error?.message ??
|
|
178
168
|
(stderr.length > 0
|
|
179
169
|
? stderr
|
|
180
|
-
: `Sandbox startup exited ${String(result.status)} (${String(result.signal)}).`)
|
|
170
|
+
: `Sandbox startup exited ${String(result.status)} (${String(result.signal)}).`)}`,
|
|
181
171
|
},
|
|
182
172
|
};
|
|
183
173
|
}
|
|
184
174
|
return { state: "ready", sandbox };
|
|
185
175
|
}
|
|
186
176
|
catch (cause) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
native.close();
|
|
190
|
-
}
|
|
191
|
-
finally {
|
|
192
|
-
if (agentTemporaryDirectory !== undefined)
|
|
193
|
-
rmSync(agentTemporaryDirectory, { recursive: true });
|
|
194
|
-
}
|
|
177
|
+
if (staging === undefined)
|
|
178
|
+
native?.close();
|
|
195
179
|
return {
|
|
196
180
|
state: "unavailable",
|
|
197
181
|
reason: cause instanceof LocalSandboxUnavailable
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BirdDogMail } from "@fungi.computer/bird-dog";
|
|
2
|
-
import type { Accepted, CancelResult, CommandSettlement, VerifiedAgentReply } from "@fungi.computer/shiitake";
|
|
2
|
+
import type { Accepted, CancelResult, CommandSettlement, DelegationReceipt, VerifiedAgentReply } from "@fungi.computer/shiitake";
|
|
3
3
|
import type { AgentControlDirectory, AgentDelegationInput, AgentDelegationInvocation, AgentRecallInput } from "@fungi.computer/shiitake/agent-control";
|
|
4
4
|
import { createFleetDelegation } from "@fungi.computer/shiitake/fleet";
|
|
5
5
|
/**
|
|
@@ -8,7 +8,7 @@ import { createFleetDelegation } from "@fungi.computer/shiitake/fleet";
|
|
|
8
8
|
* owner when it is idle; `readSettlement` reads one exact command there.
|
|
9
9
|
*/
|
|
10
10
|
export type PrivateLocalMailRouting = Readonly<{
|
|
11
|
-
deliver: (mail: BirdDogMail) => Promise<
|
|
11
|
+
deliver: (mail: BirdDogMail) => Promise<DelegationReceipt>;
|
|
12
12
|
readSettlement: (agentId: string, sessionId: string, commandId: string) => Promise<CommandSettlement | undefined>;
|
|
13
13
|
}>;
|
|
14
14
|
/**
|
|
@@ -29,7 +29,7 @@ export declare function createPrivateLocalDelegation(input: Readonly<{
|
|
|
29
29
|
}[] | undefined>) | undefined;
|
|
30
30
|
accept: (mail: BirdDogMail, meta?: BirdDogMail["meta"], reply?: VerifiedAgentReply) => Promise<Accepted>;
|
|
31
31
|
}>): {
|
|
32
|
-
send: (task: AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: AgentDelegationInvocation) => Promise<
|
|
32
|
+
send: (task: AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: AgentDelegationInvocation) => Promise<DelegationReceipt>;
|
|
33
33
|
recall: (task: AgentRecallInput, sourceSessionId: string, signal: AbortSignal) => Promise<CancelResult>;
|
|
34
34
|
receive: (mail: BirdDogMail) => Promise<Accepted>;
|
|
35
35
|
outstanding: () => Promise<import("@fungi.computer/shiitake/fleet").OutstandingDelegation | undefined>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { deterministicReplayKey } from "@fungi.computer/cairn";
|
|
2
1
|
import { AgentArchiveRefused } from "@fungi.computer/shiitake/agent-control";
|
|
3
|
-
import { createFleetDelegation, delegationProvenance, delegationReplyMeta, delegationSendMeta, } from "@fungi.computer/shiitake/fleet";
|
|
2
|
+
import { agentDelegationRequestKey, createFleetDelegation, delegationProvenance, delegationReplyMeta, delegationSendMeta, } from "@fungi.computer/shiitake/fleet";
|
|
4
3
|
/** How long one Local delegation grant stays valid while it is admitted. */
|
|
5
4
|
const grantLifetimeMs = 60_000;
|
|
6
5
|
/**
|
|
@@ -32,21 +31,17 @@ export function createPrivateLocalDelegation(input) {
|
|
|
32
31
|
}
|
|
33
32
|
return agents;
|
|
34
33
|
}
|
|
35
|
-
function requestKey(task, sourceSessionId) {
|
|
36
|
-
return deterministicReplayKey("local-agent-delegation", [
|
|
37
|
-
input.agentId,
|
|
38
|
-
sourceSessionId,
|
|
39
|
-
task.agentId,
|
|
40
|
-
task.sessionId,
|
|
41
|
-
task.requestId,
|
|
42
|
-
]);
|
|
43
|
-
}
|
|
44
34
|
/** Send with one caller-owned identity; follow-up replies return here. */
|
|
45
35
|
async function send(task, sourceSessionId, signal, invocation) {
|
|
46
36
|
if (invocation === undefined)
|
|
47
37
|
throw new Error("Delegation requires a tool invocation");
|
|
48
38
|
const agents = await authorizedMembers(task.agentId, signal);
|
|
49
|
-
const requestId = await
|
|
39
|
+
const requestId = await agentDelegationRequestKey({
|
|
40
|
+
owner: { kind: "local", agentId: input.agentId },
|
|
41
|
+
sourceSessionId,
|
|
42
|
+
targetAgentId: task.agentId,
|
|
43
|
+
requestId: task.requestId,
|
|
44
|
+
});
|
|
50
45
|
signal.throwIfAborted();
|
|
51
46
|
if (task.delivery === "steer") {
|
|
52
47
|
const opened = await input.directory.open(task.agentId, signal);
|
|
@@ -83,12 +78,21 @@ export function createPrivateLocalDelegation(input) {
|
|
|
83
78
|
/** Resolve only this sender's receipt, then let the target own the pending guard. */
|
|
84
79
|
async function recall(task, sourceSessionId, signal) {
|
|
85
80
|
await authorizedMembers(task.agentId, signal);
|
|
86
|
-
const requestId = await
|
|
81
|
+
const requestId = await agentDelegationRequestKey({
|
|
82
|
+
owner: { kind: "local", agentId: input.agentId },
|
|
83
|
+
sourceSessionId,
|
|
84
|
+
targetAgentId: task.agentId,
|
|
85
|
+
requestId: task.requestId,
|
|
86
|
+
});
|
|
87
87
|
signal.throwIfAborted();
|
|
88
88
|
const opened = await input.directory.open(task.agentId, signal);
|
|
89
89
|
try {
|
|
90
|
-
const
|
|
91
|
-
|
|
90
|
+
const receipt = fleet.receipt(requestId);
|
|
91
|
+
if (receipt?.sessionId !== undefined &&
|
|
92
|
+
receipt.sessionId !== task.sessionId)
|
|
93
|
+
return { status: "unknown" };
|
|
94
|
+
const session = opened.agent.session(receipt?.sessionId ?? task.sessionId);
|
|
95
|
+
const admitted = receipt ?? (await session.withdraw(requestId));
|
|
92
96
|
return admitted.status === "accepted"
|
|
93
97
|
? await session.cancel({
|
|
94
98
|
commandId: admitted.commandId,
|
|
@@ -110,7 +114,11 @@ export function createPrivateLocalDelegation(input) {
|
|
|
110
114
|
if (mail.to.agentId !== input.agentId && mesh === undefined)
|
|
111
115
|
throw new Error("Local addressed mail recipient is not authorized");
|
|
112
116
|
const agents = mesh ?? (await members(new AbortController().signal));
|
|
113
|
-
|
|
117
|
+
const delegation = mail.replyTo.length === 0 ? fleet.authenticateReply(mail) : undefined;
|
|
118
|
+
const frozenUndeliverable = delegation?.reply.outcome === "undeliverable" &&
|
|
119
|
+
delegation.reply.agentId === mail.from.agentId;
|
|
120
|
+
if (!agents.some(({ agentId }) => agentId === mail.from.agentId) &&
|
|
121
|
+
!frozenUndeliverable)
|
|
114
122
|
throw new Error("Local mail sender is outside this project");
|
|
115
123
|
if (mail.replyTo.length > 0) {
|
|
116
124
|
const [destination] = mail.replyTo;
|
|
@@ -120,13 +128,18 @@ export function createPrivateLocalDelegation(input) {
|
|
|
120
128
|
throw new Error("Local delegated work must reply to its sender");
|
|
121
129
|
return input.accept(mail, mail.meta);
|
|
122
130
|
}
|
|
123
|
-
|
|
131
|
+
if (mail.reply?.outcome === "accepted") {
|
|
132
|
+
const receipt = fleet.recordAcceptedNotice(mail);
|
|
133
|
+
if (receipt === undefined)
|
|
134
|
+
throw new Error("Local acceptance notice does not match sent work");
|
|
135
|
+
return receipt;
|
|
136
|
+
}
|
|
124
137
|
if (delegation === undefined)
|
|
125
138
|
throw new Error("Local reply does not match work this Agent sent");
|
|
126
139
|
const replier = agents.find(({ agentId }) => agentId === delegation.reply.agentId);
|
|
127
|
-
if (replier === undefined)
|
|
140
|
+
if (replier === undefined && !frozenUndeliverable)
|
|
128
141
|
throw new Error("Local reply sender is outside this project");
|
|
129
|
-
const meta = delegationReplyMeta(delegation, replier.
|
|
142
|
+
const meta = delegationReplyMeta(delegation, replier?.name ?? delegation.reply.agentId);
|
|
130
143
|
return input.accept({ ...mail, meta }, meta, delegation.reply);
|
|
131
144
|
}
|
|
132
145
|
return { send, recall, receive, outstanding: fleet.outstanding };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LocalApprovalReview } from "./private-mcp-form-session.js";
|
|
2
|
+
/** Build the host's review before the approval record is staged or displayed. */
|
|
3
|
+
export declare function localExecEscapeReview(request: {
|
|
4
|
+
command?: string | undefined;
|
|
5
|
+
executable?: string | undefined;
|
|
6
|
+
args?: string[] | undefined;
|
|
7
|
+
escape?: {
|
|
8
|
+
justification: string;
|
|
9
|
+
} | undefined;
|
|
10
|
+
}, cwd: string): LocalApprovalReview;
|