@hachej/boring-agent 0.1.20 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/{chunk-W73RUHY3.js → chunk-6R7O6BHW.js} +24 -0
- package/dist/front/index.d.ts +1 -1
- package/dist/front/index.js +63 -21
- package/dist/front/styles.css +61 -21
- package/dist/{harness-DRrTn_5T.d.ts → harness-BCit36Ha.d.ts} +15 -1
- package/dist/server/index.d.ts +181 -11
- package/dist/server/index.js +1871 -292
- package/dist/shared/index.d.ts +15 -15
- package/dist/shared/index.js +5 -1
- package/docs/ERROR_CODES.md +8 -0
- package/docs/runtime-provisioning.md +153 -0
- package/package.json +2 -2
package/dist/server/index.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ErrorCode,
|
|
3
|
+
noopTelemetry,
|
|
4
|
+
safeCapture,
|
|
3
5
|
validateTool
|
|
4
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-6R7O6BHW.js";
|
|
5
7
|
|
|
6
8
|
// src/server/sandbox/direct/createDirectSandbox.ts
|
|
7
9
|
import { spawn } from "child_process";
|
|
8
10
|
|
|
9
|
-
// src/server/sandbox/workspacePythonEnv.ts
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
|
|
12
11
|
// src/server/config/env.ts
|
|
13
12
|
function getEnv(name) {
|
|
14
13
|
return process.env[name];
|
|
@@ -17,21 +16,72 @@ function getEnvSnapshot() {
|
|
|
17
16
|
return { ...process.env };
|
|
18
17
|
}
|
|
19
18
|
|
|
19
|
+
// src/server/workspace/runtimeLayout.ts
|
|
20
|
+
import { join, resolve } from "path";
|
|
21
|
+
var BORING_AGENT_DIR = ".boring-agent";
|
|
22
|
+
var BORING_AGENT_GITIGNORE_CONTENT = "*\n";
|
|
23
|
+
var BORING_AGENT_RUNTIME_DIR_NAMES = [
|
|
24
|
+
"node",
|
|
25
|
+
"venv",
|
|
26
|
+
"sdk",
|
|
27
|
+
"skills",
|
|
28
|
+
"cache",
|
|
29
|
+
"tmp"
|
|
30
|
+
];
|
|
31
|
+
function getBoringAgentRuntimePaths(runtimeWorkspaceRoot) {
|
|
32
|
+
const workspaceRoot = resolve(runtimeWorkspaceRoot);
|
|
33
|
+
const agentDir = join(workspaceRoot, BORING_AGENT_DIR);
|
|
34
|
+
const node = join(agentDir, "node");
|
|
35
|
+
const venv = join(agentDir, "venv");
|
|
36
|
+
const sdk = join(agentDir, "sdk");
|
|
37
|
+
const uvHome = join(sdk, "uv");
|
|
38
|
+
const cache = join(agentDir, "cache");
|
|
39
|
+
return {
|
|
40
|
+
workspaceRoot,
|
|
41
|
+
agentDir,
|
|
42
|
+
node,
|
|
43
|
+
nodeModules: join(node, "node_modules"),
|
|
44
|
+
nodeBin: join(node, "node_modules", ".bin"),
|
|
45
|
+
venv,
|
|
46
|
+
venvBin: join(venv, "bin"),
|
|
47
|
+
venvPython: join(venv, "bin", "python"),
|
|
48
|
+
sdk,
|
|
49
|
+
uvHome,
|
|
50
|
+
uvBin: join(uvHome, "bin"),
|
|
51
|
+
skills: join(agentDir, "skills"),
|
|
52
|
+
cache,
|
|
53
|
+
nodeCache: join(cache, "npm"),
|
|
54
|
+
uvCache: join(cache, "uv"),
|
|
55
|
+
pipCache: join(cache, "pip"),
|
|
56
|
+
tmp: join(agentDir, "tmp")
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function getBoringAgentPathEntries(paths) {
|
|
60
|
+
return [paths.nodeBin, paths.venvBin, paths.uvBin];
|
|
61
|
+
}
|
|
62
|
+
function getBoringAgentRuntimeEnv(paths, adapterCacheRoot = paths.cache) {
|
|
63
|
+
return {
|
|
64
|
+
BORING_AGENT_WORKSPACE_ROOT: paths.workspaceRoot,
|
|
65
|
+
VIRTUAL_ENV: paths.venv,
|
|
66
|
+
UV_CACHE_DIR: join(adapterCacheRoot, "uv"),
|
|
67
|
+
PIP_CACHE_DIR: join(adapterCacheRoot, "pip"),
|
|
68
|
+
npm_config_cache: join(adapterCacheRoot, "npm")
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
20
72
|
// src/server/sandbox/workspacePythonEnv.ts
|
|
21
73
|
function withWorkspacePythonEnv(opts) {
|
|
22
74
|
const { workspaceRoot, env, sandboxRoot } = opts;
|
|
23
75
|
const runtimeRoot = sandboxRoot ?? workspaceRoot;
|
|
24
|
-
const
|
|
25
|
-
const venvBin = join(venvRoot, "bin");
|
|
26
|
-
const shimBin = join(runtimeRoot, ".boring-agent", "bin");
|
|
76
|
+
const paths = getBoringAgentRuntimePaths(runtimeRoot);
|
|
27
77
|
const baseEnv = env ?? getEnvSnapshot();
|
|
28
|
-
const pathParts =
|
|
78
|
+
const pathParts = getBoringAgentPathEntries(paths);
|
|
29
79
|
const existingPath = baseEnv.PATH;
|
|
30
80
|
if (existingPath) pathParts.push(existingPath);
|
|
31
81
|
return {
|
|
32
82
|
...baseEnv,
|
|
33
83
|
PATH: pathParts.join(":"),
|
|
34
|
-
VIRTUAL_ENV: baseEnv.VIRTUAL_ENV ??
|
|
84
|
+
VIRTUAL_ENV: baseEnv.VIRTUAL_ENV ?? paths.venv,
|
|
35
85
|
BORING_AGENT_WORKSPACE_ROOT: baseEnv.BORING_AGENT_WORKSPACE_ROOT ?? runtimeRoot
|
|
36
86
|
};
|
|
37
87
|
}
|
|
@@ -92,7 +142,7 @@ function createDirectSandbox() {
|
|
|
92
142
|
const maxOutputBytes = opts?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
93
143
|
const workspaceRoot = workspace.root;
|
|
94
144
|
const cwd = opts?.cwd ?? workspaceRoot;
|
|
95
|
-
return await new Promise((
|
|
145
|
+
return await new Promise((resolve9, reject) => {
|
|
96
146
|
const child = spawn(cmd, {
|
|
97
147
|
cwd,
|
|
98
148
|
env: withWorkspacePythonEnv({ workspaceRoot, env: opts?.env }),
|
|
@@ -121,7 +171,7 @@ function createDirectSandbox() {
|
|
|
121
171
|
if (settled) return;
|
|
122
172
|
settled = true;
|
|
123
173
|
cleanup();
|
|
124
|
-
|
|
174
|
+
resolve9({
|
|
125
175
|
stdout: new Uint8Array(Buffer.concat(stdoutChunks)),
|
|
126
176
|
stderr: new Uint8Array(Buffer.concat(stderrChunks)),
|
|
127
177
|
exitCode: typeof exitCode === "number" ? exitCode : timedOut ? 124 : 1,
|
|
@@ -183,7 +233,7 @@ function createDirectSandbox() {
|
|
|
183
233
|
import { access } from "fs/promises";
|
|
184
234
|
import { constants } from "fs";
|
|
185
235
|
import { spawn as spawn2 } from "child_process";
|
|
186
|
-
import { dirname, isAbsolute as isAbsolute2, join as join2, relative, resolve, sep } from "path";
|
|
236
|
+
import { dirname, isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve2, sep } from "path";
|
|
187
237
|
|
|
188
238
|
// src/server/sandbox/bwrap/buildBwrapArgs.ts
|
|
189
239
|
import { isAbsolute } from "path";
|
|
@@ -300,7 +350,7 @@ function terminateProcess2(child, signal) {
|
|
|
300
350
|
}
|
|
301
351
|
function computeSandboxCwd(workspaceRoot, cwd) {
|
|
302
352
|
if (!cwd) return SANDBOX_HOME2;
|
|
303
|
-
const absoluteCwd = isAbsolute2(cwd) ? cwd :
|
|
353
|
+
const absoluteCwd = isAbsolute2(cwd) ? cwd : resolve2(workspaceRoot, cwd);
|
|
304
354
|
const relPath = relative(workspaceRoot, absoluteCwd);
|
|
305
355
|
if (relPath === "") return SANDBOX_HOME2;
|
|
306
356
|
if (relPath === ".." || relPath.startsWith(`..${sep}`)) {
|
|
@@ -400,7 +450,7 @@ function createBwrapSandbox() {
|
|
|
400
450
|
"-c",
|
|
401
451
|
cmd
|
|
402
452
|
];
|
|
403
|
-
return await new Promise((
|
|
453
|
+
return await new Promise((resolve9, reject) => {
|
|
404
454
|
const child = spawn2("bwrap", args, {
|
|
405
455
|
env: withWorkspacePythonEnv({ workspaceRoot, env: opts?.env, sandboxRoot: SANDBOX_HOME2 }),
|
|
406
456
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -427,7 +477,7 @@ function createBwrapSandbox() {
|
|
|
427
477
|
if (settled) return;
|
|
428
478
|
settled = true;
|
|
429
479
|
cleanup();
|
|
430
|
-
|
|
480
|
+
resolve9({
|
|
431
481
|
stdout: new Uint8Array(Buffer.concat(stdoutChunks)),
|
|
432
482
|
stderr: new Uint8Array(Buffer.concat(stderrChunks)),
|
|
433
483
|
exitCode: typeof exitCode === "number" ? exitCode : timedOut ? 124 : 1,
|
|
@@ -1222,7 +1272,7 @@ import chokidar from "chokidar";
|
|
|
1222
1272
|
|
|
1223
1273
|
// src/server/workspace/paths.ts
|
|
1224
1274
|
import { lstat, realpath, stat } from "fs/promises";
|
|
1225
|
-
import { dirname as dirname2, isAbsolute as isAbsolute3, relative as relative2, resolve as
|
|
1275
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve3 } from "path";
|
|
1226
1276
|
function createPathValidationError(reason, requestedPath, message) {
|
|
1227
1277
|
return Object.assign(new Error(message), {
|
|
1228
1278
|
statusCode: 400,
|
|
@@ -1255,15 +1305,15 @@ function validatePath(workspaceRoot, relPath) {
|
|
|
1255
1305
|
if (hasTraversalSegment(normalized) || normalized.startsWith("~") || normalized.startsWith("$") || /[\r\n]/.test(normalized)) {
|
|
1256
1306
|
throw createPathValidationError("path-escape", relPath, "Path escapes workspace root");
|
|
1257
1307
|
}
|
|
1258
|
-
const resolvedRoot =
|
|
1259
|
-
const resolvedPath =
|
|
1308
|
+
const resolvedRoot = resolve3(workspaceRoot);
|
|
1309
|
+
const resolvedPath = resolve3(workspaceRoot, relPath);
|
|
1260
1310
|
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}/`)) {
|
|
1261
1311
|
throw createPathValidationError("path-escape", relPath, "Path escapes workspace root");
|
|
1262
1312
|
}
|
|
1263
1313
|
return resolvedPath;
|
|
1264
1314
|
}
|
|
1265
1315
|
async function assertRealPathWithinWorkspace(workspaceRoot, absPath) {
|
|
1266
|
-
const realRoot = await realpath(
|
|
1316
|
+
const realRoot = await realpath(resolve3(workspaceRoot));
|
|
1267
1317
|
const realCandidate = await realpath(absPath);
|
|
1268
1318
|
const rel = relative2(realRoot, realCandidate);
|
|
1269
1319
|
if (rel.startsWith("..") || isAbsolute3(rel)) {
|
|
@@ -1810,8 +1860,8 @@ function fileRoutes(app, opts, done) {
|
|
|
1810
1860
|
error: { code: ERROR_CODE_INTERNAL, message: "workspace does not support binary reads" }
|
|
1811
1861
|
});
|
|
1812
1862
|
}
|
|
1813
|
-
const
|
|
1814
|
-
if (
|
|
1863
|
+
const stat10 = await workspace.stat(path4);
|
|
1864
|
+
if (stat10.kind !== "file") {
|
|
1815
1865
|
return reply.code(400).send({
|
|
1816
1866
|
error: { code: ERROR_CODE_VALIDATION_ERROR, message: "path is not a file", field: "path" }
|
|
1817
1867
|
});
|
|
@@ -1829,12 +1879,12 @@ function fileRoutes(app, opts, done) {
|
|
|
1829
1879
|
try {
|
|
1830
1880
|
const workspace = await resolveWorkspace(request);
|
|
1831
1881
|
if (workspace.readFileWithStat) {
|
|
1832
|
-
const { content: content2, stat:
|
|
1833
|
-
return { content: content2, mtimeMs:
|
|
1882
|
+
const { content: content2, stat: stat11 } = await workspace.readFileWithStat(path4);
|
|
1883
|
+
return { content: content2, mtimeMs: stat11.kind === "file" ? stat11.mtimeMs : void 0 };
|
|
1834
1884
|
}
|
|
1835
1885
|
const content = await workspace.readFile(path4);
|
|
1836
|
-
const
|
|
1837
|
-
return { content, mtimeMs:
|
|
1886
|
+
const stat10 = await workspace.stat(path4);
|
|
1887
|
+
return { content, mtimeMs: stat10.kind === "file" ? stat10.mtimeMs : void 0 };
|
|
1838
1888
|
} catch (err) {
|
|
1839
1889
|
return classifyError(err, reply, "file");
|
|
1840
1890
|
}
|
|
@@ -1883,11 +1933,11 @@ function fileRoutes(app, opts, done) {
|
|
|
1883
1933
|
if (dir) await workspace.mkdir(dir, { recursive: true });
|
|
1884
1934
|
}
|
|
1885
1935
|
const content = body.content;
|
|
1886
|
-
const
|
|
1936
|
+
const stat10 = workspace.writeFileWithStat ? await workspace.writeFileWithStat(path4, content) : await (async () => {
|
|
1887
1937
|
await workspace.writeFile(path4, content);
|
|
1888
1938
|
return await workspace.stat(path4);
|
|
1889
1939
|
})();
|
|
1890
|
-
return { ok: true, mtimeMs:
|
|
1940
|
+
return { ok: true, mtimeMs: stat10.kind === "file" ? stat10.mtimeMs : void 0 };
|
|
1891
1941
|
} catch (err) {
|
|
1892
1942
|
return classifyError(err, reply, "file");
|
|
1893
1943
|
}
|
|
@@ -1920,7 +1970,7 @@ function fileRoutes(app, opts, done) {
|
|
|
1920
1970
|
}
|
|
1921
1971
|
const bytes = Buffer.from(contentBase64, "base64");
|
|
1922
1972
|
await workspace.mkdir(dir, { recursive: true });
|
|
1923
|
-
const
|
|
1973
|
+
const stat10 = workspace.writeBinaryFileWithStat ? await workspace.writeBinaryFileWithStat(path4, bytes) : await (async () => {
|
|
1924
1974
|
if (!workspace.writeBinaryFile) {
|
|
1925
1975
|
throw new Error("workspace does not support binary uploads");
|
|
1926
1976
|
}
|
|
@@ -1934,7 +1984,7 @@ function fileRoutes(app, opts, done) {
|
|
|
1934
1984
|
ok: true,
|
|
1935
1985
|
path: path4,
|
|
1936
1986
|
markdownUrl: markdownUrlFor(sourcePath, path4),
|
|
1937
|
-
mtimeMs:
|
|
1987
|
+
mtimeMs: stat10.kind === "file" ? stat10.mtimeMs : void 0
|
|
1938
1988
|
};
|
|
1939
1989
|
} catch (err) {
|
|
1940
1990
|
return classifyError(err, reply, "upload");
|
|
@@ -2021,8 +2071,8 @@ function fileRoutes(app, opts, done) {
|
|
|
2021
2071
|
if (path4 === null) return;
|
|
2022
2072
|
try {
|
|
2023
2073
|
const workspace = await resolveWorkspace(request);
|
|
2024
|
-
const
|
|
2025
|
-
return
|
|
2074
|
+
const stat10 = await workspace.stat(path4);
|
|
2075
|
+
return stat10;
|
|
2026
2076
|
} catch (err) {
|
|
2027
2077
|
return classifyError(err, reply, "path");
|
|
2028
2078
|
}
|
|
@@ -2034,7 +2084,7 @@ function fileRoutes(app, opts, done) {
|
|
|
2034
2084
|
import { createHash as createHash3 } from "crypto";
|
|
2035
2085
|
import { constants as constants2 } from "fs";
|
|
2036
2086
|
import { access as access2, chmod as chmod3, cp, mkdir as mkdir4, readdir as readdir2, readFile as readFile4, realpath as realpath2, stat as stat3, writeFile as writeFile4 } from "fs/promises";
|
|
2037
|
-
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join3, relative as relative5, resolve as
|
|
2087
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join3, relative as relative5, resolve as resolve4 } from "path";
|
|
2038
2088
|
import { fileURLToPath } from "url";
|
|
2039
2089
|
import { execFile } from "child_process";
|
|
2040
2090
|
import { promisify } from "util";
|
|
@@ -2143,8 +2193,8 @@ function resolveTemplateTarget(workspaceRoot, target) {
|
|
|
2143
2193
|
if (rawTarget.length === 0 || rawTarget.includes("\0") || rawTarget.includes("\\") || rawTarget.startsWith("/") || rawTarget.startsWith("//") || /^[A-Za-z]:[\\/]/.test(rawTarget) || rawTarget.split("/").includes("..")) {
|
|
2144
2194
|
throw new Error(`Unsafe runtime template target: ${JSON.stringify(rawTarget)}. Template targets must be relative paths inside the workspace.`);
|
|
2145
2195
|
}
|
|
2146
|
-
const root =
|
|
2147
|
-
const resolved =
|
|
2196
|
+
const root = resolve4(workspaceRoot);
|
|
2197
|
+
const resolved = resolve4(root, rawTarget);
|
|
2148
2198
|
const rel = relative5(root, resolved);
|
|
2149
2199
|
if (rel.startsWith("..") || isAbsolute4(rel)) {
|
|
2150
2200
|
throw new Error(`Unsafe runtime template target: ${JSON.stringify(rawTarget)} resolves outside the workspace.`);
|
|
@@ -2171,7 +2221,7 @@ function nodePackageTarget(workspaceRoot, packageName) {
|
|
|
2171
2221
|
}
|
|
2172
2222
|
async function copyIfExists(source, target) {
|
|
2173
2223
|
if (!await exists(source)) return false;
|
|
2174
|
-
if (
|
|
2224
|
+
if (resolve4(source) === resolve4(target)) return true;
|
|
2175
2225
|
if (await exists(target) && await realpath2(source) === await realpath2(target)) return true;
|
|
2176
2226
|
await cp(source, target, {
|
|
2177
2227
|
recursive: true,
|
|
@@ -2205,11 +2255,11 @@ async function ensureNodePackages(workspaceRoot, specs) {
|
|
|
2205
2255
|
}
|
|
2206
2256
|
async function ensurePython(workspaceRoot, specs) {
|
|
2207
2257
|
if (specs.length === 0) return;
|
|
2208
|
-
const venvPython = join3(workspaceRoot, ".venv", "bin", "python");
|
|
2258
|
+
const venvPython = join3(workspaceRoot, ".boring-agent", "venv", "bin", "python");
|
|
2209
2259
|
const uv = await commandExists("uv");
|
|
2210
2260
|
if (!await exists(venvPython)) {
|
|
2211
|
-
if (uv) await run("uv", ["venv", ".venv"], workspaceRoot);
|
|
2212
|
-
else await run("/usr/bin/python3", ["-m", "venv", ".venv"], workspaceRoot);
|
|
2261
|
+
if (uv) await run("uv", ["venv", ".boring-agent/venv"], workspaceRoot);
|
|
2262
|
+
else await run("/usr/bin/python3", ["-m", "venv", ".boring-agent/venv"], workspaceRoot);
|
|
2213
2263
|
}
|
|
2214
2264
|
for (const spec of specs) {
|
|
2215
2265
|
const projectDir = dirname5(toPath(spec.projectFile));
|
|
@@ -2238,7 +2288,7 @@ function assertEnvKey(key) {
|
|
|
2238
2288
|
}
|
|
2239
2289
|
async function isRuntimeMaterialized(workspaceRoot, contributions) {
|
|
2240
2290
|
const hasPython = contributions.some(({ provisioning }) => (provisioning.python ?? []).length > 0);
|
|
2241
|
-
if (hasPython && !await exists(join3(workspaceRoot, ".venv", "bin", "python"))) return false;
|
|
2291
|
+
if (hasPython && !await exists(join3(workspaceRoot, ".boring-agent", "venv", "bin", "python"))) return false;
|
|
2242
2292
|
for (const { provisioning } of contributions) {
|
|
2243
2293
|
for (const spec of provisioning.nodePackages ?? []) {
|
|
2244
2294
|
if (!await exists(join3(nodePackageTarget(workspaceRoot, spec.packageName), "package.json"))) return false;
|
|
@@ -2248,7 +2298,7 @@ async function isRuntimeMaterialized(workspaceRoot, contributions) {
|
|
|
2248
2298
|
}
|
|
2249
2299
|
async function writeShims(workspaceRoot, env) {
|
|
2250
2300
|
const shimDir = join3(workspaceRoot, ".boring-agent", "bin");
|
|
2251
|
-
const venvBin = join3(workspaceRoot, ".venv", "bin");
|
|
2301
|
+
const venvBin = join3(workspaceRoot, ".boring-agent", "venv", "bin");
|
|
2252
2302
|
await mkdir4(shimDir, { recursive: true });
|
|
2253
2303
|
const exports = Object.entries(env).map(([key, value]) => {
|
|
2254
2304
|
assertEnvKey(key);
|
|
@@ -2260,7 +2310,7 @@ SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
|
2260
2310
|
WORKSPACE_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)"
|
|
2261
2311
|
export BORING_AGENT_WORKSPACE_ROOT="$WORKSPACE_ROOT"
|
|
2262
2312
|
${exports}
|
|
2263
|
-
VENV_BIN="$WORKSPACE_ROOT/.venv/bin"
|
|
2313
|
+
VENV_BIN="$WORKSPACE_ROOT/.boring-agent/venv/bin"
|
|
2264
2314
|
`;
|
|
2265
2315
|
await writeExecutable(join3(shimDir, "python"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2266
2316
|
`);
|
|
@@ -2359,11 +2409,11 @@ function createTimedLruCache(ttlMs, maxEntries) {
|
|
|
2359
2409
|
}
|
|
2360
2410
|
};
|
|
2361
2411
|
}
|
|
2362
|
-
function cloneStat(
|
|
2412
|
+
function cloneStat(stat10) {
|
|
2363
2413
|
return {
|
|
2364
|
-
size:
|
|
2365
|
-
mtimeMs:
|
|
2366
|
-
kind:
|
|
2414
|
+
size: stat10.size,
|
|
2415
|
+
mtimeMs: stat10.mtimeMs,
|
|
2416
|
+
kind: stat10.kind
|
|
2367
2417
|
};
|
|
2368
2418
|
}
|
|
2369
2419
|
function cloneEntries(entries) {
|
|
@@ -2521,15 +2571,15 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2521
2571
|
remote.fs.readFile(sandboxPath, "utf8"),
|
|
2522
2572
|
remote.fs.stat(sandboxPath)
|
|
2523
2573
|
]);
|
|
2524
|
-
const
|
|
2574
|
+
const stat10 = {
|
|
2525
2575
|
size: fileStat.size,
|
|
2526
2576
|
mtimeMs: fileStat.mtimeMs,
|
|
2527
2577
|
kind: fileStat.isDirectory() ? "dir" : "file"
|
|
2528
2578
|
};
|
|
2529
|
-
statCache.set(sandboxPath,
|
|
2579
|
+
statCache.set(sandboxPath, stat10);
|
|
2530
2580
|
return {
|
|
2531
2581
|
content: typeof content === "string" ? content : Buffer.from(content).toString("utf-8"),
|
|
2532
|
-
stat: cloneStat(
|
|
2582
|
+
stat: cloneStat(stat10)
|
|
2533
2583
|
};
|
|
2534
2584
|
}
|
|
2535
2585
|
const version = metadataVersion;
|
|
@@ -2689,11 +2739,801 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2689
2739
|
};
|
|
2690
2740
|
}
|
|
2691
2741
|
|
|
2742
|
+
// src/server/sandbox/vercel-sandbox/provisioningAdapter.ts
|
|
2743
|
+
import { mkdtemp } from "fs/promises";
|
|
2744
|
+
import { join as join4 } from "path";
|
|
2745
|
+
import { tmpdir } from "os";
|
|
2746
|
+
|
|
2747
|
+
// src/server/workspace/provisioning/errors.ts
|
|
2748
|
+
var ProvisioningError = class extends Error {
|
|
2749
|
+
code;
|
|
2750
|
+
details;
|
|
2751
|
+
constructor(code, message, details = {}, cause) {
|
|
2752
|
+
super(message, { cause });
|
|
2753
|
+
this.name = "ProvisioningError";
|
|
2754
|
+
this.code = code;
|
|
2755
|
+
this.details = details;
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
function toProvisioningError(code, phase, error, details = {}) {
|
|
2759
|
+
if (error instanceof ProvisioningError) return error;
|
|
2760
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2761
|
+
return new ProvisioningError(
|
|
2762
|
+
code,
|
|
2763
|
+
`Workspace provisioning failed during ${phase}: ${message}`,
|
|
2764
|
+
{ phase, ...details },
|
|
2765
|
+
error
|
|
2766
|
+
);
|
|
2767
|
+
}
|
|
2768
|
+
function logProvisioning(logger, level, message, fields = {}) {
|
|
2769
|
+
logger?.[level]?.(message, fields);
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
// src/server/sandbox/vercel-sandbox/provisioningAdapter.ts
|
|
2773
|
+
var VERCEL_PROVISIONING_CACHE_ROOT = "/tmp/boring-agent-cache";
|
|
2774
|
+
function artifactExtension(kind) {
|
|
2775
|
+
return kind === "node" ? ".tgz" : ".tar.gz";
|
|
2776
|
+
}
|
|
2777
|
+
function artifactName(kind, id, fingerprint2) {
|
|
2778
|
+
const safeId = id.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
2779
|
+
const safeFingerprint = fingerprint2.replace(/^sha256:/, "");
|
|
2780
|
+
return `${safeId}-${safeFingerprint}${artifactExtension(kind)}`;
|
|
2781
|
+
}
|
|
2782
|
+
function createVercelProvisioningAdapter(options) {
|
|
2783
|
+
return {
|
|
2784
|
+
mode: "vercel-sandbox",
|
|
2785
|
+
async exec(command, args, opts) {
|
|
2786
|
+
return await options.exec(command, args, {
|
|
2787
|
+
cwd: opts?.cwd ?? options.runtimeLayout.workspaceRoot,
|
|
2788
|
+
env: opts?.env,
|
|
2789
|
+
timeoutMs: opts?.timeoutMs
|
|
2790
|
+
});
|
|
2791
|
+
},
|
|
2792
|
+
async resolveInstallSource(source, opts) {
|
|
2793
|
+
const name = artifactName(opts.kind, opts.id, opts.fingerprint);
|
|
2794
|
+
const workspaceRel = `.boring-agent/tmp/${name}`;
|
|
2795
|
+
const runtimePath = `${options.runtimeLayout.tmp}/${name}`;
|
|
2796
|
+
if (!await options.workspaceFs.exists(workspaceRel)) {
|
|
2797
|
+
const artifactDir = await mkdtemp(join4(tmpdir(), "boring-agent-vercel-artifact-"));
|
|
2798
|
+
const outputPath = join4(artifactDir, name);
|
|
2799
|
+
try {
|
|
2800
|
+
await options.prepareArtifact({
|
|
2801
|
+
kind: opts.kind,
|
|
2802
|
+
id: opts.id,
|
|
2803
|
+
fingerprint: opts.fingerprint,
|
|
2804
|
+
source,
|
|
2805
|
+
outputPath
|
|
2806
|
+
});
|
|
2807
|
+
await options.workspaceFs.copyFromHost(outputPath, workspaceRel);
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
throw toProvisioningError(
|
|
2810
|
+
ErrorCode.enum.PROVISIONING_ARTIFACT_FAILED,
|
|
2811
|
+
"adapter-artifact",
|
|
2812
|
+
error,
|
|
2813
|
+
{ runtime: opts.kind, id: opts.id, artifact: workspaceRel }
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
return runtimePath;
|
|
2818
|
+
},
|
|
2819
|
+
workspaceFs: options.workspaceFs,
|
|
2820
|
+
getRuntimeCacheRoot() {
|
|
2821
|
+
return options.cacheRoot ?? VERCEL_PROVISIONING_CACHE_ROOT;
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// src/server/workspace/provisioning/fingerprint.ts
|
|
2827
|
+
import { createHash as createHash4 } from "crypto";
|
|
2828
|
+
import { constants as constants3 } from "fs";
|
|
2829
|
+
import { access as access3, mkdir as mkdir5, open, readFile as readFile5, rename as rename4, rm } from "fs/promises";
|
|
2830
|
+
import { dirname as dirname6 } from "path";
|
|
2831
|
+
var FINGERPRINT_RE = /^sha256:[a-f0-9]{64}$/;
|
|
2832
|
+
function isValidFingerprint(value) {
|
|
2833
|
+
return FINGERPRINT_RE.test(value.trim());
|
|
2834
|
+
}
|
|
2835
|
+
function normalizeSource(value) {
|
|
2836
|
+
if (value === void 0) return void 0;
|
|
2837
|
+
return String(value);
|
|
2838
|
+
}
|
|
2839
|
+
function stableStringify(value) {
|
|
2840
|
+
if (value === null || typeof value !== "object") {
|
|
2841
|
+
return JSON.stringify(value);
|
|
2842
|
+
}
|
|
2843
|
+
if (Array.isArray(value)) {
|
|
2844
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
2845
|
+
}
|
|
2846
|
+
const record = value;
|
|
2847
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
2848
|
+
}
|
|
2849
|
+
function createRuntimeFingerprint(input) {
|
|
2850
|
+
const hash = createHash4("sha256");
|
|
2851
|
+
hash.update(stableStringify(input));
|
|
2852
|
+
return `sha256:${hash.digest("hex")}`;
|
|
2853
|
+
}
|
|
2854
|
+
function createNodeRuntimeFingerprint(input) {
|
|
2855
|
+
return createRuntimeFingerprint({
|
|
2856
|
+
kind: "node",
|
|
2857
|
+
nodeVersion: input.nodeVersion,
|
|
2858
|
+
npmVersion: input.npmVersion,
|
|
2859
|
+
packages: input.packages.map((pkg) => ({
|
|
2860
|
+
id: pkg.id,
|
|
2861
|
+
packageName: pkg.packageName,
|
|
2862
|
+
packageRoot: normalizeSource(pkg.packageRoot),
|
|
2863
|
+
version: pkg.version,
|
|
2864
|
+
expectedBins: pkg.expectedBins ?? []
|
|
2865
|
+
}))
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
function createPythonRuntimeFingerprint(input) {
|
|
2869
|
+
return createRuntimeFingerprint({
|
|
2870
|
+
kind: "python",
|
|
2871
|
+
pythonVersion: input.pythonVersion,
|
|
2872
|
+
uvVersion: input.uvVersion,
|
|
2873
|
+
packages: input.packages.map((pkg) => ({
|
|
2874
|
+
id: pkg.id,
|
|
2875
|
+
projectFile: normalizeSource(pkg.projectFile),
|
|
2876
|
+
packageName: pkg.packageName,
|
|
2877
|
+
packageRoot: normalizeSource(pkg.packageRoot),
|
|
2878
|
+
version: pkg.version,
|
|
2879
|
+
extraLibs: pkg.extraLibs ?? [],
|
|
2880
|
+
env: Object.fromEntries(
|
|
2881
|
+
Object.entries(pkg.env ?? {}).map(([key, value]) => [key, normalizeSource(value)])
|
|
2882
|
+
),
|
|
2883
|
+
expectedBins: pkg.expectedBins ?? []
|
|
2884
|
+
}))
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// src/server/workspace/provisioning/node.ts
|
|
2889
|
+
import { join as join5, relative as relative6, sep as sep3 } from "path";
|
|
2890
|
+
var NODE_RUNTIME_REL = ".boring-agent/node";
|
|
2891
|
+
var NODE_PACKAGE_JSON_REL = `${NODE_RUNTIME_REL}/package.json`;
|
|
2892
|
+
var NODE_FINGERPRINT_REL = `${NODE_RUNTIME_REL}/.fingerprint`;
|
|
2893
|
+
function parseNodeMajor(version) {
|
|
2894
|
+
const match = version.trim().match(/^v?(\d+)\./);
|
|
2895
|
+
return match ? Number(match[1]) : null;
|
|
2896
|
+
}
|
|
2897
|
+
async function commandOutput(adapter, command, args) {
|
|
2898
|
+
const result = await adapter.exec(command, args);
|
|
2899
|
+
return result?.stdout?.trim() ?? "";
|
|
2900
|
+
}
|
|
2901
|
+
async function ensureNodeEnv(adapter) {
|
|
2902
|
+
try {
|
|
2903
|
+
const nodeVersion = await commandOutput(adapter, "node", ["--version"]);
|
|
2904
|
+
const npmVersion = await commandOutput(adapter, "npm", ["--version"]);
|
|
2905
|
+
const major = parseNodeMajor(nodeVersion || process.version);
|
|
2906
|
+
if (major === null || major < 18) {
|
|
2907
|
+
throw new Error(`Unsupported Node.js version for runtime provisioning: ${nodeVersion || process.version}`);
|
|
2908
|
+
}
|
|
2909
|
+
return {
|
|
2910
|
+
nodeVersion: nodeVersion || process.version,
|
|
2911
|
+
npmVersion: npmVersion || "unknown"
|
|
2912
|
+
};
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
throw toProvisioningError(
|
|
2915
|
+
ErrorCode.enum.PROVISIONING_NODE_PREFLIGHT_FAILED,
|
|
2916
|
+
"node-preflight",
|
|
2917
|
+
error,
|
|
2918
|
+
{ runtime: "node" }
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
function nodeInstallSource(spec) {
|
|
2923
|
+
return spec.version ? `${spec.packageName}@${spec.version}` : spec.packageName;
|
|
2924
|
+
}
|
|
2925
|
+
function expectedNodeOutputs(paths, packages) {
|
|
2926
|
+
return [
|
|
2927
|
+
join5(paths.node, "package-lock.json"),
|
|
2928
|
+
...packages.flatMap(
|
|
2929
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join5(paths.nodeBin, bin))
|
|
2930
|
+
)
|
|
2931
|
+
];
|
|
2932
|
+
}
|
|
2933
|
+
function toWorkspaceRel(paths, absolutePath) {
|
|
2934
|
+
return relative6(paths.workspaceRoot, absolutePath).split(sep3).join("/");
|
|
2935
|
+
}
|
|
2936
|
+
async function shouldInstallNodeRuntime(options) {
|
|
2937
|
+
const currentFingerprint = (await options.adapter.workspaceFs.readText(NODE_FINGERPRINT_REL))?.trim();
|
|
2938
|
+
if (!currentFingerprint || !isValidFingerprint(currentFingerprint) || currentFingerprint !== options.desiredFingerprint) {
|
|
2939
|
+
return true;
|
|
2940
|
+
}
|
|
2941
|
+
for (const output of options.expectedOutputs) {
|
|
2942
|
+
if (!await options.adapter.workspaceFs.exists(toWorkspaceRel(options.runtimeLayout, output))) return true;
|
|
2943
|
+
}
|
|
2944
|
+
return false;
|
|
2945
|
+
}
|
|
2946
|
+
async function ensureNodeRuntime(options) {
|
|
2947
|
+
if (options.packages.length === 0) {
|
|
2948
|
+
return { changed: false, pathEntries: [], fingerprint: null };
|
|
2949
|
+
}
|
|
2950
|
+
const versions = await ensureNodeEnv(options.adapter);
|
|
2951
|
+
const fingerprint2 = createNodeRuntimeFingerprint({
|
|
2952
|
+
...versions,
|
|
2953
|
+
packages: options.packages
|
|
2954
|
+
});
|
|
2955
|
+
const expectedOutputs = expectedNodeOutputs(options.runtimeLayout, options.packages);
|
|
2956
|
+
if (!await shouldInstallNodeRuntime({
|
|
2957
|
+
adapter: options.adapter,
|
|
2958
|
+
runtimeLayout: options.runtimeLayout,
|
|
2959
|
+
desiredFingerprint: fingerprint2,
|
|
2960
|
+
expectedOutputs
|
|
2961
|
+
})) {
|
|
2962
|
+
return {
|
|
2963
|
+
changed: false,
|
|
2964
|
+
pathEntries: [options.runtimeLayout.nodeBin],
|
|
2965
|
+
fingerprint: fingerprint2
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
const installSources = [];
|
|
2969
|
+
for (const pkg of options.packages) {
|
|
2970
|
+
if (pkg.packageRoot) {
|
|
2971
|
+
const packageFingerprint = createRuntimeFingerprint({ kind: "node-source", package: pkg });
|
|
2972
|
+
installSources.push(await options.adapter.resolveInstallSource(pkg.packageRoot, {
|
|
2973
|
+
kind: "node",
|
|
2974
|
+
id: pkg.id,
|
|
2975
|
+
fingerprint: packageFingerprint
|
|
2976
|
+
}));
|
|
2977
|
+
continue;
|
|
2978
|
+
}
|
|
2979
|
+
installSources.push(nodeInstallSource(pkg));
|
|
2980
|
+
}
|
|
2981
|
+
await options.adapter.workspaceFs.rm(NODE_RUNTIME_REL);
|
|
2982
|
+
await options.adapter.workspaceFs.mkdir(NODE_RUNTIME_REL);
|
|
2983
|
+
await options.adapter.workspaceFs.writeText(
|
|
2984
|
+
NODE_PACKAGE_JSON_REL,
|
|
2985
|
+
`${JSON.stringify({ name: "boring-agent-runtime", private: true }, null, 2)}
|
|
2986
|
+
`
|
|
2987
|
+
);
|
|
2988
|
+
try {
|
|
2989
|
+
await options.adapter.exec("npm", [
|
|
2990
|
+
"install",
|
|
2991
|
+
"--prefix",
|
|
2992
|
+
options.runtimeLayout.node,
|
|
2993
|
+
...installSources
|
|
2994
|
+
], {
|
|
2995
|
+
cwd: options.runtimeLayout.workspaceRoot,
|
|
2996
|
+
env: getBoringAgentRuntimeEnv(
|
|
2997
|
+
options.runtimeLayout,
|
|
2998
|
+
options.adapter.getRuntimeCacheRoot()
|
|
2999
|
+
)
|
|
3000
|
+
});
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
throw toProvisioningError(
|
|
3003
|
+
ErrorCode.enum.PROVISIONING_NPM_INSTALL_FAILED,
|
|
3004
|
+
"node-packages",
|
|
3005
|
+
error,
|
|
3006
|
+
{ runtime: "node", packageIds: options.packages.map((pkg) => pkg.id) }
|
|
3007
|
+
);
|
|
3008
|
+
}
|
|
3009
|
+
await options.adapter.workspaceFs.writeText(NODE_FINGERPRINT_REL, `${fingerprint2}
|
|
3010
|
+
`);
|
|
3011
|
+
return {
|
|
3012
|
+
changed: true,
|
|
3013
|
+
pathEntries: [options.runtimeLayout.nodeBin],
|
|
3014
|
+
fingerprint: fingerprint2
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
// src/server/workspace/provisioning/python.ts
|
|
3019
|
+
import { dirname as dirname7, join as join6, relative as relative7, sep as sep4 } from "path";
|
|
3020
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3021
|
+
var VENV_REL = ".boring-agent/venv";
|
|
3022
|
+
var VENV_FINGERPRINT_REL = `${VENV_REL}/.fingerprint`;
|
|
3023
|
+
var UV_BIN_REL = ".boring-agent/sdk/uv/bin/uv";
|
|
3024
|
+
function sourceToPath(source) {
|
|
3025
|
+
return source instanceof URL ? fileURLToPath2(source) : source;
|
|
3026
|
+
}
|
|
3027
|
+
async function commandOutput2(adapter, command, args) {
|
|
3028
|
+
const result = await adapter.exec(command, args);
|
|
3029
|
+
return result?.stdout?.trim() ?? "";
|
|
3030
|
+
}
|
|
3031
|
+
async function ensureUv(options) {
|
|
3032
|
+
try {
|
|
3033
|
+
return {
|
|
3034
|
+
uvBin: "uv",
|
|
3035
|
+
uvVersion: await commandOutput2(options.adapter, "uv", ["--version"]) || "uv unknown",
|
|
3036
|
+
installedWorkspaceUv: false
|
|
3037
|
+
};
|
|
3038
|
+
} catch {
|
|
3039
|
+
if (!options.uvStandaloneSource) {
|
|
3040
|
+
throw toProvisioningError(
|
|
3041
|
+
ErrorCode.enum.PROVISIONING_UV_BOOTSTRAP_FAILED,
|
|
3042
|
+
"uv-bootstrap",
|
|
3043
|
+
new Error("uv is required for Python runtime provisioning; install uv or provide a standalone uv binary"),
|
|
3044
|
+
{ runtime: "python" }
|
|
3045
|
+
);
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
try {
|
|
3049
|
+
await options.adapter.workspaceFs.mkdir(".boring-agent/sdk/uv/bin");
|
|
3050
|
+
await options.adapter.workspaceFs.rm(UV_BIN_REL);
|
|
3051
|
+
await options.adapter.workspaceFs.copyFromHost(options.uvStandaloneSource, UV_BIN_REL);
|
|
3052
|
+
const uvBin = join6(options.runtimeLayout.uvBin, "uv");
|
|
3053
|
+
await options.adapter.exec("chmod", ["+x", uvBin], { cwd: options.runtimeLayout.workspaceRoot });
|
|
3054
|
+
return {
|
|
3055
|
+
uvBin,
|
|
3056
|
+
uvVersion: await commandOutput2(options.adapter, uvBin, ["--version"]) || "uv unknown",
|
|
3057
|
+
installedWorkspaceUv: true
|
|
3058
|
+
};
|
|
3059
|
+
} catch (error) {
|
|
3060
|
+
throw toProvisioningError(
|
|
3061
|
+
ErrorCode.enum.PROVISIONING_UV_BOOTSTRAP_FAILED,
|
|
3062
|
+
"uv-bootstrap",
|
|
3063
|
+
error,
|
|
3064
|
+
{ runtime: "python" }
|
|
3065
|
+
);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
async function ensurePythonEnv(adapter) {
|
|
3069
|
+
return await commandOutput2(adapter, "python3", ["--version"]) || "Python unknown";
|
|
3070
|
+
}
|
|
3071
|
+
function pythonInstallSource(spec) {
|
|
3072
|
+
if (spec.version && spec.packageName) return `${spec.packageName}==${spec.version}`;
|
|
3073
|
+
if (spec.packageName) return spec.packageName;
|
|
3074
|
+
throw new Error(`Python runtime package ${spec.id} must declare packageName when no projectFile/packageRoot is provided`);
|
|
3075
|
+
}
|
|
3076
|
+
function sourceRootForPythonSpec(spec) {
|
|
3077
|
+
if (spec.packageRoot) return spec.packageRoot;
|
|
3078
|
+
if (spec.projectFile) return dirname7(sourceToPath(spec.projectFile));
|
|
3079
|
+
return null;
|
|
3080
|
+
}
|
|
3081
|
+
function expectedPythonOutputs(paths, packages) {
|
|
3082
|
+
return [
|
|
3083
|
+
paths.venvPython,
|
|
3084
|
+
...packages.flatMap(
|
|
3085
|
+
(pkg) => (pkg.expectedBins ?? []).map((bin) => join6(paths.venvBin, bin))
|
|
3086
|
+
)
|
|
3087
|
+
];
|
|
3088
|
+
}
|
|
3089
|
+
function collectPythonEnv(packages) {
|
|
3090
|
+
const env = {};
|
|
3091
|
+
for (const pkg of packages) {
|
|
3092
|
+
for (const [key, value] of Object.entries(pkg.env ?? {})) {
|
|
3093
|
+
env[key] = String(value);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
return env;
|
|
3097
|
+
}
|
|
3098
|
+
function toWorkspaceRel2(paths, absolutePath) {
|
|
3099
|
+
return relative7(paths.workspaceRoot, absolutePath).split(sep4).join("/");
|
|
3100
|
+
}
|
|
3101
|
+
async function shouldInstallPythonRuntime(options) {
|
|
3102
|
+
const currentFingerprint = (await options.adapter.workspaceFs.readText(VENV_FINGERPRINT_REL))?.trim();
|
|
3103
|
+
if (!currentFingerprint || !isValidFingerprint(currentFingerprint) || currentFingerprint !== options.desiredFingerprint) {
|
|
3104
|
+
return true;
|
|
3105
|
+
}
|
|
3106
|
+
for (const output of options.expectedOutputs) {
|
|
3107
|
+
if (!await options.adapter.workspaceFs.exists(toWorkspaceRel2(options.runtimeLayout, output))) return true;
|
|
3108
|
+
}
|
|
3109
|
+
return false;
|
|
3110
|
+
}
|
|
3111
|
+
async function ensurePythonRuntime(options) {
|
|
3112
|
+
const env = collectPythonEnv(options.packages);
|
|
3113
|
+
if (options.packages.length === 0) {
|
|
3114
|
+
return { changed: false, env, pathEntries: [], fingerprint: null };
|
|
3115
|
+
}
|
|
3116
|
+
const pythonVersion = await ensurePythonEnv(options.adapter);
|
|
3117
|
+
const uv = await ensureUv(options);
|
|
3118
|
+
const fingerprint2 = createPythonRuntimeFingerprint({
|
|
3119
|
+
packages: options.packages,
|
|
3120
|
+
pythonVersion,
|
|
3121
|
+
uvVersion: uv.uvVersion
|
|
3122
|
+
});
|
|
3123
|
+
const expectedOutputs = expectedPythonOutputs(options.runtimeLayout, options.packages);
|
|
3124
|
+
if (!await shouldInstallPythonRuntime({
|
|
3125
|
+
adapter: options.adapter,
|
|
3126
|
+
runtimeLayout: options.runtimeLayout,
|
|
3127
|
+
desiredFingerprint: fingerprint2,
|
|
3128
|
+
expectedOutputs
|
|
3129
|
+
})) {
|
|
3130
|
+
return {
|
|
3131
|
+
changed: false,
|
|
3132
|
+
env,
|
|
3133
|
+
pathEntries: [options.runtimeLayout.venvBin, options.runtimeLayout.uvBin],
|
|
3134
|
+
fingerprint: fingerprint2
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
const installSources = [];
|
|
3138
|
+
for (const pkg of options.packages) {
|
|
3139
|
+
const sourceRoot = sourceRootForPythonSpec(pkg);
|
|
3140
|
+
if (sourceRoot) {
|
|
3141
|
+
const packageFingerprint = createRuntimeFingerprint({ kind: "python-source", package: pkg });
|
|
3142
|
+
installSources.push(await options.adapter.resolveInstallSource(sourceRoot, {
|
|
3143
|
+
kind: "python",
|
|
3144
|
+
id: pkg.id,
|
|
3145
|
+
fingerprint: packageFingerprint
|
|
3146
|
+
}));
|
|
3147
|
+
} else {
|
|
3148
|
+
installSources.push(pythonInstallSource(pkg));
|
|
3149
|
+
}
|
|
3150
|
+
installSources.push(...pkg.extraLibs ?? []);
|
|
3151
|
+
}
|
|
3152
|
+
await options.adapter.workspaceFs.rm(VENV_REL);
|
|
3153
|
+
try {
|
|
3154
|
+
await options.adapter.exec(uv.uvBin, ["venv", options.runtimeLayout.venv], {
|
|
3155
|
+
cwd: options.runtimeLayout.workspaceRoot,
|
|
3156
|
+
env: getBoringAgentRuntimeEnv(
|
|
3157
|
+
options.runtimeLayout,
|
|
3158
|
+
options.adapter.getRuntimeCacheRoot()
|
|
3159
|
+
)
|
|
3160
|
+
});
|
|
3161
|
+
await options.adapter.exec(uv.uvBin, [
|
|
3162
|
+
"pip",
|
|
3163
|
+
"install",
|
|
3164
|
+
"--python",
|
|
3165
|
+
options.runtimeLayout.venvPython,
|
|
3166
|
+
...installSources
|
|
3167
|
+
], {
|
|
3168
|
+
cwd: options.runtimeLayout.workspaceRoot,
|
|
3169
|
+
env: {
|
|
3170
|
+
...getBoringAgentRuntimeEnv(
|
|
3171
|
+
options.runtimeLayout,
|
|
3172
|
+
options.adapter.getRuntimeCacheRoot()
|
|
3173
|
+
),
|
|
3174
|
+
...env
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
throw toProvisioningError(
|
|
3179
|
+
ErrorCode.enum.PROVISIONING_UV_INSTALL_FAILED,
|
|
3180
|
+
"python-packages",
|
|
3181
|
+
error,
|
|
3182
|
+
{ runtime: "python", packageIds: options.packages.map((pkg) => pkg.id) }
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
await options.adapter.workspaceFs.writeText(VENV_FINGERPRINT_REL, `${fingerprint2}
|
|
3186
|
+
`);
|
|
3187
|
+
return {
|
|
3188
|
+
changed: true,
|
|
3189
|
+
env,
|
|
3190
|
+
pathEntries: [options.runtimeLayout.venvBin, options.runtimeLayout.uvBin],
|
|
3191
|
+
fingerprint: fingerprint2
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
// src/server/workspace/provisioning/skills.ts
|
|
3196
|
+
import { stat as stat4 } from "fs/promises";
|
|
3197
|
+
import { join as join7 } from "path";
|
|
3198
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3199
|
+
var GENERATED_SKILLS_REL = ".boring-agent/skills";
|
|
3200
|
+
var USER_SKILLS_REL = ".agents/skills";
|
|
3201
|
+
function sourceToPath2(source) {
|
|
3202
|
+
return source instanceof URL ? fileURLToPath3(source) : source;
|
|
3203
|
+
}
|
|
3204
|
+
function assertSafeSegment(kind, value) {
|
|
3205
|
+
if (value.length === 0 || value.includes("\0") || value.includes("/") || value.includes("\\") || value === "." || value === "..") {
|
|
3206
|
+
throw new Error(`Invalid ${kind} for plugin skill mirror: ${value}`);
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
function getProvisionedSkillPaths(paths) {
|
|
3210
|
+
return [paths.skills, join7(paths.workspaceRoot, USER_SKILLS_REL)];
|
|
3211
|
+
}
|
|
3212
|
+
async function mirrorPluginSkills(options) {
|
|
3213
|
+
await options.adapter.workspaceFs.rm(GENERATED_SKILLS_REL);
|
|
3214
|
+
await options.adapter.workspaceFs.mkdir(GENERATED_SKILLS_REL);
|
|
3215
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3216
|
+
let copiedSkillCount = 0;
|
|
3217
|
+
for (const plugin of options.plugins) {
|
|
3218
|
+
assertSafeSegment("plugin id", plugin.id);
|
|
3219
|
+
for (const skill of plugin.skills ?? []) {
|
|
3220
|
+
assertSafeSegment("skill name", skill.name);
|
|
3221
|
+
const key = `${plugin.id}/${skill.name}`;
|
|
3222
|
+
if (seen.has(key)) {
|
|
3223
|
+
throw new Error(`Duplicate plugin skill mirror target: ${key}`);
|
|
3224
|
+
}
|
|
3225
|
+
seen.add(key);
|
|
3226
|
+
const sourcePath = sourceToPath2(skill.source);
|
|
3227
|
+
const sourceStat = await stat4(sourcePath);
|
|
3228
|
+
const skillTarget = `${GENERATED_SKILLS_REL}/${plugin.id}/${skill.name}`;
|
|
3229
|
+
const target = sourceStat.isDirectory() ? skillTarget : `${skillTarget}/SKILL.md`;
|
|
3230
|
+
await options.adapter.workspaceFs.copyFromHost(skill.source, target);
|
|
3231
|
+
copiedSkillCount += 1;
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
return {
|
|
3235
|
+
changed: copiedSkillCount > 0,
|
|
3236
|
+
skillPaths: getProvisionedSkillPaths(options.runtimeLayout)
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
// src/server/workspace/provisioning/workspaceFiles.ts
|
|
3241
|
+
import { basename, join as joinPath } from "path";
|
|
3242
|
+
import { posix } from "path";
|
|
3243
|
+
import { readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
3244
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL } from "url";
|
|
3245
|
+
function sourceToPath3(source) {
|
|
3246
|
+
return source instanceof URL ? fileURLToPath4(source) : source;
|
|
3247
|
+
}
|
|
3248
|
+
function toWorkspaceRel3(...parts) {
|
|
3249
|
+
return posix.normalize(parts.filter(Boolean).join("/"));
|
|
3250
|
+
}
|
|
3251
|
+
function sourceForChild(rootSource, childPath) {
|
|
3252
|
+
return rootSource instanceof URL ? pathToFileURL(childPath) : childPath;
|
|
3253
|
+
}
|
|
3254
|
+
async function collectTemplateWorkItems(template) {
|
|
3255
|
+
const sourcePath = sourceToPath3(template.path);
|
|
3256
|
+
const sourceStat = await stat5(sourcePath);
|
|
3257
|
+
const targetPrefix = template.target ?? "";
|
|
3258
|
+
if (!sourceStat.isDirectory()) {
|
|
3259
|
+
const targetRel = toWorkspaceRel3(targetPrefix || basename(sourcePath));
|
|
3260
|
+
return [{
|
|
3261
|
+
hostSourcePath: sourcePath,
|
|
3262
|
+
source: template.path,
|
|
3263
|
+
targetRel,
|
|
3264
|
+
kind: "file"
|
|
3265
|
+
}];
|
|
3266
|
+
}
|
|
3267
|
+
const items = [];
|
|
3268
|
+
async function walk(dir, rel) {
|
|
3269
|
+
const targetRel = toWorkspaceRel3(targetPrefix, rel);
|
|
3270
|
+
if (rel !== "") {
|
|
3271
|
+
items.push({
|
|
3272
|
+
hostSourcePath: dir,
|
|
3273
|
+
source: sourceForChild(template.path, dir),
|
|
3274
|
+
targetRel,
|
|
3275
|
+
kind: "directory"
|
|
3276
|
+
});
|
|
3277
|
+
} else if (targetPrefix) {
|
|
3278
|
+
items.push({
|
|
3279
|
+
hostSourcePath: dir,
|
|
3280
|
+
source: sourceForChild(template.path, dir),
|
|
3281
|
+
targetRel: toWorkspaceRel3(targetPrefix),
|
|
3282
|
+
kind: "directory"
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
3286
|
+
for (const entry of entries) {
|
|
3287
|
+
const childPath = joinPath(dir, entry.name);
|
|
3288
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
3289
|
+
if (entry.isDirectory()) {
|
|
3290
|
+
await walk(childPath, childRel);
|
|
3291
|
+
} else if (entry.isFile()) {
|
|
3292
|
+
items.push({
|
|
3293
|
+
hostSourcePath: childPath,
|
|
3294
|
+
source: sourceForChild(template.path, childPath),
|
|
3295
|
+
targetRel: toWorkspaceRel3(targetPrefix, childRel),
|
|
3296
|
+
kind: "file"
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
await walk(sourcePath, "");
|
|
3302
|
+
return items;
|
|
3303
|
+
}
|
|
3304
|
+
async function seedTemplate(options) {
|
|
3305
|
+
const items = await collectTemplateWorkItems(options.template);
|
|
3306
|
+
let changed = false;
|
|
3307
|
+
for (const item of items) {
|
|
3308
|
+
let exists2;
|
|
3309
|
+
try {
|
|
3310
|
+
exists2 = await options.adapter.workspaceFs.exists(item.targetRel);
|
|
3311
|
+
if (exists2) continue;
|
|
3312
|
+
if (item.kind === "directory") {
|
|
3313
|
+
await options.adapter.workspaceFs.mkdir(item.targetRel);
|
|
3314
|
+
} else {
|
|
3315
|
+
await options.adapter.workspaceFs.copyFromHost(item.source, item.targetRel);
|
|
3316
|
+
}
|
|
3317
|
+
changed = true;
|
|
3318
|
+
} catch (error) {
|
|
3319
|
+
throw new Error(
|
|
3320
|
+
`Failed to seed workspace template file (plugin=${options.pluginId}, template=${options.template.id}, source=${item.hostSourcePath}, targetRel=${item.targetRel}): ${error.message}`,
|
|
3321
|
+
{ cause: error }
|
|
3322
|
+
);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
return changed;
|
|
3326
|
+
}
|
|
3327
|
+
async function seedWorkspaceFiles(options) {
|
|
3328
|
+
let changed = false;
|
|
3329
|
+
for (const plugin of options.plugins) {
|
|
3330
|
+
for (const template of plugin.provisioning?.templateDirs ?? []) {
|
|
3331
|
+
const templateChanged = await seedTemplate({
|
|
3332
|
+
adapter: options.adapter,
|
|
3333
|
+
pluginId: plugin.id,
|
|
3334
|
+
template
|
|
3335
|
+
});
|
|
3336
|
+
changed = changed || templateChanged;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return { changed };
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
// src/server/workspace/provisioning/provisionWorkspaceRuntime.ts
|
|
3343
|
+
async function ensureRuntimeLayout(opts) {
|
|
3344
|
+
let changed = false;
|
|
3345
|
+
const dirs = [
|
|
3346
|
+
BORING_AGENT_DIR,
|
|
3347
|
+
...BORING_AGENT_RUNTIME_DIR_NAMES.map((dir) => `${BORING_AGENT_DIR}/${dir}`)
|
|
3348
|
+
];
|
|
3349
|
+
for (const dir of dirs) {
|
|
3350
|
+
if (!await opts.adapter.workspaceFs.exists(dir)) changed = true;
|
|
3351
|
+
await opts.adapter.workspaceFs.mkdir(dir);
|
|
3352
|
+
}
|
|
3353
|
+
const gitignorePath = `${BORING_AGENT_DIR}/.gitignore`;
|
|
3354
|
+
const currentGitignore = await opts.adapter.workspaceFs.readText(gitignorePath);
|
|
3355
|
+
if (currentGitignore !== BORING_AGENT_GITIGNORE_CONTENT) {
|
|
3356
|
+
await opts.adapter.workspaceFs.writeText(gitignorePath, BORING_AGENT_GITIGNORE_CONTENT);
|
|
3357
|
+
changed = true;
|
|
3358
|
+
}
|
|
3359
|
+
return changed;
|
|
3360
|
+
}
|
|
3361
|
+
function collectNodePackages(plugins) {
|
|
3362
|
+
return plugins.flatMap((plugin) => plugin.provisioning?.nodePackages ?? []);
|
|
3363
|
+
}
|
|
3364
|
+
function collectPythonPackages(plugins) {
|
|
3365
|
+
return plugins.flatMap((plugin) => plugin.provisioning?.python ?? []);
|
|
3366
|
+
}
|
|
3367
|
+
function countSkills(plugins) {
|
|
3368
|
+
return plugins.reduce((count, plugin) => count + (plugin.skills?.length ?? 0), 0);
|
|
3369
|
+
}
|
|
3370
|
+
function countTemplateDirs(plugins) {
|
|
3371
|
+
return plugins.reduce((count, plugin) => count + (plugin.provisioning?.templateDirs?.length ?? 0), 0);
|
|
3372
|
+
}
|
|
3373
|
+
function changedString(result) {
|
|
3374
|
+
const changed = result?.changed;
|
|
3375
|
+
return typeof changed === "boolean" ? changed ? "true" : "false" : void 0;
|
|
3376
|
+
}
|
|
3377
|
+
function telemetryProperties(opts, extra = {}) {
|
|
3378
|
+
return {
|
|
3379
|
+
runtimeMode: opts.telemetryContext?.runtimeMode ?? opts.adapter.mode,
|
|
3380
|
+
workspaceId: opts.telemetryContext?.workspaceId,
|
|
3381
|
+
sessionId: opts.telemetryContext?.sessionId,
|
|
3382
|
+
requestId: opts.telemetryContext?.requestId,
|
|
3383
|
+
...extra
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
function captureProvisioningEvent(opts, name, properties) {
|
|
3387
|
+
if (!opts.telemetry) return;
|
|
3388
|
+
safeCapture(opts.telemetry, {
|
|
3389
|
+
name,
|
|
3390
|
+
properties: telemetryProperties(opts, properties)
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
async function runPhase(options) {
|
|
3394
|
+
const startedAt = Date.now();
|
|
3395
|
+
logProvisioning(options.logger, "info", `workspace provisioning ${options.phase} started`, options.details);
|
|
3396
|
+
try {
|
|
3397
|
+
const result = await options.run();
|
|
3398
|
+
const changed = changedString(result);
|
|
3399
|
+
captureProvisioningEvent(options.opts, "agent.runtime.provisioning.step", {
|
|
3400
|
+
phase: options.telemetryPhase,
|
|
3401
|
+
status: "ok",
|
|
3402
|
+
durationMs: Date.now() - startedAt,
|
|
3403
|
+
...changed ? { changed } : {}
|
|
3404
|
+
});
|
|
3405
|
+
logProvisioning(options.logger, "info", `workspace provisioning ${options.phase} completed`, options.details);
|
|
3406
|
+
return result;
|
|
3407
|
+
} catch (error) {
|
|
3408
|
+
const provisioningError = toProvisioningError(
|
|
3409
|
+
options.code,
|
|
3410
|
+
options.phase,
|
|
3411
|
+
error,
|
|
3412
|
+
options.details
|
|
3413
|
+
);
|
|
3414
|
+
captureProvisioningEvent(options.opts, "agent.runtime.provisioning.step", {
|
|
3415
|
+
phase: options.telemetryPhase,
|
|
3416
|
+
status: "error",
|
|
3417
|
+
durationMs: Date.now() - startedAt,
|
|
3418
|
+
errorCode: provisioningError.code
|
|
3419
|
+
});
|
|
3420
|
+
logProvisioning(options.logger, "error", `workspace provisioning ${options.phase} failed`, {
|
|
3421
|
+
code: provisioningError.code,
|
|
3422
|
+
...provisioningError.details
|
|
3423
|
+
});
|
|
3424
|
+
throw provisioningError;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
async function provisionWorkspaceRuntime(opts) {
|
|
3428
|
+
const logger = opts.logger;
|
|
3429
|
+
const startedAt = Date.now();
|
|
3430
|
+
const pluginIds = opts.plugins.map((plugin) => plugin.id);
|
|
3431
|
+
const nodePackages = collectNodePackages(opts.plugins);
|
|
3432
|
+
const pythonPackages = collectPythonPackages(opts.plugins);
|
|
3433
|
+
const summaryCounts = {
|
|
3434
|
+
nodePackageCount: nodePackages.length,
|
|
3435
|
+
pythonPackageCount: pythonPackages.length,
|
|
3436
|
+
skillCount: countSkills(opts.plugins),
|
|
3437
|
+
templateDirCount: countTemplateDirs(opts.plugins)
|
|
3438
|
+
};
|
|
3439
|
+
captureProvisioningEvent(opts, "agent.runtime.provisioning.started", {
|
|
3440
|
+
status: "started",
|
|
3441
|
+
...summaryCounts
|
|
3442
|
+
});
|
|
3443
|
+
try {
|
|
3444
|
+
const layoutChanged = await runPhase({
|
|
3445
|
+
opts,
|
|
3446
|
+
logger,
|
|
3447
|
+
phase: "layout",
|
|
3448
|
+
telemetryPhase: "layout",
|
|
3449
|
+
code: ErrorCode.enum.PROVISIONING_LAYOUT_FAILED,
|
|
3450
|
+
details: { workspaceRoot: opts.runtimeLayout.workspaceRoot },
|
|
3451
|
+
run: () => ensureRuntimeLayout(opts)
|
|
3452
|
+
});
|
|
3453
|
+
const skills = await runPhase({
|
|
3454
|
+
opts,
|
|
3455
|
+
logger,
|
|
3456
|
+
phase: "skill mirror",
|
|
3457
|
+
telemetryPhase: "skills-mirror",
|
|
3458
|
+
code: ErrorCode.enum.PROVISIONING_SKILLS_FAILED,
|
|
3459
|
+
details: { workspaceRoot: opts.runtimeLayout.workspaceRoot, pluginIds },
|
|
3460
|
+
run: () => mirrorPluginSkills({
|
|
3461
|
+
plugins: opts.plugins,
|
|
3462
|
+
adapter: opts.adapter,
|
|
3463
|
+
runtimeLayout: opts.runtimeLayout
|
|
3464
|
+
})
|
|
3465
|
+
});
|
|
3466
|
+
const workspaceFiles = await runPhase({
|
|
3467
|
+
opts,
|
|
3468
|
+
logger,
|
|
3469
|
+
phase: "workspace files",
|
|
3470
|
+
telemetryPhase: "workspace-files",
|
|
3471
|
+
code: ErrorCode.enum.PROVISIONING_TEMPLATES_FAILED,
|
|
3472
|
+
details: { workspaceRoot: opts.runtimeLayout.workspaceRoot, pluginIds },
|
|
3473
|
+
run: () => seedWorkspaceFiles({
|
|
3474
|
+
plugins: opts.plugins,
|
|
3475
|
+
adapter: opts.adapter
|
|
3476
|
+
})
|
|
3477
|
+
});
|
|
3478
|
+
const node = await runPhase({
|
|
3479
|
+
opts,
|
|
3480
|
+
logger,
|
|
3481
|
+
phase: "node packages",
|
|
3482
|
+
telemetryPhase: "node-packages",
|
|
3483
|
+
code: ErrorCode.enum.PROVISIONING_NPM_INSTALL_FAILED,
|
|
3484
|
+
details: { workspaceRoot: opts.runtimeLayout.workspaceRoot, packageIds: nodePackages.map((pkg) => pkg.id) },
|
|
3485
|
+
run: () => ensureNodeRuntime({
|
|
3486
|
+
adapter: opts.adapter,
|
|
3487
|
+
runtimeLayout: opts.runtimeLayout,
|
|
3488
|
+
packages: nodePackages
|
|
3489
|
+
})
|
|
3490
|
+
});
|
|
3491
|
+
const python = await runPhase({
|
|
3492
|
+
opts,
|
|
3493
|
+
logger,
|
|
3494
|
+
phase: "python packages",
|
|
3495
|
+
telemetryPhase: "python-packages",
|
|
3496
|
+
code: ErrorCode.enum.PROVISIONING_UV_INSTALL_FAILED,
|
|
3497
|
+
details: { workspaceRoot: opts.runtimeLayout.workspaceRoot, packageIds: pythonPackages.map((pkg) => pkg.id) },
|
|
3498
|
+
run: () => ensurePythonRuntime({
|
|
3499
|
+
adapter: opts.adapter,
|
|
3500
|
+
runtimeLayout: opts.runtimeLayout,
|
|
3501
|
+
packages: pythonPackages
|
|
3502
|
+
})
|
|
3503
|
+
});
|
|
3504
|
+
const result = {
|
|
3505
|
+
changed: layoutChanged || skills.changed || workspaceFiles.changed || node.changed || python.changed,
|
|
3506
|
+
env: {
|
|
3507
|
+
...getBoringAgentRuntimeEnv(opts.runtimeLayout, opts.adapter.getRuntimeCacheRoot()),
|
|
3508
|
+
...python.env
|
|
3509
|
+
},
|
|
3510
|
+
pathEntries: getBoringAgentPathEntries(opts.runtimeLayout),
|
|
3511
|
+
skillPaths: skills.skillPaths
|
|
3512
|
+
};
|
|
3513
|
+
captureProvisioningEvent(opts, "agent.runtime.provisioning.completed", {
|
|
3514
|
+
status: "ok",
|
|
3515
|
+
durationMs: Date.now() - startedAt,
|
|
3516
|
+
changed: result.changed ? "true" : "false",
|
|
3517
|
+
...summaryCounts
|
|
3518
|
+
});
|
|
3519
|
+
return result;
|
|
3520
|
+
} catch (error) {
|
|
3521
|
+
const code = error?.code;
|
|
3522
|
+
captureProvisioningEvent(opts, "agent.runtime.provisioning.failed", {
|
|
3523
|
+
status: "error",
|
|
3524
|
+
durationMs: Date.now() - startedAt,
|
|
3525
|
+
...typeof code === "string" ? { errorCode: code } : {},
|
|
3526
|
+
...summaryCounts
|
|
3527
|
+
});
|
|
3528
|
+
throw error;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
|
|
2692
3532
|
// src/server/runtime/resolveMode.ts
|
|
2693
3533
|
import { spawnSync } from "child_process";
|
|
2694
3534
|
|
|
2695
3535
|
// src/server/runtime/modes/direct.ts
|
|
2696
|
-
import { mkdir as
|
|
3536
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
2697
3537
|
|
|
2698
3538
|
// src/server/runtime/createServerFileSearch.ts
|
|
2699
3539
|
var DEFAULT_LIMIT = 500;
|
|
@@ -2751,28 +3591,14 @@ function createServerFileSearch(workspace, sandbox) {
|
|
|
2751
3591
|
}
|
|
2752
3592
|
|
|
2753
3593
|
// src/server/workspace/provision.ts
|
|
2754
|
-
import { cp as cp2
|
|
2755
|
-
import { dirname as dirname6, join as join4 } from "path";
|
|
2756
|
-
var PROVISION_MARKER_REL_PATH = ".boring-agent/provisioned";
|
|
2757
|
-
async function fileExists(targetPath) {
|
|
2758
|
-
try {
|
|
2759
|
-
await stat4(targetPath);
|
|
2760
|
-
return true;
|
|
2761
|
-
} catch (error) {
|
|
2762
|
-
if (error.code === "ENOENT") {
|
|
2763
|
-
return false;
|
|
2764
|
-
}
|
|
2765
|
-
throw error;
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
3594
|
+
import { cp as cp2 } from "fs/promises";
|
|
2768
3595
|
async function copyTemplate(templatePath, workspaceRoot) {
|
|
2769
3596
|
if (!templatePath) return;
|
|
2770
|
-
const markerPath = join4(workspaceRoot, PROVISION_MARKER_REL_PATH);
|
|
2771
|
-
if (await fileExists(markerPath)) return;
|
|
2772
3597
|
try {
|
|
2773
3598
|
await cp2(templatePath, workspaceRoot, {
|
|
2774
3599
|
recursive: true,
|
|
2775
|
-
errorOnExist: false
|
|
3600
|
+
errorOnExist: false,
|
|
3601
|
+
force: false
|
|
2776
3602
|
});
|
|
2777
3603
|
} catch (error) {
|
|
2778
3604
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2781,16 +3607,230 @@ async function copyTemplate(templatePath, workspaceRoot) {
|
|
|
2781
3607
|
{ cause: error }
|
|
2782
3608
|
);
|
|
2783
3609
|
}
|
|
2784
|
-
|
|
2785
|
-
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
// src/server/runtime/modes/provisioningAdapter.ts
|
|
3613
|
+
import { spawn as spawn3 } from "child_process";
|
|
3614
|
+
import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readFile as readFile6, realpath as realpath3, rm as rm2, stat as stat6, writeFile as writeFile5 } from "fs/promises";
|
|
3615
|
+
import { dirname as dirname8, isAbsolute as isAbsolute5, relative as relative8, resolve as resolve5, sep as sep5 } from "path";
|
|
3616
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
3617
|
+
var LOCAL_SANDBOX_WORKSPACE_ROOT = "/workspace";
|
|
3618
|
+
function sourceToPath4(source) {
|
|
3619
|
+
return source instanceof URL ? fileURLToPath5(source) : source;
|
|
3620
|
+
}
|
|
3621
|
+
async function assertExistingInsideWorkspace(root, relPath) {
|
|
3622
|
+
const absPath = validatePath(root, relPath);
|
|
3623
|
+
try {
|
|
3624
|
+
await assertRealPathWithinWorkspace(root, absPath);
|
|
3625
|
+
return absPath;
|
|
3626
|
+
} catch (error) {
|
|
3627
|
+
if (error.code === "ENOENT") return null;
|
|
3628
|
+
throw error;
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
async function prepareWritablePath(root, relPath) {
|
|
3632
|
+
const absPath = validatePath(root, relPath);
|
|
3633
|
+
await mkdir6(dirname8(absPath), { recursive: true });
|
|
3634
|
+
await assertRealPathWithinWorkspace(root, dirname8(absPath));
|
|
3635
|
+
try {
|
|
3636
|
+
const targetStat = await lstat3(absPath);
|
|
3637
|
+
if (targetStat.isSymbolicLink()) {
|
|
3638
|
+
throw Object.assign(new Error("Target path is a symlink"), {
|
|
3639
|
+
statusCode: 400,
|
|
3640
|
+
reason: "symlink-escape",
|
|
3641
|
+
requestedPath: relPath
|
|
3642
|
+
});
|
|
3643
|
+
}
|
|
3644
|
+
} catch (error) {
|
|
3645
|
+
if (error.code !== "ENOENT") throw error;
|
|
3646
|
+
}
|
|
3647
|
+
return absPath;
|
|
3648
|
+
}
|
|
3649
|
+
async function spawnCommand(command, args, opts) {
|
|
3650
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
3651
|
+
const child = spawn3(command, args, {
|
|
3652
|
+
cwd: opts.cwd,
|
|
3653
|
+
env: { ...process.env, ...opts.env },
|
|
3654
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3655
|
+
shell: false,
|
|
3656
|
+
windowsHide: true
|
|
3657
|
+
});
|
|
3658
|
+
const stdout = [];
|
|
3659
|
+
const stderr = [];
|
|
3660
|
+
let timeout = null;
|
|
3661
|
+
let settled = false;
|
|
3662
|
+
const settle = (error) => {
|
|
3663
|
+
if (settled) return;
|
|
3664
|
+
settled = true;
|
|
3665
|
+
if (timeout) clearTimeout(timeout);
|
|
3666
|
+
if (error) rejectPromise(error);
|
|
3667
|
+
else resolvePromise({
|
|
3668
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
3669
|
+
stderr: Buffer.concat(stderr).toString("utf8")
|
|
3670
|
+
});
|
|
3671
|
+
};
|
|
3672
|
+
child.stdout?.on("data", (chunk2) => stdout.push(chunk2));
|
|
3673
|
+
child.stderr?.on("data", (chunk2) => stderr.push(chunk2));
|
|
3674
|
+
child.on("error", settle);
|
|
3675
|
+
child.on("close", (code) => {
|
|
3676
|
+
if (code === 0) {
|
|
3677
|
+
settle();
|
|
3678
|
+
return;
|
|
3679
|
+
}
|
|
3680
|
+
const message = Buffer.concat(stderr).toString("utf8").trim();
|
|
3681
|
+
settle(new Error(`Command failed (${command}) with exit code ${code ?? "unknown"}${message ? `: ${message}` : ""}`));
|
|
3682
|
+
});
|
|
3683
|
+
timeout = setTimeout(() => {
|
|
3684
|
+
child.kill("SIGTERM");
|
|
3685
|
+
settle(new Error(`Command timed out after ${opts.timeoutMs}ms: ${command}`));
|
|
3686
|
+
}, opts.timeoutMs);
|
|
3687
|
+
});
|
|
3688
|
+
}
|
|
3689
|
+
function defaultExecOptions(paths, opts) {
|
|
3690
|
+
return {
|
|
3691
|
+
cwd: opts?.cwd ?? paths.workspaceRoot,
|
|
3692
|
+
env: opts?.env ?? {},
|
|
3693
|
+
timeoutMs: opts?.timeoutMs ?? 12e4
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
function mapWorkspacePathToLocalSandbox(paths, value) {
|
|
3697
|
+
const absolute = isAbsolute5(value) ? value : resolve5(paths.workspaceRoot, value);
|
|
3698
|
+
const relPath = relative8(paths.workspaceRoot, absolute);
|
|
3699
|
+
if (relPath === "") return LOCAL_SANDBOX_WORKSPACE_ROOT;
|
|
3700
|
+
if (relPath === ".." || relPath.startsWith(`..${sep5}`)) return value;
|
|
3701
|
+
return `${LOCAL_SANDBOX_WORKSPACE_ROOT}/${relPath.split(sep5).join("/")}`;
|
|
3702
|
+
}
|
|
3703
|
+
function mapValueToLocalSandbox(paths, value) {
|
|
3704
|
+
return value.startsWith(paths.workspaceRoot) ? mapWorkspacePathToLocalSandbox(paths, value) : value;
|
|
3705
|
+
}
|
|
3706
|
+
function mapEnvToLocalSandbox(paths, env) {
|
|
3707
|
+
return Object.fromEntries(
|
|
3708
|
+
Object.entries(env).map(([key, value]) => [key, mapValueToLocalSandbox(paths, value)])
|
|
3709
|
+
);
|
|
3710
|
+
}
|
|
3711
|
+
function sanitizeInstallSourcePart(value) {
|
|
3712
|
+
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3713
|
+
return sanitized.length > 0 ? sanitized : "source";
|
|
3714
|
+
}
|
|
3715
|
+
async function copyExternalSourceIntoWorkspace(paths, sourcePath, opts) {
|
|
3716
|
+
const fingerprint2 = opts.fingerprint.replace(/^sha256:/, "");
|
|
3717
|
+
const relTarget = `.boring-agent/tmp/${sanitizeInstallSourcePart(opts.kind)}-${sanitizeInstallSourcePart(opts.id)}-${sanitizeInstallSourcePart(fingerprint2)}-source`;
|
|
3718
|
+
const absTarget = validatePath(paths.workspaceRoot, relTarget);
|
|
3719
|
+
await rm2(absTarget, { recursive: true, force: true });
|
|
3720
|
+
await mkdir6(dirname8(absTarget), { recursive: true });
|
|
3721
|
+
await assertRealPathWithinWorkspace(paths.workspaceRoot, dirname8(absTarget));
|
|
3722
|
+
const sourceStat = await stat6(sourcePath);
|
|
3723
|
+
await cp3(sourcePath, absTarget, {
|
|
3724
|
+
recursive: sourceStat.isDirectory(),
|
|
3725
|
+
force: false,
|
|
3726
|
+
errorOnExist: true
|
|
3727
|
+
});
|
|
3728
|
+
return `${LOCAL_SANDBOX_WORKSPACE_ROOT}/${relTarget}`;
|
|
3729
|
+
}
|
|
3730
|
+
function createWorkspaceFs(workspaceRoot) {
|
|
3731
|
+
return {
|
|
3732
|
+
async exists(workspaceRelativePath) {
|
|
3733
|
+
const absPath = await assertExistingInsideWorkspace(workspaceRoot, workspaceRelativePath);
|
|
3734
|
+
if (!absPath) return false;
|
|
3735
|
+
await lstat3(absPath);
|
|
3736
|
+
return true;
|
|
3737
|
+
},
|
|
3738
|
+
async rm(workspaceRelativePath) {
|
|
3739
|
+
const absPath = await assertExistingInsideWorkspace(workspaceRoot, workspaceRelativePath);
|
|
3740
|
+
if (!absPath) return;
|
|
3741
|
+
await rm2(absPath, { recursive: true, force: true });
|
|
3742
|
+
},
|
|
3743
|
+
async mkdir(workspaceRelativePath) {
|
|
3744
|
+
const absPath = validatePath(workspaceRoot, workspaceRelativePath);
|
|
3745
|
+
await mkdir6(absPath, { recursive: true });
|
|
3746
|
+
await assertRealPathWithinWorkspace(workspaceRoot, absPath);
|
|
3747
|
+
},
|
|
3748
|
+
async writeText(workspaceRelativePath, content) {
|
|
3749
|
+
const absPath = await prepareWritablePath(workspaceRoot, workspaceRelativePath);
|
|
3750
|
+
await writeFile5(absPath, content, "utf8");
|
|
3751
|
+
},
|
|
3752
|
+
async readText(workspaceRelativePath) {
|
|
3753
|
+
const absPath = await assertExistingInsideWorkspace(workspaceRoot, workspaceRelativePath);
|
|
3754
|
+
if (!absPath) return null;
|
|
3755
|
+
return await readFile6(absPath, "utf8");
|
|
3756
|
+
},
|
|
3757
|
+
async copyFromHost(hostSourcePath, workspaceRelativeTarget) {
|
|
3758
|
+
const sourcePath = sourceToPath4(hostSourcePath);
|
|
3759
|
+
const absTarget = await prepareWritablePath(workspaceRoot, workspaceRelativeTarget);
|
|
3760
|
+
const sourceStat = await stat6(sourcePath);
|
|
3761
|
+
await cp3(sourcePath, absTarget, {
|
|
3762
|
+
recursive: sourceStat.isDirectory(),
|
|
3763
|
+
force: false,
|
|
3764
|
+
errorOnExist: true
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
function createDirectProvisioningAdapter(paths, runner = spawnCommand) {
|
|
3770
|
+
return {
|
|
3771
|
+
mode: "direct",
|
|
3772
|
+
async exec(command, args, opts) {
|
|
3773
|
+
return await runner(command, args, defaultExecOptions(paths, opts));
|
|
3774
|
+
},
|
|
3775
|
+
async resolveInstallSource(source) {
|
|
3776
|
+
return sourceToPath4(source);
|
|
3777
|
+
},
|
|
3778
|
+
workspaceFs: createWorkspaceFs(paths.workspaceRoot),
|
|
3779
|
+
getRuntimeCacheRoot() {
|
|
3780
|
+
return paths.cache;
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
function createLocalProvisioningAdapter(paths, runner = spawnCommand) {
|
|
3785
|
+
const sourceMounts = /* @__PURE__ */ new Map();
|
|
3786
|
+
return {
|
|
3787
|
+
mode: "local",
|
|
3788
|
+
async exec(command, args, opts) {
|
|
3789
|
+
const execOpts = defaultExecOptions(paths, opts);
|
|
3790
|
+
const bwrapArgs = buildBwrapArgs(paths.workspaceRoot, {
|
|
3791
|
+
extraArgs: [
|
|
3792
|
+
"--dir",
|
|
3793
|
+
"/mnt",
|
|
3794
|
+
"--dir",
|
|
3795
|
+
"/mnt/boring-agent-sources",
|
|
3796
|
+
...[...sourceMounts.entries()].flatMap(([host, sandbox]) => ["--ro-bind", host, sandbox])
|
|
3797
|
+
]
|
|
3798
|
+
});
|
|
3799
|
+
return await runner("bwrap", [
|
|
3800
|
+
...bwrapArgs,
|
|
3801
|
+
mapValueToLocalSandbox(paths, command),
|
|
3802
|
+
...args.map((arg) => mapValueToLocalSandbox(paths, arg))
|
|
3803
|
+
], {
|
|
3804
|
+
...execOpts,
|
|
3805
|
+
cwd: paths.workspaceRoot,
|
|
3806
|
+
env: mapEnvToLocalSandbox(paths, execOpts.env)
|
|
3807
|
+
});
|
|
3808
|
+
},
|
|
3809
|
+
async resolveInstallSource(source, opts) {
|
|
3810
|
+
const hostPath = sourceToPath4(source);
|
|
3811
|
+
const realWorkspaceRoot = await realpath3(paths.workspaceRoot);
|
|
3812
|
+
const realSource = await realpath3(hostPath);
|
|
3813
|
+
const relPath = relative8(realWorkspaceRoot, realSource);
|
|
3814
|
+
if (relPath === "") return LOCAL_SANDBOX_WORKSPACE_ROOT;
|
|
3815
|
+
if (!relPath.startsWith("..") && !isAbsolute5(relPath)) {
|
|
3816
|
+
return `${LOCAL_SANDBOX_WORKSPACE_ROOT}/${relPath.split(sep5).join("/")}`;
|
|
3817
|
+
}
|
|
3818
|
+
return await copyExternalSourceIntoWorkspace(paths, realSource, opts);
|
|
3819
|
+
},
|
|
3820
|
+
workspaceFs: createWorkspaceFs(paths.workspaceRoot),
|
|
3821
|
+
getRuntimeCacheRoot() {
|
|
3822
|
+
return paths.cache;
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
2786
3825
|
}
|
|
2787
3826
|
|
|
2788
3827
|
// src/server/runtime/modes/direct.ts
|
|
2789
3828
|
var directModeAdapter = {
|
|
2790
3829
|
id: "direct",
|
|
2791
3830
|
workspaceFsCapability: "strong",
|
|
3831
|
+
createProvisioningAdapter: (runtimeLayout) => createDirectProvisioningAdapter(runtimeLayout),
|
|
2792
3832
|
async create(ctx) {
|
|
2793
|
-
await
|
|
3833
|
+
await mkdir7(ctx.workspaceRoot, { recursive: true });
|
|
2794
3834
|
await copyTemplate(ctx.templatePath, ctx.workspaceRoot);
|
|
2795
3835
|
const workspace = createNodeWorkspace(ctx.workspaceRoot);
|
|
2796
3836
|
const sandbox = createDirectSandbox();
|
|
@@ -2804,15 +3844,16 @@ var directModeAdapter = {
|
|
|
2804
3844
|
};
|
|
2805
3845
|
|
|
2806
3846
|
// src/server/runtime/modes/local.ts
|
|
2807
|
-
import { mkdir as
|
|
3847
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
2808
3848
|
var localModeAdapter = {
|
|
2809
3849
|
id: "local",
|
|
2810
3850
|
workspaceFsCapability: "strong",
|
|
3851
|
+
createProvisioningAdapter: (runtimeLayout) => createLocalProvisioningAdapter(runtimeLayout),
|
|
2811
3852
|
async create(ctx) {
|
|
2812
3853
|
if (process.platform !== "linux") {
|
|
2813
3854
|
throw new Error("local mode requires Linux with bubblewrap");
|
|
2814
3855
|
}
|
|
2815
|
-
await
|
|
3856
|
+
await mkdir8(ctx.workspaceRoot, { recursive: true });
|
|
2816
3857
|
await copyTemplate(ctx.templatePath, ctx.workspaceRoot);
|
|
2817
3858
|
const workspace = createNodeWorkspace(ctx.workspaceRoot);
|
|
2818
3859
|
const sandbox = createBwrapSandbox();
|
|
@@ -2826,6 +3867,11 @@ var localModeAdapter = {
|
|
|
2826
3867
|
};
|
|
2827
3868
|
|
|
2828
3869
|
// src/server/runtime/modes/vercel-sandbox.ts
|
|
3870
|
+
import { execFile as execFile2 } from "child_process";
|
|
3871
|
+
import { mkdir as mkdir9, readFile as readFile8, readdir as readdir5, rename as rename5, stat as stat7 } from "fs/promises";
|
|
3872
|
+
import { dirname as dirname9, join as join8 } from "path";
|
|
3873
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
3874
|
+
import { promisify as promisify2 } from "util";
|
|
2829
3875
|
import { Sandbox as VercelSandbox } from "@vercel/sandbox";
|
|
2830
3876
|
|
|
2831
3877
|
// src/server/sandbox/vercel-sandbox/createVercelSandboxExec.ts
|
|
@@ -2980,8 +4026,8 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
|
2980
4026
|
}
|
|
2981
4027
|
|
|
2982
4028
|
// src/server/sandbox/vercel-sandbox/packageTemplate.ts
|
|
2983
|
-
import { createHash as
|
|
2984
|
-
import { readFile as
|
|
4029
|
+
import { createHash as createHash5 } from "crypto";
|
|
4030
|
+
import { readFile as readFile7, readdir as readdir4 } from "fs/promises";
|
|
2985
4031
|
import path3 from "path";
|
|
2986
4032
|
import { Readable } from "stream";
|
|
2987
4033
|
import { createGzip } from "zlib";
|
|
@@ -2994,7 +4040,7 @@ function log2(msg, meta = {}) {
|
|
|
2994
4040
|
`);
|
|
2995
4041
|
}
|
|
2996
4042
|
async function collectFiles(dir, base = "") {
|
|
2997
|
-
const entries = await
|
|
4043
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
2998
4044
|
const files = [];
|
|
2999
4045
|
for (const entry of entries) {
|
|
3000
4046
|
const fullPath = path3.join(dir, entry.name);
|
|
@@ -3002,13 +4048,13 @@ async function collectFiles(dir, base = "") {
|
|
|
3002
4048
|
if (entry.isDirectory()) {
|
|
3003
4049
|
files.push(...await collectFiles(fullPath, relPath));
|
|
3004
4050
|
} else if (entry.isFile()) {
|
|
3005
|
-
files.push({ rel: relPath, content: await
|
|
4051
|
+
files.push({ rel: relPath, content: await readFile7(fullPath) });
|
|
3006
4052
|
}
|
|
3007
4053
|
}
|
|
3008
4054
|
return files.sort((a, b) => a.rel.localeCompare(b.rel));
|
|
3009
4055
|
}
|
|
3010
4056
|
function computeTemplateHash(files) {
|
|
3011
|
-
const hash =
|
|
4057
|
+
const hash = createHash5("sha256");
|
|
3012
4058
|
for (const file of files) {
|
|
3013
4059
|
hash.update(file.rel);
|
|
3014
4060
|
hash.update("\0");
|
|
@@ -3049,11 +4095,11 @@ async function buildTarGz(files) {
|
|
|
3049
4095
|
}
|
|
3050
4096
|
chunks.push(Buffer.alloc(1024));
|
|
3051
4097
|
const tarBuffer = Buffer.concat(chunks);
|
|
3052
|
-
return new Promise((
|
|
4098
|
+
return new Promise((resolve9, reject) => {
|
|
3053
4099
|
const gzip = createGzip({ level: 6 });
|
|
3054
4100
|
const gzChunks = [];
|
|
3055
4101
|
gzip.on("data", (chunk2) => gzChunks.push(chunk2));
|
|
3056
|
-
gzip.on("end", () =>
|
|
4102
|
+
gzip.on("end", () => resolve9(Buffer.concat(gzChunks)));
|
|
3057
4103
|
gzip.on("error", reject);
|
|
3058
4104
|
Readable.from(tarBuffer).pipe(gzip);
|
|
3059
4105
|
});
|
|
@@ -3091,6 +4137,46 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
3091
4137
|
// src/server/runtime/modes/vercel-sandbox.ts
|
|
3092
4138
|
var ORPHAN_GUARD_MAX_IDLE_MS = 24 * 60 * 60 * 1e3;
|
|
3093
4139
|
var VERCEL_SANDBOX_TIMEOUT_MS_ENV = "BORING_AGENT_VERCEL_SANDBOX_TIMEOUT_MS";
|
|
4140
|
+
var VERCEL_SANDBOX_RUNTIME_ENV = "BORING_AGENT_VERCEL_SANDBOX_RUNTIME";
|
|
4141
|
+
var DEFAULT_VERCEL_SANDBOX_RUNTIME = "node24";
|
|
4142
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
4143
|
+
function sandboxTelemetryProperties(ctx, extra = {}) {
|
|
4144
|
+
return {
|
|
4145
|
+
runtimeMode: "vercel-sandbox",
|
|
4146
|
+
workspaceId: ctx?.workspaceId,
|
|
4147
|
+
sessionId: ctx?.sessionId,
|
|
4148
|
+
requestId: ctx?.requestId,
|
|
4149
|
+
...extra
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
function captureSandboxSetupEvent(telemetry, ctx, name, properties) {
|
|
4153
|
+
if (!telemetry) return;
|
|
4154
|
+
safeCapture(telemetry, {
|
|
4155
|
+
name,
|
|
4156
|
+
properties: sandboxTelemetryProperties(ctx, properties)
|
|
4157
|
+
});
|
|
4158
|
+
}
|
|
4159
|
+
async function runSandboxSetupStep(options) {
|
|
4160
|
+
const startedAt = Date.now();
|
|
4161
|
+
try {
|
|
4162
|
+
const result = await options.run();
|
|
4163
|
+
captureSandboxSetupEvent(options.telemetry, options.ctx, "agent.runtime.sandbox.setup.step", {
|
|
4164
|
+
phase: options.phase,
|
|
4165
|
+
status: "ok",
|
|
4166
|
+
durationMs: Date.now() - startedAt
|
|
4167
|
+
});
|
|
4168
|
+
return result;
|
|
4169
|
+
} catch (error) {
|
|
4170
|
+
const code = error?.code;
|
|
4171
|
+
captureSandboxSetupEvent(options.telemetry, options.ctx, "agent.runtime.sandbox.setup.step", {
|
|
4172
|
+
phase: options.phase,
|
|
4173
|
+
status: "error",
|
|
4174
|
+
durationMs: Date.now() - startedAt,
|
|
4175
|
+
...typeof code === "string" ? { errorCode: code } : {}
|
|
4176
|
+
});
|
|
4177
|
+
throw error;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
3094
4180
|
function createDefaultVercelClient(auth, opts = {}) {
|
|
3095
4181
|
const credentials = {
|
|
3096
4182
|
token: auth.token,
|
|
@@ -3103,12 +4189,14 @@ function createDefaultVercelClient(auth, opts = {}) {
|
|
|
3103
4189
|
const base = {
|
|
3104
4190
|
...createOptions,
|
|
3105
4191
|
...params?.name ? { name: params.name } : {},
|
|
4192
|
+
...opts.runtime ? { runtime: opts.runtime } : {},
|
|
3106
4193
|
persistent: params?.persistent ?? true,
|
|
3107
4194
|
snapshotExpiration: params?.snapshotExpiration ?? 0
|
|
3108
4195
|
};
|
|
3109
4196
|
if (params?.source?.type === "snapshot") {
|
|
4197
|
+
const { runtime: _runtime, ...snapshotBase } = base;
|
|
3110
4198
|
return await VercelSandbox.create({
|
|
3111
|
-
...
|
|
4199
|
+
...snapshotBase,
|
|
3112
4200
|
source: { type: "snapshot", snapshotId: params.source.snapshotId }
|
|
3113
4201
|
});
|
|
3114
4202
|
}
|
|
@@ -3180,6 +4268,161 @@ async function seedTemplateIntoVercelWorkspace(workspace, templatePath, logger)
|
|
|
3180
4268
|
fileCount: files.length
|
|
3181
4269
|
});
|
|
3182
4270
|
}
|
|
4271
|
+
function provisioningSourceToPath(source) {
|
|
4272
|
+
return source instanceof URL ? fileURLToPath6(source) : source;
|
|
4273
|
+
}
|
|
4274
|
+
async function prepareVercelProvisioningArtifact(request) {
|
|
4275
|
+
const sourcePath = provisioningSourceToPath(request.source);
|
|
4276
|
+
await mkdir9(dirname9(request.outputPath), { recursive: true });
|
|
4277
|
+
if (request.kind === "node") {
|
|
4278
|
+
const { stdout } = await execFileAsync2("npm", [
|
|
4279
|
+
"pack",
|
|
4280
|
+
sourcePath,
|
|
4281
|
+
"--pack-destination",
|
|
4282
|
+
dirname9(request.outputPath)
|
|
4283
|
+
], { maxBuffer: 1024 * 1024 * 20 });
|
|
4284
|
+
const packedName = stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
4285
|
+
if (!packedName) throw new Error(`npm pack produced no artifact for ${sourcePath}`);
|
|
4286
|
+
await rename5(join8(dirname9(request.outputPath), packedName), request.outputPath);
|
|
4287
|
+
return;
|
|
4288
|
+
}
|
|
4289
|
+
await execFileAsync2("tar", ["-czf", request.outputPath, "-C", sourcePath, "."], {
|
|
4290
|
+
maxBuffer: 1024 * 1024 * 20
|
|
4291
|
+
});
|
|
4292
|
+
}
|
|
4293
|
+
function shellSingleQuote(value) {
|
|
4294
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4295
|
+
}
|
|
4296
|
+
async function copyHostPathToVercelWorkspace(options) {
|
|
4297
|
+
const sourcePath = provisioningSourceToPath(options.source);
|
|
4298
|
+
const sourceStat = await stat7(sourcePath);
|
|
4299
|
+
if (sourceStat.isDirectory()) {
|
|
4300
|
+
await options.workspace.mkdir(options.targetRel, { recursive: true });
|
|
4301
|
+
for (const entry of await readdir5(sourcePath, { withFileTypes: true })) {
|
|
4302
|
+
await copyHostPathToVercelWorkspace({
|
|
4303
|
+
workspace: options.workspace,
|
|
4304
|
+
source: join8(sourcePath, entry.name),
|
|
4305
|
+
targetRel: `${options.targetRel}/${entry.name}`
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
if (!sourceStat.isFile()) return;
|
|
4311
|
+
const bytes = new Uint8Array(await readFile8(sourcePath));
|
|
4312
|
+
if (options.workspace.writeBinaryFile) await options.workspace.writeBinaryFile(options.targetRel, bytes);
|
|
4313
|
+
else await options.workspace.writeFile(options.targetRel, Buffer.from(bytes).toString("utf8"));
|
|
4314
|
+
}
|
|
4315
|
+
function isMissingWorkspacePathError(error) {
|
|
4316
|
+
if (error.code === "ENOENT") return true;
|
|
4317
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4318
|
+
return /\bENOENT\b|no such file|file not found/i.test(message);
|
|
4319
|
+
}
|
|
4320
|
+
function createVercelWorkspaceFs(options) {
|
|
4321
|
+
const exists2 = async (rel) => {
|
|
4322
|
+
try {
|
|
4323
|
+
await options.workspace.stat(rel);
|
|
4324
|
+
return true;
|
|
4325
|
+
} catch (error) {
|
|
4326
|
+
if (isMissingWorkspacePathError(error)) return false;
|
|
4327
|
+
throw error;
|
|
4328
|
+
}
|
|
4329
|
+
};
|
|
4330
|
+
return {
|
|
4331
|
+
exists: exists2,
|
|
4332
|
+
async rm(rel) {
|
|
4333
|
+
const targetExists = await exists2(rel);
|
|
4334
|
+
if (!targetExists) return;
|
|
4335
|
+
const target = `${VERCEL_SANDBOX_REMOTE_ROOT}/${rel.replace(/^\/+/, "")}`;
|
|
4336
|
+
const result = await options.sandbox.runCommand({
|
|
4337
|
+
cmd: "sh",
|
|
4338
|
+
args: ["-c", `rm -rf -- ${shellSingleQuote(target)}`]
|
|
4339
|
+
});
|
|
4340
|
+
if ((result.exitCode ?? 1) !== 0) {
|
|
4341
|
+
const err = await (result.stderr?.() ?? Promise.resolve(""));
|
|
4342
|
+
throw new Error(err || `failed to remove ${rel}`);
|
|
4343
|
+
}
|
|
4344
|
+
},
|
|
4345
|
+
async mkdir(rel) {
|
|
4346
|
+
await options.workspace.mkdir(rel, { recursive: true });
|
|
4347
|
+
},
|
|
4348
|
+
async writeText(rel, content) {
|
|
4349
|
+
await options.workspace.writeFile(rel, content);
|
|
4350
|
+
},
|
|
4351
|
+
async readText(rel) {
|
|
4352
|
+
try {
|
|
4353
|
+
return await options.workspace.readFile(rel);
|
|
4354
|
+
} catch (error) {
|
|
4355
|
+
if (isMissingWorkspacePathError(error)) return null;
|
|
4356
|
+
throw error;
|
|
4357
|
+
}
|
|
4358
|
+
},
|
|
4359
|
+
async copyFromHost(source, targetRel) {
|
|
4360
|
+
await copyHostPathToVercelWorkspace({ workspace: options.workspace, source, targetRel });
|
|
4361
|
+
}
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
async function ensureVercelProvisioningParts(options) {
|
|
4365
|
+
const auth = resolveVercelAuth(options.getEnvVar);
|
|
4366
|
+
const teamId = requireEnvVar("VERCEL_TEAM_ID", options.getEnvVar);
|
|
4367
|
+
const projectId = options.getEnvVar("VERCEL_PROJECT_ID")?.trim();
|
|
4368
|
+
const timeoutMs = readOptionalPositiveIntegerEnv(VERCEL_SANDBOX_TIMEOUT_MS_ENV, options.getEnvVar);
|
|
4369
|
+
const runtime = options.getEnvVar(VERCEL_SANDBOX_RUNTIME_ENV)?.trim() || DEFAULT_VERCEL_SANDBOX_RUNTIME;
|
|
4370
|
+
const vercelClient = options.vercelClient ?? createDefaultVercelClient({
|
|
4371
|
+
token: auth.token,
|
|
4372
|
+
teamId,
|
|
4373
|
+
projectId
|
|
4374
|
+
}, { timeoutMs, runtime });
|
|
4375
|
+
const workspaceId = options.ctx?.workspaceId ?? options.ctx?.workspaceRoot ?? options.runtimeLayout.workspaceRoot;
|
|
4376
|
+
const telemetry = options.ctx?.telemetry;
|
|
4377
|
+
const totalStartedAt = Date.now();
|
|
4378
|
+
captureSandboxSetupEvent(telemetry, options.ctx, "agent.runtime.sandbox.setup.started", {
|
|
4379
|
+
status: "started"
|
|
4380
|
+
});
|
|
4381
|
+
try {
|
|
4382
|
+
const sandboxHandle = await runSandboxSetupStep({
|
|
4383
|
+
telemetry,
|
|
4384
|
+
ctx: options.ctx,
|
|
4385
|
+
phase: "resolve-handle",
|
|
4386
|
+
run: async () => await resolveSandboxHandle(workspaceId, options.store, vercelClient, {
|
|
4387
|
+
logger: options.logger,
|
|
4388
|
+
maxIdleMs: options.orphanGuardMaxIdleMs === null ? void 0 : options.orphanGuardMaxIdleMs ?? ORPHAN_GUARD_MAX_IDLE_MS,
|
|
4389
|
+
expiredSandboxPolicy: options.expiredSandboxPolicy
|
|
4390
|
+
})
|
|
4391
|
+
});
|
|
4392
|
+
await runSandboxSetupStep({
|
|
4393
|
+
telemetry,
|
|
4394
|
+
ctx: options.ctx,
|
|
4395
|
+
phase: "ensure-workspace-root",
|
|
4396
|
+
run: async () => {
|
|
4397
|
+
await ensureVercelWorkspaceRoot(sandboxHandle);
|
|
4398
|
+
}
|
|
4399
|
+
});
|
|
4400
|
+
const workspace = createVercelSandboxWorkspace(sandboxHandle);
|
|
4401
|
+
const workspaceFs = createVercelWorkspaceFs({ workspace, sandbox: sandboxHandle });
|
|
4402
|
+
const sandbox = createVercelSandboxExec(sandboxHandle);
|
|
4403
|
+
await runSandboxSetupStep({
|
|
4404
|
+
telemetry,
|
|
4405
|
+
ctx: options.ctx,
|
|
4406
|
+
phase: "sandbox-init",
|
|
4407
|
+
run: async () => {
|
|
4408
|
+
await sandbox.init?.({ workspace, sessionId: options.ctx?.sessionId ?? "default" });
|
|
4409
|
+
}
|
|
4410
|
+
});
|
|
4411
|
+
captureSandboxSetupEvent(telemetry, options.ctx, "agent.runtime.sandbox.setup.completed", {
|
|
4412
|
+
status: "ok",
|
|
4413
|
+
durationMs: Date.now() - totalStartedAt
|
|
4414
|
+
});
|
|
4415
|
+
return { workspace, workspaceFs, sandbox, sandboxHandle };
|
|
4416
|
+
} catch (error) {
|
|
4417
|
+
const code = error?.code;
|
|
4418
|
+
captureSandboxSetupEvent(telemetry, options.ctx, "agent.runtime.sandbox.setup.failed", {
|
|
4419
|
+
status: "error",
|
|
4420
|
+
durationMs: Date.now() - totalStartedAt,
|
|
4421
|
+
...typeof code === "string" ? { errorCode: code } : {}
|
|
4422
|
+
});
|
|
4423
|
+
throw error;
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
3183
4426
|
async function ensureTemplateExecutables(sandbox) {
|
|
3184
4427
|
if (!sandbox.runCommand) return;
|
|
3185
4428
|
await sandbox.runCommand({
|
|
@@ -3235,7 +4478,67 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
3235
4478
|
async dispose() {
|
|
3236
4479
|
await snapshotScheduler?.shutdown();
|
|
3237
4480
|
},
|
|
4481
|
+
createProvisioningAdapter(runtimeLayout, ctx) {
|
|
4482
|
+
let partsPromise = null;
|
|
4483
|
+
const getParts = () => {
|
|
4484
|
+
partsPromise ??= ensureVercelProvisioningParts({
|
|
4485
|
+
store,
|
|
4486
|
+
getEnvVar,
|
|
4487
|
+
logger,
|
|
4488
|
+
vercelClient: opts.vercelClient,
|
|
4489
|
+
runtimeLayout,
|
|
4490
|
+
ctx,
|
|
4491
|
+
expiredSandboxPolicy: opts.expiredSandboxPolicy,
|
|
4492
|
+
orphanGuardMaxIdleMs: opts.orphanGuardMaxIdleMs
|
|
4493
|
+
});
|
|
4494
|
+
return partsPromise;
|
|
4495
|
+
};
|
|
4496
|
+
return createVercelProvisioningAdapter({
|
|
4497
|
+
runtimeLayout,
|
|
4498
|
+
workspaceFs: {
|
|
4499
|
+
async exists(rel) {
|
|
4500
|
+
return await (await getParts()).workspaceFs.exists(rel);
|
|
4501
|
+
},
|
|
4502
|
+
async rm(rel) {
|
|
4503
|
+
return await (await getParts()).workspaceFs.rm(rel);
|
|
4504
|
+
},
|
|
4505
|
+
async mkdir(rel) {
|
|
4506
|
+
return await (await getParts()).workspaceFs.mkdir(rel);
|
|
4507
|
+
},
|
|
4508
|
+
async writeText(rel, content) {
|
|
4509
|
+
return await (await getParts()).workspaceFs.writeText(rel, content);
|
|
4510
|
+
},
|
|
4511
|
+
async readText(rel) {
|
|
4512
|
+
return await (await getParts()).workspaceFs.readText(rel);
|
|
4513
|
+
},
|
|
4514
|
+
async copyFromHost(source, target) {
|
|
4515
|
+
return await (await getParts()).workspaceFs.copyFromHost(source, target);
|
|
4516
|
+
}
|
|
4517
|
+
},
|
|
4518
|
+
async exec(command, args, execOpts) {
|
|
4519
|
+
const { sandbox } = await getParts();
|
|
4520
|
+
const commandLine = [command, ...args].map(shellSingleQuote).join(" ");
|
|
4521
|
+
const result = await sandbox.exec(commandLine, {
|
|
4522
|
+
cwd: execOpts?.cwd,
|
|
4523
|
+
env: execOpts?.env,
|
|
4524
|
+
timeoutMs: execOpts?.timeoutMs
|
|
4525
|
+
});
|
|
4526
|
+
const stdout = Buffer.from(result.stdout).toString("utf8");
|
|
4527
|
+
const stderr = Buffer.from(result.stderr).toString("utf8");
|
|
4528
|
+
if (result.exitCode !== 0) {
|
|
4529
|
+
throw new Error(`Command failed (${command}) with exit code ${result.exitCode}${stderr.trim() ? `: ${stderr.trim()}` : ""}`);
|
|
4530
|
+
}
|
|
4531
|
+
return { stdout, stderr };
|
|
4532
|
+
},
|
|
4533
|
+
prepareArtifact: prepareVercelProvisioningArtifact
|
|
4534
|
+
});
|
|
4535
|
+
},
|
|
3238
4536
|
async create(ctx) {
|
|
4537
|
+
const telemetry = ctx.telemetry;
|
|
4538
|
+
const totalStartedAt = Date.now();
|
|
4539
|
+
captureSandboxSetupEvent(telemetry, ctx, "agent.runtime.sandbox.setup.started", {
|
|
4540
|
+
status: "started"
|
|
4541
|
+
});
|
|
3239
4542
|
const auth = resolveVercelAuth(getEnvVar);
|
|
3240
4543
|
const teamId = requireEnvVar("VERCEL_TEAM_ID", getEnvVar);
|
|
3241
4544
|
const projectId = getEnvVar("VERCEL_PROJECT_ID")?.trim();
|
|
@@ -3243,80 +4546,127 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
3243
4546
|
VERCEL_SANDBOX_TIMEOUT_MS_ENV,
|
|
3244
4547
|
getEnvVar
|
|
3245
4548
|
);
|
|
4549
|
+
const runtime = getEnvVar(VERCEL_SANDBOX_RUNTIME_ENV)?.trim() || DEFAULT_VERCEL_SANDBOX_RUNTIME;
|
|
3246
4550
|
const vercelClient = opts.vercelClient ?? createDefaultVercelClient({
|
|
3247
4551
|
token: auth.token,
|
|
3248
4552
|
teamId,
|
|
3249
4553
|
projectId
|
|
3250
|
-
}, { timeoutMs });
|
|
4554
|
+
}, { timeoutMs, runtime });
|
|
3251
4555
|
logger.info("[vercel-sandbox:mode] auth resolved", {
|
|
3252
4556
|
source: auth.source,
|
|
3253
4557
|
hasProjectId: Boolean(projectId),
|
|
3254
|
-
timeoutMs: timeoutMs ?? null
|
|
4558
|
+
timeoutMs: timeoutMs ?? null,
|
|
4559
|
+
runtime
|
|
3255
4560
|
});
|
|
3256
4561
|
const workspaceId = ctx.workspaceId ?? ctx.workspaceRoot;
|
|
3257
4562
|
let tarballUrl;
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
tarballUrl,
|
|
3278
|
-
logger,
|
|
3279
|
-
maxIdleMs: opts.orphanGuardMaxIdleMs === null ? void 0 : opts.orphanGuardMaxIdleMs ?? ORPHAN_GUARD_MAX_IDLE_MS,
|
|
3280
|
-
expiredSandboxPolicy: opts.expiredSandboxPolicy
|
|
4563
|
+
try {
|
|
4564
|
+
if (ctx.templatePath) {
|
|
4565
|
+
try {
|
|
4566
|
+
const result = await runSandboxSetupStep({
|
|
4567
|
+
telemetry,
|
|
4568
|
+
ctx,
|
|
4569
|
+
phase: "template-package",
|
|
4570
|
+
run: async () => await packageTemplate(ctx.templatePath, opts.packageTemplateOpts)
|
|
4571
|
+
});
|
|
4572
|
+
tarballUrl = result.url;
|
|
4573
|
+
logger.info("[vercel-sandbox:mode] template packaged", {
|
|
4574
|
+
hash: result.hash,
|
|
4575
|
+
url: result.url
|
|
4576
|
+
});
|
|
4577
|
+
} catch (error) {
|
|
4578
|
+
logger.info("[vercel-sandbox:mode] template packaging failed, will use writeFiles fallback", {
|
|
4579
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
3281
4582
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
4583
|
+
const sandboxHandle = await runSandboxSetupStep({
|
|
4584
|
+
telemetry,
|
|
4585
|
+
ctx,
|
|
4586
|
+
phase: "resolve-handle",
|
|
4587
|
+
run: async () => await resolveSandboxHandle(
|
|
4588
|
+
workspaceId,
|
|
4589
|
+
store,
|
|
4590
|
+
vercelClient,
|
|
4591
|
+
{
|
|
4592
|
+
tarballUrl,
|
|
4593
|
+
logger,
|
|
4594
|
+
maxIdleMs: opts.orphanGuardMaxIdleMs === null ? void 0 : opts.orphanGuardMaxIdleMs ?? ORPHAN_GUARD_MAX_IDLE_MS,
|
|
4595
|
+
expiredSandboxPolicy: opts.expiredSandboxPolicy
|
|
4596
|
+
}
|
|
4597
|
+
)
|
|
4598
|
+
});
|
|
4599
|
+
const sandboxId = sandboxHandle.name ?? sandboxHandle.sandboxId ?? "unknown-sandbox";
|
|
4600
|
+
logger.info("[vercel-sandbox:mode] resolved sandbox handle", {
|
|
3292
4601
|
workspaceId,
|
|
3293
|
-
|
|
3294
|
-
|
|
4602
|
+
sandboxId,
|
|
4603
|
+
snapshotId: sandboxHandle.currentSnapshotId ?? sandboxHandle.sourceSnapshotId ?? null,
|
|
4604
|
+
tarballUrl: tarballUrl ?? null
|
|
3295
4605
|
});
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
4606
|
+
if (snapshotScheduler && sandboxHandle.sandboxId) {
|
|
4607
|
+
snapshotScheduler.trackWorkspace({
|
|
4608
|
+
workspaceId,
|
|
4609
|
+
sandbox: sandboxHandle,
|
|
4610
|
+
store
|
|
4611
|
+
});
|
|
4612
|
+
}
|
|
4613
|
+
const markDirty = () => snapshotScheduler?.markDirty(workspaceId);
|
|
4614
|
+
const workspace = createVercelSandboxWorkspace(sandboxHandle, {
|
|
4615
|
+
onMutation: markDirty
|
|
4616
|
+
});
|
|
4617
|
+
await runSandboxSetupStep({
|
|
4618
|
+
telemetry,
|
|
4619
|
+
ctx,
|
|
4620
|
+
phase: "ensure-workspace-root",
|
|
4621
|
+
run: async () => {
|
|
4622
|
+
await ensureVercelWorkspaceRoot(sandboxHandle);
|
|
4623
|
+
}
|
|
4624
|
+
});
|
|
4625
|
+
if (ctx.templatePath) {
|
|
4626
|
+
if (!tarballUrl) {
|
|
4627
|
+
logger.info("[vercel-sandbox:mode] falling back to writeFiles for template", {
|
|
4628
|
+
templatePath: ctx.templatePath
|
|
4629
|
+
});
|
|
4630
|
+
}
|
|
4631
|
+
await runSandboxSetupStep({
|
|
4632
|
+
telemetry,
|
|
4633
|
+
ctx,
|
|
4634
|
+
phase: "template-seed",
|
|
4635
|
+
run: async () => {
|
|
4636
|
+
await seedTemplateIntoVercelWorkspace(workspace, ctx.templatePath, logger);
|
|
4637
|
+
await ensureTemplateExecutables(sandboxHandle);
|
|
4638
|
+
}
|
|
3306
4639
|
});
|
|
3307
4640
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
4641
|
+
const sandbox = createVercelSandboxExec(sandboxHandle, {
|
|
4642
|
+
onMutation: markDirty
|
|
4643
|
+
});
|
|
4644
|
+
await runSandboxSetupStep({
|
|
4645
|
+
telemetry,
|
|
4646
|
+
ctx,
|
|
4647
|
+
phase: "sandbox-init",
|
|
4648
|
+
run: async () => {
|
|
4649
|
+
await sandbox.init?.({ workspace, sessionId: ctx.sessionId });
|
|
4650
|
+
}
|
|
4651
|
+
});
|
|
4652
|
+
captureSandboxSetupEvent(telemetry, ctx, "agent.runtime.sandbox.setup.completed", {
|
|
4653
|
+
status: "ok",
|
|
4654
|
+
durationMs: Date.now() - totalStartedAt
|
|
4655
|
+
});
|
|
4656
|
+
return {
|
|
4657
|
+
workspace,
|
|
4658
|
+
sandbox,
|
|
4659
|
+
fileSearch: createServerFileSearch(workspace, sandbox)
|
|
4660
|
+
};
|
|
4661
|
+
} catch (error) {
|
|
4662
|
+
const code = error?.code;
|
|
4663
|
+
captureSandboxSetupEvent(telemetry, ctx, "agent.runtime.sandbox.setup.failed", {
|
|
4664
|
+
status: "error",
|
|
4665
|
+
durationMs: Date.now() - totalStartedAt,
|
|
4666
|
+
...typeof code === "string" ? { errorCode: code } : {}
|
|
4667
|
+
});
|
|
4668
|
+
throw error;
|
|
3310
4669
|
}
|
|
3311
|
-
const sandbox = createVercelSandboxExec(sandboxHandle, {
|
|
3312
|
-
onMutation: markDirty
|
|
3313
|
-
});
|
|
3314
|
-
await sandbox.init?.({ workspace, sessionId: ctx.sessionId });
|
|
3315
|
-
return {
|
|
3316
|
-
workspace,
|
|
3317
|
-
sandbox,
|
|
3318
|
-
fileSearch: createServerFileSearch(workspace, sandbox)
|
|
3319
|
-
};
|
|
3320
4670
|
}
|
|
3321
4671
|
};
|
|
3322
4672
|
}
|
|
@@ -3360,8 +4710,8 @@ import Fastify from "fastify";
|
|
|
3360
4710
|
|
|
3361
4711
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
3362
4712
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
3363
|
-
import { mkdir as
|
|
3364
|
-
import { extname as extname2, join as
|
|
4713
|
+
import { mkdir as mkdir11, writeFile as writeFile7 } from "fs/promises";
|
|
4714
|
+
import { extname as extname2, join as join10 } from "path";
|
|
3365
4715
|
import {
|
|
3366
4716
|
createAgentSession,
|
|
3367
4717
|
SessionManager,
|
|
@@ -3374,7 +4724,20 @@ import {
|
|
|
3374
4724
|
} from "@mariozechner/pi-coding-agent";
|
|
3375
4725
|
|
|
3376
4726
|
// src/server/harness/pi-coding-agent/tool-adapter.ts
|
|
3377
|
-
function
|
|
4727
|
+
function toolTelemetryProperties(toolName2, sessionId, status, startedAt, result) {
|
|
4728
|
+
const properties = {
|
|
4729
|
+
toolName: toolName2,
|
|
4730
|
+
status,
|
|
4731
|
+
durationMs: Date.now() - startedAt
|
|
4732
|
+
};
|
|
4733
|
+
if (sessionId) properties.sessionId = sessionId;
|
|
4734
|
+
const errorCode = result?.details?.code;
|
|
4735
|
+
if (status === "error") {
|
|
4736
|
+
properties.errorCode = ErrorCode.safeParse(errorCode).success ? errorCode : ErrorCode.enum.TOOL_EXECUTION_ERROR;
|
|
4737
|
+
}
|
|
4738
|
+
return properties;
|
|
4739
|
+
}
|
|
4740
|
+
function adaptToolForPi(tool, sessionId, telemetry = noopTelemetry) {
|
|
3378
4741
|
return {
|
|
3379
4742
|
name: tool.name,
|
|
3380
4743
|
label: tool.name,
|
|
@@ -3382,24 +4745,47 @@ function adaptToolForPi(tool, sessionId) {
|
|
|
3382
4745
|
parameters: tool.parameters,
|
|
3383
4746
|
promptSnippet: tool.promptSnippet ?? tool.description,
|
|
3384
4747
|
async execute(toolCallId, params, signal, onUpdate, _ctx) {
|
|
3385
|
-
const
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
4748
|
+
const startedAt = Date.now();
|
|
4749
|
+
let emittedFailure = false;
|
|
4750
|
+
try {
|
|
4751
|
+
const result = await tool.execute(params, {
|
|
4752
|
+
toolCallId,
|
|
4753
|
+
abortSignal: signal ?? new AbortController().signal,
|
|
4754
|
+
onUpdate: onUpdate ? (partial) => onUpdate({ content: [{ type: "text", text: partial }], details: void 0 }) : void 0,
|
|
4755
|
+
sessionId
|
|
4756
|
+
});
|
|
4757
|
+
safeCapture(telemetry, {
|
|
4758
|
+
name: result.isError ? "agent.tool.failed" : "agent.tool.completed",
|
|
4759
|
+
properties: toolTelemetryProperties(
|
|
4760
|
+
tool.name,
|
|
4761
|
+
sessionId,
|
|
4762
|
+
result.isError ? "error" : "ok",
|
|
4763
|
+
startedAt,
|
|
4764
|
+
result
|
|
4765
|
+
)
|
|
4766
|
+
});
|
|
4767
|
+
if (result.isError) {
|
|
4768
|
+
emittedFailure = true;
|
|
4769
|
+
throw new Error(result.content.map((c) => c.text).join("\n"));
|
|
4770
|
+
}
|
|
4771
|
+
return {
|
|
4772
|
+
content: result.content,
|
|
4773
|
+
details: result.details
|
|
4774
|
+
};
|
|
4775
|
+
} catch (error) {
|
|
4776
|
+
if (!emittedFailure) {
|
|
4777
|
+
safeCapture(telemetry, {
|
|
4778
|
+
name: "agent.tool.failed",
|
|
4779
|
+
properties: toolTelemetryProperties(tool.name, sessionId, "error", startedAt)
|
|
4780
|
+
});
|
|
4781
|
+
}
|
|
4782
|
+
throw error;
|
|
3393
4783
|
}
|
|
3394
|
-
return {
|
|
3395
|
-
content: result.content,
|
|
3396
|
-
details: result.details
|
|
3397
|
-
};
|
|
3398
4784
|
}
|
|
3399
4785
|
};
|
|
3400
4786
|
}
|
|
3401
|
-
function adaptToolsForPi(tools, sessionId) {
|
|
3402
|
-
return tools.map((tool) => adaptToolForPi(tool, sessionId));
|
|
4787
|
+
function adaptToolsForPi(tools, sessionId, telemetry) {
|
|
4788
|
+
return tools.map((tool) => adaptToolForPi(tool, sessionId, telemetry));
|
|
3403
4789
|
}
|
|
3404
4790
|
|
|
3405
4791
|
// src/server/harness/pi-coding-agent/stream-adapter.ts
|
|
@@ -3587,17 +4973,17 @@ function piEventToChunks(event) {
|
|
|
3587
4973
|
// src/server/harness/pi-coding-agent/sessions.ts
|
|
3588
4974
|
import { randomUUID } from "crypto";
|
|
3589
4975
|
import {
|
|
3590
|
-
readdir as
|
|
3591
|
-
readFile as
|
|
4976
|
+
readdir as readdir6,
|
|
4977
|
+
readFile as readFile9,
|
|
3592
4978
|
stat as fsStat,
|
|
3593
|
-
rm,
|
|
3594
|
-
mkdir as
|
|
4979
|
+
rm as rm3,
|
|
4980
|
+
mkdir as mkdir10,
|
|
3595
4981
|
writeFile as writeFile6,
|
|
3596
4982
|
appendFile,
|
|
3597
4983
|
utimes
|
|
3598
4984
|
} from "fs/promises";
|
|
3599
4985
|
import { readFileSync, readdirSync } from "fs";
|
|
3600
|
-
import { join as
|
|
4986
|
+
import { join as join9 } from "path";
|
|
3601
4987
|
import { homedir as homedir3 } from "os";
|
|
3602
4988
|
import {
|
|
3603
4989
|
parseSessionEntries,
|
|
@@ -3606,7 +4992,7 @@ import {
|
|
|
3606
4992
|
} from "@mariozechner/pi-coding-agent";
|
|
3607
4993
|
function defaultSessionDir(cwd) {
|
|
3608
4994
|
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
3609
|
-
return
|
|
4995
|
+
return join9(homedir3(), ".pi", "agent", "sessions", safePath);
|
|
3610
4996
|
}
|
|
3611
4997
|
var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
3612
4998
|
var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
|
|
@@ -3615,7 +5001,7 @@ function sessionDirForNamespace(namespace) {
|
|
|
3615
5001
|
if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
|
|
3616
5002
|
throw new Error("session namespace must contain only letters, numbers, underscores, and dashes");
|
|
3617
5003
|
}
|
|
3618
|
-
return
|
|
5004
|
+
return join9(homedir3(), ".pi", "agent", "sessions", safeNamespace);
|
|
3619
5005
|
}
|
|
3620
5006
|
var PiSessionStore = class {
|
|
3621
5007
|
cwd;
|
|
@@ -3629,15 +5015,15 @@ var PiSessionStore = class {
|
|
|
3629
5015
|
this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace) : defaultSessionDir(cwd));
|
|
3630
5016
|
}
|
|
3631
5017
|
async list(ctx) {
|
|
3632
|
-
const files = await
|
|
5018
|
+
const files = await readdir6(this.sessionDir).catch(() => []);
|
|
3633
5019
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
3634
5020
|
const summaries = await Promise.all(
|
|
3635
|
-
jsonlFiles.map((f) => this.summarizeFile(
|
|
5021
|
+
jsonlFiles.map((f) => this.summarizeFile(join9(this.sessionDir, f)))
|
|
3636
5022
|
);
|
|
3637
5023
|
return summaries.filter((s) => s !== null).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
3638
5024
|
}
|
|
3639
5025
|
async create(ctx, init) {
|
|
3640
|
-
await
|
|
5026
|
+
await mkdir10(this.sessionDir, { recursive: true });
|
|
3641
5027
|
const id = randomUUID();
|
|
3642
5028
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3643
5029
|
const header = {
|
|
@@ -3658,7 +5044,7 @@ var PiSessionStore = class {
|
|
|
3658
5044
|
};
|
|
3659
5045
|
lines.push(JSON.stringify(infoEntry));
|
|
3660
5046
|
}
|
|
3661
|
-
const filepath =
|
|
5047
|
+
const filepath = join9(this.sessionDir, `${id}.jsonl`);
|
|
3662
5048
|
await writeFile6(filepath, lines.join("\n") + "\n", "utf-8");
|
|
3663
5049
|
return {
|
|
3664
5050
|
id,
|
|
@@ -3672,7 +5058,7 @@ var PiSessionStore = class {
|
|
|
3672
5058
|
const filepath = await this.resolveSessionFile(sessionId);
|
|
3673
5059
|
let content;
|
|
3674
5060
|
try {
|
|
3675
|
-
content = await
|
|
5061
|
+
content = await readFile9(filepath, "utf-8");
|
|
3676
5062
|
} catch {
|
|
3677
5063
|
throw new Error(`Session not found: ${sessionId}`);
|
|
3678
5064
|
}
|
|
@@ -3715,7 +5101,7 @@ var PiSessionStore = class {
|
|
|
3715
5101
|
loadPiSessionFileSync(sessionId) {
|
|
3716
5102
|
if (!SAFE_ID.test(sessionId)) return null;
|
|
3717
5103
|
try {
|
|
3718
|
-
const direct =
|
|
5104
|
+
const direct = join9(this.sessionDir, `${sessionId}.jsonl`);
|
|
3719
5105
|
let content;
|
|
3720
5106
|
try {
|
|
3721
5107
|
content = readFileSync(direct, "utf-8");
|
|
@@ -3724,7 +5110,7 @@ var PiSessionStore = class {
|
|
|
3724
5110
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
3725
5111
|
);
|
|
3726
5112
|
if (files.length === 0) return null;
|
|
3727
|
-
content = readFileSync(
|
|
5113
|
+
content = readFileSync(join9(this.sessionDir, files[0]), "utf-8");
|
|
3728
5114
|
}
|
|
3729
5115
|
const entries = safeParseEntries(content);
|
|
3730
5116
|
let piFilePath = null;
|
|
@@ -3742,7 +5128,7 @@ var PiSessionStore = class {
|
|
|
3742
5128
|
async loadPiSessionFile(_ctx, sessionId) {
|
|
3743
5129
|
try {
|
|
3744
5130
|
const filepath = await this.resolveSessionFile(sessionId);
|
|
3745
|
-
const content = await
|
|
5131
|
+
const content = await readFile9(filepath, "utf-8");
|
|
3746
5132
|
const entries = safeParseEntries(content);
|
|
3747
5133
|
let piFilePath = null;
|
|
3748
5134
|
for (const e of entries) {
|
|
@@ -3769,7 +5155,7 @@ var PiSessionStore = class {
|
|
|
3769
5155
|
const filepath = await this.resolveSessionFile(sessionId).catch(
|
|
3770
5156
|
() => null
|
|
3771
5157
|
);
|
|
3772
|
-
if (filepath) await
|
|
5158
|
+
if (filepath) await rm3(filepath, { force: true });
|
|
3773
5159
|
}
|
|
3774
5160
|
touchSession(sessionId, title) {
|
|
3775
5161
|
this.resolveSessionFile(sessionId).then((filepath) => {
|
|
@@ -3792,24 +5178,24 @@ var PiSessionStore = class {
|
|
|
3792
5178
|
if (!SAFE_ID.test(sessionId)) {
|
|
3793
5179
|
throw new Error(`Session not found: ${sessionId}`);
|
|
3794
5180
|
}
|
|
3795
|
-
const direct =
|
|
5181
|
+
const direct = join9(this.sessionDir, `${sessionId}.jsonl`);
|
|
3796
5182
|
try {
|
|
3797
5183
|
await fsStat(direct);
|
|
3798
5184
|
return direct;
|
|
3799
5185
|
} catch {
|
|
3800
5186
|
}
|
|
3801
|
-
const files = await
|
|
5187
|
+
const files = await readdir6(this.sessionDir).catch(() => []);
|
|
3802
5188
|
const match = files.find(
|
|
3803
5189
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
3804
5190
|
);
|
|
3805
5191
|
if (!match) throw new Error(`Session not found: ${sessionId}`);
|
|
3806
|
-
return
|
|
5192
|
+
return join9(this.sessionDir, match);
|
|
3807
5193
|
}
|
|
3808
5194
|
async summarizeFile(filepath) {
|
|
3809
5195
|
try {
|
|
3810
5196
|
const [fileStat, content] = await Promise.all([
|
|
3811
5197
|
fsStat(filepath),
|
|
3812
|
-
|
|
5198
|
+
readFile9(filepath, "utf-8")
|
|
3813
5199
|
]);
|
|
3814
5200
|
const firstNewline = content.indexOf("\n");
|
|
3815
5201
|
if (firstNewline === -1) return null;
|
|
@@ -3959,7 +5345,7 @@ var DEFAULT_POLL_MS = 250;
|
|
|
3959
5345
|
var MAX_PROMPT_CHARS = 1200;
|
|
3960
5346
|
var MAX_TITLE_CHARS = 80;
|
|
3961
5347
|
function sleep(ms) {
|
|
3962
|
-
return new Promise((
|
|
5348
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
3963
5349
|
}
|
|
3964
5350
|
function truncateForPrompt(text) {
|
|
3965
5351
|
const trimmed = text.trim();
|
|
@@ -4380,8 +5766,8 @@ function mergeInjectedProjectPackages(settingsJson, piPackages) {
|
|
|
4380
5766
|
}
|
|
4381
5767
|
function createResourceSettingsManager(cwd, agentDir, piPackages) {
|
|
4382
5768
|
if (piPackages.length === 0) return SettingsManager2.create(cwd, agentDir);
|
|
4383
|
-
const globalSettingsPath =
|
|
4384
|
-
const projectSettingsPath =
|
|
5769
|
+
const globalSettingsPath = join10(agentDir, "settings.json");
|
|
5770
|
+
const projectSettingsPath = join10(cwd, ".pi", "settings.json");
|
|
4385
5771
|
let globalSettingsOverrideJson;
|
|
4386
5772
|
let projectSettingsOverrideJson;
|
|
4387
5773
|
const storage = {
|
|
@@ -4534,7 +5920,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
4534
5920
|
const { session: piSession } = await createAgentSession({
|
|
4535
5921
|
cwd: ctx.workdir,
|
|
4536
5922
|
tools: [],
|
|
4537
|
-
customTools: adaptToolsForPi(opts.tools, input.sessionId),
|
|
5923
|
+
customTools: adaptToolsForPi(opts.tools, input.sessionId, opts.telemetry),
|
|
4538
5924
|
model,
|
|
4539
5925
|
thinkingLevel: input.thinkingLevel ?? "off",
|
|
4540
5926
|
sessionManager,
|
|
@@ -4982,8 +6368,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
4982
6368
|
const base = basenameForAttachment(a.filename ?? "image");
|
|
4983
6369
|
const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4984
6370
|
const relPath = `${DEFAULT_ATTACHMENT_DIR}/${base}-${unique}.${ext}`;
|
|
4985
|
-
await
|
|
4986
|
-
await writeFile7(
|
|
6371
|
+
await mkdir11(join10(ctx.workdir, DEFAULT_ATTACHMENT_DIR), { recursive: true });
|
|
6372
|
+
await writeFile7(join10(ctx.workdir, relPath), bytes);
|
|
4987
6373
|
savedPaths.push(relPath);
|
|
4988
6374
|
} catch (err) {
|
|
4989
6375
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5072,25 +6458,25 @@ ${attachmentNotes.join("\n")}` : message,
|
|
|
5072
6458
|
}
|
|
5073
6459
|
|
|
5074
6460
|
// src/server/harness/pi-coding-agent/pluginLoader.ts
|
|
5075
|
-
import { readdir as
|
|
5076
|
-
import { join as
|
|
6461
|
+
import { readdir as readdir7, stat as stat8, readFile as readFile10 } from "fs/promises";
|
|
6462
|
+
import { join as join11, extname as extname3, resolve as resolve6, sep as sep6, relative as relative9 } from "path";
|
|
5077
6463
|
import { homedir as homedir4 } from "os";
|
|
5078
|
-
import { pathToFileURL } from "url";
|
|
6464
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5079
6465
|
var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs"]);
|
|
5080
|
-
var GLOBAL_DIR =
|
|
6466
|
+
var GLOBAL_DIR = join11(homedir4(), ".pi", "agent", "extensions");
|
|
5081
6467
|
var LOCAL_DIR = ".pi/extensions";
|
|
5082
6468
|
var EXTENSIONS_JSON = ".pi/extensions.json";
|
|
5083
6469
|
async function dirExists(path4) {
|
|
5084
6470
|
try {
|
|
5085
|
-
const s = await
|
|
6471
|
+
const s = await stat8(path4);
|
|
5086
6472
|
return s.isDirectory();
|
|
5087
6473
|
} catch {
|
|
5088
6474
|
return false;
|
|
5089
6475
|
}
|
|
5090
6476
|
}
|
|
5091
|
-
async function
|
|
6477
|
+
async function fileExists(path4) {
|
|
5092
6478
|
try {
|
|
5093
|
-
const s = await
|
|
6479
|
+
const s = await stat8(path4);
|
|
5094
6480
|
return s.isFile();
|
|
5095
6481
|
} catch {
|
|
5096
6482
|
return false;
|
|
@@ -5098,8 +6484,8 @@ async function fileExists2(path4) {
|
|
|
5098
6484
|
}
|
|
5099
6485
|
async function discoverFromDir(dir, source) {
|
|
5100
6486
|
if (!await dirExists(dir)) return [];
|
|
5101
|
-
const entries = await
|
|
5102
|
-
return entries.filter((e) => VALID_EXTENSIONS.has(extname3(e))).map((e) => ({ path:
|
|
6487
|
+
const entries = await readdir7(dir);
|
|
6488
|
+
return entries.filter((e) => VALID_EXTENSIONS.has(extname3(e))).map((e) => ({ path: join11(dir, e), source }));
|
|
5103
6489
|
}
|
|
5104
6490
|
function extractTools(mod) {
|
|
5105
6491
|
const tools = [];
|
|
@@ -5124,41 +6510,41 @@ function extractTools(mod) {
|
|
|
5124
6510
|
return tools;
|
|
5125
6511
|
}
|
|
5126
6512
|
async function loadModule(filePath, importFn) {
|
|
5127
|
-
const url =
|
|
6513
|
+
const url = pathToFileURL2(filePath).href;
|
|
5128
6514
|
const mod = await importFn(url);
|
|
5129
6515
|
return extractTools(mod);
|
|
5130
6516
|
}
|
|
5131
6517
|
async function discoverNpmPlugins(cwd) {
|
|
5132
|
-
const nodeModulesDir =
|
|
6518
|
+
const nodeModulesDir = join11(cwd, "node_modules");
|
|
5133
6519
|
if (!await dirExists(nodeModulesDir)) return [];
|
|
5134
6520
|
try {
|
|
5135
|
-
const entries = await
|
|
5136
|
-
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) =>
|
|
6521
|
+
const entries = await readdir7(nodeModulesDir);
|
|
6522
|
+
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) => join11(nodeModulesDir, e));
|
|
5137
6523
|
} catch {
|
|
5138
6524
|
return [];
|
|
5139
6525
|
}
|
|
5140
6526
|
}
|
|
5141
6527
|
async function loadNpmPlugin(pkgDir, importFn) {
|
|
5142
|
-
const pkgJsonPath =
|
|
5143
|
-
if (!await
|
|
5144
|
-
const pkgJson = JSON.parse(await
|
|
6528
|
+
const pkgJsonPath = join11(pkgDir, "package.json");
|
|
6529
|
+
if (!await fileExists(pkgJsonPath)) return [];
|
|
6530
|
+
const pkgJson = JSON.parse(await readFile10(pkgJsonPath, "utf-8"));
|
|
5145
6531
|
const main = pkgJson.main ?? "index.js";
|
|
5146
|
-
const resolvedMain =
|
|
5147
|
-
const resolvedPkgDir =
|
|
5148
|
-
if (resolvedMain !== resolvedPkgDir && !resolvedMain.startsWith(resolvedPkgDir +
|
|
6532
|
+
const resolvedMain = resolve6(pkgDir, main);
|
|
6533
|
+
const resolvedPkgDir = resolve6(pkgDir);
|
|
6534
|
+
if (resolvedMain !== resolvedPkgDir && !resolvedMain.startsWith(resolvedPkgDir + sep6)) {
|
|
5149
6535
|
return [];
|
|
5150
6536
|
}
|
|
5151
|
-
const relPath =
|
|
6537
|
+
const relPath = relative9(resolvedPkgDir, resolvedMain);
|
|
5152
6538
|
if (relPath.startsWith("..")) {
|
|
5153
6539
|
return [];
|
|
5154
6540
|
}
|
|
5155
6541
|
return loadModule(resolvedMain, importFn);
|
|
5156
6542
|
}
|
|
5157
6543
|
async function loadExtensionsJson(cwd) {
|
|
5158
|
-
const configPath =
|
|
5159
|
-
if (!await
|
|
6544
|
+
const configPath = join11(cwd, EXTENSIONS_JSON);
|
|
6545
|
+
if (!await fileExists(configPath)) return null;
|
|
5160
6546
|
try {
|
|
5161
|
-
const raw = await
|
|
6547
|
+
const raw = await readFile10(configPath, "utf-8");
|
|
5162
6548
|
return JSON.parse(raw);
|
|
5163
6549
|
} catch {
|
|
5164
6550
|
return null;
|
|
@@ -5173,7 +6559,7 @@ async function loadPlugins(options) {
|
|
|
5173
6559
|
const globals = await discoverFromDir(GLOBAL_DIR, "global");
|
|
5174
6560
|
candidates.push(...globals);
|
|
5175
6561
|
}
|
|
5176
|
-
const localDir =
|
|
6562
|
+
const localDir = join11(options.cwd, LOCAL_DIR);
|
|
5177
6563
|
const locals = await discoverFromDir(localDir, "local");
|
|
5178
6564
|
candidates.push(...locals);
|
|
5179
6565
|
const npmDirs = await discoverNpmPlugins(options.cwd);
|
|
@@ -5183,7 +6569,7 @@ async function loadPlugins(options) {
|
|
|
5183
6569
|
const config = await loadExtensionsJson(options.cwd);
|
|
5184
6570
|
if (config?.npm) {
|
|
5185
6571
|
for (const pkg of config.npm) {
|
|
5186
|
-
const pkgDir =
|
|
6572
|
+
const pkgDir = join11(options.cwd, "node_modules", pkg);
|
|
5187
6573
|
if (await dirExists(pkgDir)) {
|
|
5188
6574
|
const already = candidates.some((c) => c.path === pkgDir);
|
|
5189
6575
|
if (!already) {
|
|
@@ -5231,9 +6617,9 @@ import {
|
|
|
5231
6617
|
} from "@mariozechner/pi-coding-agent";
|
|
5232
6618
|
|
|
5233
6619
|
// src/server/tools/operations/bound.ts
|
|
5234
|
-
import { constants as
|
|
5235
|
-
import { access as
|
|
5236
|
-
import { dirname as
|
|
6620
|
+
import { constants as constants4 } from "fs";
|
|
6621
|
+
import { access as access4, lstat as lstat4, mkdir as mkdir12, readFile as readFile11, readdir as readdir8, readlink, realpath as realpath4, stat as stat9, writeFile as writeFile8 } from "fs/promises";
|
|
6622
|
+
import { dirname as dirname10, isAbsolute as isAbsolute6, relative as relative10, resolve as resolve7 } from "path";
|
|
5237
6623
|
function toPosixPath(value) {
|
|
5238
6624
|
return value.split("\\").join("/");
|
|
5239
6625
|
}
|
|
@@ -5278,19 +6664,19 @@ function matchesGlob(relativePath, pattern) {
|
|
|
5278
6664
|
}
|
|
5279
6665
|
function shouldSkipDir(relativePath, ignore) {
|
|
5280
6666
|
const normalizedRel = toPosixPath(relativePath);
|
|
5281
|
-
const
|
|
5282
|
-
if (
|
|
6667
|
+
const basename4 = normalizedRel.split("/").at(-1) ?? normalizedRel;
|
|
6668
|
+
if (basename4 === ".git" || basename4 === "node_modules") return true;
|
|
5283
6669
|
return ignore.some((pattern) => {
|
|
5284
6670
|
return matchesGlob(normalizedRel, pattern) || matchesGlob(`${normalizedRel}/`, pattern);
|
|
5285
6671
|
});
|
|
5286
6672
|
}
|
|
5287
6673
|
async function walkMatches(root, current, pattern, ignore, limit, out) {
|
|
5288
6674
|
if (out.length >= limit) return;
|
|
5289
|
-
const entries = await
|
|
6675
|
+
const entries = await readdir8(current, { withFileTypes: true });
|
|
5290
6676
|
for (const entry of entries) {
|
|
5291
6677
|
if (out.length >= limit) return;
|
|
5292
|
-
const absolutePath =
|
|
5293
|
-
const relativePath = toPosixPath(
|
|
6678
|
+
const absolutePath = resolve7(current, entry.name);
|
|
6679
|
+
const relativePath = toPosixPath(relative10(root, absolutePath));
|
|
5294
6680
|
if (entry.isDirectory() && shouldSkipDir(relativePath, ignore)) continue;
|
|
5295
6681
|
if (matchesGlob(relativePath, pattern)) {
|
|
5296
6682
|
out.push(absolutePath);
|
|
@@ -5304,26 +6690,26 @@ async function findNearestExistingAncestor(absPath) {
|
|
|
5304
6690
|
let current = absPath;
|
|
5305
6691
|
for (; ; ) {
|
|
5306
6692
|
try {
|
|
5307
|
-
await
|
|
6693
|
+
await stat9(current);
|
|
5308
6694
|
return current;
|
|
5309
6695
|
} catch {
|
|
5310
|
-
const parent =
|
|
6696
|
+
const parent = dirname10(current);
|
|
5311
6697
|
if (parent === current) return current;
|
|
5312
6698
|
current = parent;
|
|
5313
6699
|
}
|
|
5314
6700
|
}
|
|
5315
6701
|
}
|
|
5316
6702
|
async function assertWithinWorkspace(workspaceRoot, absPath) {
|
|
5317
|
-
const realRoot = await
|
|
6703
|
+
const realRoot = await realpath4(resolve7(workspaceRoot));
|
|
5318
6704
|
try {
|
|
5319
|
-
const s = await
|
|
6705
|
+
const s = await lstat4(absPath);
|
|
5320
6706
|
if (s.isSymbolicLink()) {
|
|
5321
6707
|
const target = await readlink(absPath);
|
|
5322
|
-
const resolvedTarget =
|
|
6708
|
+
const resolvedTarget = resolve7(dirname10(absPath), target);
|
|
5323
6709
|
const nearestAncestor = await findNearestExistingAncestor(resolvedTarget);
|
|
5324
|
-
const realAncestor = await
|
|
5325
|
-
const rel2 =
|
|
5326
|
-
if (rel2.startsWith("..") ||
|
|
6710
|
+
const realAncestor = await realpath4(nearestAncestor);
|
|
6711
|
+
const rel2 = relative10(realRoot, realAncestor);
|
|
6712
|
+
if (rel2.startsWith("..") || isAbsolute6(rel2)) {
|
|
5327
6713
|
throw new Error(`path "${absPath}" is outside workspace`);
|
|
5328
6714
|
}
|
|
5329
6715
|
return;
|
|
@@ -5337,22 +6723,22 @@ async function assertWithinWorkspace(workspaceRoot, absPath) {
|
|
|
5337
6723
|
}
|
|
5338
6724
|
let realCandidate;
|
|
5339
6725
|
try {
|
|
5340
|
-
realCandidate = await
|
|
6726
|
+
realCandidate = await realpath4(absPath);
|
|
5341
6727
|
} catch (err) {
|
|
5342
6728
|
const code = err.code;
|
|
5343
6729
|
if (code === "ENOENT") {
|
|
5344
|
-
const nearestAncestor = await findNearestExistingAncestor(
|
|
5345
|
-
const realAncestor = await
|
|
5346
|
-
const rel2 =
|
|
5347
|
-
if (rel2.startsWith("..") ||
|
|
6730
|
+
const nearestAncestor = await findNearestExistingAncestor(dirname10(absPath));
|
|
6731
|
+
const realAncestor = await realpath4(nearestAncestor);
|
|
6732
|
+
const rel2 = relative10(realRoot, realAncestor);
|
|
6733
|
+
if (rel2.startsWith("..") || isAbsolute6(rel2)) {
|
|
5348
6734
|
throw new Error(`path "${absPath}" is outside workspace`);
|
|
5349
6735
|
}
|
|
5350
6736
|
return;
|
|
5351
6737
|
}
|
|
5352
6738
|
throw err;
|
|
5353
6739
|
}
|
|
5354
|
-
const rel =
|
|
5355
|
-
if (rel.startsWith("..") ||
|
|
6740
|
+
const rel = relative10(realRoot, realCandidate);
|
|
6741
|
+
if (rel.startsWith("..") || isAbsolute6(rel)) {
|
|
5356
6742
|
throw new Error(`path "${absPath}" is outside workspace`);
|
|
5357
6743
|
}
|
|
5358
6744
|
}
|
|
@@ -5360,28 +6746,28 @@ function boundFs(workspaceRoot) {
|
|
|
5360
6746
|
const read = {
|
|
5361
6747
|
async readFile(absolutePath) {
|
|
5362
6748
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5363
|
-
return await
|
|
6749
|
+
return await readFile11(absolutePath);
|
|
5364
6750
|
},
|
|
5365
6751
|
async access(absolutePath) {
|
|
5366
6752
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5367
|
-
await
|
|
6753
|
+
await access4(absolutePath, constants4.R_OK);
|
|
5368
6754
|
}
|
|
5369
6755
|
};
|
|
5370
6756
|
const write = {
|
|
5371
6757
|
async writeFile(absolutePath, content) {
|
|
5372
6758
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5373
|
-
await
|
|
6759
|
+
await mkdir12(dirname10(absolutePath), { recursive: true });
|
|
5374
6760
|
await writeFile8(absolutePath, content);
|
|
5375
6761
|
},
|
|
5376
6762
|
async mkdir(dir) {
|
|
5377
6763
|
await assertWithinWorkspace(workspaceRoot, dir);
|
|
5378
|
-
await
|
|
6764
|
+
await mkdir12(dir, { recursive: true });
|
|
5379
6765
|
}
|
|
5380
6766
|
};
|
|
5381
6767
|
const edit = {
|
|
5382
6768
|
async readFile(absolutePath) {
|
|
5383
6769
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5384
|
-
return await
|
|
6770
|
+
return await readFile11(absolutePath);
|
|
5385
6771
|
},
|
|
5386
6772
|
async writeFile(absolutePath, content) {
|
|
5387
6773
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
@@ -5389,14 +6775,14 @@ function boundFs(workspaceRoot) {
|
|
|
5389
6775
|
},
|
|
5390
6776
|
async access(absolutePath) {
|
|
5391
6777
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5392
|
-
await
|
|
6778
|
+
await access4(absolutePath, constants4.R_OK | constants4.W_OK);
|
|
5393
6779
|
}
|
|
5394
6780
|
};
|
|
5395
6781
|
const find = {
|
|
5396
6782
|
async exists(absolutePath) {
|
|
5397
6783
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5398
6784
|
try {
|
|
5399
|
-
await
|
|
6785
|
+
await stat9(absolutePath);
|
|
5400
6786
|
return true;
|
|
5401
6787
|
} catch (err) {
|
|
5402
6788
|
if (err.code === "ENOENT") return false;
|
|
@@ -5413,18 +6799,18 @@ function boundFs(workspaceRoot) {
|
|
|
5413
6799
|
const grep = {
|
|
5414
6800
|
async isDirectory(absolutePath) {
|
|
5415
6801
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5416
|
-
return (await
|
|
6802
|
+
return (await stat9(absolutePath)).isDirectory();
|
|
5417
6803
|
},
|
|
5418
6804
|
async readFile(absolutePath) {
|
|
5419
6805
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5420
|
-
return await
|
|
6806
|
+
return await readFile11(absolutePath, "utf8");
|
|
5421
6807
|
}
|
|
5422
6808
|
};
|
|
5423
6809
|
const ls = {
|
|
5424
6810
|
async exists(absolutePath) {
|
|
5425
6811
|
try {
|
|
5426
6812
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5427
|
-
await
|
|
6813
|
+
await stat9(absolutePath);
|
|
5428
6814
|
return true;
|
|
5429
6815
|
} catch {
|
|
5430
6816
|
return false;
|
|
@@ -5432,18 +6818,18 @@ function boundFs(workspaceRoot) {
|
|
|
5432
6818
|
},
|
|
5433
6819
|
async stat(absolutePath) {
|
|
5434
6820
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5435
|
-
return await
|
|
6821
|
+
return await stat9(absolutePath);
|
|
5436
6822
|
},
|
|
5437
6823
|
async readdir(absolutePath) {
|
|
5438
6824
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5439
|
-
return await
|
|
6825
|
+
return await readdir8(absolutePath);
|
|
5440
6826
|
}
|
|
5441
6827
|
};
|
|
5442
6828
|
return { read, write, edit, find, grep, ls };
|
|
5443
6829
|
}
|
|
5444
6830
|
|
|
5445
6831
|
// src/server/tools/operations/vercel.ts
|
|
5446
|
-
import { isAbsolute as
|
|
6832
|
+
import { isAbsolute as isAbsolute7, relative as relative11 } from "path";
|
|
5447
6833
|
function rootAliases(workspace) {
|
|
5448
6834
|
const aliases = [workspace.root];
|
|
5449
6835
|
if (workspace.root === VERCEL_SANDBOX_WORKSPACE_ROOT) aliases.push(VERCEL_SANDBOX_REMOTE_ROOT);
|
|
@@ -5451,11 +6837,11 @@ function rootAliases(workspace) {
|
|
|
5451
6837
|
return aliases;
|
|
5452
6838
|
}
|
|
5453
6839
|
function isOutsideWorkspaceRel(rel) {
|
|
5454
|
-
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") ||
|
|
6840
|
+
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute7(rel);
|
|
5455
6841
|
}
|
|
5456
6842
|
function toRelPath(workspace, absolutePath) {
|
|
5457
6843
|
for (const root of rootAliases(workspace)) {
|
|
5458
|
-
const rel =
|
|
6844
|
+
const rel = relative11(root, absolutePath);
|
|
5459
6845
|
if (!isOutsideWorkspaceRel(rel)) return rel;
|
|
5460
6846
|
}
|
|
5461
6847
|
const skillMarker = "/.agents/skills/";
|
|
@@ -5471,10 +6857,11 @@ function toRelPath(workspace, absolutePath) {
|
|
|
5471
6857
|
`path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${rootAliases(workspace).join(" / ")}`
|
|
5472
6858
|
);
|
|
5473
6859
|
}
|
|
5474
|
-
function vercelBashOps(sandbox) {
|
|
6860
|
+
function vercelBashOps(sandbox, opts = {}) {
|
|
5475
6861
|
return {
|
|
5476
6862
|
exec(command, cwd, { onData, signal, timeout, env }) {
|
|
5477
|
-
const
|
|
6863
|
+
const effectiveEnv = opts.mergeEnv ? opts.mergeEnv(env) : env;
|
|
6864
|
+
const filteredEnv = effectiveEnv ? Object.fromEntries(Object.entries(effectiveEnv).filter((e) => e[1] != null)) : void 0;
|
|
5478
6865
|
return sandbox.exec(command, {
|
|
5479
6866
|
cwd,
|
|
5480
6867
|
env: filteredEnv,
|
|
@@ -5638,7 +7025,7 @@ import {
|
|
|
5638
7025
|
truncateHead,
|
|
5639
7026
|
truncateLine
|
|
5640
7027
|
} from "@mariozechner/pi-coding-agent";
|
|
5641
|
-
import { resolve as
|
|
7028
|
+
import { resolve as resolve8, relative as relative12 } from "path";
|
|
5642
7029
|
|
|
5643
7030
|
// src/server/catalog/tools/_shared.ts
|
|
5644
7031
|
function makeError(message) {
|
|
@@ -5790,8 +7177,8 @@ function vercelGrepTool(sandbox, workspaceRoot) {
|
|
|
5790
7177
|
return makeError("pattern is required");
|
|
5791
7178
|
}
|
|
5792
7179
|
if (workspaceRoot && typeof params.path === "string" && params.path.length > 0) {
|
|
5793
|
-
const resolved =
|
|
5794
|
-
const rel =
|
|
7180
|
+
const resolved = resolve8(workspaceRoot, params.path);
|
|
7181
|
+
const rel = relative12(workspaceRoot, resolved);
|
|
5795
7182
|
if (rel.startsWith("..") || rel.startsWith("/")) {
|
|
5796
7183
|
return makeError(`path "${params.path}" is outside workspace`);
|
|
5797
7184
|
}
|
|
@@ -5879,7 +7266,20 @@ import {
|
|
|
5879
7266
|
function shellEscape2(s) {
|
|
5880
7267
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
5881
7268
|
}
|
|
5882
|
-
function
|
|
7269
|
+
function mergeRuntimeEnv(runtime, commandEnv) {
|
|
7270
|
+
const current = runtime?.getCurrent?.() ?? runtime;
|
|
7271
|
+
if (!current?.env && !current?.pathEntries?.length) return commandEnv;
|
|
7272
|
+
const merged = {
|
|
7273
|
+
...current.env ?? {},
|
|
7274
|
+
...commandEnv ?? {}
|
|
7275
|
+
};
|
|
7276
|
+
const pathParts = [...current.pathEntries ?? []];
|
|
7277
|
+
if (current.env?.PATH) pathParts.push(current.env.PATH);
|
|
7278
|
+
if (commandEnv?.PATH) pathParts.push(commandEnv.PATH);
|
|
7279
|
+
if (pathParts.length > 0) merged.PATH = pathParts.join(":");
|
|
7280
|
+
return merged;
|
|
7281
|
+
}
|
|
7282
|
+
function bwrapSpawnHook(workspaceRoot, runtime) {
|
|
5883
7283
|
const args = buildBwrapArgs(workspaceRoot);
|
|
5884
7284
|
const bwrapPrefix = ["bwrap", ...args].map(shellEscape2).join(" ");
|
|
5885
7285
|
return (context) => ({
|
|
@@ -5887,30 +7287,41 @@ function bwrapSpawnHook(workspaceRoot) {
|
|
|
5887
7287
|
command: `${bwrapPrefix} bash -lc ${shellEscape2(context.command)}`,
|
|
5888
7288
|
env: withWorkspacePythonEnv({
|
|
5889
7289
|
workspaceRoot,
|
|
5890
|
-
env: context.env,
|
|
7290
|
+
env: mergeRuntimeEnv(runtime, context.env),
|
|
5891
7291
|
sandboxRoot: "/workspace"
|
|
5892
7292
|
})
|
|
5893
7293
|
});
|
|
5894
7294
|
}
|
|
5895
|
-
function directSpawnHook(workspaceRoot) {
|
|
7295
|
+
function directSpawnHook(workspaceRoot, runtime) {
|
|
5896
7296
|
return (context) => ({
|
|
5897
7297
|
...context,
|
|
5898
|
-
env: withWorkspacePythonEnv({
|
|
7298
|
+
env: withWorkspacePythonEnv({
|
|
7299
|
+
workspaceRoot,
|
|
7300
|
+
env: mergeRuntimeEnv(runtime, context.env)
|
|
7301
|
+
})
|
|
5899
7302
|
});
|
|
5900
7303
|
}
|
|
5901
|
-
|
|
7304
|
+
var VERCEL_SAFE_DEFAULT_PATH = "/vercel/runtimes/node24/bin:/vercel/runtimes/node22/bin:/usr/local/bin:/usr/bin:/bin";
|
|
7305
|
+
function bashOptionsForMode(bundle, runtime) {
|
|
5902
7306
|
switch (bundle.sandbox.provider) {
|
|
5903
7307
|
case "vercel-sandbox":
|
|
5904
|
-
return {
|
|
7308
|
+
return {
|
|
7309
|
+
operations: vercelBashOps(bundle.sandbox, {
|
|
7310
|
+
// The pi bash tool's env may include the host process env. Never
|
|
7311
|
+
// forward host secrets into a remote sandbox; provide only the
|
|
7312
|
+
// provisioned runtime env plus a conservative remote PATH tail.
|
|
7313
|
+
mergeEnv: () => mergeRuntimeEnv(runtime, { PATH: VERCEL_SAFE_DEFAULT_PATH })
|
|
7314
|
+
})
|
|
7315
|
+
};
|
|
5905
7316
|
case "bwrap":
|
|
5906
7317
|
return {
|
|
5907
7318
|
operations: createLocalBashOperations(),
|
|
5908
|
-
spawnHook: bwrapSpawnHook(bundle.workspace.root)
|
|
7319
|
+
spawnHook: bwrapSpawnHook(bundle.workspace.root, runtime)
|
|
5909
7320
|
};
|
|
5910
7321
|
default:
|
|
5911
7322
|
return {
|
|
5912
7323
|
operations: createLocalBashOperations(),
|
|
5913
|
-
spawnHook: directSpawnHook(bundle.workspace.root)
|
|
7324
|
+
spawnHook: directSpawnHook(bundle.workspace.root, runtime)
|
|
5914
7325
|
};
|
|
5915
7326
|
}
|
|
5916
7327
|
}
|
|
@@ -5993,9 +7404,9 @@ function createExecuteIsolatedCodeTool(sandbox) {
|
|
|
5993
7404
|
}
|
|
5994
7405
|
};
|
|
5995
7406
|
}
|
|
5996
|
-
function buildHarnessAgentTools(bundle) {
|
|
7407
|
+
function buildHarnessAgentTools(bundle, runtime) {
|
|
5997
7408
|
const tools = [
|
|
5998
|
-
adaptPiTool2(createBashToolDefinition(bundle.workspace.root, bashOptionsForMode(bundle)))
|
|
7409
|
+
adaptPiTool2(createBashToolDefinition(bundle.workspace.root, bashOptionsForMode(bundle, runtime)))
|
|
5999
7410
|
];
|
|
6000
7411
|
if (bundle.sandbox.capabilities.includes("isolated-code")) {
|
|
6001
7412
|
tools.push(createExecuteIsolatedCodeTool(bundle.sandbox));
|
|
@@ -6220,7 +7631,7 @@ var PathError = class extends Error {
|
|
|
6220
7631
|
this.name = "PathError";
|
|
6221
7632
|
}
|
|
6222
7633
|
};
|
|
6223
|
-
function
|
|
7634
|
+
function joinPath2(base, name) {
|
|
6224
7635
|
if (base === ".") return name;
|
|
6225
7636
|
return `${base}/${name}`;
|
|
6226
7637
|
}
|
|
@@ -6235,7 +7646,7 @@ async function listTree(workspace, dir, recursive) {
|
|
|
6235
7646
|
const batch = queue.shift();
|
|
6236
7647
|
for (const e of batch.items) {
|
|
6237
7648
|
if (entries.length >= MAX_ENTRIES) break;
|
|
6238
|
-
const entryPath =
|
|
7649
|
+
const entryPath = joinPath2(batch.parentDir, e.name);
|
|
6239
7650
|
entries.push({ name: e.name, kind: e.kind, path: entryPath });
|
|
6240
7651
|
if (recursive && e.kind === "dir" && batch.depth < MAX_DEPTH) {
|
|
6241
7652
|
try {
|
|
@@ -6544,8 +7955,17 @@ var chatBodySchema = z.object({
|
|
|
6544
7955
|
})
|
|
6545
7956
|
).max(20).optional()
|
|
6546
7957
|
});
|
|
7958
|
+
function addTelemetryProperty(properties, key, value) {
|
|
7959
|
+
if (typeof value === "string" && value) properties[key] = value;
|
|
7960
|
+
if (typeof value === "number" && Number.isFinite(value)) properties[key] = value;
|
|
7961
|
+
}
|
|
7962
|
+
function safeTelemetryErrorCode(value) {
|
|
7963
|
+
const parsed = ErrorCode.safeParse(value);
|
|
7964
|
+
return parsed.success ? parsed.data : ErrorCode.enum.INTERNAL_ERROR;
|
|
7965
|
+
}
|
|
6547
7966
|
function chatRoutes(app, opts, done) {
|
|
6548
7967
|
const { sessionChangesTracker } = opts;
|
|
7968
|
+
const telemetry = opts.telemetry ?? noopTelemetry;
|
|
6549
7969
|
const validateBody = createBodyValidator(chatBodySchema);
|
|
6550
7970
|
const buffers = new StreamBufferStore();
|
|
6551
7971
|
const lastFollowUpBySession = /* @__PURE__ */ new Map();
|
|
@@ -6590,8 +8010,18 @@ function chatRoutes(app, opts, done) {
|
|
|
6590
8010
|
async (request, reply) => {
|
|
6591
8011
|
const { sessionId, message, model, thinkingLevel, attachments } = request.body;
|
|
6592
8012
|
const turnId = randomUUID3();
|
|
8013
|
+
const startedAt = Date.now();
|
|
8014
|
+
const telemetryProperties2 = {
|
|
8015
|
+
sessionId,
|
|
8016
|
+
requestId: request.id
|
|
8017
|
+
};
|
|
8018
|
+
addTelemetryProperty(telemetryProperties2, "workspaceId", request.workspaceContext?.workspaceId);
|
|
8019
|
+
addTelemetryProperty(telemetryProperties2, "modelProvider", model?.provider);
|
|
6593
8020
|
request.log.info({ sessionId, turnId, model, thinkingLevel }, "[chat] start");
|
|
6594
|
-
|
|
8021
|
+
safeCapture(telemetry, {
|
|
8022
|
+
name: "agent.chat.started",
|
|
8023
|
+
properties: telemetryProperties2
|
|
8024
|
+
});
|
|
6595
8025
|
const abortController = new AbortController();
|
|
6596
8026
|
let streamStarted = false;
|
|
6597
8027
|
let streamCompleted = false;
|
|
@@ -6600,18 +8030,24 @@ function chatRoutes(app, opts, done) {
|
|
|
6600
8030
|
abortController.abort();
|
|
6601
8031
|
}
|
|
6602
8032
|
});
|
|
6603
|
-
const ctx = {
|
|
6604
|
-
abortSignal: abortController.signal,
|
|
6605
|
-
workdir: runtime.workdir
|
|
6606
|
-
};
|
|
6607
8033
|
const buf = buffers.create(sessionId, turnId);
|
|
6608
8034
|
try {
|
|
8035
|
+
const runtime = await resolveRuntime(request);
|
|
8036
|
+
const ctx = {
|
|
8037
|
+
abortSignal: abortController.signal,
|
|
8038
|
+
workdir: runtime.workdir
|
|
8039
|
+
};
|
|
8040
|
+
safeCapture(telemetry, {
|
|
8041
|
+
name: "agent.chat.message.submitted",
|
|
8042
|
+
properties: telemetryProperties2
|
|
8043
|
+
});
|
|
6609
8044
|
const chunks = runtime.harness.sendMessage(
|
|
6610
8045
|
{ sessionId, message, model, thinkingLevel, attachments },
|
|
6611
8046
|
ctx
|
|
6612
8047
|
);
|
|
6613
8048
|
const stream = createUIMessageStream({
|
|
6614
8049
|
async execute({ writer }) {
|
|
8050
|
+
let streamFailed = false;
|
|
6615
8051
|
try {
|
|
6616
8052
|
for await (const chunk2 of chunks) {
|
|
6617
8053
|
const c = chunk2;
|
|
@@ -6623,7 +8059,17 @@ function chatRoutes(app, opts, done) {
|
|
|
6623
8059
|
writer.write(c);
|
|
6624
8060
|
}
|
|
6625
8061
|
} catch (err) {
|
|
8062
|
+
streamFailed = true;
|
|
6626
8063
|
request.log.error({ err, sessionId }, "[chat] stream error");
|
|
8064
|
+
safeCapture(telemetry, {
|
|
8065
|
+
name: "agent.chat.failed",
|
|
8066
|
+
properties: {
|
|
8067
|
+
...telemetryProperties2,
|
|
8068
|
+
status: "error",
|
|
8069
|
+
durationMs: Date.now() - startedAt,
|
|
8070
|
+
errorCode: ErrorCode.enum.INTERNAL_ERROR
|
|
8071
|
+
}
|
|
8072
|
+
});
|
|
6627
8073
|
const errChunk = {
|
|
6628
8074
|
type: "error",
|
|
6629
8075
|
errorText: "internal error"
|
|
@@ -6632,6 +8078,16 @@ function chatRoutes(app, opts, done) {
|
|
|
6632
8078
|
writer.write(errChunk);
|
|
6633
8079
|
} finally {
|
|
6634
8080
|
streamCompleted = true;
|
|
8081
|
+
if (!streamFailed) {
|
|
8082
|
+
safeCapture(telemetry, {
|
|
8083
|
+
name: "agent.chat.completed",
|
|
8084
|
+
properties: {
|
|
8085
|
+
...telemetryProperties2,
|
|
8086
|
+
status: "ok",
|
|
8087
|
+
durationMs: Date.now() - startedAt
|
|
8088
|
+
}
|
|
8089
|
+
});
|
|
8090
|
+
}
|
|
6635
8091
|
buf.markComplete(() => buffers.evict(sessionId, turnId));
|
|
6636
8092
|
}
|
|
6637
8093
|
}
|
|
@@ -6651,6 +8107,15 @@ function chatRoutes(app, opts, done) {
|
|
|
6651
8107
|
return;
|
|
6652
8108
|
} catch (err) {
|
|
6653
8109
|
request.log.error({ err, sessionId }, "[chat] error");
|
|
8110
|
+
safeCapture(telemetry, {
|
|
8111
|
+
name: "agent.chat.failed",
|
|
8112
|
+
properties: {
|
|
8113
|
+
...telemetryProperties2,
|
|
8114
|
+
status: "error",
|
|
8115
|
+
durationMs: Date.now() - startedAt,
|
|
8116
|
+
errorCode: safeTelemetryErrorCode(err?.code)
|
|
8117
|
+
}
|
|
8118
|
+
});
|
|
6654
8119
|
buf.markComplete(() => buffers.evict(sessionId, turnId));
|
|
6655
8120
|
if (streamStarted) return;
|
|
6656
8121
|
return reply.code(500).send({
|
|
@@ -6695,17 +8160,17 @@ function chatRoutes(app, opts, done) {
|
|
|
6695
8160
|
const replayed = buf.replay(cursor);
|
|
6696
8161
|
for (const e of replayed) writer.write(e.chunk);
|
|
6697
8162
|
if (buf.complete) return;
|
|
6698
|
-
await new Promise((
|
|
8163
|
+
await new Promise((resolve9) => {
|
|
6699
8164
|
const unsub = buf.subscribe(
|
|
6700
8165
|
(e) => writer.write(e.chunk),
|
|
6701
8166
|
() => {
|
|
6702
8167
|
unsub();
|
|
6703
|
-
|
|
8168
|
+
resolve9();
|
|
6704
8169
|
}
|
|
6705
8170
|
);
|
|
6706
8171
|
request.raw.on("close", () => {
|
|
6707
8172
|
unsub();
|
|
6708
|
-
|
|
8173
|
+
resolve9();
|
|
6709
8174
|
});
|
|
6710
8175
|
});
|
|
6711
8176
|
}
|
|
@@ -7061,7 +8526,7 @@ function classifySessionError(err, reply) {
|
|
|
7061
8526
|
}
|
|
7062
8527
|
});
|
|
7063
8528
|
}
|
|
7064
|
-
function
|
|
8529
|
+
function stableStringify2(value) {
|
|
7065
8530
|
if (typeof value === "string") return value;
|
|
7066
8531
|
try {
|
|
7067
8532
|
return JSON.stringify(value, null, 2);
|
|
@@ -7087,10 +8552,10 @@ function formatToolPart(part) {
|
|
|
7087
8552
|
const name = toolName(part);
|
|
7088
8553
|
const lines = [`[tool:${name}]`];
|
|
7089
8554
|
if ("input" in part) {
|
|
7090
|
-
lines.push("input:",
|
|
8555
|
+
lines.push("input:", stableStringify2(part.input));
|
|
7091
8556
|
}
|
|
7092
8557
|
if ("output" in part) {
|
|
7093
|
-
lines.push("output:",
|
|
8558
|
+
lines.push("output:", stableStringify2(part.output));
|
|
7094
8559
|
}
|
|
7095
8560
|
if (typeof part.errorText === "string" && part.errorText.length > 0) {
|
|
7096
8561
|
lines.push("error:", part.errorText);
|
|
@@ -7640,8 +9105,21 @@ async function createAgentApp(opts = {}) {
|
|
|
7640
9105
|
pluginTools.push(...plugin.tools);
|
|
7641
9106
|
}
|
|
7642
9107
|
}
|
|
9108
|
+
const getRuntimeProvisioning = opts.getRuntimeProvisioning ?? (() => opts.runtimeProvisioning);
|
|
9109
|
+
const runtimePi = {
|
|
9110
|
+
...opts.pi,
|
|
9111
|
+
additionalSkillPaths: [
|
|
9112
|
+
...getRuntimeProvisioning()?.skillPaths ?? [],
|
|
9113
|
+
...opts.pi?.additionalSkillPaths ?? []
|
|
9114
|
+
]
|
|
9115
|
+
};
|
|
7643
9116
|
const tools = [
|
|
7644
|
-
...buildHarnessAgentTools(runtimeBundle
|
|
9117
|
+
...buildHarnessAgentTools(runtimeBundle, {
|
|
9118
|
+
getCurrent: () => {
|
|
9119
|
+
const current = getRuntimeProvisioning();
|
|
9120
|
+
return current ? { env: current.env, pathEntries: current.pathEntries } : void 0;
|
|
9121
|
+
}
|
|
9122
|
+
}),
|
|
7645
9123
|
...opts.disableDefaultFileTools ? [] : buildFilesystemAgentTools(runtimeBundle),
|
|
7646
9124
|
...opts.extraTools ?? [],
|
|
7647
9125
|
...pluginTools
|
|
@@ -7651,7 +9129,7 @@ async function createAgentApp(opts = {}) {
|
|
|
7651
9129
|
pi: {
|
|
7652
9130
|
noContextFiles: true,
|
|
7653
9131
|
noSkills: true,
|
|
7654
|
-
...
|
|
9132
|
+
...runtimePi
|
|
7655
9133
|
}
|
|
7656
9134
|
}));
|
|
7657
9135
|
const harness = await harnessFactory({
|
|
@@ -7660,7 +9138,8 @@ async function createAgentApp(opts = {}) {
|
|
|
7660
9138
|
sessionNamespace: opts.sessionNamespace,
|
|
7661
9139
|
sessionDir: opts.sessionDir,
|
|
7662
9140
|
systemPromptAppend: opts.systemPromptAppend,
|
|
7663
|
-
systemPromptDynamic: opts.systemPromptDynamic
|
|
9141
|
+
systemPromptDynamic: opts.systemPromptDynamic,
|
|
9142
|
+
telemetry: opts.telemetry
|
|
7664
9143
|
});
|
|
7665
9144
|
const sessionChangesTracker = new InMemorySessionChangesTracker();
|
|
7666
9145
|
const readyTracker = new ReadyStatusTracker({
|
|
@@ -7688,7 +9167,8 @@ async function createAgentApp(opts = {}) {
|
|
|
7688
9167
|
await app.register(chatRoutes, {
|
|
7689
9168
|
harness,
|
|
7690
9169
|
workdir: runtimeBundle.workspace.root,
|
|
7691
|
-
sessionChangesTracker
|
|
9170
|
+
sessionChangesTracker,
|
|
9171
|
+
telemetry: opts.telemetry
|
|
7692
9172
|
});
|
|
7693
9173
|
await app.register(sessionRoutes, {
|
|
7694
9174
|
sessionStore: harness.sessions,
|
|
@@ -7699,9 +9179,18 @@ async function createAgentApp(opts = {}) {
|
|
|
7699
9179
|
await app.register(modelsRoutes);
|
|
7700
9180
|
await app.register(skillsRoutes, {
|
|
7701
9181
|
workspaceRoot,
|
|
7702
|
-
additionalSkillPaths:
|
|
7703
|
-
piPackages:
|
|
7704
|
-
noSkills:
|
|
9182
|
+
additionalSkillPaths: runtimePi.additionalSkillPaths,
|
|
9183
|
+
piPackages: runtimePi.packages,
|
|
9184
|
+
noSkills: runtimePi.noSkills,
|
|
9185
|
+
getAdditionalSkillPaths: () => [
|
|
9186
|
+
...getRuntimeProvisioning()?.skillPaths ?? [],
|
|
9187
|
+
...opts.pi?.additionalSkillPaths ?? [],
|
|
9188
|
+
...opts.pi?.getHotReloadableResources?.().additionalSkillPaths ?? []
|
|
9189
|
+
],
|
|
9190
|
+
getPiPackages: () => [
|
|
9191
|
+
...opts.pi?.packages ?? [],
|
|
9192
|
+
...opts.pi?.getHotReloadableResources?.().packages ?? []
|
|
9193
|
+
]
|
|
7705
9194
|
});
|
|
7706
9195
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
7707
9196
|
await app.register(catalogRoutes, { tools });
|
|
@@ -7735,7 +9224,7 @@ function applyCspHeaders(response, opts = {}) {
|
|
|
7735
9224
|
}
|
|
7736
9225
|
|
|
7737
9226
|
// src/server/registerAgentRoutes.ts
|
|
7738
|
-
import { basename as
|
|
9227
|
+
import { basename as basename3 } from "path";
|
|
7739
9228
|
import { AuthStorage as AuthStorage3, ModelRegistry as ModelRegistry3 } from "@mariozechner/pi-coding-agent";
|
|
7740
9229
|
|
|
7741
9230
|
// src/server/catalog/mergeTools.ts
|
|
@@ -7770,8 +9259,8 @@ function mergeTools(options) {
|
|
|
7770
9259
|
}
|
|
7771
9260
|
|
|
7772
9261
|
// src/server/tools/upload/index.ts
|
|
7773
|
-
import { readFile as
|
|
7774
|
-
import { extname as extname4, join as
|
|
9262
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
9263
|
+
import { extname as extname4, join as join12 } from "path";
|
|
7775
9264
|
var DEFAULT_UPLOAD_DIR = "assets/images";
|
|
7776
9265
|
var MAX_UPLOAD_BYTES2 = 10 * 1024 * 1024;
|
|
7777
9266
|
function contentTypeFromExt(path4) {
|
|
@@ -7839,7 +9328,7 @@ function buildUploadAgentTools(bundle) {
|
|
|
7839
9328
|
const rawDir = typeof input.directory === "string" ? input.directory.trim() : "";
|
|
7840
9329
|
const dir = rawDir ? rawDir.replace(/^\.\/+/, "").replace(/\/+$/, "") : DEFAULT_UPLOAD_DIR;
|
|
7841
9330
|
try {
|
|
7842
|
-
const bytes = await
|
|
9331
|
+
const bytes = await readFile12(join12(cwd, filePath));
|
|
7843
9332
|
if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES2) {
|
|
7844
9333
|
return {
|
|
7845
9334
|
content: [{ type: "text", text: `file must be between 1 byte and ${MAX_UPLOAD_BYTES2} bytes` }],
|
|
@@ -7882,7 +9371,7 @@ var DEFAULT_WORKSPACE_ID3 = "default";
|
|
|
7882
9371
|
var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
|
|
7883
9372
|
var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
|
|
7884
9373
|
function pluginNameFromPath(path4) {
|
|
7885
|
-
const fileName =
|
|
9374
|
+
const fileName = basename3(path4);
|
|
7886
9375
|
if (fileName.endsWith(".mjs")) return fileName.slice(0, -4);
|
|
7887
9376
|
if (fileName.endsWith(".js")) return fileName.slice(0, -3);
|
|
7888
9377
|
return fileName;
|
|
@@ -7977,16 +9466,47 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
7977
9466
|
const pi = opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot: root, request }) : opts.pi;
|
|
7978
9467
|
return { root, pi };
|
|
7979
9468
|
}
|
|
9469
|
+
async function runRuntimeProvisioning(workspaceId, scope, request) {
|
|
9470
|
+
if (opts.provisionWorkspace === false || !opts.provisionRuntime) return void 0;
|
|
9471
|
+
const modeCtx = {
|
|
9472
|
+
workspaceRoot: scope.root,
|
|
9473
|
+
sessionId: workspaceId,
|
|
9474
|
+
workspaceId,
|
|
9475
|
+
templatePath: scope.templatePath,
|
|
9476
|
+
requestId: request?.id,
|
|
9477
|
+
telemetry: opts.telemetry
|
|
9478
|
+
};
|
|
9479
|
+
const runtimeLayout = getBoringAgentRuntimePaths(
|
|
9480
|
+
resolvedMode === "vercel-sandbox" ? VERCEL_SANDBOX_WORKSPACE_ROOT : scope.root
|
|
9481
|
+
);
|
|
9482
|
+
return await opts.provisionRuntime({
|
|
9483
|
+
workspaceId,
|
|
9484
|
+
workspaceRoot: scope.root,
|
|
9485
|
+
runtimeMode: resolvedMode,
|
|
9486
|
+
runtimeLayout,
|
|
9487
|
+
provisioningAdapter: modeAdapter.createProvisioningAdapter?.(runtimeLayout, modeCtx),
|
|
9488
|
+
request
|
|
9489
|
+
});
|
|
9490
|
+
}
|
|
7980
9491
|
async function createRuntimeBinding(workspaceId, scope, request) {
|
|
7981
9492
|
const root = scope.root;
|
|
7982
|
-
const
|
|
9493
|
+
const modeCtx = {
|
|
7983
9494
|
workspaceRoot: root,
|
|
7984
9495
|
sessionId: workspaceId,
|
|
7985
9496
|
workspaceId,
|
|
7986
|
-
templatePath: scope.templatePath
|
|
7987
|
-
|
|
9497
|
+
templatePath: scope.templatePath,
|
|
9498
|
+
requestId: request?.id,
|
|
9499
|
+
telemetry: opts.telemetry
|
|
9500
|
+
};
|
|
9501
|
+
let runtimeProvisioning = await runRuntimeProvisioning(workspaceId, scope, request);
|
|
9502
|
+
const runtimeBundle = await modeAdapter.create(modeCtx);
|
|
7988
9503
|
const standardTools = [
|
|
7989
|
-
...buildHarnessAgentTools(runtimeBundle
|
|
9504
|
+
...buildHarnessAgentTools(runtimeBundle, {
|
|
9505
|
+
getCurrent: () => runtimeProvisioning ? {
|
|
9506
|
+
env: runtimeProvisioning.env,
|
|
9507
|
+
pathEntries: runtimeProvisioning.pathEntries
|
|
9508
|
+
} : void 0
|
|
9509
|
+
}),
|
|
7990
9510
|
...buildFilesystemAgentTools(runtimeBundle),
|
|
7991
9511
|
...buildUploadAgentTools(runtimeBundle)
|
|
7992
9512
|
];
|
|
@@ -8025,14 +9545,19 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8025
9545
|
pi: {
|
|
8026
9546
|
noContextFiles: true,
|
|
8027
9547
|
noSkills: true,
|
|
8028
|
-
...scope.pi
|
|
9548
|
+
...scope.pi,
|
|
9549
|
+
additionalSkillPaths: [
|
|
9550
|
+
...runtimeProvisioning?.skillPaths ?? [],
|
|
9551
|
+
...scope.pi?.additionalSkillPaths ?? []
|
|
9552
|
+
]
|
|
8029
9553
|
}
|
|
8030
9554
|
}));
|
|
8031
9555
|
const harness = await harnessFactory({
|
|
8032
9556
|
tools,
|
|
8033
9557
|
cwd: root,
|
|
8034
9558
|
sessionNamespace: scope.sessionNamespace,
|
|
8035
|
-
systemPromptAppend: opts.systemPromptAppend
|
|
9559
|
+
systemPromptAppend: opts.systemPromptAppend,
|
|
9560
|
+
telemetry: opts.telemetry
|
|
8036
9561
|
});
|
|
8037
9562
|
const readyTracker = new ReadyStatusTracker({
|
|
8038
9563
|
sandboxReady: resolvedMode !== "vercel-sandbox",
|
|
@@ -8043,6 +9568,11 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8043
9568
|
}
|
|
8044
9569
|
return {
|
|
8045
9570
|
runtimeBundle,
|
|
9571
|
+
runtimeProvisioning,
|
|
9572
|
+
reprovision: async (reloadRequest) => {
|
|
9573
|
+
runtimeProvisioning = await runRuntimeProvisioning(workspaceId, scope, reloadRequest);
|
|
9574
|
+
return runtimeProvisioning;
|
|
9575
|
+
},
|
|
8046
9576
|
harness,
|
|
8047
9577
|
tools,
|
|
8048
9578
|
readyTracker
|
|
@@ -8106,6 +9636,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8106
9636
|
return await recreateRuntimeBinding(workspaceId, scope);
|
|
8107
9637
|
}
|
|
8108
9638
|
}
|
|
9639
|
+
const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
|
|
8109
9640
|
const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
|
|
8110
9641
|
const skillsScopeByRequest = /* @__PURE__ */ new WeakMap();
|
|
8111
9642
|
function getSkillsScopeForRequest(request) {
|
|
@@ -8194,7 +9725,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8194
9725
|
workdir: binding.runtimeBundle.workspace.root
|
|
8195
9726
|
};
|
|
8196
9727
|
},
|
|
8197
|
-
sessionChangesTracker
|
|
9728
|
+
sessionChangesTracker,
|
|
9729
|
+
telemetry: opts.telemetry
|
|
8198
9730
|
});
|
|
8199
9731
|
await app.register(sessionRoutes, {
|
|
8200
9732
|
getSessionStore: async (request) => {
|
|
@@ -8215,15 +9747,55 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8215
9747
|
await app.register(modelsRoutes);
|
|
8216
9748
|
await app.register(skillsRoutes, {
|
|
8217
9749
|
workspaceRoot,
|
|
8218
|
-
additionalSkillPaths:
|
|
9750
|
+
additionalSkillPaths: [
|
|
9751
|
+
...staticBinding?.runtimeProvisioning?.skillPaths ?? [],
|
|
9752
|
+
...opts.pi?.additionalSkillPaths ?? []
|
|
9753
|
+
],
|
|
8219
9754
|
piPackages: opts.pi?.packages,
|
|
8220
9755
|
noSkills: opts.pi?.noSkills,
|
|
8221
9756
|
getWorkspaceRoot: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).root,
|
|
8222
|
-
getAdditionalSkillPaths: staticBinding ? void 0 : async (request) =>
|
|
9757
|
+
getAdditionalSkillPaths: staticBinding && !hasRuntimeProvisioningInput ? void 0 : async (request) => {
|
|
9758
|
+
const scope = await getSkillsScopeForRequest(request);
|
|
9759
|
+
if (!hasRuntimeProvisioningInput) return scope.pi?.additionalSkillPaths;
|
|
9760
|
+
const binding = await getBindingForRequest(request);
|
|
9761
|
+
return [
|
|
9762
|
+
...binding.runtimeProvisioning?.skillPaths ?? [],
|
|
9763
|
+
...scope.pi?.additionalSkillPaths ?? []
|
|
9764
|
+
];
|
|
9765
|
+
},
|
|
8223
9766
|
getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi?.packages,
|
|
8224
9767
|
getNoSkills: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi?.noSkills
|
|
8225
9768
|
});
|
|
8226
9769
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
9770
|
+
app.post("/api/v1/agent/reload", async (request, reply) => {
|
|
9771
|
+
const workspaceId = getRequestWorkspaceId(request);
|
|
9772
|
+
const binding = await getBindingForRequest(request);
|
|
9773
|
+
if (!binding.harness.reloadSession) {
|
|
9774
|
+
return reply.status(501).send({ ok: false, error: "Agent harness does not support reload" });
|
|
9775
|
+
}
|
|
9776
|
+
try {
|
|
9777
|
+
binding.runtimeProvisioning = await binding.reprovision(request);
|
|
9778
|
+
const hookResult = await opts.beforeReload?.({
|
|
9779
|
+
workspaceId,
|
|
9780
|
+
workspaceRoot: binding.runtimeBundle.workspace.root,
|
|
9781
|
+
request
|
|
9782
|
+
});
|
|
9783
|
+
const reloadSessionId = request.body?.sessionId || sessionId;
|
|
9784
|
+
const reloaded = await binding.harness.reloadSession(reloadSessionId);
|
|
9785
|
+
const restart_warnings = hookResult?.restart_warnings;
|
|
9786
|
+
const diagnostics = hookResult?.diagnostics;
|
|
9787
|
+
return {
|
|
9788
|
+
ok: true,
|
|
9789
|
+
sessionId: reloadSessionId,
|
|
9790
|
+
reloaded,
|
|
9791
|
+
...restart_warnings && restart_warnings.length > 0 ? { restart_warnings } : {},
|
|
9792
|
+
...diagnostics && diagnostics.length > 0 ? { diagnostics } : {}
|
|
9793
|
+
};
|
|
9794
|
+
} catch (error) {
|
|
9795
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9796
|
+
return reply.status(422).send({ ok: false, error: message });
|
|
9797
|
+
}
|
|
9798
|
+
});
|
|
8227
9799
|
await app.register(
|
|
8228
9800
|
catalogRoutes,
|
|
8229
9801
|
staticBinding ? { tools: staticBinding.tools } : { getTools: async (request) => (await getBindingForRequest(request)).tools }
|
|
@@ -8239,6 +9811,8 @@ export {
|
|
|
8239
9811
|
FileHandleStore,
|
|
8240
9812
|
PI_PACKAGE_RESOURCE_FILTERS,
|
|
8241
9813
|
UV_SETUP_COMMANDS,
|
|
9814
|
+
VERCEL_PROVISIONING_CACHE_ROOT,
|
|
9815
|
+
VERCEL_SANDBOX_WORKSPACE_ROOT,
|
|
8242
9816
|
UV_SETUP_COMMANDS as VERCEL_UV_SETUP_COMMANDS,
|
|
8243
9817
|
applyCspHeaders,
|
|
8244
9818
|
autoDetectMode,
|
|
@@ -8254,14 +9828,19 @@ export {
|
|
|
8254
9828
|
createNodeWorkspace,
|
|
8255
9829
|
createResourceSettingsManager,
|
|
8256
9830
|
createVercelDeploymentSnapshotProvider,
|
|
9831
|
+
createVercelProvisioningAdapter,
|
|
8257
9832
|
createVercelSandboxWorkspace,
|
|
8258
9833
|
fileRoutes,
|
|
9834
|
+
getBoringAgentPathEntries,
|
|
9835
|
+
getBoringAgentRuntimeEnv,
|
|
9836
|
+
getBoringAgentRuntimePaths,
|
|
8259
9837
|
hasBwrap,
|
|
8260
9838
|
mergePiPackageSources,
|
|
8261
9839
|
piPackageSourceKey,
|
|
8262
9840
|
prepareDeploymentSnapshot,
|
|
8263
9841
|
prepareVercelDeploymentSnapshot,
|
|
8264
9842
|
provisionRuntimeWorkspace,
|
|
9843
|
+
provisionWorkspaceRuntime,
|
|
8265
9844
|
registerAgentRoutes,
|
|
8266
9845
|
resolveMode,
|
|
8267
9846
|
resolveSandboxHandle
|