@finalai/final-agent-provider 0.10.0 → 0.11.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/HOSTING.md +334 -245
- package/README.md +126 -40
- package/WORKSPACE-EGRESS.md +11 -11
- package/dist/modules/catalog.d.mts +9 -0
- package/dist/modules/catalog.mjs +21 -0
- package/dist/modules/catalog.mjs.map +7 -0
- package/dist/modules/channels/projection.mjs +15 -8
- package/dist/modules/channels/projection.mjs.map +2 -2
- package/dist/modules/harnesses/acp-agent.d.mts +4 -0
- package/dist/modules/harnesses/acp-agent.mjs +35 -0
- package/dist/modules/harnesses/acp-agent.mjs.map +7 -0
- package/dist/modules/harnesses/adapters.d.mts +7 -0
- package/dist/modules/harnesses/adapters.mjs +74 -0
- package/dist/modules/harnesses/adapters.mjs.map +7 -0
- package/dist/modules/harnesses/claude-code.d.mts +14 -0
- package/dist/modules/harnesses/claude-code.mjs +79 -0
- package/dist/modules/harnesses/claude-code.mjs.map +7 -0
- package/dist/modules/harnesses/codex.d.mts +12 -0
- package/dist/modules/harnesses/codex.mjs +90 -0
- package/dist/modules/harnesses/codex.mjs.map +7 -0
- package/dist/modules/harnesses/config-fields.d.mts +98 -57
- package/dist/modules/harnesses/config-fields.mjs +58 -35
- package/dist/modules/harnesses/config-fields.mjs.map +3 -3
- package/dist/modules/harnesses/definitions.d.mts +3 -0
- package/dist/modules/harnesses/definitions.mjs +21 -0
- package/dist/modules/harnesses/definitions.mjs.map +7 -0
- package/dist/modules/harnesses/fidelity.d.mts +1 -1
- package/dist/modules/harnesses/fidelity.mjs +19 -16
- package/dist/modules/harnesses/fidelity.mjs.map +2 -2
- package/dist/modules/harnesses/final.d.mts +12 -0
- package/dist/modules/harnesses/final.mjs +70 -0
- package/dist/modules/harnesses/final.mjs.map +7 -0
- package/dist/modules/harnesses/index.d.mts +13 -12
- package/dist/modules/harnesses/index.mjs +86 -45
- package/dist/modules/harnesses/index.mjs.map +3 -3
- package/dist/modules/harnesses/names.d.mts +43 -0
- package/dist/modules/harnesses/names.mjs +59 -0
- package/dist/modules/harnesses/names.mjs.map +7 -0
- package/dist/modules/harnesses/nanocode-claude.d.mts +18 -0
- package/dist/modules/harnesses/nanocode-claude.mjs +101 -0
- package/dist/modules/harnesses/nanocode-claude.mjs.map +7 -0
- package/dist/modules/harnesses/nanocode-codex.d.mts +13 -0
- package/dist/modules/harnesses/nanocode-codex.mjs +64 -0
- package/dist/modules/harnesses/nanocode-codex.mjs.map +7 -0
- package/dist/modules/harnesses/pi.d.mts +12 -0
- package/dist/modules/harnesses/pi.mjs +71 -0
- package/dist/modules/harnesses/pi.mjs.map +7 -0
- package/dist/modules/integrations.d.mts +9 -0
- package/dist/modules/integrations.mjs +29 -0
- package/dist/modules/integrations.mjs.map +7 -0
- package/dist/modules/provider/general.bundle.d.mts +1 -0
- package/dist/modules/provider/general.bundle.mjs +5 -0
- package/dist/modules/provider/general.bundle.mjs.map +7 -0
- package/dist/modules/provider/general.d.mts +11 -0
- package/dist/modules/provider/general.mjs +130 -0
- package/dist/modules/provider/general.mjs.map +7 -0
- package/dist/modules/provider/index.d.mts +3 -0
- package/dist/modules/provider/index.mjs +13 -0
- package/dist/modules/provider/index.mjs.map +7 -0
- package/dist/modules/sandboxes/access.d.mts +34 -0
- package/dist/modules/sandboxes/access.mjs +15 -0
- package/dist/modules/sandboxes/access.mjs.map +7 -0
- package/dist/modules/sandboxes/computer.d.mts +24 -51
- package/dist/modules/sandboxes/computer.mjs +523 -888
- package/dist/modules/sandboxes/computer.mjs.map +3 -3
- package/dist/modules/sandboxes/computers-api.d.mts +3 -2
- package/dist/modules/sandboxes/computers-api.mjs +2 -4
- package/dist/modules/sandboxes/computers-api.mjs.map +2 -2
- package/dist/modules/sandboxes/device-api.d.mts +4 -3
- package/dist/modules/sandboxes/device-api.mjs +2 -4
- package/dist/modules/sandboxes/device-api.mjs.map +2 -2
- package/dist/modules/sandboxes/device.d.mts +6 -6
- package/dist/modules/sandboxes/device.mjs +126 -121
- package/dist/modules/sandboxes/device.mjs.map +3 -3
- package/dist/modules/sandboxes/egress.d.mts +10 -9
- package/dist/modules/sandboxes/egress.mjs +73 -56
- package/dist/modules/sandboxes/egress.mjs.map +2 -2
- package/dist/modules/sandboxes/environment.d.mts +35 -269
- package/dist/modules/sandboxes/environment.mjs +27 -104
- package/dist/modules/sandboxes/environment.mjs.map +3 -3
- package/dist/modules/sandboxes/index.d.mts +9 -69
- package/dist/modules/sandboxes/index.mjs +35 -95
- package/dist/modules/sandboxes/index.mjs.map +3 -3
- package/dist/modules/sandboxes/local.d.mts +28 -0
- package/dist/modules/sandboxes/local.mjs +499 -0
- package/dist/modules/sandboxes/local.mjs.map +7 -0
- package/dist/modules/sandboxes/session.d.mts +0 -3
- package/dist/modules/sandboxes/session.mjs +2 -22
- package/dist/modules/sandboxes/session.mjs.map +2 -2
- package/dist/modules/sandboxes/testing/fake-machine.mjs +0 -28
- package/dist/modules/sandboxes/testing/fake-machine.mjs.map +2 -2
- package/package.json +76 -104
- package/dist/modules/connections.d.mts +0 -11
- package/dist/modules/connections.mjs +0 -16
- package/dist/modules/connections.mjs.map +0 -7
- package/dist/modules/harnesses/acp-describe.d.mts +0 -20
- package/dist/modules/harnesses/acp-describe.mjs +0 -110
- package/dist/modules/harnesses/acp-describe.mjs.map +0 -7
- package/dist/modules/harnesses/local-stdio.d.mts +0 -47
- package/dist/modules/harnesses/local-stdio.mjs +0 -41
- package/dist/modules/harnesses/local-stdio.mjs.map +0 -7
- package/dist/modules/harnesses/remote-acp.d.mts +0 -76
- package/dist/modules/harnesses/remote-acp.mjs +0 -83
- package/dist/modules/harnesses/remote-acp.mjs.map +0 -7
- package/dist/modules/harnesses/remote-claude-code.d.mts +0 -88
- package/dist/modules/harnesses/remote-claude-code.mjs +0 -113
- package/dist/modules/harnesses/remote-claude-code.mjs.map +0 -7
- package/dist/modules/harnesses/remote-client-tls.d.mts +0 -35
- package/dist/modules/harnesses/remote-client-tls.mjs +0 -37
- package/dist/modules/harnesses/remote-client-tls.mjs.map +0 -7
- package/dist/modules/harnesses/remote-codex.d.mts +0 -98
- package/dist/modules/harnesses/remote-codex.mjs +0 -128
- package/dist/modules/harnesses/remote-codex.mjs.map +0 -7
- package/dist/modules/harnesses/remote-pi.d.mts +0 -78
- package/dist/modules/harnesses/remote-pi.mjs +0 -104
- package/dist/modules/harnesses/remote-pi.mjs.map +0 -7
- package/dist/modules/harnesses/remote-url.d.mts +0 -5
- package/dist/modules/harnesses/remote-url.mjs +0 -19
- package/dist/modules/harnesses/remote-url.mjs.map +0 -7
- package/dist/modules/harnesses/serverless-final.d.mts +0 -63
- package/dist/modules/harnesses/serverless-final.mjs +0 -88
- package/dist/modules/harnesses/serverless-final.mjs.map +0 -7
- package/dist/modules/harnesses/serverless-nanocode-claude.d.mts +0 -64
- package/dist/modules/harnesses/serverless-nanocode-claude.mjs +0 -105
- package/dist/modules/harnesses/serverless-nanocode-claude.mjs.map +0 -7
- package/dist/modules/harnesses/serverless-nanocode-codex.d.mts +0 -66
- package/dist/modules/harnesses/serverless-nanocode-codex.mjs +0 -113
- package/dist/modules/harnesses/serverless-nanocode-codex.mjs.map +0 -7
- package/dist/modules/harnesses/serverless-pi.d.mts +0 -64
- package/dist/modules/harnesses/serverless-pi.mjs +0 -98
- package/dist/modules/harnesses/serverless-pi.mjs.map +0 -7
- package/dist/modules/runtime/index.d.mts +0 -7
- package/dist/modules/runtime/index.mjs +0 -18
- package/dist/modules/runtime/index.mjs.map +0 -7
- package/dist/modules/runtime/local-stdio.d.mts +0 -10
- package/dist/modules/runtime/local-stdio.mjs +0 -106
- package/dist/modules/runtime/local-stdio.mjs.map +0 -7
- package/dist/modules/runtime/remote-acp.d.mts +0 -16
- package/dist/modules/runtime/remote-acp.mjs +0 -152
- package/dist/modules/runtime/remote-acp.mjs.map +0 -7
- package/dist/modules/runtime/remote-claude-code.d.mts +0 -5
- package/dist/modules/runtime/remote-claude-code.mjs +0 -47
- package/dist/modules/runtime/remote-claude-code.mjs.map +0 -7
- package/dist/modules/runtime/remote-codex.d.mts +0 -5
- package/dist/modules/runtime/remote-codex.mjs +0 -47
- package/dist/modules/runtime/remote-codex.mjs.map +0 -7
- package/dist/modules/runtime/remote-host.d.mts +0 -127
- package/dist/modules/runtime/remote-host.mjs +0 -387
- package/dist/modules/runtime/remote-host.mjs.map +0 -7
- package/dist/modules/runtime/remote-pi.d.mts +0 -5
- package/dist/modules/runtime/remote-pi.mjs +0 -44
- package/dist/modules/runtime/remote-pi.mjs.map +0 -7
- package/dist/modules/sandboxes/guide.d.mts +0 -35
- package/dist/modules/sandboxes/guide.mjs +0 -119
- package/dist/modules/sandboxes/guide.mjs.map +0 -7
- package/dist/modules/sandboxes/workspace-setup.d.mts +0 -42
- package/dist/modules/sandboxes/workspace-setup.mjs +0 -242
- package/dist/modules/sandboxes/workspace-setup.mjs.map +0 -7
- package/dist/modules/testing/harness.d.mts +0 -9
- package/dist/modules/testing/harness.mjs +0 -123
- package/dist/modules/testing/harness.mjs.map +0 -7
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { Capability, FilesAccess, ShellAccess } from "@finalai/agent-protocol";
|
|
2
|
+
import {
|
|
3
|
+
defineSandboxProvider,
|
|
4
|
+
encodeSandboxText,
|
|
5
|
+
platformProcessEnvironment,
|
|
6
|
+
PortProtocol,
|
|
7
|
+
readTextFileStream,
|
|
8
|
+
runCollect,
|
|
9
|
+
SandboxEntryType
|
|
10
|
+
} from "@finalai/agent-provider/sandbox";
|
|
11
|
+
import * as z from "zod";
|
|
12
|
+
const LOCAL_PROVIDER = "local";
|
|
13
|
+
const LOCAL_INHERITED_ENV = [
|
|
14
|
+
"PATH",
|
|
15
|
+
"HOME",
|
|
16
|
+
"USER",
|
|
17
|
+
"LANG",
|
|
18
|
+
"LC_ALL",
|
|
19
|
+
"TMPDIR",
|
|
20
|
+
"TERM",
|
|
21
|
+
"SHELL"
|
|
22
|
+
];
|
|
23
|
+
const fsError = (code, message) => Object.assign(new Error(`${code}: ${message}`), { code });
|
|
24
|
+
const errorCode = (error) => {
|
|
25
|
+
const parsed = z.object({ code: z.string() }).safeParse(error);
|
|
26
|
+
return parsed.success ? parsed.data.code : null;
|
|
27
|
+
};
|
|
28
|
+
const missing = (error) => {
|
|
29
|
+
const code = errorCode(error);
|
|
30
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
31
|
+
};
|
|
32
|
+
const signalExitCode = (signal) => {
|
|
33
|
+
if (signal === "SIGKILL") return 137;
|
|
34
|
+
if (signal === "SIGINT") return 130;
|
|
35
|
+
return 143;
|
|
36
|
+
};
|
|
37
|
+
const loadNode = () => {
|
|
38
|
+
if (globalThis.process?.getBuiltinModule == null)
|
|
39
|
+
throw new Error("A local sandbox runs only in Node");
|
|
40
|
+
return {
|
|
41
|
+
childProcess: process.getBuiltinModule("node:child_process"),
|
|
42
|
+
fs: process.getBuiltinModule("node:fs"),
|
|
43
|
+
fsPromises: process.getBuiltinModule("node:fs/promises"),
|
|
44
|
+
os: process.getBuiltinModule("node:os"),
|
|
45
|
+
path: process.getBuiltinModule("node:path"),
|
|
46
|
+
streamPromises: process.getBuiltinModule("node:stream/promises")
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
let loaded = null;
|
|
50
|
+
const node = () => loaded ??= loadNode();
|
|
51
|
+
const isSignal = (value) => value in node().os.constants.signals;
|
|
52
|
+
const drained = (stream) => new Promise((resolve) => {
|
|
53
|
+
const done = () => {
|
|
54
|
+
stream.off("drain", done);
|
|
55
|
+
stream.off("close", done);
|
|
56
|
+
stream.off("error", done);
|
|
57
|
+
resolve();
|
|
58
|
+
};
|
|
59
|
+
stream.once("drain", done);
|
|
60
|
+
stream.once("close", done);
|
|
61
|
+
stream.once("error", done);
|
|
62
|
+
});
|
|
63
|
+
const BRIDGE_HIGH_WATER_BYTES = 64 * 1024;
|
|
64
|
+
const nodeToWeb = (readable, extra) => {
|
|
65
|
+
let open = true;
|
|
66
|
+
return new ReadableStream(
|
|
67
|
+
{
|
|
68
|
+
start: (controller) => {
|
|
69
|
+
const close = () => {
|
|
70
|
+
if (!open) return;
|
|
71
|
+
open = false;
|
|
72
|
+
controller.close();
|
|
73
|
+
};
|
|
74
|
+
const push = (bytes) => {
|
|
75
|
+
if (!open) return;
|
|
76
|
+
controller.enqueue(bytes);
|
|
77
|
+
if ((controller.desiredSize ?? 0) <= 0) readable?.pause();
|
|
78
|
+
};
|
|
79
|
+
extra?.({ push, close });
|
|
80
|
+
if (readable == null) {
|
|
81
|
+
close();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
readable.on("data", (chunk) => push(new Uint8Array(chunk)));
|
|
85
|
+
readable.pause();
|
|
86
|
+
readable.once("end", close);
|
|
87
|
+
readable.once("error", (error) => {
|
|
88
|
+
if (!open) return;
|
|
89
|
+
open = false;
|
|
90
|
+
controller.error(error);
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
pull: () => {
|
|
94
|
+
readable?.resume();
|
|
95
|
+
},
|
|
96
|
+
cancel: () => {
|
|
97
|
+
open = false;
|
|
98
|
+
readable?.destroy();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{ highWaterMark: BRIDGE_HIGH_WATER_BYTES, size: (chunk) => chunk.byteLength }
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
const LOCAL_STOP_GRACE_MS = 2e3;
|
|
105
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
106
|
+
const within = async (promise, ms) => {
|
|
107
|
+
let timer;
|
|
108
|
+
try {
|
|
109
|
+
return await Promise.race([
|
|
110
|
+
promise.then(() => true),
|
|
111
|
+
new Promise((resolve) => {
|
|
112
|
+
timer = setTimeout(() => resolve(false), Math.max(0, ms));
|
|
113
|
+
})
|
|
114
|
+
]);
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const MAX_SYMLINKS = 40;
|
|
120
|
+
const ownGroup = () => process.platform !== "win32";
|
|
121
|
+
const signalProcessTree = (child, signal) => {
|
|
122
|
+
if (ownGroup() && child.pid != null) {
|
|
123
|
+
try {
|
|
124
|
+
process.kill(-child.pid, signal);
|
|
125
|
+
return;
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
child.kill(signal);
|
|
130
|
+
};
|
|
131
|
+
const inside = (root, path) => {
|
|
132
|
+
const { isAbsolute, relative, sep } = node().path;
|
|
133
|
+
const rel = relative(root, path);
|
|
134
|
+
return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
135
|
+
};
|
|
136
|
+
const createLocalSandbox = async (options) => {
|
|
137
|
+
const { childProcess, fs, fsPromises, path: paths, streamPromises } = node();
|
|
138
|
+
const root = paths.resolve(options.root);
|
|
139
|
+
await fsPromises.mkdir(root, { recursive: true });
|
|
140
|
+
const realRoot = await fsPromises.realpath(root);
|
|
141
|
+
const id = options.id ?? `local-${crypto.randomUUID()}`;
|
|
142
|
+
const processes = /* @__PURE__ */ new Set();
|
|
143
|
+
const groups = /* @__PURE__ */ new Set();
|
|
144
|
+
const groupAlive = (pgid) => {
|
|
145
|
+
try {
|
|
146
|
+
process.kill(-pgid, 0);
|
|
147
|
+
return true;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const inherited = {};
|
|
153
|
+
for (const name of options.inheritEnv ?? LOCAL_INHERITED_ENV) {
|
|
154
|
+
const value = process.env[name];
|
|
155
|
+
if (value != null) inherited[name] = value;
|
|
156
|
+
}
|
|
157
|
+
const resolvePath = (path) => paths.isAbsolute(path) ? paths.resolve(path) : paths.join(root, path);
|
|
158
|
+
const confined = async (path, within2) => {
|
|
159
|
+
const bounds = within2 == null ? [realRoot] : [realRoot, await confined(within2)];
|
|
160
|
+
let existing = resolvePath(path);
|
|
161
|
+
const rest = [];
|
|
162
|
+
let links = 0;
|
|
163
|
+
for (; ; ) {
|
|
164
|
+
const real = await fsPromises.realpath(existing).catch((error) => {
|
|
165
|
+
if (missing(error)) return null;
|
|
166
|
+
throw error;
|
|
167
|
+
});
|
|
168
|
+
if (real != null) {
|
|
169
|
+
const target = paths.join(real, ...rest.toReversed());
|
|
170
|
+
for (const bound of bounds)
|
|
171
|
+
if (!inside(bound, target)) throw fsError("EACCES", `${path} is outside the sandbox`);
|
|
172
|
+
return target;
|
|
173
|
+
}
|
|
174
|
+
const entry = await fsPromises.lstat(existing).catch((error) => {
|
|
175
|
+
if (missing(error)) return null;
|
|
176
|
+
throw error;
|
|
177
|
+
});
|
|
178
|
+
if (entry?.isSymbolicLink() === true) {
|
|
179
|
+
links += 1;
|
|
180
|
+
if (links > MAX_SYMLINKS) throw fsError("ELOOP", `${path} has too many symlinks`);
|
|
181
|
+
const directory = await fsPromises.realpath(paths.dirname(existing));
|
|
182
|
+
existing = paths.resolve(directory, await fsPromises.readlink(existing));
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const parent = paths.dirname(existing);
|
|
186
|
+
if (parent === existing) throw fsError("ENOENT", `${path} does not exist`);
|
|
187
|
+
rest.push(paths.basename(existing));
|
|
188
|
+
existing = parent;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const confinedEntry = async (path) => {
|
|
192
|
+
const logical = resolvePath(path);
|
|
193
|
+
if (logical === root) throw fsError("EPERM", "refusing to remove the sandbox root");
|
|
194
|
+
return paths.join(await confined(paths.dirname(logical)), paths.basename(logical));
|
|
195
|
+
};
|
|
196
|
+
const statOf = async (path, within2) => {
|
|
197
|
+
let target;
|
|
198
|
+
try {
|
|
199
|
+
target = await confined(path, within2);
|
|
200
|
+
const entry = await fsPromises.stat(target);
|
|
201
|
+
return {
|
|
202
|
+
path: resolvePath(path),
|
|
203
|
+
type: entry.isDirectory() ? SandboxEntryType.Directory : entry.isFile() ? SandboxEntryType.File : SandboxEntryType.Other,
|
|
204
|
+
size: entry.isDirectory() ? 0 : entry.size,
|
|
205
|
+
modifiedAt: entry.mtime.toISOString(),
|
|
206
|
+
mode: entry.mode & 4095
|
|
207
|
+
};
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (missing(error)) return null;
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const writeBytes = async (path, data) => {
|
|
214
|
+
const target = await confined(path);
|
|
215
|
+
await fsPromises.mkdir(paths.dirname(target), { recursive: true });
|
|
216
|
+
await fsPromises.writeFile(target, data);
|
|
217
|
+
};
|
|
218
|
+
const spawn = async (spawnOptions) => {
|
|
219
|
+
const signal = spawnOptions.abortSignal;
|
|
220
|
+
signal?.throwIfAborted();
|
|
221
|
+
const cwd = await confined(spawnOptions.cwd ?? ".");
|
|
222
|
+
signal?.throwIfAborted();
|
|
223
|
+
const base = await options.baseEnv?.();
|
|
224
|
+
signal?.throwIfAborted();
|
|
225
|
+
const env = { ...inherited, ...options.env, ...base, ...spawnOptions.env };
|
|
226
|
+
const shell = spawnOptions.shell === true;
|
|
227
|
+
const child = childProcess.spawn(
|
|
228
|
+
shell ? [spawnOptions.command, ...spawnOptions.args ?? []].join(" ") : spawnOptions.command,
|
|
229
|
+
shell ? [] : spawnOptions.args ?? [],
|
|
230
|
+
{ cwd, env, shell, stdio: ["pipe", "pipe", "pipe"], detached: ownGroup() }
|
|
231
|
+
);
|
|
232
|
+
processes.add(child);
|
|
233
|
+
if (ownGroup() && child.pid != null) {
|
|
234
|
+
for (const pgid of groups) if (!groupAlive(pgid)) groups.delete(pgid);
|
|
235
|
+
groups.add(child.pid);
|
|
236
|
+
}
|
|
237
|
+
let timedOut = false;
|
|
238
|
+
const stderr = nodeToWeb(
|
|
239
|
+
child.stderr,
|
|
240
|
+
({ push, close }) => child.once("error", (error) => {
|
|
241
|
+
push(new TextEncoder().encode(`${error.message}
|
|
242
|
+
`));
|
|
243
|
+
close();
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
const exited = new Promise((settle) => {
|
|
247
|
+
child.once("error", (error) => {
|
|
248
|
+
processes.delete(child);
|
|
249
|
+
settle({ exitCode: errorCode(error) === "ENOENT" ? 127 : 126 });
|
|
250
|
+
});
|
|
251
|
+
child.once("exit", (code, signal2) => {
|
|
252
|
+
processes.delete(child);
|
|
253
|
+
settle({ exitCode: timedOut ? 124 : code ?? signalExitCode(signal2) });
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
const timer = spawnOptions.timeoutMs == null ? null : setTimeout(() => {
|
|
257
|
+
timedOut = true;
|
|
258
|
+
signalProcessTree(child, "SIGKILL");
|
|
259
|
+
}, spawnOptions.timeoutMs);
|
|
260
|
+
let escalation = null;
|
|
261
|
+
const terminate = (signal2) => {
|
|
262
|
+
signalProcessTree(child, signal2);
|
|
263
|
+
if (signal2 !== "SIGTERM" || escalation != null) return;
|
|
264
|
+
escalation = setTimeout(() => signalProcessTree(child, "SIGKILL"), LOCAL_STOP_GRACE_MS);
|
|
265
|
+
};
|
|
266
|
+
const onAbort = () => terminate("SIGTERM");
|
|
267
|
+
spawnOptions.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
268
|
+
const settled = () => {
|
|
269
|
+
if (timer != null) clearTimeout(timer);
|
|
270
|
+
const groupLeft = ownGroup() && child.pid != null && groupAlive(child.pid);
|
|
271
|
+
if (escalation != null && !groupLeft) clearTimeout(escalation);
|
|
272
|
+
spawnOptions.abortSignal?.removeEventListener("abort", onAbort);
|
|
273
|
+
};
|
|
274
|
+
child.once("close", settled);
|
|
275
|
+
child.once("error", settled);
|
|
276
|
+
const stdin = child.stdin;
|
|
277
|
+
stdin?.on("error", () => void 0);
|
|
278
|
+
if (typeof spawnOptions.stdin === "string") stdin?.end(spawnOptions.stdin);
|
|
279
|
+
else if (spawnOptions.stdin != null) {
|
|
280
|
+
const reader = spawnOptions.stdin.getReader();
|
|
281
|
+
let finished = false;
|
|
282
|
+
void exited.then(() => {
|
|
283
|
+
finished = true;
|
|
284
|
+
void reader.cancel().catch(() => void 0);
|
|
285
|
+
});
|
|
286
|
+
void (async () => {
|
|
287
|
+
for (; ; ) {
|
|
288
|
+
const next = await reader.read();
|
|
289
|
+
if (next.done || finished || stdin == null || stdin.destroyed) break;
|
|
290
|
+
if (!stdin.write(next.value)) await Promise.race([drained(stdin), exited]);
|
|
291
|
+
}
|
|
292
|
+
stdin?.end();
|
|
293
|
+
})().catch(() => stdin?.destroy());
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
pid: child.pid == null ? null : String(child.pid),
|
|
297
|
+
ref: null,
|
|
298
|
+
stdout: nodeToWeb(child.stdout),
|
|
299
|
+
stderr,
|
|
300
|
+
write: async (data, writeOptions) => {
|
|
301
|
+
if (spawnOptions.stdin != null) throw new Error("stdin was provided at spawn");
|
|
302
|
+
if (stdin == null) throw new Error("the process has no stdin");
|
|
303
|
+
await new Promise(
|
|
304
|
+
(done, fail) => stdin.write(data, (error) => error == null ? done() : fail(error))
|
|
305
|
+
);
|
|
306
|
+
if (writeOptions?.end === true) stdin.end();
|
|
307
|
+
},
|
|
308
|
+
wait: () => exited,
|
|
309
|
+
// SIGTERM (killed after the grace period if ignored) and SIGKILL resolve once the
|
|
310
|
+
// command has ended; any other signal (SIGSTOP, SIGCONT, ...) once it is sent.
|
|
311
|
+
kill: async (signal2) => {
|
|
312
|
+
const name = signal2 ?? "SIGTERM";
|
|
313
|
+
const sent = isSignal(name) ? name : "SIGTERM";
|
|
314
|
+
terminate(sent);
|
|
315
|
+
if (sent === "SIGTERM" || sent === "SIGKILL") await exited;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
const sandbox = {
|
|
320
|
+
id,
|
|
321
|
+
root,
|
|
322
|
+
defaultWorkingDirectory: root,
|
|
323
|
+
ports: [],
|
|
324
|
+
description: `This machine, in ${root}.`,
|
|
325
|
+
readFile: async ({ path, root: within2 }) => {
|
|
326
|
+
let target;
|
|
327
|
+
try {
|
|
328
|
+
target = await confined(path, within2);
|
|
329
|
+
const entry = await fsPromises.stat(target);
|
|
330
|
+
if (entry.isDirectory()) throw fsError("EISDIR", `${path} is a directory`);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
if (missing(error)) return null;
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
return nodeToWeb(fs.createReadStream(target));
|
|
336
|
+
},
|
|
337
|
+
readBinaryFile: async ({ path }) => {
|
|
338
|
+
try {
|
|
339
|
+
return new Uint8Array(await fsPromises.readFile(await confined(path)));
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (missing(error)) return null;
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
readTextFile: async (readOptions) => {
|
|
346
|
+
const stream = await sandbox.readFile({ path: readOptions.path });
|
|
347
|
+
return stream == null ? null : readTextFileStream(stream, readOptions);
|
|
348
|
+
},
|
|
349
|
+
writeFile: async ({ path, content }) => {
|
|
350
|
+
const target = await confined(path);
|
|
351
|
+
await fsPromises.mkdir(paths.dirname(target), { recursive: true });
|
|
352
|
+
const reader = content.getReader();
|
|
353
|
+
const output = fs.createWriteStream(target);
|
|
354
|
+
await streamPromises.pipeline(
|
|
355
|
+
(async function* () {
|
|
356
|
+
for (; ; ) {
|
|
357
|
+
const next = await reader.read();
|
|
358
|
+
if (next.done) return;
|
|
359
|
+
yield next.value;
|
|
360
|
+
}
|
|
361
|
+
})(),
|
|
362
|
+
output
|
|
363
|
+
);
|
|
364
|
+
},
|
|
365
|
+
writeBinaryFile: ({ path, content }) => writeBytes(path, content),
|
|
366
|
+
writeTextFile: ({ path, content, encoding }) => writeBytes(path, encodeSandboxText(content, encoding)),
|
|
367
|
+
spawn,
|
|
368
|
+
run: (runOptions) => runCollect({ spawn }, runOptions),
|
|
369
|
+
stat: ({ path, root: within2 }) => statOf(path, within2),
|
|
370
|
+
readDir: async ({ path, root: within2 }) => {
|
|
371
|
+
const target = await confined(path, within2);
|
|
372
|
+
const logical = resolvePath(path);
|
|
373
|
+
const entries = await fsPromises.readdir(target, { withFileTypes: true });
|
|
374
|
+
return Promise.all(
|
|
375
|
+
entries.map(async (entry) => {
|
|
376
|
+
const type = entry.isDirectory() ? SandboxEntryType.Directory : entry.isFile() ? SandboxEntryType.File : entry.isSymbolicLink() ? SandboxEntryType.Symlink : SandboxEntryType.Other;
|
|
377
|
+
const size = entry.isFile() ? (await fsPromises.lstat(paths.join(target, entry.name))).size : null;
|
|
378
|
+
return { name: entry.name, path: paths.join(logical, entry.name), type, size };
|
|
379
|
+
})
|
|
380
|
+
);
|
|
381
|
+
},
|
|
382
|
+
resolvePath,
|
|
383
|
+
removePath: async ({ path, recursive, force }) => {
|
|
384
|
+
const target = await confinedEntry(path);
|
|
385
|
+
await fsPromises.rm(target, { recursive: recursive === true, force: force === true });
|
|
386
|
+
},
|
|
387
|
+
getPortEndpoint: async ({ port, protocol }) => ({
|
|
388
|
+
url: `${protocol ?? PortProtocol.Http}://127.0.0.1:${port}`
|
|
389
|
+
}),
|
|
390
|
+
getPortUrl: async (portOptions) => (await sandbox.getPortEndpoint(portOptions)).url,
|
|
391
|
+
// Stopping ends the commands it started; the directory is the caller's and stays.
|
|
392
|
+
// A command that ignores SIGTERM is killed once the grace period ends.
|
|
393
|
+
stop: async () => {
|
|
394
|
+
const children = [...processes];
|
|
395
|
+
const pgids = new Set(groups);
|
|
396
|
+
groups.clear();
|
|
397
|
+
for (const child of children) if (ownGroup() && child.pid != null) pgids.add(child.pid);
|
|
398
|
+
const closed = Promise.all(
|
|
399
|
+
children.map(
|
|
400
|
+
(child) => new Promise((done) => {
|
|
401
|
+
if (child.exitCode != null || child.signalCode != null) done();
|
|
402
|
+
else child.once("exit", () => done());
|
|
403
|
+
})
|
|
404
|
+
)
|
|
405
|
+
);
|
|
406
|
+
const signalAll = (signal) => {
|
|
407
|
+
for (const child of children) signalProcessTree(child, signal);
|
|
408
|
+
for (const pgid of pgids) {
|
|
409
|
+
try {
|
|
410
|
+
process.kill(-pgid, signal);
|
|
411
|
+
} catch {
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const settled = async (deadline) => {
|
|
416
|
+
if (!await within(closed, deadline - Date.now())) return false;
|
|
417
|
+
while ([...pgids].some(groupAlive)) {
|
|
418
|
+
if (Date.now() >= deadline) return false;
|
|
419
|
+
await pause(25);
|
|
420
|
+
}
|
|
421
|
+
return true;
|
|
422
|
+
};
|
|
423
|
+
signalAll("SIGTERM");
|
|
424
|
+
if (await settled(Date.now() + LOCAL_STOP_GRACE_MS)) return;
|
|
425
|
+
signalAll("SIGKILL");
|
|
426
|
+
await settled(Date.now() + LOCAL_STOP_GRACE_MS);
|
|
427
|
+
},
|
|
428
|
+
destroy: () => sandbox.stop()
|
|
429
|
+
};
|
|
430
|
+
return sandbox;
|
|
431
|
+
};
|
|
432
|
+
const localEnvironmentSchema = z.strictObject({
|
|
433
|
+
root: z.string().min(1),
|
|
434
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
435
|
+
inheritEnv: z.array(z.string().min(1)).optional()
|
|
436
|
+
});
|
|
437
|
+
const localStateSchema = z.object({ version: z.literal(1), root: z.string() });
|
|
438
|
+
const handleOf = (sandbox) => ({
|
|
439
|
+
sandbox,
|
|
440
|
+
onSandboxStop: () => sandbox.stop(),
|
|
441
|
+
onSandboxDelete: () => sandbox.destroy(),
|
|
442
|
+
onRuntimeShutdown: () => sandbox.stop()
|
|
443
|
+
});
|
|
444
|
+
const createLocalSandboxProvider = () => {
|
|
445
|
+
const live = /* @__PURE__ */ new Map();
|
|
446
|
+
return defineSandboxProvider({
|
|
447
|
+
name: LOCAL_PROVIDER,
|
|
448
|
+
environment: (rawOptions) => {
|
|
449
|
+
const options = localEnvironmentSchema.parse(rawOptions);
|
|
450
|
+
const root = node().path.resolve(options.root);
|
|
451
|
+
const open = async (ctx, sandboxId) => {
|
|
452
|
+
const sandbox = await createLocalSandbox({
|
|
453
|
+
root,
|
|
454
|
+
id: sandboxId,
|
|
455
|
+
env: options.env,
|
|
456
|
+
inheritEnv: options.inheritEnv,
|
|
457
|
+
baseEnv: platformProcessEnvironment(ctx, {})
|
|
458
|
+
});
|
|
459
|
+
live.set(sandboxId, sandbox);
|
|
460
|
+
return sandbox;
|
|
461
|
+
};
|
|
462
|
+
return {
|
|
463
|
+
capabilities: async () => ({
|
|
464
|
+
[Capability.Files]: FilesAccess.Write,
|
|
465
|
+
[Capability.Shell]: ShellAccess.Execute
|
|
466
|
+
}),
|
|
467
|
+
artifactKey: async () => `local:${root}`,
|
|
468
|
+
prepare: async () => ({ version: 1, root }),
|
|
469
|
+
create: async () => ({ state: { version: 1, root } }),
|
|
470
|
+
// This process is the machine: it runs as long as the sandbox can be reached.
|
|
471
|
+
awaitRunning: async () => void 0,
|
|
472
|
+
attach: async (ctx, { state }) => {
|
|
473
|
+
const existing = live.get(ctx.sandboxId);
|
|
474
|
+
const fresh = existing == null && await node().fsPromises.stat(root).catch(() => null) == null;
|
|
475
|
+
const sandbox = existing ?? await open(ctx, ctx.sandboxId);
|
|
476
|
+
return { handle: handleOf(sandbox), state, fresh, booted: existing == null };
|
|
477
|
+
},
|
|
478
|
+
detach: async (_ctx, { state }) => state,
|
|
479
|
+
park: async (ctx, { state }) => {
|
|
480
|
+
await live.get(ctx.sandboxId)?.stop();
|
|
481
|
+
live.delete(ctx.sandboxId);
|
|
482
|
+
return state;
|
|
483
|
+
},
|
|
484
|
+
delete: async (ctx) => {
|
|
485
|
+
await live.get(ctx.sandboxId)?.destroy();
|
|
486
|
+
live.delete(ctx.sandboxId);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
};
|
|
492
|
+
export {
|
|
493
|
+
LOCAL_INHERITED_ENV,
|
|
494
|
+
LOCAL_PROVIDER,
|
|
495
|
+
LOCAL_STOP_GRACE_MS,
|
|
496
|
+
createLocalSandbox,
|
|
497
|
+
createLocalSandboxProvider
|
|
498
|
+
};
|
|
499
|
+
//# sourceMappingURL=local.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/sandboxes/local.ts"],
|
|
4
|
+
"sourcesContent": ["import { Capability, FilesAccess, ShellAccess } from '@finalai/agent-protocol';\nimport type { JsonValue } from '@finalai/agent-provider';\nimport {\n defineSandboxProvider,\n encodeSandboxText,\n platformProcessEnvironment,\n PortProtocol,\n readTextFileStream,\n runCollect,\n SandboxEntryType,\n type DirEntryIface,\n type NetworkSandboxSessionIface,\n type SandboxProcessIface,\n type SandboxProcessOptionsIface,\n type SandboxProviderHandleIface,\n type SandboxProviderIface,\n type SandboxResourceContextIface,\n type StatIface,\n} from '@finalai/agent-provider/sandbox';\nimport type { ChildProcess } from 'node:child_process';\nimport type { Readable, Writable } from 'node:stream';\nimport * as z from 'zod';\n\n// A sandbox served from the calling process: files under one directory of\n// this machine and commands run in it, with Node's own fs and child_process.\n// The agents client provides it for any `files` or `shell` capability the\n// caller left out; nothing it does leaves this machine except what the model\n// asks for through the handle. Node only.\n\nexport const LOCAL_PROVIDER = 'local';\n\n// Variables a local command inherits from this process; anything else (API\n// keys, tokens) stays out of the model's shell unless passed in `env`.\nexport const LOCAL_INHERITED_ENV = [\n 'PATH',\n 'HOME',\n 'USER',\n 'LANG',\n 'LC_ALL',\n 'TMPDIR',\n 'TERM',\n 'SHELL',\n];\n\nexport interface LocalSandboxOptionsIface {\n // The directory the sandbox is confined to; created when missing.\n root: string;\n // Plain variables for every command, over the inherited ones.\n env?: Record<string, string>;\n // Names inherited from this process's environment (default LOCAL_INHERITED_ENV).\n inheritEnv?: string[];\n id?: string;\n // Every process's environment from the host, read again for each process.\n baseEnv?: () => Promise<Record<string, string>>;\n}\n\nexport interface LocalSandboxIface extends NetworkSandboxSessionIface {\n readonly root: string;\n}\n\nconst fsError = (code: string, message: string) =>\n Object.assign(new Error(`${code}: ${message}`), { code });\n\nconst errorCode = (error: unknown): string | null => {\n const parsed = z.object({ code: z.string() }).safeParse(error);\n return parsed.success ? parsed.data.code : null;\n};\n\nconst missing = (error: unknown) => {\n const code = errorCode(error);\n return code === 'ENOENT' || code === 'ENOTDIR';\n};\n\nconst signalExitCode = (signal: NodeJS.Signals | null): number => {\n if (signal === 'SIGKILL') return 137;\n if (signal === 'SIGINT') return 130;\n return 143;\n};\n\n// Node's built-ins, read on first use so that importing this module does not\n// tie a browser bundle of the client to Node.\nconst loadNode = () => {\n if (globalThis.process?.getBuiltinModule == null)\n throw new Error('A local sandbox runs only in Node');\n return {\n childProcess: process.getBuiltinModule('node:child_process'),\n fs: process.getBuiltinModule('node:fs'),\n fsPromises: process.getBuiltinModule('node:fs/promises'),\n os: process.getBuiltinModule('node:os'),\n path: process.getBuiltinModule('node:path'),\n streamPromises: process.getBuiltinModule('node:stream/promises'),\n };\n};\nlet loaded: ReturnType<typeof loadNode> | null = null;\nconst node = () => (loaded ??= loadNode());\n\nconst isSignal = (value: string): value is NodeJS.Signals => value in node().os.constants.signals;\n\n// Settles when a writable can take more, or will take nothing more.\nconst drained = (stream: Writable): Promise<void> =>\n new Promise((resolve) => {\n const done = () => {\n stream.off('drain', done);\n stream.off('close', done);\n stream.off('error', done);\n resolve();\n };\n stream.once('drain', done);\n stream.once('close', done);\n stream.once('error', done);\n });\n\n// How much a bridged stream buffers before it stops reading its Node stream.\nconst BRIDGE_HIGH_WATER_BYTES = 64 * 1024;\n\n// A Node stream as a web stream, with backpressure: the Node stream pauses\n// whenever the web stream's queue is full and reads on when its reader pulls.\n// `extra` may push bytes and close the stream from elsewhere.\nconst nodeToWeb = (\n readable: Readable | null,\n extra?: (bridge: { push: (bytes: Uint8Array) => void; close: () => void }) => void,\n): ReadableStream<Uint8Array> => {\n let open = true;\n return new ReadableStream<Uint8Array>(\n {\n start: (controller) => {\n const close = () => {\n if (!open) return;\n open = false;\n controller.close();\n };\n const push = (bytes: Uint8Array) => {\n if (!open) return;\n controller.enqueue(bytes);\n if ((controller.desiredSize ?? 0) <= 0) readable?.pause();\n };\n extra?.({ push, close });\n if (readable == null) {\n close();\n return;\n }\n readable.on('data', (chunk: Buffer) => push(new Uint8Array(chunk)));\n // Reads start when the reader pulls.\n readable.pause();\n readable.once('end', close);\n readable.once('error', (error) => {\n if (!open) return;\n open = false;\n controller.error(error);\n });\n },\n pull: () => {\n readable?.resume();\n },\n cancel: () => {\n open = false;\n readable?.destroy();\n },\n },\n { highWaterMark: BRIDGE_HIGH_WATER_BYTES, size: (chunk) => chunk.byteLength },\n );\n};\n\n// How long stopping waits for commands to end on SIGTERM before SIGKILL.\nexport const LOCAL_STOP_GRACE_MS = 2_000;\n\n// Referenced on purpose: a script that ends with `await sandbox.stop()` stays\n// alive until a stubborn command has been killed.\nconst pause = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));\n\n// Whether `promise` settles within `ms`; the timer is cleared either way.\nconst within = async (promise: Promise<unknown>, ms: number): Promise<boolean> => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise.then(() => true),\n new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(false), Math.max(0, ms));\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n};\n\n// Symlinks one path may pass through before it is refused as a loop.\nconst MAX_SYMLINKS = 40;\n\n// A command runs in its own process group (POSIX), so that a timeout, a cancel\n// or a stop ends everything it started; a shell's children would otherwise\n// outlive it and hold its output open.\nconst ownGroup = (): boolean => process.platform !== 'win32';\n\nconst signalProcessTree = (child: ChildProcess, signal: NodeJS.Signals): void => {\n if (ownGroup() && child.pid != null) {\n try {\n process.kill(-child.pid, signal);\n return;\n } catch {\n // The group has already exited; signal the child itself.\n }\n }\n child.kill(signal);\n};\n\n// A leading `..` component leaves the root; a name that merely starts with\n// two dots (`..config`) does not.\nconst inside = (root: string, path: string): boolean => {\n const { isAbsolute, relative, sep } = node().path;\n const rel = relative(root, path);\n return rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);\n};\n\nexport const createLocalSandbox = async (\n options: LocalSandboxOptionsIface,\n): Promise<LocalSandboxIface> => {\n const { childProcess, fs, fsPromises, path: paths, streamPromises } = node();\n const root = paths.resolve(options.root);\n await fsPromises.mkdir(root, { recursive: true });\n const realRoot = await fsPromises.realpath(root);\n const id = options.id ?? `local-${crypto.randomUUID()}`;\n const processes = new Set<ChildProcess>();\n // Every process group a command started: a shell's backgrounded children\n // outlive it in its group, and stopping the sandbox ends them too.\n const groups = new Set<number>();\n const groupAlive = (pgid: number): boolean => {\n try {\n process.kill(-pgid, 0);\n return true;\n } catch {\n return false;\n }\n };\n const inherited: Record<string, string> = {};\n for (const name of options.inheritEnv ?? LOCAL_INHERITED_ENV) {\n const value = process.env[name];\n if (value != null) inherited[name] = value;\n }\n\n const resolvePath = (path: string): string =>\n paths.isAbsolute(path) ? paths.resolve(path) : paths.join(root, path);\n\n // The real location of `path` (or of its nearest existing ancestor, for a\n // path about to be created), refused unless it stays under `within`: no `..`\n // and no symlink leads out of the sandbox. A dangling symlink is followed to\n // where it points, since writing through it creates its target.\n const confined = async (path: string, within?: string): Promise<string> => {\n const bounds = within == null ? [realRoot] : [realRoot, await confined(within)];\n let existing = resolvePath(path);\n const rest: string[] = [];\n let links = 0;\n for (;;) {\n const real = await fsPromises.realpath(existing).catch((error: unknown) => {\n if (missing(error)) return null;\n throw error;\n });\n if (real != null) {\n const target = paths.join(real, ...rest.toReversed());\n for (const bound of bounds)\n if (!inside(bound, target)) throw fsError('EACCES', `${path} is outside the sandbox`);\n return target;\n }\n const entry = await fsPromises.lstat(existing).catch((error: unknown) => {\n if (missing(error)) return null;\n throw error;\n });\n if (entry?.isSymbolicLink() === true) {\n links += 1;\n if (links > MAX_SYMLINKS) throw fsError('ELOOP', `${path} has too many symlinks`);\n // Relative to the link's real directory, as the kernel resolves it.\n const directory = await fsPromises.realpath(paths.dirname(existing));\n existing = paths.resolve(directory, await fsPromises.readlink(existing));\n continue;\n }\n const parent = paths.dirname(existing);\n if (parent === existing) throw fsError('ENOENT', `${path} does not exist`);\n rest.push(paths.basename(existing));\n existing = parent;\n }\n };\n\n // The real location of the entry `path` names, its last component not\n // followed: removing a symlink removes the link, not what it points to.\n const confinedEntry = async (path: string): Promise<string> => {\n const logical = resolvePath(path);\n if (logical === root) throw fsError('EPERM', 'refusing to remove the sandbox root');\n return paths.join(await confined(paths.dirname(logical)), paths.basename(logical));\n };\n\n const statOf = async (path: string, within?: string): Promise<StatIface | null> => {\n let target: string;\n try {\n target = await confined(path, within);\n const entry = await fsPromises.stat(target);\n return {\n path: resolvePath(path),\n type: entry.isDirectory()\n ? SandboxEntryType.Directory\n : entry.isFile()\n ? SandboxEntryType.File\n : SandboxEntryType.Other,\n size: entry.isDirectory() ? 0 : entry.size,\n modifiedAt: entry.mtime.toISOString(),\n mode: entry.mode & 0o7777,\n };\n } catch (error) {\n if (missing(error)) return null;\n throw error;\n }\n };\n\n const writeBytes = async (path: string, data: Uint8Array): Promise<void> => {\n const target = await confined(path);\n await fsPromises.mkdir(paths.dirname(target), { recursive: true });\n await fsPromises.writeFile(target, data);\n };\n\n const spawn = async (spawnOptions: SandboxProcessOptionsIface): Promise<SandboxProcessIface> => {\n // A command cancelled before it starts never runs.\n const signal = spawnOptions.abortSignal;\n signal?.throwIfAborted();\n const cwd = await confined(spawnOptions.cwd ?? '.');\n signal?.throwIfAborted();\n const base = await options.baseEnv?.();\n signal?.throwIfAborted();\n const env = { ...inherited, ...options.env, ...base, ...spawnOptions.env };\n const shell = spawnOptions.shell === true;\n const child = childProcess.spawn(\n shell ? [spawnOptions.command, ...(spawnOptions.args ?? [])].join(' ') : spawnOptions.command,\n shell ? [] : (spawnOptions.args ?? []),\n { cwd, env, shell, stdio: ['pipe', 'pipe', 'pipe'], detached: ownGroup() },\n );\n processes.add(child);\n if (ownGroup() && child.pid != null) {\n for (const pgid of groups) if (!groupAlive(pgid)) groups.delete(pgid);\n groups.add(child.pid);\n }\n let timedOut = false;\n // A command that never started reports why on stderr.\n const stderr = nodeToWeb(child.stderr, ({ push, close }) =>\n child.once('error', (error) => {\n push(new TextEncoder().encode(`${error.message}\\n`));\n close();\n }),\n );\n const exited = new Promise<{ exitCode: number }>((settle) => {\n child.once('error', (error) => {\n processes.delete(child);\n settle({ exitCode: errorCode(error) === 'ENOENT' ? 127 : 126 });\n });\n // Exit, not close: output nobody reads holds the pipes (and `close`) open, and the\n // command has ended all the same.\n child.once('exit', (code, signal) => {\n processes.delete(child);\n settle({ exitCode: timedOut ? 124 : (code ?? signalExitCode(signal)) });\n });\n });\n const timer =\n spawnOptions.timeoutMs == null\n ? null\n : setTimeout(() => {\n timedOut = true;\n signalProcessTree(child, 'SIGKILL');\n }, spawnOptions.timeoutMs);\n // A command that ignores SIGTERM is killed once the stop grace period ends.\n let escalation: ReturnType<typeof setTimeout> | null = null;\n const terminate = (signal: NodeJS.Signals) => {\n signalProcessTree(child, signal);\n if (signal !== 'SIGTERM' || escalation != null) return;\n escalation = setTimeout(() => signalProcessTree(child, 'SIGKILL'), LOCAL_STOP_GRACE_MS);\n };\n const onAbort = () => terminate('SIGTERM');\n spawnOptions.abortSignal?.addEventListener('abort', onAbort, { once: true });\n // The deadline, a cancel and the SIGKILL escalation stand until the output closes: a\n // descendant that outlives the command holds it open and is ended with the group.\n const settled = () => {\n if (timer != null) clearTimeout(timer);\n const groupLeft = ownGroup() && child.pid != null && groupAlive(child.pid);\n if (escalation != null && !groupLeft) clearTimeout(escalation);\n spawnOptions.abortSignal?.removeEventListener('abort', onAbort);\n };\n child.once('close', settled);\n child.once('error', settled);\n const stdin = child.stdin;\n stdin?.on('error', () => undefined);\n if (typeof spawnOptions.stdin === 'string') stdin?.end(spawnOptions.stdin);\n else if (spawnOptions.stdin != null) {\n const reader = spawnOptions.stdin.getReader();\n // Reads on only as fast as the command takes its input, and stops when it exits.\n let finished = false;\n void exited.then(() => {\n finished = true;\n void reader.cancel().catch(() => undefined);\n });\n void (async () => {\n for (;;) {\n const next = await reader.read();\n if (next.done || finished || stdin == null || stdin.destroyed) break;\n if (!stdin.write(next.value)) await Promise.race([drained(stdin), exited]);\n }\n stdin?.end();\n })().catch(() => stdin?.destroy());\n }\n return {\n pid: child.pid == null ? null : String(child.pid),\n ref: null,\n stdout: nodeToWeb(child.stdout),\n stderr,\n write: async (data, writeOptions) => {\n if (spawnOptions.stdin != null) throw new Error('stdin was provided at spawn');\n if (stdin == null) throw new Error('the process has no stdin');\n await new Promise<void>((done, fail) =>\n stdin.write(data, (error) => (error == null ? done() : fail(error))),\n );\n if (writeOptions?.end === true) stdin.end();\n },\n wait: () => exited,\n // SIGTERM (killed after the grace period if ignored) and SIGKILL resolve once the\n // command has ended; any other signal (SIGSTOP, SIGCONT, ...) once it is sent.\n kill: async (signal) => {\n const name = signal ?? 'SIGTERM';\n const sent = isSignal(name) ? name : 'SIGTERM';\n terminate(sent);\n if (sent === 'SIGTERM' || sent === 'SIGKILL') await exited;\n },\n };\n };\n\n const sandbox: LocalSandboxIface = {\n id,\n root,\n defaultWorkingDirectory: root,\n ports: [],\n description: `This machine, in ${root}.`,\n readFile: async ({ path, root: within }) => {\n let target: string;\n try {\n target = await confined(path, within);\n const entry = await fsPromises.stat(target);\n if (entry.isDirectory()) throw fsError('EISDIR', `${path} is a directory`);\n } catch (error) {\n if (missing(error)) return null;\n throw error;\n }\n return nodeToWeb(fs.createReadStream(target));\n },\n readBinaryFile: async ({ path }) => {\n try {\n return new Uint8Array(await fsPromises.readFile(await confined(path)));\n } catch (error) {\n if (missing(error)) return null;\n throw error;\n }\n },\n readTextFile: async (readOptions) => {\n const stream = await sandbox.readFile({ path: readOptions.path });\n return stream == null ? null : readTextFileStream(stream, readOptions);\n },\n writeFile: async ({ path, content }) => {\n const target = await confined(path);\n await fsPromises.mkdir(paths.dirname(target), { recursive: true });\n const reader = content.getReader();\n const output = fs.createWriteStream(target);\n await streamPromises.pipeline(\n (async function* () {\n for (;;) {\n const next = await reader.read();\n if (next.done) return;\n yield next.value;\n }\n })(),\n output,\n );\n },\n writeBinaryFile: ({ path, content }) => writeBytes(path, content),\n writeTextFile: ({ path, content, encoding }) =>\n writeBytes(path, encodeSandboxText(content, encoding)),\n spawn,\n run: (runOptions) => runCollect({ spawn }, runOptions),\n stat: ({ path, root: within }) => statOf(path, within),\n readDir: async ({ path, root: within }): Promise<DirEntryIface[]> => {\n const target = await confined(path, within);\n const logical = resolvePath(path);\n const entries = await fsPromises.readdir(target, { withFileTypes: true });\n return Promise.all(\n entries.map(async (entry) => {\n const type = entry.isDirectory()\n ? SandboxEntryType.Directory\n : entry.isFile()\n ? SandboxEntryType.File\n : entry.isSymbolicLink()\n ? SandboxEntryType.Symlink\n : SandboxEntryType.Other;\n const size = entry.isFile()\n ? (await fsPromises.lstat(paths.join(target, entry.name))).size\n : null;\n return { name: entry.name, path: paths.join(logical, entry.name), type, size };\n }),\n );\n },\n resolvePath,\n removePath: async ({ path, recursive, force }) => {\n const target = await confinedEntry(path);\n await fsPromises.rm(target, { recursive: recursive === true, force: force === true });\n },\n getPortEndpoint: async ({ port, protocol }) => ({\n url: `${protocol ?? PortProtocol.Http}://127.0.0.1:${port}`,\n }),\n getPortUrl: async (portOptions) => (await sandbox.getPortEndpoint(portOptions)).url,\n // Stopping ends the commands it started; the directory is the caller's and stays.\n // A command that ignores SIGTERM is killed once the grace period ends.\n stop: async () => {\n const children = [...processes];\n const pgids = new Set(groups);\n groups.clear();\n for (const child of children) if (ownGroup() && child.pid != null) pgids.add(child.pid);\n const closed = Promise.all(\n children.map(\n (child) =>\n new Promise<void>((done) => {\n if (child.exitCode != null || child.signalCode != null) done();\n else child.once('exit', () => done());\n }),\n ),\n );\n const signalAll = (signal: NodeJS.Signals) => {\n for (const child of children) signalProcessTree(child, signal);\n for (const pgid of pgids) {\n try {\n process.kill(-pgid, signal);\n } catch {\n // The group has already exited.\n }\n }\n };\n // Whether every command closed and every group is gone by the deadline.\n const settled = async (deadline: number): Promise<boolean> => {\n if (!(await within(closed, deadline - Date.now()))) return false;\n while ([...pgids].some(groupAlive)) {\n if (Date.now() >= deadline) return false;\n await pause(25);\n }\n return true;\n };\n signalAll('SIGTERM');\n if (await settled(Date.now() + LOCAL_STOP_GRACE_MS)) return;\n signalAll('SIGKILL');\n await settled(Date.now() + LOCAL_STOP_GRACE_MS);\n },\n destroy: () => sandbox.stop(),\n };\n return sandbox;\n};\n\nconst localEnvironmentSchema = z.strictObject({\n root: z.string().min(1),\n env: z.record(z.string(), z.string()).optional(),\n inheritEnv: z.array(z.string().min(1)).optional(),\n});\n\nconst localStateSchema = z.object({ version: z.literal(1), root: z.string() });\n\ntype LocalArtifact = { version: 1; root: string };\ntype LocalState = z.infer<typeof localStateSchema>;\n\nconst handleOf = (sandbox: LocalSandboxIface): SandboxProviderHandleIface<LocalSandboxIface> => ({\n sandbox,\n onSandboxStop: () => sandbox.stop(),\n onSandboxDelete: () => sandbox.destroy(),\n onRuntimeShutdown: () => sandbox.stop(),\n});\n\n// The local sandbox as a provider, for a host that drives sandboxes through\n// the provider lifecycle. Deleting it stops its commands and leaves the\n// directory, which belongs to whoever runs this process.\nexport const createLocalSandboxProvider = (): SandboxProviderIface<\n JsonValue,\n LocalArtifact,\n LocalState,\n LocalSandboxIface\n> => {\n const live = new Map<string, LocalSandboxIface>();\n return defineSandboxProvider<JsonValue, LocalArtifact, LocalState, LocalSandboxIface>({\n name: LOCAL_PROVIDER,\n environment: (rawOptions) => {\n const options = localEnvironmentSchema.parse(rawOptions);\n const root = node().path.resolve(options.root);\n const open = async (ctx: SandboxResourceContextIface, sandboxId: string) => {\n const sandbox = await createLocalSandbox({\n root,\n id: sandboxId,\n env: options.env,\n inheritEnv: options.inheritEnv,\n baseEnv: platformProcessEnvironment(ctx, {}),\n });\n live.set(sandboxId, sandbox);\n return sandbox;\n };\n return {\n capabilities: async () => ({\n [Capability.Files]: FilesAccess.Write,\n [Capability.Shell]: ShellAccess.Execute,\n }),\n artifactKey: async () => `local:${root}`,\n prepare: async () => ({ version: 1, root }),\n create: async () => ({ state: { version: 1, root } }),\n // This process is the machine: it runs as long as the sandbox can be reached.\n awaitRunning: async () => undefined,\n attach: async (ctx, { state }) => {\n const existing = live.get(ctx.sandboxId);\n const fresh =\n existing == null &&\n (await node()\n .fsPromises.stat(root)\n .catch(() => null)) == null;\n const sandbox = existing ?? (await open(ctx, ctx.sandboxId));\n return { handle: handleOf(sandbox), state, fresh, booted: existing == null };\n },\n detach: async (_ctx, { state }) => state,\n park: async (ctx, { state }) => {\n await live.get(ctx.sandboxId)?.stop();\n live.delete(ctx.sandboxId);\n return state;\n },\n delete: async (ctx) => {\n await live.get(ctx.sandboxId)?.destroy();\n live.delete(ctx.sandboxId);\n },\n };\n },\n });\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["within", "signal"]
|
|
7
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SandboxAccess } from '@finalai/agent-provider';
|
|
2
1
|
import type { NetworkSandboxSessionIface, PortProtocol } from '@finalai/agent-provider/sandbox';
|
|
3
2
|
import { type SandboxCommandEncoderIface } from "./commands.mjs";
|
|
4
3
|
import { type SandboxPathsIface } from "./paths.mjs";
|
|
@@ -29,5 +28,3 @@ export interface CreateTransportSessionArgsIface {
|
|
|
29
28
|
}
|
|
30
29
|
export declare const createTransportSession: (args: CreateTransportSessionArgsIface) => NetworkSandboxSessionIface;
|
|
31
30
|
export declare const readOnlySession: <S extends NetworkSandboxSessionIface>(session: S) => S;
|
|
32
|
-
export declare const executeOnlySession: <S extends NetworkSandboxSessionIface>(session: S) => S;
|
|
33
|
-
export declare const sessionForAccess: <S extends NetworkSandboxSessionIface>(session: S, access: SandboxAccess) => S;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SandboxAccess } from "@finalai/agent-provider";
|
|
2
1
|
import { createConfinedFileReader, SandboxEntryType } from "@finalai/agent-provider/sandbox";
|
|
3
2
|
import { encodeSandboxText } from "@finalai/agent-provider/sandbox/bytes";
|
|
4
3
|
import { readTextFileStream } from "@finalai/agent-provider/sandbox/convenience";
|
|
@@ -299,7 +298,7 @@ const createTransportSession = (args) => {
|
|
|
299
298
|
return { ...session, confinedFileReader: createConfinedFileReader(session) };
|
|
300
299
|
};
|
|
301
300
|
const readOnlyRefusal = async () => {
|
|
302
|
-
throw new Error("this sandbox
|
|
301
|
+
throw new Error("this sandbox handle is read-only");
|
|
303
302
|
};
|
|
304
303
|
const readOnlySession = (session) => {
|
|
305
304
|
const restricted = {
|
|
@@ -324,27 +323,8 @@ const readOnlySession = (session) => {
|
|
|
324
323
|
};
|
|
325
324
|
return restricted;
|
|
326
325
|
};
|
|
327
|
-
const fileWriteRefusal = async () => {
|
|
328
|
-
throw new Error("this sandbox binding runs processes but does not write files");
|
|
329
|
-
};
|
|
330
|
-
const executeOnlySession = (session) => {
|
|
331
|
-
const restricted = {
|
|
332
|
-
...session,
|
|
333
|
-
writeFile: fileWriteRefusal,
|
|
334
|
-
writeBinaryFile: fileWriteRefusal,
|
|
335
|
-
writeTextFile: fileWriteRefusal,
|
|
336
|
-
removePath: fileWriteRefusal,
|
|
337
|
-
stop: fileWriteRefusal,
|
|
338
|
-
destroy: fileWriteRefusal
|
|
339
|
-
};
|
|
340
|
-
delete restricted.egress;
|
|
341
|
-
return restricted;
|
|
342
|
-
};
|
|
343
|
-
const sessionForAccess = (session, access) => access === SandboxAccess.Read ? readOnlySession(session) : access === SandboxAccess.Execute ? executeOnlySession(session) : session;
|
|
344
326
|
export {
|
|
345
327
|
createTransportSession,
|
|
346
|
-
|
|
347
|
-
readOnlySession,
|
|
348
|
-
sessionForAccess
|
|
328
|
+
readOnlySession
|
|
349
329
|
};
|
|
350
330
|
//# sourceMappingURL=session.mjs.map
|