@fungi.computer/truffle 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/private-files-patch.js +38 -21
- package/dist/private-invocation-computer.d.ts +9 -2
- package/dist/private-local-bwrap.d.ts +13 -5
- package/dist/private-local-bwrap.js +65 -30
- package/dist/private-local-containment.d.ts +10 -2
- package/dist/private-local-containment.js +64 -23
- package/dist/private-local-linux-sandbox.d.ts +4 -2
- package/dist/private-local-linux-sandbox.js +16 -3
- package/dist/private-local-native-digests.js +2 -0
- package/dist/private-local-native-staging.d.ts +17 -0
- package/dist/private-local-native-staging.js +49 -0
- package/dist/private-local-runtime-host.js +13 -6
- package/dist/private-local-sandbox-policy.d.ts +21 -0
- package/dist/private-local-sandbox-policy.js +439 -10
- package/dist/private-local-sandbox.d.ts +11 -0
- package/dist/private-local-sandbox.js +93 -18
- package/dist/private-local-service-discovery.d.ts +1 -1
- package/dist/private-local-service-owner.d.ts +1 -1
- package/dist/private-local-service.js +13 -6
- package/dist/private-local-session-discovery.d.ts +1 -1
- package/native/bin/bwrap-build-source/Dockerfile +1 -1
- package/package.json +11 -11
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createNativePayloadStaging } from "./private-local-native-staging.js";
|
|
2
5
|
import { z } from "zod";
|
|
3
|
-
import { establishLocalSandbox, LocalSandboxUnavailable, sandboxUnavailableReasonSchema, } from "./private-local-containment.js";
|
|
4
|
-
import { protectedSandboxPaths, resolveAgentFolder, sandboxPolicy, } from "./private-local-sandbox-policy.js";
|
|
6
|
+
import { establishLocalSandbox, LocalSandboxUnavailable, sandboxUnavailableReason, sandboxUnavailableReasonSchema, } from "./private-local-containment.js";
|
|
7
|
+
import { protectedSandboxPaths, assertHostCodeOutsideAgent, UnsafeHostCode, resolveAgentFolder, sandboxPolicy, } from "./private-local-sandbox-policy.js";
|
|
5
8
|
/** Availability is not a hostile-program security or an approval status. */
|
|
6
9
|
export const sandboxStatusSchema = z.discriminatedUnion("state", [
|
|
7
10
|
z.strictObject({
|
|
@@ -25,59 +28,126 @@ function contains(root, path) {
|
|
|
25
28
|
export function createLocalSandboxControls(input) {
|
|
26
29
|
const required = input.required();
|
|
27
30
|
const folder = resolveAgentFolder(input.project, input.folder);
|
|
31
|
+
// The packed Node wrapper passes the paths it actually loaded. In source
|
|
32
|
+
// runs this module is the executing host authority instead. Check before
|
|
33
|
+
// staging or probing, independent of the optional/required setting.
|
|
34
|
+
try {
|
|
35
|
+
const launcher = process.env.FUNGI_LAUNCHER;
|
|
36
|
+
const cli = launcher === undefined
|
|
37
|
+
? fileURLToPath(import.meta.url)
|
|
38
|
+
: process.env.FUNGI_BUNDLED_CLI;
|
|
39
|
+
if (cli === undefined)
|
|
40
|
+
throw new UnsafeHostCode("Packed CLI entry was not provided by the launcher; reinstall Fungi outside the project.");
|
|
41
|
+
assertHostCodeOutsideAgent(input.project, cli, launcher ?? cli);
|
|
42
|
+
}
|
|
43
|
+
catch (cause) {
|
|
44
|
+
throw new LocalSandboxUnavailable(cause instanceof UnsafeHostCode
|
|
45
|
+
? sandboxUnavailableReason(cause)
|
|
46
|
+
: {
|
|
47
|
+
kind: "unsafe-host-code",
|
|
48
|
+
message: `Fungi host JavaScript identity cannot be verified (${cause instanceof Error ? cause.message : String(cause)}); reinstall Fungi using a copying install outside the project.`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const platform = input.platform === undefined ? {} : { platform: input.platform };
|
|
52
|
+
const native = createNativePayloadStaging(input.project, input.readNativePatchSource, input.probeNativeRunLiveness);
|
|
28
53
|
const initial = establishLocalSandbox({
|
|
29
54
|
project: input.project,
|
|
30
55
|
folder: folder.absolute,
|
|
31
|
-
|
|
56
|
+
...platform,
|
|
57
|
+
}, native);
|
|
32
58
|
let sandbox = initial.state === "ready" ? initial.sandbox : undefined;
|
|
59
|
+
let everReady = initial.state === "ready";
|
|
33
60
|
let unavailable = initial.state === "unavailable" ? initial.reason : undefined;
|
|
34
61
|
if (unavailable !== undefined) {
|
|
35
|
-
if (unavailable.kind === "invalid-folder")
|
|
62
|
+
if (unavailable.kind === "invalid-folder") {
|
|
63
|
+
native.close();
|
|
36
64
|
throw new Error(unavailable.message);
|
|
37
|
-
|
|
65
|
+
}
|
|
66
|
+
if (required) {
|
|
67
|
+
native.close();
|
|
38
68
|
throw new LocalSandboxUnavailable(unavailable);
|
|
69
|
+
}
|
|
39
70
|
}
|
|
40
71
|
let closed = false;
|
|
41
72
|
const assertFolder = () => {
|
|
42
73
|
if (closed)
|
|
43
74
|
throw new Error("Local sandbox owner is closed");
|
|
44
|
-
|
|
45
|
-
|
|
75
|
+
try {
|
|
76
|
+
const current = resolveAgentFolder(input.project, input.folder);
|
|
77
|
+
if (current.absolute === folder.absolute)
|
|
78
|
+
return;
|
|
46
79
|
throw new Error("Local Agent folder authority changed while open");
|
|
80
|
+
}
|
|
81
|
+
catch (cause) {
|
|
82
|
+
if (!everReady)
|
|
83
|
+
throw cause;
|
|
84
|
+
unavailable = {
|
|
85
|
+
kind: "invalid-folder",
|
|
86
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
87
|
+
};
|
|
88
|
+
throw new LocalSandboxUnavailable(unavailable);
|
|
89
|
+
}
|
|
47
90
|
};
|
|
48
|
-
|
|
91
|
+
function launch(cwd, argv, readOnlyHelperFile) {
|
|
49
92
|
assertFolder();
|
|
50
93
|
if (sandbox === undefined) {
|
|
51
94
|
const admission = establishLocalSandbox({
|
|
52
95
|
project: input.project,
|
|
53
96
|
folder: folder.absolute,
|
|
54
|
-
|
|
55
|
-
|
|
97
|
+
...platform,
|
|
98
|
+
}, native);
|
|
99
|
+
if (admission.state === "ready") {
|
|
56
100
|
sandbox = admission.sandbox;
|
|
101
|
+
everReady = true;
|
|
102
|
+
}
|
|
57
103
|
else
|
|
58
104
|
unavailable = admission.reason;
|
|
59
105
|
}
|
|
60
106
|
if (sandbox !== undefined) {
|
|
61
107
|
try {
|
|
62
|
-
const command = sandbox.command(cwd, argv);
|
|
108
|
+
const command = sandbox.command(cwd, argv, readOnlyHelperFile);
|
|
63
109
|
unavailable = undefined;
|
|
64
110
|
return command;
|
|
65
111
|
}
|
|
66
112
|
catch (cause) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
113
|
+
unavailable =
|
|
114
|
+
cause instanceof LocalSandboxUnavailable
|
|
115
|
+
? cause.reason
|
|
116
|
+
: sandboxUnavailableReason(cause);
|
|
117
|
+
throw new LocalSandboxUnavailable(unavailable);
|
|
70
118
|
}
|
|
71
119
|
}
|
|
72
120
|
if (unavailable === undefined)
|
|
73
121
|
throw new Error("Local sandbox preparation produced no result");
|
|
74
|
-
if (unavailable.kind === "invalid-folder")
|
|
122
|
+
if (unavailable.kind === "invalid-folder" && !everReady)
|
|
75
123
|
throw new Error(unavailable.message);
|
|
124
|
+
// A formerly ready sandbox never becomes optional bare execution.
|
|
125
|
+
// Before readiness, only availability failures permit fallback.
|
|
126
|
+
if (everReady ||
|
|
127
|
+
(unavailable.kind !== "missing" &&
|
|
128
|
+
unavailable.kind !== "unsupported-platform" &&
|
|
129
|
+
unavailable.kind !== "startup-refused"))
|
|
130
|
+
throw new LocalSandboxUnavailable(unavailable);
|
|
76
131
|
if (input.required())
|
|
77
132
|
throw new LocalSandboxUnavailable(unavailable);
|
|
78
|
-
//
|
|
133
|
+
// Files is never handed back its installed executable for a bare retry.
|
|
134
|
+
if (readOnlyHelperFile !== undefined) {
|
|
135
|
+
try {
|
|
136
|
+
return {
|
|
137
|
+
bareNative: true,
|
|
138
|
+
executable: native.patch(readOnlyHelperFile),
|
|
139
|
+
args: [],
|
|
140
|
+
environment: { TMPDIR: process.env.TMPDIR ?? tmpdir() },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (cause) {
|
|
144
|
+
unavailable = sandboxUnavailableReason(cause);
|
|
145
|
+
throw new LocalSandboxUnavailable(unavailable);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// No executable has started. Generic exec retains its ordinary fallback.
|
|
79
149
|
return undefined;
|
|
80
|
-
}
|
|
150
|
+
}
|
|
81
151
|
const assertReady = () => {
|
|
82
152
|
launch(folder.absolute, ["/usr/bin/true"]);
|
|
83
153
|
};
|
|
@@ -108,7 +178,12 @@ export function createLocalSandboxControls(input) {
|
|
|
108
178
|
if (closed)
|
|
109
179
|
return;
|
|
110
180
|
closed = true;
|
|
111
|
-
|
|
181
|
+
try {
|
|
182
|
+
sandbox?.close();
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
native.close();
|
|
186
|
+
}
|
|
112
187
|
},
|
|
113
188
|
};
|
|
114
189
|
}
|
|
@@ -131,10 +131,10 @@ export declare const archiveRefusalSchema: z.ZodObject<{
|
|
|
131
131
|
reason: z.ZodEnum<{
|
|
132
132
|
pending: "pending";
|
|
133
133
|
running: "running";
|
|
134
|
+
unreadable: "unreadable";
|
|
134
135
|
archived: "archived";
|
|
135
136
|
not_creator: "not_creator";
|
|
136
137
|
self: "self";
|
|
137
|
-
unreadable: "unreadable";
|
|
138
138
|
}>;
|
|
139
139
|
error: z.ZodString;
|
|
140
140
|
}, z.core.$strict>;
|
|
@@ -114,7 +114,7 @@ onRecoveryDisabled?: ServiceLogLifecycle["recoveryDisabled"], configRoot?: Priva
|
|
|
114
114
|
state: "unavailable";
|
|
115
115
|
requireSandbox: boolean;
|
|
116
116
|
reason: {
|
|
117
|
-
kind: "missing" | "invalid-digest" | "digest-mismatch" | "not-executable" | "unsupported-platform" | "invalid-folder" | "backend-refused";
|
|
117
|
+
kind: "missing" | "missing-artifact" | "invalid-digest" | "digest-mismatch" | "unreadable" | "not-executable" | "staging-failed" | "unsupported-platform" | "invalid-folder" | "unsafe-native-payload" | "unsafe-host-code" | "startup-refused" | "backend-refused";
|
|
118
118
|
message: string;
|
|
119
119
|
};
|
|
120
120
|
}>;
|
|
@@ -232,12 +232,19 @@ export function createPrivateLocalServiceManager(options = {}) {
|
|
|
232
232
|
if (await serviceOwnerIsLive(directory))
|
|
233
233
|
return "busy";
|
|
234
234
|
const bundledCli = process.env.FUNGI_BUNDLED_CLI;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
]
|
|
240
|
-
|
|
235
|
+
// A detached Bun service also starts in the invoking project's cwd.
|
|
236
|
+
// Keep the host process from importing Agent-authored bunfig or .env.
|
|
237
|
+
const bunFlags = options.executable === undefined && "bun" in process.versions
|
|
238
|
+
? ["--config=/dev/null", "--no-env-file"]
|
|
239
|
+
: [];
|
|
240
|
+
const serviceEntry = bundledCli ??
|
|
241
|
+
fileURLToPath(new URL("./private-local-service-process.js", import.meta.url));
|
|
242
|
+
const child = spawn(options.executable ?? process.execPath, [
|
|
243
|
+
...bunFlags,
|
|
244
|
+
serviceEntry,
|
|
245
|
+
...(bundledCli === undefined ? [] : ["--internal-service"]),
|
|
246
|
+
directory,
|
|
247
|
+
], {
|
|
241
248
|
detached: true,
|
|
242
249
|
stdio: "ignore",
|
|
243
250
|
// Service startup retains its existing inherited inputs. This is
|
|
@@ -37,7 +37,7 @@ export declare function readPrivateLocalAgentProfile(path: string): {
|
|
|
37
37
|
model: {
|
|
38
38
|
readonly providerId: string;
|
|
39
39
|
readonly modelId: string;
|
|
40
|
-
readonly reasoning?: "
|
|
40
|
+
readonly reasoning?: "high" | "low" | "max" | "medium" | "minimal" | "xhigh" | undefined;
|
|
41
41
|
} | null;
|
|
42
42
|
};
|
|
43
43
|
export declare function readPrivateLocalSessionSummaries(path: string): Promise<readonly LocalSummary[]>;
|
|
@@ -12,7 +12,7 @@ RUN if [ "$TARGET" = linux-arm64 ]; then \
|
|
|
12
12
|
fi && apt-get update && apt-get install -y --no-install-recommends \
|
|
13
13
|
build-essential ca-certificates curl xz-utils python3-pip ninja-build \
|
|
14
14
|
pkg-config patchelf libcap-dev libseccomp-dev \
|
|
15
|
-
$(if [ "$TARGET" = linux-arm64 ]; then printf 'gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu libcap-dev:arm64'; fi) \
|
|
15
|
+
$(if [ "$TARGET" = linux-arm64 ]; then printf 'gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu libc6-dev-arm64-cross libcap-dev:arm64'; fi) \
|
|
16
16
|
&& rm -rf /var/lib/apt/lists/*
|
|
17
17
|
# prefer_static is a maintained Meson option newer than Jammy's Meson.
|
|
18
18
|
RUN pip3 install --no-cache-dir meson==1.10.2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/truffle",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Truffle: Fungi's local Agent runtime, bundled into the fungi command",
|
|
6
6
|
"type": "module",
|
|
@@ -67,20 +67,20 @@
|
|
|
67
67
|
"@fungi.computer/cairn": "0.1.0",
|
|
68
68
|
"@fungi.computer/capnweb": "0.12.0",
|
|
69
69
|
"@fungi.computer/carrier": "0.1.0",
|
|
70
|
-
"@fungi.computer/cordyceps": "0.3.
|
|
70
|
+
"@fungi.computer/cordyceps": "0.3.1",
|
|
71
71
|
"@fungi.computer/gills": "0.2.0",
|
|
72
|
-
"@fungi.computer/graft": "0.1.
|
|
73
|
-
"@fungi.computer/knapsack": "0.2.
|
|
72
|
+
"@fungi.computer/graft": "0.1.1",
|
|
73
|
+
"@fungi.computer/knapsack": "0.2.1",
|
|
74
74
|
"@fungi.computer/lantern": "0.3.0",
|
|
75
|
-
"@fungi.computer/mycelium": "0.2.
|
|
75
|
+
"@fungi.computer/mycelium": "0.2.1",
|
|
76
76
|
"@fungi.computer/nacre": "0.0.1",
|
|
77
77
|
"@fungi.computer/seam": "0.1.0",
|
|
78
|
-
"@fungi.computer/shiitake": "0.3.
|
|
79
|
-
"@fungi.computer/shiitake-extensions": "0.1.
|
|
78
|
+
"@fungi.computer/shiitake": "0.3.1",
|
|
79
|
+
"@fungi.computer/shiitake-extensions": "0.1.1",
|
|
80
80
|
"@fungi.computer/veil": "0.2.0",
|
|
81
81
|
"@fungi.computer/watchdog": "0.1.0",
|
|
82
82
|
"@fungi.computer/whistle": "0.1.0",
|
|
83
|
-
"@fungi.computer/zookeeper": "0.0.
|
|
83
|
+
"@fungi.computer/zookeeper": "0.0.2",
|
|
84
84
|
"@modelcontextprotocol/client": "2.0.0",
|
|
85
85
|
"diff": "8.0.4",
|
|
86
86
|
"effect": "4.0.2",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
99
|
"@cloudflare/codemode": "0.5.1",
|
|
100
|
-
"@fungi.computer/mule": "0.1.
|
|
100
|
+
"@fungi.computer/mule": "0.1.1",
|
|
101
101
|
"@modelcontextprotocol/server": "2.0.0",
|
|
102
102
|
"@types/node": "24.13.3",
|
|
103
103
|
"@types/proper-lockfile": "4.1.4",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"scripts": {
|
|
141
141
|
"lint": "../../node_modules/.bin/eslint src scripts test --max-warnings=0 && ../../node_modules/.bin/oxlint --config ../../oxlint.config.ts --deny-warnings src scripts test",
|
|
142
142
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
143
|
-
"build": "pnpm run clean && tsc -p tsconfig.build.json && node scripts/copy-seatbelt.mjs && esbuild src/internal/private-workerd-sandbox-worker.mjs --outfile=dist/internal/private-workerd-sandbox-worker.mjs --bundle --format=esm --platform=neutral --conditions=workerd,browser --target=es2024 --external:cloudflare:workers --metafile=dist/internal/sandbox-metafile.json --log-level=warning && esbuild src/internal/local-image-fit-worker.mjs --outfile=dist/internal/local-image-fit-worker.mjs --bundle --format=esm --platform=node --target=es2024 --metafile=dist/internal/image-fit-metafile.json --log-level=warning",
|
|
143
|
+
"build": "if [ ! -f src/private-local-native-digests.js ]; then pnpm run build:native; fi && node scripts/generate-native-digests.mjs --verify && pnpm run clean && tsc -p tsconfig.build.json && cp src/private-local-native-digests.js dist/private-local-native-digests.js && node scripts/copy-seatbelt.mjs && esbuild src/internal/private-workerd-sandbox-worker.mjs --outfile=dist/internal/private-workerd-sandbox-worker.mjs --bundle --format=esm --platform=neutral --conditions=workerd,browser --target=es2024 --external:cloudflare:workers --metafile=dist/internal/sandbox-metafile.json --log-level=warning && esbuild src/internal/local-image-fit-worker.mjs --outfile=dist/internal/local-image-fit-worker.mjs --bundle --format=esm --platform=node --target=es2024 --metafile=dist/internal/image-fit-metafile.json --log-level=warning",
|
|
144
144
|
"clean": "node --input-type=module --eval \"import { rm } from 'node:fs/promises'; await rm('dist', { recursive: true, force: true });\"",
|
|
145
145
|
"test": "pnpm run test:node && pnpm run test:merge && pnpm run test:stop-all",
|
|
146
146
|
"test:stop-all": "bun test ./test/private-local-stop-all.bun.test.ts",
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"test:bun": "pnpm run test:mesh-link && pnpm run test:bun:batch && bun test ./test/private-workerd-deadline.bun.test.ts",
|
|
150
150
|
"test:mesh-link": "bun test ./test/private-local-mesh-links.bun.test.ts",
|
|
151
151
|
"test:bun:batch": "bun test ./test/private-sqlite-approval-driver.bun.test.ts ./test/mcp-form-service.bun.test.ts ./test/private-local-service-discovery.bun.test.ts ./test/private-local-service-listener.bun.test.ts ./test/private-local-service-manager.bun.test.ts ./test/private-local-service-rebind.bun.test.ts ./test/private-local-service-stop.bun.test.ts ./test/private-local-service-ensure.bun.test.ts ./test/private-local-service-identity.bun.test.ts ./test/private-local-service-kill-resume.bun.test.ts ./test/private-local-service-idle-owners.bun.test.ts ./test/private-local-service-idle-delegation.bun.test.ts ./test/private-local-mail-retry.bun.test.ts ./test/private-local-service-log-native.bun.test.ts ./test/private-local-service-log.bun.test.ts ./test/private-local-sqlite-lifetime.bun.test.ts ./test/private-sqlite-driver.bun.test.ts ./test/private-local-reload.bun.test.ts ./test/private-local-auth-rpc.bun.test.ts ./test/private-workerd-forage.bun.test.ts ./test/local-extensions.bun.test.ts ./test/local-zookeeper-slot.bun.test.ts ./test/local-extension-skills.bun.test.ts ./test/private-local-sandbox-service.bun.test.ts",
|
|
152
|
-
"build:native": "fungi-apply-patch-build --out native/bin && node scripts/build-bwrap.mjs --out native/bin",
|
|
152
|
+
"build:native": "fungi-apply-patch-build --out native/bin && node scripts/build-bwrap.mjs --out native/bin && node scripts/generate-native-digests.mjs",
|
|
153
153
|
"test:patch": "node --test --test-timeout=30000 test/apply-patch.native.mjs",
|
|
154
154
|
"test:sandbox:linux": "node --import tsx test/fixtures/local-sandbox-proof.mts",
|
|
155
155
|
"test:sandbox:macos": "scratch=$(mktemp -d /tmp/fungi-seatbelt.XXXXXX) && scratch=$(cd \"$scratch\" && pwd -P) && mkdir \"$scratch/home\" && HOME=\"$scratch/home\" node --import tsx scripts/seatbelt-mac-proof.mjs \"$scratch\"",
|