@hachej/boring-agent 0.1.20 → 0.1.23

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