@hachej/boring-agent 0.1.18 → 0.1.22
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/{harness-DRrTn_5T.d.ts → harness-BCit36Ha.d.ts} +15 -1
- package/dist/server/index.d.ts +181 -11
- package/dist/server/index.js +1873 -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
|
}
|
|
@@ -2033,8 +2083,8 @@ function fileRoutes(app, opts, done) {
|
|
|
2033
2083
|
// src/server/workspace/provisionRuntime.ts
|
|
2034
2084
|
import { createHash as createHash3 } from "crypto";
|
|
2035
2085
|
import { constants as constants2 } from "fs";
|
|
2036
|
-
import { access as access2, chmod as chmod3, cp, mkdir as mkdir4, readdir as readdir2, readFile as readFile4, 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
|
|
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";
|
|
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,6 +2221,8 @@ function nodePackageTarget(workspaceRoot, packageName) {
|
|
|
2171
2221
|
}
|
|
2172
2222
|
async function copyIfExists(source, target) {
|
|
2173
2223
|
if (!await exists(source)) return false;
|
|
2224
|
+
if (resolve4(source) === resolve4(target)) return true;
|
|
2225
|
+
if (await exists(target) && await realpath2(source) === await realpath2(target)) return true;
|
|
2174
2226
|
await cp(source, target, {
|
|
2175
2227
|
recursive: true,
|
|
2176
2228
|
force: true,
|
|
@@ -2203,11 +2255,11 @@ async function ensureNodePackages(workspaceRoot, specs) {
|
|
|
2203
2255
|
}
|
|
2204
2256
|
async function ensurePython(workspaceRoot, specs) {
|
|
2205
2257
|
if (specs.length === 0) return;
|
|
2206
|
-
const venvPython = join3(workspaceRoot, ".venv", "bin", "python");
|
|
2258
|
+
const venvPython = join3(workspaceRoot, ".boring-agent", "venv", "bin", "python");
|
|
2207
2259
|
const uv = await commandExists("uv");
|
|
2208
2260
|
if (!await exists(venvPython)) {
|
|
2209
|
-
if (uv) await run("uv", ["venv", ".venv"], workspaceRoot);
|
|
2210
|
-
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);
|
|
2211
2263
|
}
|
|
2212
2264
|
for (const spec of specs) {
|
|
2213
2265
|
const projectDir = dirname5(toPath(spec.projectFile));
|
|
@@ -2236,7 +2288,7 @@ function assertEnvKey(key) {
|
|
|
2236
2288
|
}
|
|
2237
2289
|
async function isRuntimeMaterialized(workspaceRoot, contributions) {
|
|
2238
2290
|
const hasPython = contributions.some(({ provisioning }) => (provisioning.python ?? []).length > 0);
|
|
2239
|
-
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;
|
|
2240
2292
|
for (const { provisioning } of contributions) {
|
|
2241
2293
|
for (const spec of provisioning.nodePackages ?? []) {
|
|
2242
2294
|
if (!await exists(join3(nodePackageTarget(workspaceRoot, spec.packageName), "package.json"))) return false;
|
|
@@ -2246,7 +2298,7 @@ async function isRuntimeMaterialized(workspaceRoot, contributions) {
|
|
|
2246
2298
|
}
|
|
2247
2299
|
async function writeShims(workspaceRoot, env) {
|
|
2248
2300
|
const shimDir = join3(workspaceRoot, ".boring-agent", "bin");
|
|
2249
|
-
const venvBin = join3(workspaceRoot, ".venv", "bin");
|
|
2301
|
+
const venvBin = join3(workspaceRoot, ".boring-agent", "venv", "bin");
|
|
2250
2302
|
await mkdir4(shimDir, { recursive: true });
|
|
2251
2303
|
const exports = Object.entries(env).map(([key, value]) => {
|
|
2252
2304
|
assertEnvKey(key);
|
|
@@ -2258,7 +2310,7 @@ SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
|
2258
2310
|
WORKSPACE_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)"
|
|
2259
2311
|
export BORING_AGENT_WORKSPACE_ROOT="$WORKSPACE_ROOT"
|
|
2260
2312
|
${exports}
|
|
2261
|
-
VENV_BIN="$WORKSPACE_ROOT/.venv/bin"
|
|
2313
|
+
VENV_BIN="$WORKSPACE_ROOT/.boring-agent/venv/bin"
|
|
2262
2314
|
`;
|
|
2263
2315
|
await writeExecutable(join3(shimDir, "python"), `${base}exec "$VENV_BIN/python" "$@"
|
|
2264
2316
|
`);
|
|
@@ -2357,11 +2409,11 @@ function createTimedLruCache(ttlMs, maxEntries) {
|
|
|
2357
2409
|
}
|
|
2358
2410
|
};
|
|
2359
2411
|
}
|
|
2360
|
-
function cloneStat(
|
|
2412
|
+
function cloneStat(stat10) {
|
|
2361
2413
|
return {
|
|
2362
|
-
size:
|
|
2363
|
-
mtimeMs:
|
|
2364
|
-
kind:
|
|
2414
|
+
size: stat10.size,
|
|
2415
|
+
mtimeMs: stat10.mtimeMs,
|
|
2416
|
+
kind: stat10.kind
|
|
2365
2417
|
};
|
|
2366
2418
|
}
|
|
2367
2419
|
function cloneEntries(entries) {
|
|
@@ -2519,15 +2571,15 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2519
2571
|
remote.fs.readFile(sandboxPath, "utf8"),
|
|
2520
2572
|
remote.fs.stat(sandboxPath)
|
|
2521
2573
|
]);
|
|
2522
|
-
const
|
|
2574
|
+
const stat10 = {
|
|
2523
2575
|
size: fileStat.size,
|
|
2524
2576
|
mtimeMs: fileStat.mtimeMs,
|
|
2525
2577
|
kind: fileStat.isDirectory() ? "dir" : "file"
|
|
2526
2578
|
};
|
|
2527
|
-
statCache.set(sandboxPath,
|
|
2579
|
+
statCache.set(sandboxPath, stat10);
|
|
2528
2580
|
return {
|
|
2529
2581
|
content: typeof content === "string" ? content : Buffer.from(content).toString("utf-8"),
|
|
2530
|
-
stat: cloneStat(
|
|
2582
|
+
stat: cloneStat(stat10)
|
|
2531
2583
|
};
|
|
2532
2584
|
}
|
|
2533
2585
|
const version = metadataVersion;
|
|
@@ -2687,11 +2739,801 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
2687
2739
|
};
|
|
2688
2740
|
}
|
|
2689
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
|
+
|
|
2690
3532
|
// src/server/runtime/resolveMode.ts
|
|
2691
3533
|
import { spawnSync } from "child_process";
|
|
2692
3534
|
|
|
2693
3535
|
// src/server/runtime/modes/direct.ts
|
|
2694
|
-
import { mkdir as
|
|
3536
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
2695
3537
|
|
|
2696
3538
|
// src/server/runtime/createServerFileSearch.ts
|
|
2697
3539
|
var DEFAULT_LIMIT = 500;
|
|
@@ -2749,28 +3591,14 @@ function createServerFileSearch(workspace, sandbox) {
|
|
|
2749
3591
|
}
|
|
2750
3592
|
|
|
2751
3593
|
// src/server/workspace/provision.ts
|
|
2752
|
-
import { cp as cp2
|
|
2753
|
-
import { dirname as dirname6, join as join4 } from "path";
|
|
2754
|
-
var PROVISION_MARKER_REL_PATH = ".boring-agent/provisioned";
|
|
2755
|
-
async function fileExists(targetPath) {
|
|
2756
|
-
try {
|
|
2757
|
-
await stat4(targetPath);
|
|
2758
|
-
return true;
|
|
2759
|
-
} catch (error) {
|
|
2760
|
-
if (error.code === "ENOENT") {
|
|
2761
|
-
return false;
|
|
2762
|
-
}
|
|
2763
|
-
throw error;
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
3594
|
+
import { cp as cp2 } from "fs/promises";
|
|
2766
3595
|
async function copyTemplate(templatePath, workspaceRoot) {
|
|
2767
3596
|
if (!templatePath) return;
|
|
2768
|
-
const markerPath = join4(workspaceRoot, PROVISION_MARKER_REL_PATH);
|
|
2769
|
-
if (await fileExists(markerPath)) return;
|
|
2770
3597
|
try {
|
|
2771
3598
|
await cp2(templatePath, workspaceRoot, {
|
|
2772
3599
|
recursive: true,
|
|
2773
|
-
errorOnExist: false
|
|
3600
|
+
errorOnExist: false,
|
|
3601
|
+
force: false
|
|
2774
3602
|
});
|
|
2775
3603
|
} catch (error) {
|
|
2776
3604
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2779,16 +3607,230 @@ async function copyTemplate(templatePath, workspaceRoot) {
|
|
|
2779
3607
|
{ cause: error }
|
|
2780
3608
|
);
|
|
2781
3609
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
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
|
+
};
|
|
2784
3825
|
}
|
|
2785
3826
|
|
|
2786
3827
|
// src/server/runtime/modes/direct.ts
|
|
2787
3828
|
var directModeAdapter = {
|
|
2788
3829
|
id: "direct",
|
|
2789
3830
|
workspaceFsCapability: "strong",
|
|
3831
|
+
createProvisioningAdapter: (runtimeLayout) => createDirectProvisioningAdapter(runtimeLayout),
|
|
2790
3832
|
async create(ctx) {
|
|
2791
|
-
await
|
|
3833
|
+
await mkdir7(ctx.workspaceRoot, { recursive: true });
|
|
2792
3834
|
await copyTemplate(ctx.templatePath, ctx.workspaceRoot);
|
|
2793
3835
|
const workspace = createNodeWorkspace(ctx.workspaceRoot);
|
|
2794
3836
|
const sandbox = createDirectSandbox();
|
|
@@ -2802,15 +3844,16 @@ var directModeAdapter = {
|
|
|
2802
3844
|
};
|
|
2803
3845
|
|
|
2804
3846
|
// src/server/runtime/modes/local.ts
|
|
2805
|
-
import { mkdir as
|
|
3847
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
2806
3848
|
var localModeAdapter = {
|
|
2807
3849
|
id: "local",
|
|
2808
3850
|
workspaceFsCapability: "strong",
|
|
3851
|
+
createProvisioningAdapter: (runtimeLayout) => createLocalProvisioningAdapter(runtimeLayout),
|
|
2809
3852
|
async create(ctx) {
|
|
2810
3853
|
if (process.platform !== "linux") {
|
|
2811
3854
|
throw new Error("local mode requires Linux with bubblewrap");
|
|
2812
3855
|
}
|
|
2813
|
-
await
|
|
3856
|
+
await mkdir8(ctx.workspaceRoot, { recursive: true });
|
|
2814
3857
|
await copyTemplate(ctx.templatePath, ctx.workspaceRoot);
|
|
2815
3858
|
const workspace = createNodeWorkspace(ctx.workspaceRoot);
|
|
2816
3859
|
const sandbox = createBwrapSandbox();
|
|
@@ -2824,6 +3867,11 @@ var localModeAdapter = {
|
|
|
2824
3867
|
};
|
|
2825
3868
|
|
|
2826
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";
|
|
2827
3875
|
import { Sandbox as VercelSandbox } from "@vercel/sandbox";
|
|
2828
3876
|
|
|
2829
3877
|
// src/server/sandbox/vercel-sandbox/createVercelSandboxExec.ts
|
|
@@ -2978,8 +4026,8 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
|
2978
4026
|
}
|
|
2979
4027
|
|
|
2980
4028
|
// src/server/sandbox/vercel-sandbox/packageTemplate.ts
|
|
2981
|
-
import { createHash as
|
|
2982
|
-
import { readFile as
|
|
4029
|
+
import { createHash as createHash5 } from "crypto";
|
|
4030
|
+
import { readFile as readFile7, readdir as readdir4 } from "fs/promises";
|
|
2983
4031
|
import path3 from "path";
|
|
2984
4032
|
import { Readable } from "stream";
|
|
2985
4033
|
import { createGzip } from "zlib";
|
|
@@ -2992,7 +4040,7 @@ function log2(msg, meta = {}) {
|
|
|
2992
4040
|
`);
|
|
2993
4041
|
}
|
|
2994
4042
|
async function collectFiles(dir, base = "") {
|
|
2995
|
-
const entries = await
|
|
4043
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
2996
4044
|
const files = [];
|
|
2997
4045
|
for (const entry of entries) {
|
|
2998
4046
|
const fullPath = path3.join(dir, entry.name);
|
|
@@ -3000,13 +4048,13 @@ async function collectFiles(dir, base = "") {
|
|
|
3000
4048
|
if (entry.isDirectory()) {
|
|
3001
4049
|
files.push(...await collectFiles(fullPath, relPath));
|
|
3002
4050
|
} else if (entry.isFile()) {
|
|
3003
|
-
files.push({ rel: relPath, content: await
|
|
4051
|
+
files.push({ rel: relPath, content: await readFile7(fullPath) });
|
|
3004
4052
|
}
|
|
3005
4053
|
}
|
|
3006
4054
|
return files.sort((a, b) => a.rel.localeCompare(b.rel));
|
|
3007
4055
|
}
|
|
3008
4056
|
function computeTemplateHash(files) {
|
|
3009
|
-
const hash =
|
|
4057
|
+
const hash = createHash5("sha256");
|
|
3010
4058
|
for (const file of files) {
|
|
3011
4059
|
hash.update(file.rel);
|
|
3012
4060
|
hash.update("\0");
|
|
@@ -3047,11 +4095,11 @@ async function buildTarGz(files) {
|
|
|
3047
4095
|
}
|
|
3048
4096
|
chunks.push(Buffer.alloc(1024));
|
|
3049
4097
|
const tarBuffer = Buffer.concat(chunks);
|
|
3050
|
-
return new Promise((
|
|
4098
|
+
return new Promise((resolve9, reject) => {
|
|
3051
4099
|
const gzip = createGzip({ level: 6 });
|
|
3052
4100
|
const gzChunks = [];
|
|
3053
4101
|
gzip.on("data", (chunk2) => gzChunks.push(chunk2));
|
|
3054
|
-
gzip.on("end", () =>
|
|
4102
|
+
gzip.on("end", () => resolve9(Buffer.concat(gzChunks)));
|
|
3055
4103
|
gzip.on("error", reject);
|
|
3056
4104
|
Readable.from(tarBuffer).pipe(gzip);
|
|
3057
4105
|
});
|
|
@@ -3089,6 +4137,46 @@ async function packageTemplate(templatePath, opts = {}) {
|
|
|
3089
4137
|
// src/server/runtime/modes/vercel-sandbox.ts
|
|
3090
4138
|
var ORPHAN_GUARD_MAX_IDLE_MS = 24 * 60 * 60 * 1e3;
|
|
3091
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
|
+
}
|
|
3092
4180
|
function createDefaultVercelClient(auth, opts = {}) {
|
|
3093
4181
|
const credentials = {
|
|
3094
4182
|
token: auth.token,
|
|
@@ -3101,12 +4189,14 @@ function createDefaultVercelClient(auth, opts = {}) {
|
|
|
3101
4189
|
const base = {
|
|
3102
4190
|
...createOptions,
|
|
3103
4191
|
...params?.name ? { name: params.name } : {},
|
|
4192
|
+
...opts.runtime ? { runtime: opts.runtime } : {},
|
|
3104
4193
|
persistent: params?.persistent ?? true,
|
|
3105
4194
|
snapshotExpiration: params?.snapshotExpiration ?? 0
|
|
3106
4195
|
};
|
|
3107
4196
|
if (params?.source?.type === "snapshot") {
|
|
4197
|
+
const { runtime: _runtime, ...snapshotBase } = base;
|
|
3108
4198
|
return await VercelSandbox.create({
|
|
3109
|
-
...
|
|
4199
|
+
...snapshotBase,
|
|
3110
4200
|
source: { type: "snapshot", snapshotId: params.source.snapshotId }
|
|
3111
4201
|
});
|
|
3112
4202
|
}
|
|
@@ -3178,6 +4268,161 @@ async function seedTemplateIntoVercelWorkspace(workspace, templatePath, logger)
|
|
|
3178
4268
|
fileCount: files.length
|
|
3179
4269
|
});
|
|
3180
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
|
+
}
|
|
3181
4426
|
async function ensureTemplateExecutables(sandbox) {
|
|
3182
4427
|
if (!sandbox.runCommand) return;
|
|
3183
4428
|
await sandbox.runCommand({
|
|
@@ -3233,7 +4478,67 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
3233
4478
|
async dispose() {
|
|
3234
4479
|
await snapshotScheduler?.shutdown();
|
|
3235
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
|
+
},
|
|
3236
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
|
+
});
|
|
3237
4542
|
const auth = resolveVercelAuth(getEnvVar);
|
|
3238
4543
|
const teamId = requireEnvVar("VERCEL_TEAM_ID", getEnvVar);
|
|
3239
4544
|
const projectId = getEnvVar("VERCEL_PROJECT_ID")?.trim();
|
|
@@ -3241,80 +4546,127 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
3241
4546
|
VERCEL_SANDBOX_TIMEOUT_MS_ENV,
|
|
3242
4547
|
getEnvVar
|
|
3243
4548
|
);
|
|
4549
|
+
const runtime = getEnvVar(VERCEL_SANDBOX_RUNTIME_ENV)?.trim() || DEFAULT_VERCEL_SANDBOX_RUNTIME;
|
|
3244
4550
|
const vercelClient = opts.vercelClient ?? createDefaultVercelClient({
|
|
3245
4551
|
token: auth.token,
|
|
3246
4552
|
teamId,
|
|
3247
4553
|
projectId
|
|
3248
|
-
}, { timeoutMs });
|
|
4554
|
+
}, { timeoutMs, runtime });
|
|
3249
4555
|
logger.info("[vercel-sandbox:mode] auth resolved", {
|
|
3250
4556
|
source: auth.source,
|
|
3251
4557
|
hasProjectId: Boolean(projectId),
|
|
3252
|
-
timeoutMs: timeoutMs ?? null
|
|
4558
|
+
timeoutMs: timeoutMs ?? null,
|
|
4559
|
+
runtime
|
|
3253
4560
|
});
|
|
3254
4561
|
const workspaceId = ctx.workspaceId ?? ctx.workspaceRoot;
|
|
3255
4562
|
let tarballUrl;
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
tarballUrl,
|
|
3276
|
-
logger,
|
|
3277
|
-
maxIdleMs: opts.orphanGuardMaxIdleMs === null ? void 0 : opts.orphanGuardMaxIdleMs ?? ORPHAN_GUARD_MAX_IDLE_MS,
|
|
3278
|
-
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
|
+
}
|
|
3279
4582
|
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
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", {
|
|
3290
4601
|
workspaceId,
|
|
3291
|
-
|
|
3292
|
-
|
|
4602
|
+
sandboxId,
|
|
4603
|
+
snapshotId: sandboxHandle.currentSnapshotId ?? sandboxHandle.sourceSnapshotId ?? null,
|
|
4604
|
+
tarballUrl: tarballUrl ?? null
|
|
3293
4605
|
});
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
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
|
+
}
|
|
3304
4639
|
});
|
|
3305
4640
|
}
|
|
3306
|
-
|
|
3307
|
-
|
|
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;
|
|
3308
4669
|
}
|
|
3309
|
-
const sandbox = createVercelSandboxExec(sandboxHandle, {
|
|
3310
|
-
onMutation: markDirty
|
|
3311
|
-
});
|
|
3312
|
-
await sandbox.init?.({ workspace, sessionId: ctx.sessionId });
|
|
3313
|
-
return {
|
|
3314
|
-
workspace,
|
|
3315
|
-
sandbox,
|
|
3316
|
-
fileSearch: createServerFileSearch(workspace, sandbox)
|
|
3317
|
-
};
|
|
3318
4670
|
}
|
|
3319
4671
|
};
|
|
3320
4672
|
}
|
|
@@ -3358,8 +4710,8 @@ import Fastify from "fastify";
|
|
|
3358
4710
|
|
|
3359
4711
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
3360
4712
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
3361
|
-
import { mkdir as
|
|
3362
|
-
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";
|
|
3363
4715
|
import {
|
|
3364
4716
|
createAgentSession,
|
|
3365
4717
|
SessionManager,
|
|
@@ -3372,7 +4724,20 @@ import {
|
|
|
3372
4724
|
} from "@mariozechner/pi-coding-agent";
|
|
3373
4725
|
|
|
3374
4726
|
// src/server/harness/pi-coding-agent/tool-adapter.ts
|
|
3375
|
-
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) {
|
|
3376
4741
|
return {
|
|
3377
4742
|
name: tool.name,
|
|
3378
4743
|
label: tool.name,
|
|
@@ -3380,24 +4745,47 @@ function adaptToolForPi(tool, sessionId) {
|
|
|
3380
4745
|
parameters: tool.parameters,
|
|
3381
4746
|
promptSnippet: tool.promptSnippet ?? tool.description,
|
|
3382
4747
|
async execute(toolCallId, params, signal, onUpdate, _ctx) {
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
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;
|
|
3391
4783
|
}
|
|
3392
|
-
return {
|
|
3393
|
-
content: result.content,
|
|
3394
|
-
details: result.details
|
|
3395
|
-
};
|
|
3396
4784
|
}
|
|
3397
4785
|
};
|
|
3398
4786
|
}
|
|
3399
|
-
function adaptToolsForPi(tools, sessionId) {
|
|
3400
|
-
return tools.map((tool) => adaptToolForPi(tool, sessionId));
|
|
4787
|
+
function adaptToolsForPi(tools, sessionId, telemetry) {
|
|
4788
|
+
return tools.map((tool) => adaptToolForPi(tool, sessionId, telemetry));
|
|
3401
4789
|
}
|
|
3402
4790
|
|
|
3403
4791
|
// src/server/harness/pi-coding-agent/stream-adapter.ts
|
|
@@ -3585,17 +4973,17 @@ function piEventToChunks(event) {
|
|
|
3585
4973
|
// src/server/harness/pi-coding-agent/sessions.ts
|
|
3586
4974
|
import { randomUUID } from "crypto";
|
|
3587
4975
|
import {
|
|
3588
|
-
readdir as
|
|
3589
|
-
readFile as
|
|
4976
|
+
readdir as readdir6,
|
|
4977
|
+
readFile as readFile9,
|
|
3590
4978
|
stat as fsStat,
|
|
3591
|
-
rm,
|
|
3592
|
-
mkdir as
|
|
4979
|
+
rm as rm3,
|
|
4980
|
+
mkdir as mkdir10,
|
|
3593
4981
|
writeFile as writeFile6,
|
|
3594
4982
|
appendFile,
|
|
3595
4983
|
utimes
|
|
3596
4984
|
} from "fs/promises";
|
|
3597
4985
|
import { readFileSync, readdirSync } from "fs";
|
|
3598
|
-
import { join as
|
|
4986
|
+
import { join as join9 } from "path";
|
|
3599
4987
|
import { homedir as homedir3 } from "os";
|
|
3600
4988
|
import {
|
|
3601
4989
|
parseSessionEntries,
|
|
@@ -3604,7 +4992,7 @@ import {
|
|
|
3604
4992
|
} from "@mariozechner/pi-coding-agent";
|
|
3605
4993
|
function defaultSessionDir(cwd) {
|
|
3606
4994
|
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
3607
|
-
return
|
|
4995
|
+
return join9(homedir3(), ".pi", "agent", "sessions", safePath);
|
|
3608
4996
|
}
|
|
3609
4997
|
var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
3610
4998
|
var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
|
|
@@ -3613,7 +5001,7 @@ function sessionDirForNamespace(namespace) {
|
|
|
3613
5001
|
if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
|
|
3614
5002
|
throw new Error("session namespace must contain only letters, numbers, underscores, and dashes");
|
|
3615
5003
|
}
|
|
3616
|
-
return
|
|
5004
|
+
return join9(homedir3(), ".pi", "agent", "sessions", safeNamespace);
|
|
3617
5005
|
}
|
|
3618
5006
|
var PiSessionStore = class {
|
|
3619
5007
|
cwd;
|
|
@@ -3627,15 +5015,15 @@ var PiSessionStore = class {
|
|
|
3627
5015
|
this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace) : defaultSessionDir(cwd));
|
|
3628
5016
|
}
|
|
3629
5017
|
async list(ctx) {
|
|
3630
|
-
const files = await
|
|
5018
|
+
const files = await readdir6(this.sessionDir).catch(() => []);
|
|
3631
5019
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
3632
5020
|
const summaries = await Promise.all(
|
|
3633
|
-
jsonlFiles.map((f) => this.summarizeFile(
|
|
5021
|
+
jsonlFiles.map((f) => this.summarizeFile(join9(this.sessionDir, f)))
|
|
3634
5022
|
);
|
|
3635
5023
|
return summaries.filter((s) => s !== null).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
3636
5024
|
}
|
|
3637
5025
|
async create(ctx, init) {
|
|
3638
|
-
await
|
|
5026
|
+
await mkdir10(this.sessionDir, { recursive: true });
|
|
3639
5027
|
const id = randomUUID();
|
|
3640
5028
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3641
5029
|
const header = {
|
|
@@ -3656,7 +5044,7 @@ var PiSessionStore = class {
|
|
|
3656
5044
|
};
|
|
3657
5045
|
lines.push(JSON.stringify(infoEntry));
|
|
3658
5046
|
}
|
|
3659
|
-
const filepath =
|
|
5047
|
+
const filepath = join9(this.sessionDir, `${id}.jsonl`);
|
|
3660
5048
|
await writeFile6(filepath, lines.join("\n") + "\n", "utf-8");
|
|
3661
5049
|
return {
|
|
3662
5050
|
id,
|
|
@@ -3670,7 +5058,7 @@ var PiSessionStore = class {
|
|
|
3670
5058
|
const filepath = await this.resolveSessionFile(sessionId);
|
|
3671
5059
|
let content;
|
|
3672
5060
|
try {
|
|
3673
|
-
content = await
|
|
5061
|
+
content = await readFile9(filepath, "utf-8");
|
|
3674
5062
|
} catch {
|
|
3675
5063
|
throw new Error(`Session not found: ${sessionId}`);
|
|
3676
5064
|
}
|
|
@@ -3713,7 +5101,7 @@ var PiSessionStore = class {
|
|
|
3713
5101
|
loadPiSessionFileSync(sessionId) {
|
|
3714
5102
|
if (!SAFE_ID.test(sessionId)) return null;
|
|
3715
5103
|
try {
|
|
3716
|
-
const direct =
|
|
5104
|
+
const direct = join9(this.sessionDir, `${sessionId}.jsonl`);
|
|
3717
5105
|
let content;
|
|
3718
5106
|
try {
|
|
3719
5107
|
content = readFileSync(direct, "utf-8");
|
|
@@ -3722,7 +5110,7 @@ var PiSessionStore = class {
|
|
|
3722
5110
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
3723
5111
|
);
|
|
3724
5112
|
if (files.length === 0) return null;
|
|
3725
|
-
content = readFileSync(
|
|
5113
|
+
content = readFileSync(join9(this.sessionDir, files[0]), "utf-8");
|
|
3726
5114
|
}
|
|
3727
5115
|
const entries = safeParseEntries(content);
|
|
3728
5116
|
let piFilePath = null;
|
|
@@ -3740,7 +5128,7 @@ var PiSessionStore = class {
|
|
|
3740
5128
|
async loadPiSessionFile(_ctx, sessionId) {
|
|
3741
5129
|
try {
|
|
3742
5130
|
const filepath = await this.resolveSessionFile(sessionId);
|
|
3743
|
-
const content = await
|
|
5131
|
+
const content = await readFile9(filepath, "utf-8");
|
|
3744
5132
|
const entries = safeParseEntries(content);
|
|
3745
5133
|
let piFilePath = null;
|
|
3746
5134
|
for (const e of entries) {
|
|
@@ -3767,7 +5155,7 @@ var PiSessionStore = class {
|
|
|
3767
5155
|
const filepath = await this.resolveSessionFile(sessionId).catch(
|
|
3768
5156
|
() => null
|
|
3769
5157
|
);
|
|
3770
|
-
if (filepath) await
|
|
5158
|
+
if (filepath) await rm3(filepath, { force: true });
|
|
3771
5159
|
}
|
|
3772
5160
|
touchSession(sessionId, title) {
|
|
3773
5161
|
this.resolveSessionFile(sessionId).then((filepath) => {
|
|
@@ -3790,24 +5178,24 @@ var PiSessionStore = class {
|
|
|
3790
5178
|
if (!SAFE_ID.test(sessionId)) {
|
|
3791
5179
|
throw new Error(`Session not found: ${sessionId}`);
|
|
3792
5180
|
}
|
|
3793
|
-
const direct =
|
|
5181
|
+
const direct = join9(this.sessionDir, `${sessionId}.jsonl`);
|
|
3794
5182
|
try {
|
|
3795
5183
|
await fsStat(direct);
|
|
3796
5184
|
return direct;
|
|
3797
5185
|
} catch {
|
|
3798
5186
|
}
|
|
3799
|
-
const files = await
|
|
5187
|
+
const files = await readdir6(this.sessionDir).catch(() => []);
|
|
3800
5188
|
const match = files.find(
|
|
3801
5189
|
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
3802
5190
|
);
|
|
3803
5191
|
if (!match) throw new Error(`Session not found: ${sessionId}`);
|
|
3804
|
-
return
|
|
5192
|
+
return join9(this.sessionDir, match);
|
|
3805
5193
|
}
|
|
3806
5194
|
async summarizeFile(filepath) {
|
|
3807
5195
|
try {
|
|
3808
5196
|
const [fileStat, content] = await Promise.all([
|
|
3809
5197
|
fsStat(filepath),
|
|
3810
|
-
|
|
5198
|
+
readFile9(filepath, "utf-8")
|
|
3811
5199
|
]);
|
|
3812
5200
|
const firstNewline = content.indexOf("\n");
|
|
3813
5201
|
if (firstNewline === -1) return null;
|
|
@@ -3957,7 +5345,7 @@ var DEFAULT_POLL_MS = 250;
|
|
|
3957
5345
|
var MAX_PROMPT_CHARS = 1200;
|
|
3958
5346
|
var MAX_TITLE_CHARS = 80;
|
|
3959
5347
|
function sleep(ms) {
|
|
3960
|
-
return new Promise((
|
|
5348
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
3961
5349
|
}
|
|
3962
5350
|
function truncateForPrompt(text) {
|
|
3963
5351
|
const trimmed = text.trim();
|
|
@@ -4378,8 +5766,8 @@ function mergeInjectedProjectPackages(settingsJson, piPackages) {
|
|
|
4378
5766
|
}
|
|
4379
5767
|
function createResourceSettingsManager(cwd, agentDir, piPackages) {
|
|
4380
5768
|
if (piPackages.length === 0) return SettingsManager2.create(cwd, agentDir);
|
|
4381
|
-
const globalSettingsPath =
|
|
4382
|
-
const projectSettingsPath =
|
|
5769
|
+
const globalSettingsPath = join10(agentDir, "settings.json");
|
|
5770
|
+
const projectSettingsPath = join10(cwd, ".pi", "settings.json");
|
|
4383
5771
|
let globalSettingsOverrideJson;
|
|
4384
5772
|
let projectSettingsOverrideJson;
|
|
4385
5773
|
const storage = {
|
|
@@ -4532,7 +5920,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
4532
5920
|
const { session: piSession } = await createAgentSession({
|
|
4533
5921
|
cwd: ctx.workdir,
|
|
4534
5922
|
tools: [],
|
|
4535
|
-
customTools: adaptToolsForPi(opts.tools, input.sessionId),
|
|
5923
|
+
customTools: adaptToolsForPi(opts.tools, input.sessionId, opts.telemetry),
|
|
4536
5924
|
model,
|
|
4537
5925
|
thinkingLevel: input.thinkingLevel ?? "off",
|
|
4538
5926
|
sessionManager,
|
|
@@ -4980,8 +6368,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
4980
6368
|
const base = basenameForAttachment(a.filename ?? "image");
|
|
4981
6369
|
const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4982
6370
|
const relPath = `${DEFAULT_ATTACHMENT_DIR}/${base}-${unique}.${ext}`;
|
|
4983
|
-
await
|
|
4984
|
-
await writeFile7(
|
|
6371
|
+
await mkdir11(join10(ctx.workdir, DEFAULT_ATTACHMENT_DIR), { recursive: true });
|
|
6372
|
+
await writeFile7(join10(ctx.workdir, relPath), bytes);
|
|
4985
6373
|
savedPaths.push(relPath);
|
|
4986
6374
|
} catch (err) {
|
|
4987
6375
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5070,25 +6458,25 @@ ${attachmentNotes.join("\n")}` : message,
|
|
|
5070
6458
|
}
|
|
5071
6459
|
|
|
5072
6460
|
// src/server/harness/pi-coding-agent/pluginLoader.ts
|
|
5073
|
-
import { readdir as
|
|
5074
|
-
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";
|
|
5075
6463
|
import { homedir as homedir4 } from "os";
|
|
5076
|
-
import { pathToFileURL } from "url";
|
|
6464
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5077
6465
|
var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs"]);
|
|
5078
|
-
var GLOBAL_DIR =
|
|
6466
|
+
var GLOBAL_DIR = join11(homedir4(), ".pi", "agent", "extensions");
|
|
5079
6467
|
var LOCAL_DIR = ".pi/extensions";
|
|
5080
6468
|
var EXTENSIONS_JSON = ".pi/extensions.json";
|
|
5081
6469
|
async function dirExists(path4) {
|
|
5082
6470
|
try {
|
|
5083
|
-
const s = await
|
|
6471
|
+
const s = await stat8(path4);
|
|
5084
6472
|
return s.isDirectory();
|
|
5085
6473
|
} catch {
|
|
5086
6474
|
return false;
|
|
5087
6475
|
}
|
|
5088
6476
|
}
|
|
5089
|
-
async function
|
|
6477
|
+
async function fileExists(path4) {
|
|
5090
6478
|
try {
|
|
5091
|
-
const s = await
|
|
6479
|
+
const s = await stat8(path4);
|
|
5092
6480
|
return s.isFile();
|
|
5093
6481
|
} catch {
|
|
5094
6482
|
return false;
|
|
@@ -5096,8 +6484,8 @@ async function fileExists2(path4) {
|
|
|
5096
6484
|
}
|
|
5097
6485
|
async function discoverFromDir(dir, source) {
|
|
5098
6486
|
if (!await dirExists(dir)) return [];
|
|
5099
|
-
const entries = await
|
|
5100
|
-
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 }));
|
|
5101
6489
|
}
|
|
5102
6490
|
function extractTools(mod) {
|
|
5103
6491
|
const tools = [];
|
|
@@ -5122,41 +6510,41 @@ function extractTools(mod) {
|
|
|
5122
6510
|
return tools;
|
|
5123
6511
|
}
|
|
5124
6512
|
async function loadModule(filePath, importFn) {
|
|
5125
|
-
const url =
|
|
6513
|
+
const url = pathToFileURL2(filePath).href;
|
|
5126
6514
|
const mod = await importFn(url);
|
|
5127
6515
|
return extractTools(mod);
|
|
5128
6516
|
}
|
|
5129
6517
|
async function discoverNpmPlugins(cwd) {
|
|
5130
|
-
const nodeModulesDir =
|
|
6518
|
+
const nodeModulesDir = join11(cwd, "node_modules");
|
|
5131
6519
|
if (!await dirExists(nodeModulesDir)) return [];
|
|
5132
6520
|
try {
|
|
5133
|
-
const entries = await
|
|
5134
|
-
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));
|
|
5135
6523
|
} catch {
|
|
5136
6524
|
return [];
|
|
5137
6525
|
}
|
|
5138
6526
|
}
|
|
5139
6527
|
async function loadNpmPlugin(pkgDir, importFn) {
|
|
5140
|
-
const pkgJsonPath =
|
|
5141
|
-
if (!await
|
|
5142
|
-
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"));
|
|
5143
6531
|
const main = pkgJson.main ?? "index.js";
|
|
5144
|
-
const resolvedMain =
|
|
5145
|
-
const resolvedPkgDir =
|
|
5146
|
-
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)) {
|
|
5147
6535
|
return [];
|
|
5148
6536
|
}
|
|
5149
|
-
const relPath =
|
|
6537
|
+
const relPath = relative9(resolvedPkgDir, resolvedMain);
|
|
5150
6538
|
if (relPath.startsWith("..")) {
|
|
5151
6539
|
return [];
|
|
5152
6540
|
}
|
|
5153
6541
|
return loadModule(resolvedMain, importFn);
|
|
5154
6542
|
}
|
|
5155
6543
|
async function loadExtensionsJson(cwd) {
|
|
5156
|
-
const configPath =
|
|
5157
|
-
if (!await
|
|
6544
|
+
const configPath = join11(cwd, EXTENSIONS_JSON);
|
|
6545
|
+
if (!await fileExists(configPath)) return null;
|
|
5158
6546
|
try {
|
|
5159
|
-
const raw = await
|
|
6547
|
+
const raw = await readFile10(configPath, "utf-8");
|
|
5160
6548
|
return JSON.parse(raw);
|
|
5161
6549
|
} catch {
|
|
5162
6550
|
return null;
|
|
@@ -5171,7 +6559,7 @@ async function loadPlugins(options) {
|
|
|
5171
6559
|
const globals = await discoverFromDir(GLOBAL_DIR, "global");
|
|
5172
6560
|
candidates.push(...globals);
|
|
5173
6561
|
}
|
|
5174
|
-
const localDir =
|
|
6562
|
+
const localDir = join11(options.cwd, LOCAL_DIR);
|
|
5175
6563
|
const locals = await discoverFromDir(localDir, "local");
|
|
5176
6564
|
candidates.push(...locals);
|
|
5177
6565
|
const npmDirs = await discoverNpmPlugins(options.cwd);
|
|
@@ -5181,7 +6569,7 @@ async function loadPlugins(options) {
|
|
|
5181
6569
|
const config = await loadExtensionsJson(options.cwd);
|
|
5182
6570
|
if (config?.npm) {
|
|
5183
6571
|
for (const pkg of config.npm) {
|
|
5184
|
-
const pkgDir =
|
|
6572
|
+
const pkgDir = join11(options.cwd, "node_modules", pkg);
|
|
5185
6573
|
if (await dirExists(pkgDir)) {
|
|
5186
6574
|
const already = candidates.some((c) => c.path === pkgDir);
|
|
5187
6575
|
if (!already) {
|
|
@@ -5229,9 +6617,9 @@ import {
|
|
|
5229
6617
|
} from "@mariozechner/pi-coding-agent";
|
|
5230
6618
|
|
|
5231
6619
|
// src/server/tools/operations/bound.ts
|
|
5232
|
-
import { constants as
|
|
5233
|
-
import { access as
|
|
5234
|
-
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";
|
|
5235
6623
|
function toPosixPath(value) {
|
|
5236
6624
|
return value.split("\\").join("/");
|
|
5237
6625
|
}
|
|
@@ -5276,19 +6664,19 @@ function matchesGlob(relativePath, pattern) {
|
|
|
5276
6664
|
}
|
|
5277
6665
|
function shouldSkipDir(relativePath, ignore) {
|
|
5278
6666
|
const normalizedRel = toPosixPath(relativePath);
|
|
5279
|
-
const
|
|
5280
|
-
if (
|
|
6667
|
+
const basename4 = normalizedRel.split("/").at(-1) ?? normalizedRel;
|
|
6668
|
+
if (basename4 === ".git" || basename4 === "node_modules") return true;
|
|
5281
6669
|
return ignore.some((pattern) => {
|
|
5282
6670
|
return matchesGlob(normalizedRel, pattern) || matchesGlob(`${normalizedRel}/`, pattern);
|
|
5283
6671
|
});
|
|
5284
6672
|
}
|
|
5285
6673
|
async function walkMatches(root, current, pattern, ignore, limit, out) {
|
|
5286
6674
|
if (out.length >= limit) return;
|
|
5287
|
-
const entries = await
|
|
6675
|
+
const entries = await readdir8(current, { withFileTypes: true });
|
|
5288
6676
|
for (const entry of entries) {
|
|
5289
6677
|
if (out.length >= limit) return;
|
|
5290
|
-
const absolutePath =
|
|
5291
|
-
const relativePath = toPosixPath(
|
|
6678
|
+
const absolutePath = resolve7(current, entry.name);
|
|
6679
|
+
const relativePath = toPosixPath(relative10(root, absolutePath));
|
|
5292
6680
|
if (entry.isDirectory() && shouldSkipDir(relativePath, ignore)) continue;
|
|
5293
6681
|
if (matchesGlob(relativePath, pattern)) {
|
|
5294
6682
|
out.push(absolutePath);
|
|
@@ -5302,26 +6690,26 @@ async function findNearestExistingAncestor(absPath) {
|
|
|
5302
6690
|
let current = absPath;
|
|
5303
6691
|
for (; ; ) {
|
|
5304
6692
|
try {
|
|
5305
|
-
await
|
|
6693
|
+
await stat9(current);
|
|
5306
6694
|
return current;
|
|
5307
6695
|
} catch {
|
|
5308
|
-
const parent =
|
|
6696
|
+
const parent = dirname10(current);
|
|
5309
6697
|
if (parent === current) return current;
|
|
5310
6698
|
current = parent;
|
|
5311
6699
|
}
|
|
5312
6700
|
}
|
|
5313
6701
|
}
|
|
5314
6702
|
async function assertWithinWorkspace(workspaceRoot, absPath) {
|
|
5315
|
-
const realRoot = await
|
|
6703
|
+
const realRoot = await realpath4(resolve7(workspaceRoot));
|
|
5316
6704
|
try {
|
|
5317
|
-
const s = await
|
|
6705
|
+
const s = await lstat4(absPath);
|
|
5318
6706
|
if (s.isSymbolicLink()) {
|
|
5319
6707
|
const target = await readlink(absPath);
|
|
5320
|
-
const resolvedTarget =
|
|
6708
|
+
const resolvedTarget = resolve7(dirname10(absPath), target);
|
|
5321
6709
|
const nearestAncestor = await findNearestExistingAncestor(resolvedTarget);
|
|
5322
|
-
const realAncestor = await
|
|
5323
|
-
const rel2 =
|
|
5324
|
-
if (rel2.startsWith("..") ||
|
|
6710
|
+
const realAncestor = await realpath4(nearestAncestor);
|
|
6711
|
+
const rel2 = relative10(realRoot, realAncestor);
|
|
6712
|
+
if (rel2.startsWith("..") || isAbsolute6(rel2)) {
|
|
5325
6713
|
throw new Error(`path "${absPath}" is outside workspace`);
|
|
5326
6714
|
}
|
|
5327
6715
|
return;
|
|
@@ -5335,22 +6723,22 @@ async function assertWithinWorkspace(workspaceRoot, absPath) {
|
|
|
5335
6723
|
}
|
|
5336
6724
|
let realCandidate;
|
|
5337
6725
|
try {
|
|
5338
|
-
realCandidate = await
|
|
6726
|
+
realCandidate = await realpath4(absPath);
|
|
5339
6727
|
} catch (err) {
|
|
5340
6728
|
const code = err.code;
|
|
5341
6729
|
if (code === "ENOENT") {
|
|
5342
|
-
const nearestAncestor = await findNearestExistingAncestor(
|
|
5343
|
-
const realAncestor = await
|
|
5344
|
-
const rel2 =
|
|
5345
|
-
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)) {
|
|
5346
6734
|
throw new Error(`path "${absPath}" is outside workspace`);
|
|
5347
6735
|
}
|
|
5348
6736
|
return;
|
|
5349
6737
|
}
|
|
5350
6738
|
throw err;
|
|
5351
6739
|
}
|
|
5352
|
-
const rel =
|
|
5353
|
-
if (rel.startsWith("..") ||
|
|
6740
|
+
const rel = relative10(realRoot, realCandidate);
|
|
6741
|
+
if (rel.startsWith("..") || isAbsolute6(rel)) {
|
|
5354
6742
|
throw new Error(`path "${absPath}" is outside workspace`);
|
|
5355
6743
|
}
|
|
5356
6744
|
}
|
|
@@ -5358,28 +6746,28 @@ function boundFs(workspaceRoot) {
|
|
|
5358
6746
|
const read = {
|
|
5359
6747
|
async readFile(absolutePath) {
|
|
5360
6748
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5361
|
-
return await
|
|
6749
|
+
return await readFile11(absolutePath);
|
|
5362
6750
|
},
|
|
5363
6751
|
async access(absolutePath) {
|
|
5364
6752
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5365
|
-
await
|
|
6753
|
+
await access4(absolutePath, constants4.R_OK);
|
|
5366
6754
|
}
|
|
5367
6755
|
};
|
|
5368
6756
|
const write = {
|
|
5369
6757
|
async writeFile(absolutePath, content) {
|
|
5370
6758
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5371
|
-
await
|
|
6759
|
+
await mkdir12(dirname10(absolutePath), { recursive: true });
|
|
5372
6760
|
await writeFile8(absolutePath, content);
|
|
5373
6761
|
},
|
|
5374
6762
|
async mkdir(dir) {
|
|
5375
6763
|
await assertWithinWorkspace(workspaceRoot, dir);
|
|
5376
|
-
await
|
|
6764
|
+
await mkdir12(dir, { recursive: true });
|
|
5377
6765
|
}
|
|
5378
6766
|
};
|
|
5379
6767
|
const edit = {
|
|
5380
6768
|
async readFile(absolutePath) {
|
|
5381
6769
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5382
|
-
return await
|
|
6770
|
+
return await readFile11(absolutePath);
|
|
5383
6771
|
},
|
|
5384
6772
|
async writeFile(absolutePath, content) {
|
|
5385
6773
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
@@ -5387,14 +6775,14 @@ function boundFs(workspaceRoot) {
|
|
|
5387
6775
|
},
|
|
5388
6776
|
async access(absolutePath) {
|
|
5389
6777
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5390
|
-
await
|
|
6778
|
+
await access4(absolutePath, constants4.R_OK | constants4.W_OK);
|
|
5391
6779
|
}
|
|
5392
6780
|
};
|
|
5393
6781
|
const find = {
|
|
5394
6782
|
async exists(absolutePath) {
|
|
5395
6783
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5396
6784
|
try {
|
|
5397
|
-
await
|
|
6785
|
+
await stat9(absolutePath);
|
|
5398
6786
|
return true;
|
|
5399
6787
|
} catch (err) {
|
|
5400
6788
|
if (err.code === "ENOENT") return false;
|
|
@@ -5411,18 +6799,18 @@ function boundFs(workspaceRoot) {
|
|
|
5411
6799
|
const grep = {
|
|
5412
6800
|
async isDirectory(absolutePath) {
|
|
5413
6801
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5414
|
-
return (await
|
|
6802
|
+
return (await stat9(absolutePath)).isDirectory();
|
|
5415
6803
|
},
|
|
5416
6804
|
async readFile(absolutePath) {
|
|
5417
6805
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5418
|
-
return await
|
|
6806
|
+
return await readFile11(absolutePath, "utf8");
|
|
5419
6807
|
}
|
|
5420
6808
|
};
|
|
5421
6809
|
const ls = {
|
|
5422
6810
|
async exists(absolutePath) {
|
|
5423
6811
|
try {
|
|
5424
6812
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5425
|
-
await
|
|
6813
|
+
await stat9(absolutePath);
|
|
5426
6814
|
return true;
|
|
5427
6815
|
} catch {
|
|
5428
6816
|
return false;
|
|
@@ -5430,18 +6818,18 @@ function boundFs(workspaceRoot) {
|
|
|
5430
6818
|
},
|
|
5431
6819
|
async stat(absolutePath) {
|
|
5432
6820
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5433
|
-
return await
|
|
6821
|
+
return await stat9(absolutePath);
|
|
5434
6822
|
},
|
|
5435
6823
|
async readdir(absolutePath) {
|
|
5436
6824
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
5437
|
-
return await
|
|
6825
|
+
return await readdir8(absolutePath);
|
|
5438
6826
|
}
|
|
5439
6827
|
};
|
|
5440
6828
|
return { read, write, edit, find, grep, ls };
|
|
5441
6829
|
}
|
|
5442
6830
|
|
|
5443
6831
|
// src/server/tools/operations/vercel.ts
|
|
5444
|
-
import { isAbsolute as
|
|
6832
|
+
import { isAbsolute as isAbsolute7, relative as relative11 } from "path";
|
|
5445
6833
|
function rootAliases(workspace) {
|
|
5446
6834
|
const aliases = [workspace.root];
|
|
5447
6835
|
if (workspace.root === VERCEL_SANDBOX_WORKSPACE_ROOT) aliases.push(VERCEL_SANDBOX_REMOTE_ROOT);
|
|
@@ -5449,11 +6837,11 @@ function rootAliases(workspace) {
|
|
|
5449
6837
|
return aliases;
|
|
5450
6838
|
}
|
|
5451
6839
|
function isOutsideWorkspaceRel(rel) {
|
|
5452
|
-
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") ||
|
|
6840
|
+
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute7(rel);
|
|
5453
6841
|
}
|
|
5454
6842
|
function toRelPath(workspace, absolutePath) {
|
|
5455
6843
|
for (const root of rootAliases(workspace)) {
|
|
5456
|
-
const rel =
|
|
6844
|
+
const rel = relative11(root, absolutePath);
|
|
5457
6845
|
if (!isOutsideWorkspaceRel(rel)) return rel;
|
|
5458
6846
|
}
|
|
5459
6847
|
const skillMarker = "/.agents/skills/";
|
|
@@ -5469,10 +6857,11 @@ function toRelPath(workspace, absolutePath) {
|
|
|
5469
6857
|
`path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${rootAliases(workspace).join(" / ")}`
|
|
5470
6858
|
);
|
|
5471
6859
|
}
|
|
5472
|
-
function vercelBashOps(sandbox) {
|
|
6860
|
+
function vercelBashOps(sandbox, opts = {}) {
|
|
5473
6861
|
return {
|
|
5474
6862
|
exec(command, cwd, { onData, signal, timeout, env }) {
|
|
5475
|
-
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;
|
|
5476
6865
|
return sandbox.exec(command, {
|
|
5477
6866
|
cwd,
|
|
5478
6867
|
env: filteredEnv,
|
|
@@ -5636,7 +7025,7 @@ import {
|
|
|
5636
7025
|
truncateHead,
|
|
5637
7026
|
truncateLine
|
|
5638
7027
|
} from "@mariozechner/pi-coding-agent";
|
|
5639
|
-
import { resolve as
|
|
7028
|
+
import { resolve as resolve8, relative as relative12 } from "path";
|
|
5640
7029
|
|
|
5641
7030
|
// src/server/catalog/tools/_shared.ts
|
|
5642
7031
|
function makeError(message) {
|
|
@@ -5788,8 +7177,8 @@ function vercelGrepTool(sandbox, workspaceRoot) {
|
|
|
5788
7177
|
return makeError("pattern is required");
|
|
5789
7178
|
}
|
|
5790
7179
|
if (workspaceRoot && typeof params.path === "string" && params.path.length > 0) {
|
|
5791
|
-
const resolved =
|
|
5792
|
-
const rel =
|
|
7180
|
+
const resolved = resolve8(workspaceRoot, params.path);
|
|
7181
|
+
const rel = relative12(workspaceRoot, resolved);
|
|
5793
7182
|
if (rel.startsWith("..") || rel.startsWith("/")) {
|
|
5794
7183
|
return makeError(`path "${params.path}" is outside workspace`);
|
|
5795
7184
|
}
|
|
@@ -5877,7 +7266,20 @@ import {
|
|
|
5877
7266
|
function shellEscape2(s) {
|
|
5878
7267
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
5879
7268
|
}
|
|
5880
|
-
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) {
|
|
5881
7283
|
const args = buildBwrapArgs(workspaceRoot);
|
|
5882
7284
|
const bwrapPrefix = ["bwrap", ...args].map(shellEscape2).join(" ");
|
|
5883
7285
|
return (context) => ({
|
|
@@ -5885,30 +7287,41 @@ function bwrapSpawnHook(workspaceRoot) {
|
|
|
5885
7287
|
command: `${bwrapPrefix} bash -lc ${shellEscape2(context.command)}`,
|
|
5886
7288
|
env: withWorkspacePythonEnv({
|
|
5887
7289
|
workspaceRoot,
|
|
5888
|
-
env: context.env,
|
|
7290
|
+
env: mergeRuntimeEnv(runtime, context.env),
|
|
5889
7291
|
sandboxRoot: "/workspace"
|
|
5890
7292
|
})
|
|
5891
7293
|
});
|
|
5892
7294
|
}
|
|
5893
|
-
function directSpawnHook(workspaceRoot) {
|
|
7295
|
+
function directSpawnHook(workspaceRoot, runtime) {
|
|
5894
7296
|
return (context) => ({
|
|
5895
7297
|
...context,
|
|
5896
|
-
env: withWorkspacePythonEnv({
|
|
7298
|
+
env: withWorkspacePythonEnv({
|
|
7299
|
+
workspaceRoot,
|
|
7300
|
+
env: mergeRuntimeEnv(runtime, context.env)
|
|
7301
|
+
})
|
|
5897
7302
|
});
|
|
5898
7303
|
}
|
|
5899
|
-
|
|
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) {
|
|
5900
7306
|
switch (bundle.sandbox.provider) {
|
|
5901
7307
|
case "vercel-sandbox":
|
|
5902
|
-
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
|
+
};
|
|
5903
7316
|
case "bwrap":
|
|
5904
7317
|
return {
|
|
5905
7318
|
operations: createLocalBashOperations(),
|
|
5906
|
-
spawnHook: bwrapSpawnHook(bundle.workspace.root)
|
|
7319
|
+
spawnHook: bwrapSpawnHook(bundle.workspace.root, runtime)
|
|
5907
7320
|
};
|
|
5908
7321
|
default:
|
|
5909
7322
|
return {
|
|
5910
7323
|
operations: createLocalBashOperations(),
|
|
5911
|
-
spawnHook: directSpawnHook(bundle.workspace.root)
|
|
7324
|
+
spawnHook: directSpawnHook(bundle.workspace.root, runtime)
|
|
5912
7325
|
};
|
|
5913
7326
|
}
|
|
5914
7327
|
}
|
|
@@ -5991,9 +7404,9 @@ function createExecuteIsolatedCodeTool(sandbox) {
|
|
|
5991
7404
|
}
|
|
5992
7405
|
};
|
|
5993
7406
|
}
|
|
5994
|
-
function buildHarnessAgentTools(bundle) {
|
|
7407
|
+
function buildHarnessAgentTools(bundle, runtime) {
|
|
5995
7408
|
const tools = [
|
|
5996
|
-
adaptPiTool2(createBashToolDefinition(bundle.workspace.root, bashOptionsForMode(bundle)))
|
|
7409
|
+
adaptPiTool2(createBashToolDefinition(bundle.workspace.root, bashOptionsForMode(bundle, runtime)))
|
|
5997
7410
|
];
|
|
5998
7411
|
if (bundle.sandbox.capabilities.includes("isolated-code")) {
|
|
5999
7412
|
tools.push(createExecuteIsolatedCodeTool(bundle.sandbox));
|
|
@@ -6218,7 +7631,7 @@ var PathError = class extends Error {
|
|
|
6218
7631
|
this.name = "PathError";
|
|
6219
7632
|
}
|
|
6220
7633
|
};
|
|
6221
|
-
function
|
|
7634
|
+
function joinPath2(base, name) {
|
|
6222
7635
|
if (base === ".") return name;
|
|
6223
7636
|
return `${base}/${name}`;
|
|
6224
7637
|
}
|
|
@@ -6233,7 +7646,7 @@ async function listTree(workspace, dir, recursive) {
|
|
|
6233
7646
|
const batch = queue.shift();
|
|
6234
7647
|
for (const e of batch.items) {
|
|
6235
7648
|
if (entries.length >= MAX_ENTRIES) break;
|
|
6236
|
-
const entryPath =
|
|
7649
|
+
const entryPath = joinPath2(batch.parentDir, e.name);
|
|
6237
7650
|
entries.push({ name: e.name, kind: e.kind, path: entryPath });
|
|
6238
7651
|
if (recursive && e.kind === "dir" && batch.depth < MAX_DEPTH) {
|
|
6239
7652
|
try {
|
|
@@ -6542,8 +7955,17 @@ var chatBodySchema = z.object({
|
|
|
6542
7955
|
})
|
|
6543
7956
|
).max(20).optional()
|
|
6544
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
|
+
}
|
|
6545
7966
|
function chatRoutes(app, opts, done) {
|
|
6546
7967
|
const { sessionChangesTracker } = opts;
|
|
7968
|
+
const telemetry = opts.telemetry ?? noopTelemetry;
|
|
6547
7969
|
const validateBody = createBodyValidator(chatBodySchema);
|
|
6548
7970
|
const buffers = new StreamBufferStore();
|
|
6549
7971
|
const lastFollowUpBySession = /* @__PURE__ */ new Map();
|
|
@@ -6588,8 +8010,18 @@ function chatRoutes(app, opts, done) {
|
|
|
6588
8010
|
async (request, reply) => {
|
|
6589
8011
|
const { sessionId, message, model, thinkingLevel, attachments } = request.body;
|
|
6590
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);
|
|
6591
8020
|
request.log.info({ sessionId, turnId, model, thinkingLevel }, "[chat] start");
|
|
6592
|
-
|
|
8021
|
+
safeCapture(telemetry, {
|
|
8022
|
+
name: "agent.chat.started",
|
|
8023
|
+
properties: telemetryProperties2
|
|
8024
|
+
});
|
|
6593
8025
|
const abortController = new AbortController();
|
|
6594
8026
|
let streamStarted = false;
|
|
6595
8027
|
let streamCompleted = false;
|
|
@@ -6598,18 +8030,24 @@ function chatRoutes(app, opts, done) {
|
|
|
6598
8030
|
abortController.abort();
|
|
6599
8031
|
}
|
|
6600
8032
|
});
|
|
6601
|
-
const ctx = {
|
|
6602
|
-
abortSignal: abortController.signal,
|
|
6603
|
-
workdir: runtime.workdir
|
|
6604
|
-
};
|
|
6605
8033
|
const buf = buffers.create(sessionId, turnId);
|
|
6606
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
|
+
});
|
|
6607
8044
|
const chunks = runtime.harness.sendMessage(
|
|
6608
8045
|
{ sessionId, message, model, thinkingLevel, attachments },
|
|
6609
8046
|
ctx
|
|
6610
8047
|
);
|
|
6611
8048
|
const stream = createUIMessageStream({
|
|
6612
8049
|
async execute({ writer }) {
|
|
8050
|
+
let streamFailed = false;
|
|
6613
8051
|
try {
|
|
6614
8052
|
for await (const chunk2 of chunks) {
|
|
6615
8053
|
const c = chunk2;
|
|
@@ -6621,7 +8059,17 @@ function chatRoutes(app, opts, done) {
|
|
|
6621
8059
|
writer.write(c);
|
|
6622
8060
|
}
|
|
6623
8061
|
} catch (err) {
|
|
8062
|
+
streamFailed = true;
|
|
6624
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
|
+
});
|
|
6625
8073
|
const errChunk = {
|
|
6626
8074
|
type: "error",
|
|
6627
8075
|
errorText: "internal error"
|
|
@@ -6630,6 +8078,16 @@ function chatRoutes(app, opts, done) {
|
|
|
6630
8078
|
writer.write(errChunk);
|
|
6631
8079
|
} finally {
|
|
6632
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
|
+
}
|
|
6633
8091
|
buf.markComplete(() => buffers.evict(sessionId, turnId));
|
|
6634
8092
|
}
|
|
6635
8093
|
}
|
|
@@ -6649,6 +8107,15 @@ function chatRoutes(app, opts, done) {
|
|
|
6649
8107
|
return;
|
|
6650
8108
|
} catch (err) {
|
|
6651
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
|
+
});
|
|
6652
8119
|
buf.markComplete(() => buffers.evict(sessionId, turnId));
|
|
6653
8120
|
if (streamStarted) return;
|
|
6654
8121
|
return reply.code(500).send({
|
|
@@ -6693,17 +8160,17 @@ function chatRoutes(app, opts, done) {
|
|
|
6693
8160
|
const replayed = buf.replay(cursor);
|
|
6694
8161
|
for (const e of replayed) writer.write(e.chunk);
|
|
6695
8162
|
if (buf.complete) return;
|
|
6696
|
-
await new Promise((
|
|
8163
|
+
await new Promise((resolve9) => {
|
|
6697
8164
|
const unsub = buf.subscribe(
|
|
6698
8165
|
(e) => writer.write(e.chunk),
|
|
6699
8166
|
() => {
|
|
6700
8167
|
unsub();
|
|
6701
|
-
|
|
8168
|
+
resolve9();
|
|
6702
8169
|
}
|
|
6703
8170
|
);
|
|
6704
8171
|
request.raw.on("close", () => {
|
|
6705
8172
|
unsub();
|
|
6706
|
-
|
|
8173
|
+
resolve9();
|
|
6707
8174
|
});
|
|
6708
8175
|
});
|
|
6709
8176
|
}
|
|
@@ -7059,7 +8526,7 @@ function classifySessionError(err, reply) {
|
|
|
7059
8526
|
}
|
|
7060
8527
|
});
|
|
7061
8528
|
}
|
|
7062
|
-
function
|
|
8529
|
+
function stableStringify2(value) {
|
|
7063
8530
|
if (typeof value === "string") return value;
|
|
7064
8531
|
try {
|
|
7065
8532
|
return JSON.stringify(value, null, 2);
|
|
@@ -7085,10 +8552,10 @@ function formatToolPart(part) {
|
|
|
7085
8552
|
const name = toolName(part);
|
|
7086
8553
|
const lines = [`[tool:${name}]`];
|
|
7087
8554
|
if ("input" in part) {
|
|
7088
|
-
lines.push("input:",
|
|
8555
|
+
lines.push("input:", stableStringify2(part.input));
|
|
7089
8556
|
}
|
|
7090
8557
|
if ("output" in part) {
|
|
7091
|
-
lines.push("output:",
|
|
8558
|
+
lines.push("output:", stableStringify2(part.output));
|
|
7092
8559
|
}
|
|
7093
8560
|
if (typeof part.errorText === "string" && part.errorText.length > 0) {
|
|
7094
8561
|
lines.push("error:", part.errorText);
|
|
@@ -7638,8 +9105,21 @@ async function createAgentApp(opts = {}) {
|
|
|
7638
9105
|
pluginTools.push(...plugin.tools);
|
|
7639
9106
|
}
|
|
7640
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
|
+
};
|
|
7641
9116
|
const tools = [
|
|
7642
|
-
...buildHarnessAgentTools(runtimeBundle
|
|
9117
|
+
...buildHarnessAgentTools(runtimeBundle, {
|
|
9118
|
+
getCurrent: () => {
|
|
9119
|
+
const current = getRuntimeProvisioning();
|
|
9120
|
+
return current ? { env: current.env, pathEntries: current.pathEntries } : void 0;
|
|
9121
|
+
}
|
|
9122
|
+
}),
|
|
7643
9123
|
...opts.disableDefaultFileTools ? [] : buildFilesystemAgentTools(runtimeBundle),
|
|
7644
9124
|
...opts.extraTools ?? [],
|
|
7645
9125
|
...pluginTools
|
|
@@ -7649,7 +9129,7 @@ async function createAgentApp(opts = {}) {
|
|
|
7649
9129
|
pi: {
|
|
7650
9130
|
noContextFiles: true,
|
|
7651
9131
|
noSkills: true,
|
|
7652
|
-
...
|
|
9132
|
+
...runtimePi
|
|
7653
9133
|
}
|
|
7654
9134
|
}));
|
|
7655
9135
|
const harness = await harnessFactory({
|
|
@@ -7658,7 +9138,8 @@ async function createAgentApp(opts = {}) {
|
|
|
7658
9138
|
sessionNamespace: opts.sessionNamespace,
|
|
7659
9139
|
sessionDir: opts.sessionDir,
|
|
7660
9140
|
systemPromptAppend: opts.systemPromptAppend,
|
|
7661
|
-
systemPromptDynamic: opts.systemPromptDynamic
|
|
9141
|
+
systemPromptDynamic: opts.systemPromptDynamic,
|
|
9142
|
+
telemetry: opts.telemetry
|
|
7662
9143
|
});
|
|
7663
9144
|
const sessionChangesTracker = new InMemorySessionChangesTracker();
|
|
7664
9145
|
const readyTracker = new ReadyStatusTracker({
|
|
@@ -7686,7 +9167,8 @@ async function createAgentApp(opts = {}) {
|
|
|
7686
9167
|
await app.register(chatRoutes, {
|
|
7687
9168
|
harness,
|
|
7688
9169
|
workdir: runtimeBundle.workspace.root,
|
|
7689
|
-
sessionChangesTracker
|
|
9170
|
+
sessionChangesTracker,
|
|
9171
|
+
telemetry: opts.telemetry
|
|
7690
9172
|
});
|
|
7691
9173
|
await app.register(sessionRoutes, {
|
|
7692
9174
|
sessionStore: harness.sessions,
|
|
@@ -7697,9 +9179,18 @@ async function createAgentApp(opts = {}) {
|
|
|
7697
9179
|
await app.register(modelsRoutes);
|
|
7698
9180
|
await app.register(skillsRoutes, {
|
|
7699
9181
|
workspaceRoot,
|
|
7700
|
-
additionalSkillPaths:
|
|
7701
|
-
piPackages:
|
|
7702
|
-
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
|
+
]
|
|
7703
9194
|
});
|
|
7704
9195
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
7705
9196
|
await app.register(catalogRoutes, { tools });
|
|
@@ -7733,7 +9224,7 @@ function applyCspHeaders(response, opts = {}) {
|
|
|
7733
9224
|
}
|
|
7734
9225
|
|
|
7735
9226
|
// src/server/registerAgentRoutes.ts
|
|
7736
|
-
import { basename as
|
|
9227
|
+
import { basename as basename3 } from "path";
|
|
7737
9228
|
import { AuthStorage as AuthStorage3, ModelRegistry as ModelRegistry3 } from "@mariozechner/pi-coding-agent";
|
|
7738
9229
|
|
|
7739
9230
|
// src/server/catalog/mergeTools.ts
|
|
@@ -7768,8 +9259,8 @@ function mergeTools(options) {
|
|
|
7768
9259
|
}
|
|
7769
9260
|
|
|
7770
9261
|
// src/server/tools/upload/index.ts
|
|
7771
|
-
import { readFile as
|
|
7772
|
-
import { extname as extname4, join as
|
|
9262
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
9263
|
+
import { extname as extname4, join as join12 } from "path";
|
|
7773
9264
|
var DEFAULT_UPLOAD_DIR = "assets/images";
|
|
7774
9265
|
var MAX_UPLOAD_BYTES2 = 10 * 1024 * 1024;
|
|
7775
9266
|
function contentTypeFromExt(path4) {
|
|
@@ -7837,7 +9328,7 @@ function buildUploadAgentTools(bundle) {
|
|
|
7837
9328
|
const rawDir = typeof input.directory === "string" ? input.directory.trim() : "";
|
|
7838
9329
|
const dir = rawDir ? rawDir.replace(/^\.\/+/, "").replace(/\/+$/, "") : DEFAULT_UPLOAD_DIR;
|
|
7839
9330
|
try {
|
|
7840
|
-
const bytes = await
|
|
9331
|
+
const bytes = await readFile12(join12(cwd, filePath));
|
|
7841
9332
|
if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES2) {
|
|
7842
9333
|
return {
|
|
7843
9334
|
content: [{ type: "text", text: `file must be between 1 byte and ${MAX_UPLOAD_BYTES2} bytes` }],
|
|
@@ -7880,7 +9371,7 @@ var DEFAULT_WORKSPACE_ID3 = "default";
|
|
|
7880
9371
|
var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
|
|
7881
9372
|
var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
|
|
7882
9373
|
function pluginNameFromPath(path4) {
|
|
7883
|
-
const fileName =
|
|
9374
|
+
const fileName = basename3(path4);
|
|
7884
9375
|
if (fileName.endsWith(".mjs")) return fileName.slice(0, -4);
|
|
7885
9376
|
if (fileName.endsWith(".js")) return fileName.slice(0, -3);
|
|
7886
9377
|
return fileName;
|
|
@@ -7975,16 +9466,47 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
7975
9466
|
const pi = opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot: root, request }) : opts.pi;
|
|
7976
9467
|
return { root, pi };
|
|
7977
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
|
+
}
|
|
7978
9491
|
async function createRuntimeBinding(workspaceId, scope, request) {
|
|
7979
9492
|
const root = scope.root;
|
|
7980
|
-
const
|
|
9493
|
+
const modeCtx = {
|
|
7981
9494
|
workspaceRoot: root,
|
|
7982
9495
|
sessionId: workspaceId,
|
|
7983
9496
|
workspaceId,
|
|
7984
|
-
templatePath: scope.templatePath
|
|
7985
|
-
|
|
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);
|
|
7986
9503
|
const standardTools = [
|
|
7987
|
-
...buildHarnessAgentTools(runtimeBundle
|
|
9504
|
+
...buildHarnessAgentTools(runtimeBundle, {
|
|
9505
|
+
getCurrent: () => runtimeProvisioning ? {
|
|
9506
|
+
env: runtimeProvisioning.env,
|
|
9507
|
+
pathEntries: runtimeProvisioning.pathEntries
|
|
9508
|
+
} : void 0
|
|
9509
|
+
}),
|
|
7988
9510
|
...buildFilesystemAgentTools(runtimeBundle),
|
|
7989
9511
|
...buildUploadAgentTools(runtimeBundle)
|
|
7990
9512
|
];
|
|
@@ -8023,14 +9545,19 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8023
9545
|
pi: {
|
|
8024
9546
|
noContextFiles: true,
|
|
8025
9547
|
noSkills: true,
|
|
8026
|
-
...scope.pi
|
|
9548
|
+
...scope.pi,
|
|
9549
|
+
additionalSkillPaths: [
|
|
9550
|
+
...runtimeProvisioning?.skillPaths ?? [],
|
|
9551
|
+
...scope.pi?.additionalSkillPaths ?? []
|
|
9552
|
+
]
|
|
8027
9553
|
}
|
|
8028
9554
|
}));
|
|
8029
9555
|
const harness = await harnessFactory({
|
|
8030
9556
|
tools,
|
|
8031
9557
|
cwd: root,
|
|
8032
9558
|
sessionNamespace: scope.sessionNamespace,
|
|
8033
|
-
systemPromptAppend: opts.systemPromptAppend
|
|
9559
|
+
systemPromptAppend: opts.systemPromptAppend,
|
|
9560
|
+
telemetry: opts.telemetry
|
|
8034
9561
|
});
|
|
8035
9562
|
const readyTracker = new ReadyStatusTracker({
|
|
8036
9563
|
sandboxReady: resolvedMode !== "vercel-sandbox",
|
|
@@ -8041,6 +9568,11 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8041
9568
|
}
|
|
8042
9569
|
return {
|
|
8043
9570
|
runtimeBundle,
|
|
9571
|
+
runtimeProvisioning,
|
|
9572
|
+
reprovision: async (reloadRequest) => {
|
|
9573
|
+
runtimeProvisioning = await runRuntimeProvisioning(workspaceId, scope, reloadRequest);
|
|
9574
|
+
return runtimeProvisioning;
|
|
9575
|
+
},
|
|
8044
9576
|
harness,
|
|
8045
9577
|
tools,
|
|
8046
9578
|
readyTracker
|
|
@@ -8104,6 +9636,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8104
9636
|
return await recreateRuntimeBinding(workspaceId, scope);
|
|
8105
9637
|
}
|
|
8106
9638
|
}
|
|
9639
|
+
const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
|
|
8107
9640
|
const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
|
|
8108
9641
|
const skillsScopeByRequest = /* @__PURE__ */ new WeakMap();
|
|
8109
9642
|
function getSkillsScopeForRequest(request) {
|
|
@@ -8192,7 +9725,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8192
9725
|
workdir: binding.runtimeBundle.workspace.root
|
|
8193
9726
|
};
|
|
8194
9727
|
},
|
|
8195
|
-
sessionChangesTracker
|
|
9728
|
+
sessionChangesTracker,
|
|
9729
|
+
telemetry: opts.telemetry
|
|
8196
9730
|
});
|
|
8197
9731
|
await app.register(sessionRoutes, {
|
|
8198
9732
|
getSessionStore: async (request) => {
|
|
@@ -8213,15 +9747,55 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
8213
9747
|
await app.register(modelsRoutes);
|
|
8214
9748
|
await app.register(skillsRoutes, {
|
|
8215
9749
|
workspaceRoot,
|
|
8216
|
-
additionalSkillPaths:
|
|
9750
|
+
additionalSkillPaths: [
|
|
9751
|
+
...staticBinding?.runtimeProvisioning?.skillPaths ?? [],
|
|
9752
|
+
...opts.pi?.additionalSkillPaths ?? []
|
|
9753
|
+
],
|
|
8217
9754
|
piPackages: opts.pi?.packages,
|
|
8218
9755
|
noSkills: opts.pi?.noSkills,
|
|
8219
9756
|
getWorkspaceRoot: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).root,
|
|
8220
|
-
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
|
+
},
|
|
8221
9766
|
getPiPackages: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi?.packages,
|
|
8222
9767
|
getNoSkills: staticBinding ? void 0 : async (request) => (await getSkillsScopeForRequest(request)).pi?.noSkills
|
|
8223
9768
|
});
|
|
8224
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
|
+
});
|
|
8225
9799
|
await app.register(
|
|
8226
9800
|
catalogRoutes,
|
|
8227
9801
|
staticBinding ? { tools: staticBinding.tools } : { getTools: async (request) => (await getBindingForRequest(request)).tools }
|
|
@@ -8237,6 +9811,8 @@ export {
|
|
|
8237
9811
|
FileHandleStore,
|
|
8238
9812
|
PI_PACKAGE_RESOURCE_FILTERS,
|
|
8239
9813
|
UV_SETUP_COMMANDS,
|
|
9814
|
+
VERCEL_PROVISIONING_CACHE_ROOT,
|
|
9815
|
+
VERCEL_SANDBOX_WORKSPACE_ROOT,
|
|
8240
9816
|
UV_SETUP_COMMANDS as VERCEL_UV_SETUP_COMMANDS,
|
|
8241
9817
|
applyCspHeaders,
|
|
8242
9818
|
autoDetectMode,
|
|
@@ -8252,14 +9828,19 @@ export {
|
|
|
8252
9828
|
createNodeWorkspace,
|
|
8253
9829
|
createResourceSettingsManager,
|
|
8254
9830
|
createVercelDeploymentSnapshotProvider,
|
|
9831
|
+
createVercelProvisioningAdapter,
|
|
8255
9832
|
createVercelSandboxWorkspace,
|
|
8256
9833
|
fileRoutes,
|
|
9834
|
+
getBoringAgentPathEntries,
|
|
9835
|
+
getBoringAgentRuntimeEnv,
|
|
9836
|
+
getBoringAgentRuntimePaths,
|
|
8257
9837
|
hasBwrap,
|
|
8258
9838
|
mergePiPackageSources,
|
|
8259
9839
|
piPackageSourceKey,
|
|
8260
9840
|
prepareDeploymentSnapshot,
|
|
8261
9841
|
prepareVercelDeploymentSnapshot,
|
|
8262
9842
|
provisionRuntimeWorkspace,
|
|
9843
|
+
provisionWorkspaceRuntime,
|
|
8263
9844
|
registerAgentRoutes,
|
|
8264
9845
|
resolveMode,
|
|
8265
9846
|
resolveSandboxHandle
|