@fungi.computer/truffle 0.2.2 → 0.3.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.
Files changed (68) hide show
  1. package/dist/private-files-patch.js +3 -17
  2. package/dist/private-invocation-computer.d.ts +224 -12
  3. package/dist/private-invocation-computer.js +162 -22
  4. package/dist/private-invocation-files.d.ts +3 -2
  5. package/dist/private-invocation-files.js +22 -3
  6. package/dist/private-local-agent-control.d.ts +2 -2
  7. package/dist/private-local-bwrap.d.ts +9 -7
  8. package/dist/private-local-bwrap.js +26 -21
  9. package/dist/private-local-computer-processes.d.ts +15 -0
  10. package/dist/private-local-computer-processes.js +13 -0
  11. package/dist/private-local-containment.d.ts +15 -13
  12. package/dist/private-local-containment.js +49 -65
  13. package/dist/private-local-delegation.d.ts +3 -3
  14. package/dist/private-local-delegation.js +32 -19
  15. package/dist/private-local-exec-review.d.ts +12 -0
  16. package/dist/private-local-exec-review.js +62 -0
  17. package/dist/private-local-exec-rules.d.ts +16 -0
  18. package/dist/private-local-exec-rules.js +441 -0
  19. package/dist/private-local-git-exclude.js +25 -15
  20. package/dist/private-local-linux-sandbox.d.ts +5 -5
  21. package/dist/private-local-linux-sandbox.js +63 -30
  22. package/dist/private-local-mail.d.ts +2 -2
  23. package/dist/private-local-mesh-links.d.ts +4 -1
  24. package/dist/private-local-mesh-links.js +50 -3
  25. package/dist/private-local-native-digests.js +1 -1
  26. package/dist/private-local-native-install-error.d.ts +2 -0
  27. package/dist/private-local-native-install-error.js +10 -0
  28. package/dist/private-local-native-staging.d.ts +7 -8
  29. package/dist/private-local-native-staging.js +132 -43
  30. package/dist/private-local-permission-mode.d.ts +10 -0
  31. package/dist/private-local-permission-mode.js +18 -0
  32. package/dist/private-local-runtime-host.d.ts +28 -5
  33. package/dist/private-local-runtime-host.js +404 -66
  34. package/dist/private-local-sandbox-policy.d.ts +30 -11
  35. package/dist/private-local-sandbox-policy.js +223 -373
  36. package/dist/private-local-sandbox.d.ts +30 -25
  37. package/dist/private-local-sandbox.js +190 -100
  38. package/dist/private-local-seatbelt.d.ts +7 -13
  39. package/dist/private-local-seatbelt.js +55 -32
  40. package/dist/private-local-service-contract.d.ts +5 -4
  41. package/dist/private-local-service-discovery.d.ts +19 -7
  42. package/dist/private-local-service-discovery.js +4 -3
  43. package/dist/private-local-service-listener.js +40 -2
  44. package/dist/private-local-service-log.d.ts +18 -0
  45. package/dist/private-local-service-log.js +6 -0
  46. package/dist/private-local-service-owner.d.ts +18 -10
  47. package/dist/private-local-service-owner.js +119 -25
  48. package/dist/private-local-service.js +30 -3
  49. package/dist/private-local-session-discovery.d.ts +2 -1
  50. package/dist/private-local-session-discovery.js +18 -6
  51. package/dist/private-mcp-form-contract.d.ts +10 -1
  52. package/dist/private-mcp-form-contract.js +10 -1
  53. package/dist/private-mcp-form-session.d.ts +39 -3
  54. package/dist/private-mcp-form-session.js +28 -5
  55. package/dist/private-oyster-mcp-auth.d.ts +1 -1
  56. package/dist/private-oyster-mcp-auth.js +139 -7
  57. package/dist/private-provider-rejection.js +1 -1
  58. package/dist/seatbelt/safe-base.sbpl +2 -2
  59. package/native/bin/bwrap-build-source/Dockerfile +2 -0
  60. package/native/bin/bwrap-build-source/README.md +6 -6
  61. package/native/bin/bwrap-build-source/pin.mjs +2 -2
  62. package/native/bin/bwrap-build-source/record.mjs +12 -1
  63. package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +10 -7
  64. package/native/bin/bwrap-build.json +3 -2
  65. package/native/bin/fungi-apply-patch +0 -0
  66. package/native/bin/fungi-seccomp-workspace.bpf +0 -0
  67. package/native/bin/fungi-seccomp-workspace.bpf.sha256 +1 -0
  68. 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: "missing" | "missing-artifact" | "invalid-digest" | "digest-mismatch" | "unreadable" | "not-executable" | "staging-failed";
2
+ readonly reason: "install";
4
3
  readonly name = "LocalBwrapUnavailable";
5
- constructor(reason: "missing" | "missing-artifact" | "invalid-digest" | "digest-mismatch" | "unreadable" | "not-executable" | "staging-failed", message: string, options?: ErrorOptions);
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 seccompFilter: Uint8Array;
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
- /** Check both packaged artifacts on every use; never cache verification. */
19
- export declare function verifyLocalLinuxSandboxPayload(executable: string, policy: LocalSandboxPolicy, expected: LinuxSandboxDigests): LinuxSandboxPayload;
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(policy: LocalSandboxPolicy): LinuxSandboxPayload;
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("invalid-digest", "No built native patch digest for this platform; reinstall Fungi.");
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
- /** Check both packaged artifacts on every use; never cache verification. */
23
- export function verifyLocalLinuxSandboxPayload(executable, policy, expected) {
24
- const executableBytes = verifiedBytes(executable, "fungi-bwrap", policy.projectRoot, expected.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
- seccompFilter: verifiedBytes(join(dirname(executable), "fungi-seccomp.bpf"), "fungi-seccomp.bpf", policy.projectRoot, expected.seccomp),
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("not-executable", `Packaged ${label} is not a regular file; reinstall Fungi.`);
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("missing-artifact", `Packaged ${label} is missing; reinstall Fungi with optional dependencies enabled.`, { cause });
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("unreadable", `Packaged ${label} cannot be read; reinstall Fungi. No command was started.`, { cause });
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("digest-mismatch", `Packaged ${label} digest mismatch; reinstall Fungi. No command was started.`);
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(policy) {
53
+ export function localLinuxSandboxPayload(project, readNativeSource = readFileSync) {
52
54
  const digest = nativeDigests[`${process.platform}-${process.arch}`];
53
- if (digest?.bwrap === undefined || digest.seccomp === undefined)
54
- throw new LocalBwrapUnavailable("invalid-digest", "No built native Linux payload digest for this platform; reinstall Fungi.");
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("missing-artifact", message, { cause });
68
+ throw new LocalBwrapUnavailable("install", message, { cause });
65
69
  }
66
70
  if (directories === null)
67
- throw new LocalBwrapUnavailable("missing-artifact", message);
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("missing-artifact", message, { cause });
82
+ throw new LocalBwrapUnavailable("install", message, { cause });
79
83
  }
80
84
  }
81
85
  if (packageRoot === undefined)
82
- throw new LocalBwrapUnavailable("missing", `Missing ${name}; reinstall Fungi with optional dependencies enabled.`);
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(policy.projectRoot, packageRoot);
90
+ assertHostOwnedNativeFile(project, packageRoot);
87
91
  if (!statSync(packageRoot).isDirectory())
88
- throw new LocalBwrapUnavailable("missing-artifact", message);
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("missing-artifact", message, { cause });
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, policy, {
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
- missing: "missing";
6
- "missing-artifact": "missing-artifact";
7
- "invalid-digest": "invalid-digest";
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): SandboxAdmission;
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, mkdtempSync, openSync, realpathSync, rmSync, } from "node:fs";
3
- import { tmpdir } from "node:os";
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: "unsafe-host-code", message: cause.message };
28
+ return { kind: "install", message: cause.message };
40
29
  if (cause instanceof UnsafeNativePayload)
41
- return { kind: "unsafe-native-payload", message: cause.message };
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: "backend-refused",
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: "invalid-folder",
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-platform",
73
- message: `Local sandbox is unavailable on ${platform}.`,
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
- const native = staging ?? createNativePayloadStaging(input.project);
77
- let agentTemporaryDirectory;
67
+ let native;
78
68
  try {
79
- // Native staging is never granted as an Agent-writable temporary root.
80
- if (platform === "darwin")
81
- agentTemporaryDirectory = realpathSync(mkdtempSync(join(tmpdir(), "fungi-sandbox-")));
82
- const agentTemporary = agentTemporaryDirectory;
83
- const environmentTmp = agentTemporary ?? "/tmp";
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: "backend-refused",
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 linuxPayload = platform === "linux" ? native.linux(policy) : undefined;
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.patch(readOnlyHelperFile);
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(policy, linuxPayload, cwd, stagedArgv, undefined, stagedHelper)
110
- : seatbeltCommand({
111
- ...policy,
112
- temporaryDirectory: environmentTmp,
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
- try {
128
- if (staging === undefined)
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.probeFilter(command.seccompFilter);
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
- try {
188
- if (staging === undefined)
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<Accepted>;
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<Accepted>;
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 requestKey(task, sourceSessionId);
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 requestKey(task, sourceSessionId);
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 session = opened.agent.session(task.sessionId);
91
- const admitted = fleet.receipt(requestId) ?? (await session.withdraw(requestId));
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
- if (!agents.some(({ agentId }) => agentId === mail.from.agentId))
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
- const delegation = fleet.authenticateReply(mail);
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.name);
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,12 @@
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, gitGrant?: {
11
+ configuredRemotes: ReadonlySet<string>;
12
+ }): LocalApprovalReview;