@hachej/boring-agent 0.1.54 → 0.1.55

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.
@@ -2337,6 +2337,8 @@ function createRemoteWorkerModeAdapter(opts = {}) {
2337
2337
  await sandbox.init?.({ workspace, sessionId: ctx.sessionId });
2338
2338
  return {
2339
2339
  runtimeContext: { runtimeCwd: REMOTE_WORKER_RUNTIME_CWD },
2340
+ bash: { kind: "remote" },
2341
+ filesystem: { kind: "remote-workspace" },
2340
2342
  workspace,
2341
2343
  sandbox,
2342
2344
  fileSearch: createServerFileSearch(workspace, sandbox)
@@ -2957,8 +2959,8 @@ function fileRoutes(app, opts, done) {
2957
2959
  const dir = isImage ? normalizeUploadDir(body.directory) ?? normalizeUploadDir(settings.markdown?.imageUploadDir) ?? DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR : DEFAULT_FILE_UPLOAD_DIR;
2958
2960
  const ext = extForUpload(filename, contentType);
2959
2961
  const base = basenameForUpload(filename);
2960
- const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2961
- const path4 = `${dir}/${base}-${unique}.${ext}`;
2962
+ const unique2 = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2963
+ const path4 = `${dir}/${base}-${unique2}.${ext}`;
2962
2964
  const estimatedBytes = Math.ceil(contentBase64.length * 0.75);
2963
2965
  if (estimatedBytes === 0 || estimatedBytes > MAX_UPLOAD_BYTES) {
2964
2966
  return reply.code(400).send({
@@ -4872,6 +4874,8 @@ var directModeAdapter = {
4872
4874
  return {
4873
4875
  runtimeContext,
4874
4876
  storageRoot: ctx.workspaceRoot,
4877
+ bash: { kind: "host", preserveHostHome: true },
4878
+ filesystem: { kind: "host" },
4875
4879
  workspace,
4876
4880
  sandbox,
4877
4881
  fileSearch: createServerFileSearch(workspace, sandbox)
@@ -4901,6 +4905,8 @@ var localModeAdapter = {
4901
4905
  return {
4902
4906
  runtimeContext,
4903
4907
  storageRoot: ctx.workspaceRoot,
4908
+ bash: { kind: "local-sandbox", sandboxRoot: "/workspace" },
4909
+ filesystem: { kind: "host" },
4904
4910
  workspace,
4905
4911
  sandbox,
4906
4912
  fileSearch: createServerFileSearch(workspace, sandbox)
@@ -5168,6 +5174,38 @@ var ORPHAN_GUARD_MAX_IDLE_MS = 24 * 60 * 60 * 1e3;
5168
5174
  var VERCEL_SANDBOX_TIMEOUT_MS_ENV = "BORING_AGENT_VERCEL_SANDBOX_TIMEOUT_MS";
5169
5175
  var VERCEL_SANDBOX_RUNTIME_ENV = "BORING_AGENT_VERCEL_SANDBOX_RUNTIME";
5170
5176
  var DEFAULT_VERCEL_SANDBOX_RUNTIME = "node24";
5177
+ var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
5178
+ var VERCEL_SAFE_DEFAULT_PATH = "/vercel/runtimes/node24/bin:/vercel/runtimes/node22/bin:/usr/local/bin:/usr/bin:/bin";
5179
+ function vercelRemoteWorkspacePathOptions() {
5180
+ return {
5181
+ rootAliases: [VERCEL_SANDBOX_REMOTE_ROOT, "/vercel/sandbox"],
5182
+ toRemotePath(value) {
5183
+ if (value === VERCEL_SANDBOX_WORKSPACE_ROOT) return VERCEL_SANDBOX_REMOTE_ROOT;
5184
+ if (value.startsWith(`${VERCEL_SANDBOX_WORKSPACE_ROOT}/`)) {
5185
+ return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice(VERCEL_SANDBOX_WORKSPACE_ROOT.length)}`;
5186
+ }
5187
+ if (value === "/vercel/sandbox") return VERCEL_SANDBOX_REMOTE_ROOT;
5188
+ if (value.startsWith("/vercel/sandbox/")) {
5189
+ return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice("/vercel/sandbox".length)}`;
5190
+ }
5191
+ return value;
5192
+ },
5193
+ toRuntimePath(value) {
5194
+ if (value === VERCEL_SANDBOX_REMOTE_ROOT) return VERCEL_SANDBOX_WORKSPACE_ROOT;
5195
+ if (value.startsWith(`${VERCEL_SANDBOX_REMOTE_ROOT}/`)) {
5196
+ return `${VERCEL_SANDBOX_WORKSPACE_ROOT}${value.slice(VERCEL_SANDBOX_REMOTE_ROOT.length)}`;
5197
+ }
5198
+ if (value === "/vercel/sandbox") return VERCEL_SANDBOX_WORKSPACE_ROOT;
5199
+ if (value.startsWith("/vercel/sandbox/")) {
5200
+ return `${VERCEL_SANDBOX_WORKSPACE_ROOT}${value.slice("/vercel/sandbox".length)}`;
5201
+ }
5202
+ return value;
5203
+ },
5204
+ sanitizeErrorText(value) {
5205
+ return value.replaceAll("/vercel/sandbox", VERCEL_SANDBOX_WORKSPACE_ROOT);
5206
+ }
5207
+ };
5208
+ }
5171
5209
  function sandboxTelemetryProperties(ctx, extra = {}) {
5172
5210
  return {
5173
5211
  runtimeMode: "vercel-sandbox",
@@ -5471,6 +5509,22 @@ var DEFAULT_MODE_LOGGER = {
5471
5509
  `);
5472
5510
  }
5473
5511
  };
5512
+ function extractHttpStatus2(error) {
5513
+ const statusCode = error?.statusCode;
5514
+ if (typeof statusCode === "number") return statusCode;
5515
+ const status = error?.status;
5516
+ if (typeof status === "number") return status;
5517
+ const responseStatus = error?.response?.status;
5518
+ return typeof responseStatus === "number" ? responseStatus : null;
5519
+ }
5520
+ function isExpiredSandboxRuntimeError(error) {
5521
+ const code = error?.code;
5522
+ if (code === ErrorCode.enum.SANDBOX_EXPIRED) return true;
5523
+ const status = extractHttpStatus2(error);
5524
+ if (status === 404 || status === 410) return true;
5525
+ const message = error instanceof Error ? error.message : String(error);
5526
+ return /status code (404|410) is not ok/i.test(message);
5527
+ }
5474
5528
  function requireEnvVar(name, getEnvVar) {
5475
5529
  const value = getEnvVar(name)?.trim();
5476
5530
  if (!value) {
@@ -5510,6 +5564,33 @@ function createVercelSandboxModeAdapter(opts = {}) {
5510
5564
  return {
5511
5565
  id: "vercel-sandbox",
5512
5566
  workspaceFsCapability: "best-effort",
5567
+ readiness: {
5568
+ initialSandboxReady: false,
5569
+ initialWorkspaceReadiness: { state: "preparing" },
5570
+ onTrackerCreated: (tracker) => {
5571
+ queueMicrotask(() => tracker.markSandboxReady());
5572
+ }
5573
+ },
5574
+ cachedBindingHealthCheck: {
5575
+ intervalMs: VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS,
5576
+ async check({ runtimeBundle }) {
5577
+ try {
5578
+ await runtimeBundle.workspace.stat(".");
5579
+ return { state: "ok" };
5580
+ } catch (error) {
5581
+ if (!isExpiredSandboxRuntimeError(error)) throw error;
5582
+ return {
5583
+ state: "recreate",
5584
+ message: "[sandbox] cached runtime expired; recreating from persisted handle",
5585
+ error
5586
+ };
5587
+ }
5588
+ }
5589
+ },
5590
+ getRuntimeLayoutRoot: () => VERCEL_SANDBOX_WORKSPACE_ROOT,
5591
+ evictCachedRuntime: ({ workspaceId }) => {
5592
+ evictSandboxHandleCacheForWorkspace(workspaceId);
5593
+ },
5513
5594
  async dispose() {
5514
5595
  await snapshotScheduler?.shutdown();
5515
5596
  },
@@ -5692,7 +5773,9 @@ function createVercelSandboxModeAdapter(opts = {}) {
5692
5773
  workspace,
5693
5774
  sandbox,
5694
5775
  fileSearch: createServerFileSearch(workspace, sandbox),
5695
- runtimeContext: workspace.runtimeContext
5776
+ runtimeContext: workspace.runtimeContext,
5777
+ bash: { kind: "remote", defaultPath: VERCEL_SAFE_DEFAULT_PATH },
5778
+ filesystem: { kind: "remote-workspace", pathOptions: vercelRemoteWorkspacePathOptions() }
5696
5779
  };
5697
5780
  } catch (error) {
5698
5781
  const code = error?.code;
@@ -5736,7 +5819,13 @@ function autoDetectMode() {
5736
5819
  }
5737
5820
  return "direct";
5738
5821
  }
5739
- function resolveMode(mode = autoDetectMode()) {
5822
+ function resolveMode(mode = autoDetectMode(), opts = {}) {
5823
+ if (mode === "vercel-sandbox" && opts.sandboxHandleStore) {
5824
+ return createVercelSandboxModeAdapter({
5825
+ store: opts.sandboxHandleStore,
5826
+ orphanGuardMaxIdleMs: null
5827
+ });
5828
+ }
5740
5829
  if (isBuiltinRuntimeModeId(mode)) return MODE_ADAPTERS[mode];
5741
5830
  throw new Error(`Runtime mode "${mode}" has no built-in adapter. Pass runtimeModeAdapter to use a custom sandbox mode.`);
5742
5831
  }
@@ -5745,15 +5834,48 @@ function resolveMode(mode = autoDetectMode()) {
5745
5834
  import Fastify from "fastify";
5746
5835
 
5747
5836
  // src/server/runtime/mode.ts
5837
+ function getOptionalRuntimeBundleStorageRoot(bundle) {
5838
+ return bundle.storageRoot ?? getNodeWorkspaceHostRoot(bundle.workspace) ?? void 0;
5839
+ }
5748
5840
  function getRuntimeBundleStorageRoot(bundle) {
5749
- const hostRoot = bundle.storageRoot ?? getNodeWorkspaceHostRoot(bundle.workspace);
5841
+ const hostRoot = getOptionalRuntimeBundleStorageRoot(bundle);
5750
5842
  if (hostRoot) return hostRoot;
5751
- if (bundle.sandbox.provider === "vercel-sandbox") return bundle.workspace.root;
5752
5843
  throw new Error(
5753
5844
  `RuntimeBundle.storageRoot is required for host-filesystem tools. Mode adapters must set storageRoot to the host workspace path. Got workspace.root=${bundle.workspace.root}, sandbox.provider=${bundle.sandbox.provider}`
5754
5845
  );
5755
5846
  }
5756
5847
 
5848
+ // src/server/runtimeEnvContributions.ts
5849
+ function withRuntimeEnvContributions(runtimeBundle, baseContext, contributions, telemetry) {
5850
+ const getRuntimeEnv = async () => {
5851
+ const contributedEnv = {};
5852
+ for (const contribution of contributions) {
5853
+ Object.assign(contributedEnv, await contribution.getEnv(baseContext));
5854
+ }
5855
+ if (telemetry) {
5856
+ safeCapture(telemetry, {
5857
+ name: "agent.runtime.env_contributed",
5858
+ properties: {
5859
+ runtimeMode: baseContext.runtimeMode,
5860
+ contributionIds: contributions.map((contribution) => contribution.id)
5861
+ }
5862
+ });
5863
+ }
5864
+ return contributedEnv;
5865
+ };
5866
+ const sandbox = {
5867
+ ...runtimeBundle.sandbox,
5868
+ exec: async (cmd, execOpts = {}) => {
5869
+ const contributedEnv = await getRuntimeEnv();
5870
+ return runtimeBundle.sandbox.exec(cmd, {
5871
+ ...execOpts,
5872
+ env: { ...contributedEnv, ...execOpts.env ?? {} }
5873
+ });
5874
+ }
5875
+ };
5876
+ return { ...runtimeBundle, sandbox, getRuntimeEnv };
5877
+ }
5878
+
5757
5879
  // src/server/harness/pi-coding-agent/createHarness.ts
5758
5880
  import { existsSync, readFileSync as readFileSync2 } from "fs";
5759
5881
  import { extname as extname3, join as join11 } from "path";
@@ -6273,10 +6395,34 @@ var PiSessionStore = class {
6273
6395
  }
6274
6396
  }
6275
6397
  async loadPiSessionFile(_ctx, sessionId) {
6398
+ if (!SAFE_ID.test(sessionId)) return null;
6276
6399
  try {
6277
- const filepath = await this.resolveSessionFile(sessionId);
6278
- const content = await readFile9(filepath, "utf-8");
6279
- return extractPiSessionFilePath(safeParseEntries(content)) ?? (isTimestampNamedPiSessionFile(filepath, sessionId) ? filepath : null);
6400
+ const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
6401
+ let filepath = direct;
6402
+ let content;
6403
+ try {
6404
+ content = await readFile9(direct, "utf-8");
6405
+ } catch {
6406
+ const files = await readdir7(this.sessionDir).catch(() => []);
6407
+ const match = files.find(
6408
+ (f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
6409
+ );
6410
+ if (!match) return null;
6411
+ filepath = join10(this.sessionDir, match);
6412
+ content = await readFile9(filepath, "utf-8");
6413
+ }
6414
+ const entries = safeParseEntries(content);
6415
+ const linkedPiFile = extractPiSessionFilePath(entries);
6416
+ if (linkedPiFile) return linkedPiFile;
6417
+ if (!isTimestampNamedPiSessionFile(filepath, sessionId)) return null;
6418
+ const existingWrapper = await this.findWrapperReferencingNativeSession(filepath);
6419
+ if (existingWrapper) {
6420
+ const wrapperSessionId = await this.readSessionFileId(existingWrapper);
6421
+ if (wrapperSessionId !== sessionId) return null;
6422
+ const wrapperEntries = parseJsonlPrefixEntries(await readJsonlPrefix(existingWrapper));
6423
+ return extractPiSessionFilePath(wrapperEntries);
6424
+ }
6425
+ return await this.ensureWrapperForNativeSession(sessionId, filepath);
6280
6426
  } catch {
6281
6427
  return null;
6282
6428
  }
@@ -7182,8 +7328,8 @@ function createPiCodingAgentHarness(opts) {
7182
7328
  const { session: piSession } = await createAgentSession({
7183
7329
  cwd: runtimeCwd,
7184
7330
  // Suppress Pi's built-in filesystem/shell tools while keeping Boring's
7185
- // adapted tool catalog active. Passing `tools: []` is an allowlist of
7186
- // zero tools in Pi v0.75+, which disables customTools too.
7331
+ // adapted tool catalog active. Do NOT pass an explicit empty tool-name
7332
+ // allowlist: in the current Pi SDK that disables custom tools too.
7187
7333
  noTools: "builtin",
7188
7334
  customTools: adaptToolsForPi(opts.tools, input.sessionId, opts.telemetry),
7189
7335
  model,
@@ -7450,12 +7596,12 @@ async function loadPlugins(options) {
7450
7596
 
7451
7597
  // src/server/tools/filesystem/index.ts
7452
7598
  import {
7453
- createEditToolDefinition,
7454
- createFindToolDefinition,
7599
+ createEditToolDefinition as createEditToolDefinition2,
7600
+ createFindToolDefinition as createFindToolDefinition2,
7455
7601
  createGrepToolDefinition as createGrepToolDefinition2,
7456
- createLsToolDefinition,
7457
- createReadToolDefinition,
7458
- createWriteToolDefinition
7602
+ createLsToolDefinition as createLsToolDefinition2,
7603
+ createReadToolDefinition as createReadToolDefinition2,
7604
+ createWriteToolDefinition as createWriteToolDefinition2
7459
7605
  } from "@mariozechner/pi-coding-agent";
7460
7606
 
7461
7607
  // src/server/tools/operations/bound.ts
@@ -7596,7 +7742,7 @@ function boundFs(workspaceRoot, opts = {}) {
7596
7742
  }
7597
7743
  return absolutePath;
7598
7744
  };
7599
- const toRuntimePath2 = (absolutePath) => {
7745
+ const toRuntimePath = (absolutePath) => {
7600
7746
  if (!shouldMapRuntimeRoot || !runtimeRoot) return absolutePath;
7601
7747
  const rel = relative10(workspaceRoot, absolutePath);
7602
7748
  if (rel === "") return runtimeRoot;
@@ -7662,7 +7808,7 @@ function boundFs(workspaceRoot, opts = {}) {
7662
7808
  await assertWithinWorkspace(workspaceRoot, storageCwd);
7663
7809
  const matches = [];
7664
7810
  await walkMatches(storageCwd, storageCwd, pattern, options.ignore, options.limit, matches);
7665
- return matches.map(toRuntimePath2);
7811
+ return matches.map(toRuntimePath);
7666
7812
  }
7667
7813
  };
7668
7814
  const grep = {
@@ -7702,20 +7848,28 @@ function boundFs(workspaceRoot, opts = {}) {
7702
7848
  return { read, write, edit, find, grep, ls };
7703
7849
  }
7704
7850
 
7705
- // src/server/tools/operations/vercel.ts
7851
+ // src/server/tools/filesystem/remoteWorkspaceTools.ts
7852
+ import {
7853
+ createEditToolDefinition,
7854
+ createFindToolDefinition,
7855
+ createLsToolDefinition,
7856
+ createReadToolDefinition,
7857
+ createWriteToolDefinition
7858
+ } from "@mariozechner/pi-coding-agent";
7859
+
7860
+ // src/server/tools/operations/remoteWorkspace.ts
7706
7861
  import { isAbsolute as isAbsolute8, relative as relative11 } from "path";
7707
- var VERCEL_SANDBOX_LEGACY_ROOT = "/vercel/sandbox";
7708
- function rootAliases(workspace) {
7709
- const aliases = [workspace.root];
7710
- if (workspace.root === VERCEL_SANDBOX_WORKSPACE_ROOT) aliases.push(VERCEL_SANDBOX_LEGACY_ROOT);
7711
- if (workspace.root === VERCEL_SANDBOX_LEGACY_ROOT) aliases.push(VERCEL_SANDBOX_WORKSPACE_ROOT);
7712
- return Array.from(new Set(aliases));
7862
+ function unique(values) {
7863
+ return Array.from(new Set(values));
7864
+ }
7865
+ function rootsFor(workspace, opts = {}) {
7866
+ return unique([workspace.root, ...opts.rootAliases ?? []]);
7713
7867
  }
7714
7868
  function isOutsideWorkspaceRel(rel) {
7715
7869
  return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute8(rel);
7716
7870
  }
7717
- function toRelPath(workspace, absolutePath) {
7718
- for (const root of rootAliases(workspace)) {
7871
+ function toRelPath(workspace, absolutePath, opts = {}) {
7872
+ for (const root of rootsFor(workspace, opts)) {
7719
7873
  const rel = relative11(root, absolutePath);
7720
7874
  if (!isOutsideWorkspaceRel(rel)) return rel;
7721
7875
  }
@@ -7732,113 +7886,92 @@ function toRelPath(workspace, absolutePath) {
7732
7886
  `path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${workspace.root}`
7733
7887
  );
7734
7888
  }
7735
- function vercelBashOps(sandbox, opts = {}) {
7736
- return {
7737
- exec(command, cwd, { onData, signal, timeout, env }) {
7738
- const effectiveEnv = opts.mergeEnv ? opts.mergeEnv(env) : env;
7739
- const filteredEnv2 = effectiveEnv ? Object.fromEntries(Object.entries(effectiveEnv).filter((e) => e[1] != null)) : void 0;
7740
- return sandbox.exec(command, {
7741
- cwd,
7742
- env: filteredEnv2,
7743
- signal,
7744
- timeoutMs: timeout ? timeout * 1e3 : void 0,
7745
- onStdout: (chunk) => onData(Buffer.from(chunk)),
7746
- onStderr: (chunk) => onData(Buffer.from(chunk))
7747
- }).then((result) => ({ exitCode: result.exitCode }));
7748
- }
7749
- };
7889
+ function shellEscape(s) {
7890
+ return `'${s.replace(/'/g, "'\\''")}'`;
7891
+ }
7892
+ function findPredicate(pattern) {
7893
+ const isPathShaped = pattern.includes("/") || pattern.includes("**");
7894
+ if (!isPathShaped) return `-name ${shellEscape(pattern)}`;
7895
+ let translated = pattern.replaceAll("**", "*").replace(/^\/+/, "");
7896
+ if (!translated.startsWith("*")) translated = `*${translated}`;
7897
+ return `-path ${shellEscape(translated)}`;
7898
+ }
7899
+ function findIgnoreArgs(cwd, ignore) {
7900
+ return ignore.map((pattern) => `! -path ${shellEscape(`${cwd}/${pattern.replace(/^\/+/, "")}/*`)}`).join(" ");
7750
7901
  }
7751
- function vercelReadOps(workspace) {
7902
+ function fallbackFindCommand(pattern, cwd, options) {
7903
+ return [
7904
+ "find",
7905
+ shellEscape(cwd),
7906
+ "-maxdepth 20",
7907
+ "-type f",
7908
+ findIgnoreArgs(cwd, options.ignore),
7909
+ findPredicate(pattern),
7910
+ `| head -n ${Math.max(1, Math.trunc(options.limit))}`
7911
+ ].filter(Boolean).join(" ");
7912
+ }
7913
+ function isFdMissing(result) {
7914
+ if (result.exitCode !== 127) return false;
7915
+ const stderr = Buffer.from(result.stderr).toString("utf-8");
7916
+ return /\bfd: (?:not found|command not found)\b/i.test(stderr);
7917
+ }
7918
+ function remotePath(value, opts) {
7919
+ return opts.toRemotePath?.(value) ?? value;
7920
+ }
7921
+ function runtimePath(value, opts) {
7922
+ return opts.toRuntimePath?.(value) ?? value;
7923
+ }
7924
+ function sanitizeErrorText(value, opts) {
7925
+ return opts.sanitizeErrorText?.(value) ?? value;
7926
+ }
7927
+ function remoteWorkspaceReadOps(workspace, opts = {}) {
7752
7928
  return {
7753
7929
  async readFile(absolutePath) {
7754
- const rel = toRelPath(workspace, absolutePath);
7930
+ const rel = toRelPath(workspace, absolutePath, opts);
7755
7931
  const content = await workspace.readFile(rel);
7756
7932
  return Buffer.from(content, "utf-8");
7757
7933
  },
7758
7934
  async access(absolutePath) {
7759
- const rel = toRelPath(workspace, absolutePath);
7935
+ const rel = toRelPath(workspace, absolutePath, opts);
7760
7936
  await workspace.stat(rel);
7761
7937
  }
7762
7938
  };
7763
7939
  }
7764
- function vercelWriteOps(workspace) {
7940
+ function remoteWorkspaceWriteOps(workspace, opts = {}) {
7765
7941
  return {
7766
7942
  async writeFile(absolutePath, content) {
7767
- const rel = toRelPath(workspace, absolutePath);
7943
+ const rel = toRelPath(workspace, absolutePath, opts);
7768
7944
  await workspace.writeFile(rel, content);
7769
7945
  },
7770
7946
  async mkdir(dir) {
7771
- const rel = toRelPath(workspace, dir);
7947
+ const rel = toRelPath(workspace, dir, opts);
7772
7948
  await workspace.mkdir(rel, { recursive: true });
7773
7949
  }
7774
7950
  };
7775
7951
  }
7776
- function vercelEditOps(workspace) {
7952
+ function remoteWorkspaceEditOps(workspace, opts = {}) {
7777
7953
  return {
7778
7954
  async readFile(absolutePath) {
7779
- const rel = toRelPath(workspace, absolutePath);
7955
+ const rel = toRelPath(workspace, absolutePath, opts);
7780
7956
  const content = await workspace.readFile(rel);
7781
7957
  return Buffer.from(content, "utf-8");
7782
7958
  },
7783
7959
  async writeFile(absolutePath, content) {
7784
- const rel = toRelPath(workspace, absolutePath);
7960
+ const rel = toRelPath(workspace, absolutePath, opts);
7785
7961
  await workspace.writeFile(rel, content);
7786
7962
  },
7787
7963
  async access(absolutePath) {
7788
- const rel = toRelPath(workspace, absolutePath);
7964
+ const rel = toRelPath(workspace, absolutePath, opts);
7789
7965
  await workspace.stat(rel);
7790
7966
  }
7791
7967
  };
7792
7968
  }
7793
- function toRemotePath(value) {
7794
- if (value === VERCEL_SANDBOX_LEGACY_ROOT) return VERCEL_SANDBOX_REMOTE_ROOT;
7795
- if (value.startsWith(`${VERCEL_SANDBOX_LEGACY_ROOT}/`)) {
7796
- return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice(VERCEL_SANDBOX_LEGACY_ROOT.length)}`;
7797
- }
7798
- return value;
7799
- }
7800
- function toRuntimePath(value) {
7801
- if (value === VERCEL_SANDBOX_LEGACY_ROOT) return VERCEL_SANDBOX_WORKSPACE_ROOT;
7802
- if (value.startsWith(`${VERCEL_SANDBOX_LEGACY_ROOT}/`)) {
7803
- return `${VERCEL_SANDBOX_WORKSPACE_ROOT}${value.slice(VERCEL_SANDBOX_LEGACY_ROOT.length)}`;
7804
- }
7805
- return value;
7806
- }
7807
- function sanitizeRuntimeText(value) {
7808
- return value.replaceAll(VERCEL_SANDBOX_LEGACY_ROOT, VERCEL_SANDBOX_WORKSPACE_ROOT);
7809
- }
7810
- function findPredicate(pattern) {
7811
- const isPathShaped = pattern.includes("/") || pattern.includes("**");
7812
- if (!isPathShaped) return `-name ${shellEscape(pattern)}`;
7813
- let translated = pattern.replaceAll("**", "*").replace(/^\/+/, "");
7814
- if (!translated.startsWith("*")) translated = `*${translated}`;
7815
- return `-path ${shellEscape(translated)}`;
7816
- }
7817
- function findIgnoreArgs(cwd, ignore) {
7818
- return ignore.map((pattern) => `! -path ${shellEscape(`${cwd}/${pattern.replace(/^\/+/, "")}/*`)}`).join(" ");
7819
- }
7820
- function fallbackFindCommand(pattern, cwd, options) {
7821
- return [
7822
- "find",
7823
- shellEscape(cwd),
7824
- "-maxdepth 20",
7825
- "-type f",
7826
- findIgnoreArgs(cwd, options.ignore),
7827
- findPredicate(pattern),
7828
- `| head -n ${Math.max(1, Math.trunc(options.limit))}`
7829
- ].filter(Boolean).join(" ");
7830
- }
7831
- function isFdMissing(result) {
7832
- if (result.exitCode !== 127) return false;
7833
- const stderr = Buffer.from(result.stderr).toString("utf-8");
7834
- return /\bfd: (?:not found|command not found)\b/i.test(stderr);
7835
- }
7836
- function vercelFindOps(sandbox, workspace) {
7969
+ function remoteWorkspaceFindOps(sandbox, workspace, opts = {}) {
7837
7970
  return {
7838
7971
  async exists(absolutePath) {
7839
7972
  if (workspace) {
7840
7973
  try {
7841
- const rel = toRelPath(workspace, absolutePath);
7974
+ const rel = toRelPath(workspace, absolutePath, opts);
7842
7975
  await workspace.stat(rel);
7843
7976
  return true;
7844
7977
  } catch {
@@ -7851,11 +7984,9 @@ function vercelFindOps(sandbox, workspace) {
7851
7984
  return result.exitCode === 0;
7852
7985
  },
7853
7986
  async glob(pattern, cwd, options) {
7854
- const remoteCwd = toRemotePath(cwd);
7987
+ const remoteCwd = remotePath(cwd, opts);
7855
7988
  const args = ["fd", "--glob", "--no-require-git", "--max-results", String(options.limit)];
7856
- for (const ig of options.ignore) {
7857
- args.push("--exclude", ig);
7858
- }
7989
+ for (const ig of options.ignore) args.push("--exclude", ig);
7859
7990
  args.push(pattern, remoteCwd);
7860
7991
  let result = await sandbox.exec(args.map(shellEscape).join(" "), {
7861
7992
  timeoutMs: 3e4,
@@ -7868,19 +7999,19 @@ function vercelFindOps(sandbox, workspace) {
7868
7999
  });
7869
8000
  }
7870
8001
  if (result.exitCode !== 0 && result.exitCode !== 1) {
7871
- const stderr = sanitizeRuntimeText(Buffer.from(result.stderr).toString("utf-8").trim());
8002
+ const stderr = sanitizeErrorText(Buffer.from(result.stderr).toString("utf-8").trim(), opts);
7872
8003
  throw new Error(`file search failed (exit ${result.exitCode}): ${stderr}`);
7873
8004
  }
7874
8005
  const stdout = Buffer.from(result.stdout).toString("utf-8");
7875
- return stdout.split("\n").filter(Boolean).map(toRuntimePath);
8006
+ return stdout.split("\n").filter(Boolean).map((value) => runtimePath(value, opts));
7876
8007
  }
7877
8008
  };
7878
8009
  }
7879
- function vercelLsOps(workspace) {
8010
+ function remoteWorkspaceLsOps(workspace, opts = {}) {
7880
8011
  return {
7881
8012
  async exists(absolutePath) {
7882
8013
  try {
7883
- const rel = toRelPath(workspace, absolutePath);
8014
+ const rel = toRelPath(workspace, absolutePath, opts);
7884
8015
  await workspace.stat(rel);
7885
8016
  return true;
7886
8017
  } catch {
@@ -7888,22 +8019,18 @@ function vercelLsOps(workspace) {
7888
8019
  }
7889
8020
  },
7890
8021
  async stat(absolutePath) {
7891
- const rel = toRelPath(workspace, absolutePath);
7892
- const s = await workspace.stat(rel);
7893
- return { isDirectory: () => s.kind === "dir" };
8022
+ const rel = toRelPath(workspace, absolutePath, opts);
8023
+ const stat11 = await workspace.stat(rel);
8024
+ return { isDirectory: () => stat11.kind === "dir" };
7894
8025
  },
7895
8026
  async readdir(absolutePath) {
7896
- const rel = toRelPath(workspace, absolutePath);
7897
- const entries = await workspace.readdir(rel);
7898
- return entries.map((e) => e.name);
8027
+ const rel = toRelPath(workspace, absolutePath, opts);
8028
+ return (await workspace.readdir(rel)).map((entry) => entry.name);
7899
8029
  }
7900
8030
  };
7901
8031
  }
7902
- function shellEscape(arg) {
7903
- return `'${arg.replace(/'/g, "'\\''")}'`;
7904
- }
7905
8032
 
7906
- // src/server/tools/vercelGrepTool.ts
8033
+ // src/server/tools/remoteWorkspaceGrepTool.ts
7907
8034
  import {
7908
8035
  createGrepToolDefinition,
7909
8036
  formatSize,
@@ -7927,7 +8054,7 @@ function decode(bytes) {
7927
8054
  return decoder.decode(bytes);
7928
8055
  }
7929
8056
 
7930
- // src/server/tools/vercelGrepTool.ts
8057
+ // src/server/tools/remoteWorkspaceGrepTool.ts
7931
8058
  var PI_GREP_TOOL = createGrepToolDefinition("/");
7932
8059
  var DEFAULT_LIMIT2 = 100;
7933
8060
  var GREP_MAX_LINE_LENGTH = 500;
@@ -7943,7 +8070,7 @@ function numberParam(value, fallback, min = 1) {
7943
8070
  function optionalStringParam(value) {
7944
8071
  return typeof value === "string" && value.length > 0 ? value : void 0;
7945
8072
  }
7946
- function buildRgCommand(params) {
8073
+ function buildRgCommand(params, searchPath) {
7947
8074
  const args = ["--json", "--line-number", "--color=never", "--hidden"];
7948
8075
  if (params.ignoreCase === true) args.push("--ignore-case");
7949
8076
  if (params.literal === true) args.push("--fixed-strings");
@@ -7952,11 +8079,10 @@ function buildRgCommand(params) {
7952
8079
  const context = numberParam(params.context, 0, 0);
7953
8080
  if (context > 0) args.push("--context", String(context));
7954
8081
  const pattern = params.pattern;
7955
- const searchPath = optionalStringParam(params.path) ?? ".";
7956
8082
  args.push("--", quoteShell(pattern), quoteShell(searchPath));
7957
8083
  return `rg ${args.join(" ")}`;
7958
8084
  }
7959
- function parseRgJson(stdout, limit) {
8085
+ function parseRgJson(stdout, limit, pathOptions) {
7960
8086
  const outputLines = [];
7961
8087
  let matchCount = 0;
7962
8088
  let matchLimitReached = false;
@@ -7989,7 +8115,7 @@ function parseRgJson(stdout, limit) {
7989
8115
  const { text, wasTruncated } = truncateLine(sanitized);
7990
8116
  if (wasTruncated) linesTruncated = true;
7991
8117
  const separator = isMatch ? ":" : "-";
7992
- outputLines.push(`${filePath}${separator}${lineNumber}${separator} ${text}`);
8118
+ outputLines.push(`${pathOptions?.toRuntimePath?.(filePath) ?? filePath}${separator}${lineNumber}${separator} ${text}`);
7993
8119
  }
7994
8120
  return { outputLines, matchCount, matchLimitReached, linesTruncated };
7995
8121
  }
@@ -8010,6 +8136,29 @@ function syntheticExecTruncation(output) {
8010
8136
  maxBytes: GREP_MAX_OUTPUT_BYTES
8011
8137
  };
8012
8138
  }
8139
+ function isOutsideWorkspace(rel) {
8140
+ return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || rel.startsWith("/");
8141
+ }
8142
+ function isUnderRoot(root, path4) {
8143
+ const rel = relative12(root, path4);
8144
+ return !isOutsideWorkspace(rel);
8145
+ }
8146
+ function normalizeSearchPath(rawPath, workspaceRoot, pathOptions) {
8147
+ const path4 = optionalStringParam(rawPath) ?? ".";
8148
+ if (!workspaceRoot) return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
8149
+ if (!path4.startsWith("/")) {
8150
+ const resolved = resolve10(workspaceRoot, path4);
8151
+ const rel = relative12(workspaceRoot, resolved);
8152
+ if (isOutsideWorkspace(rel)) return { ok: false, message: `path "${path4}" is outside workspace` };
8153
+ return { ok: true, path: path4 };
8154
+ }
8155
+ const roots = [workspaceRoot, ...pathOptions?.rootAliases ?? []];
8156
+ if (!roots.some((root) => isUnderRoot(root, path4))) return { ok: false, message: `path "${path4}" is outside workspace` };
8157
+ return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
8158
+ }
8159
+ function sanitizeErrorMessage(message, pathOptions) {
8160
+ return pathOptions?.sanitizeErrorText?.(message) ?? message;
8161
+ }
8013
8162
  function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
8014
8163
  if (parsed.matchCount === 0) {
8015
8164
  return {
@@ -8047,7 +8196,7 @@ function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
8047
8196
  details: Object.keys(details).length > 0 ? details : void 0
8048
8197
  };
8049
8198
  }
8050
- function vercelGrepTool(sandbox, workspaceRoot) {
8199
+ function remoteWorkspaceGrepTool(sandbox, workspaceRoot, pathOptions) {
8051
8200
  return {
8052
8201
  name: PI_GREP_TOOL.name,
8053
8202
  description: PI_GREP_TOOL.description,
@@ -8061,36 +8210,31 @@ function vercelGrepTool(sandbox, workspaceRoot) {
8061
8210
  if (typeof params.pattern !== "string" || params.pattern.length === 0) {
8062
8211
  return makeError("pattern is required");
8063
8212
  }
8064
- if (workspaceRoot && typeof params.path === "string" && params.path.length > 0) {
8065
- const resolved = resolve10(workspaceRoot, params.path);
8066
- const rel = relative12(workspaceRoot, resolved);
8067
- if (rel.startsWith("..") || rel.startsWith("/")) {
8068
- return makeError(`path "${params.path}" is outside workspace`);
8069
- }
8070
- }
8213
+ const searchPath = normalizeSearchPath(params.path, workspaceRoot, pathOptions);
8214
+ if (!searchPath.ok) return makeError(searchPath.message);
8071
8215
  try {
8072
- const result = await sandbox.exec(buildRgCommand(params), {
8216
+ const result = await sandbox.exec(buildRgCommand(params, searchPath.path), {
8073
8217
  signal: ctx.abortSignal,
8074
8218
  timeoutMs: GREP_TIMEOUT_MS,
8075
8219
  maxOutputBytes: GREP_MAX_OUTPUT_BYTES
8076
8220
  });
8077
8221
  if (result.exitCode !== 0 && result.exitCode !== 1) {
8078
- const stderr = decode(result.stderr).trim();
8222
+ const stderr = sanitizeErrorMessage(decode(result.stderr).trim(), pathOptions);
8079
8223
  const message = stderr || `ripgrep exited with code ${result.exitCode}`;
8080
8224
  return makeError(`grep failed: ${message}`);
8081
8225
  }
8082
8226
  const limit = numberParam(params.limit, DEFAULT_LIMIT2);
8083
- const parsed = parseRgJson(decode(result.stdout), limit);
8227
+ const parsed = parseRgJson(decode(result.stdout), limit, pathOptions);
8084
8228
  return buildSuccessResult(parsed, limit, result.truncated);
8085
8229
  } catch (error) {
8086
- const message = error instanceof Error ? error.message : "unknown error";
8230
+ const message = error instanceof Error ? sanitizeErrorMessage(error.message, pathOptions) : "unknown error";
8087
8231
  return makeError(`grep failed: ${message}`);
8088
8232
  }
8089
8233
  }
8090
8234
  };
8091
8235
  }
8092
8236
 
8093
- // src/server/tools/filesystem/index.ts
8237
+ // src/server/tools/filesystem/remoteWorkspaceTools.ts
8094
8238
  function isTextContent(content) {
8095
8239
  return content.type === "text" && typeof content.text === "string";
8096
8240
  }
@@ -8121,35 +8265,73 @@ function adaptPiTool(piTool) {
8121
8265
  }
8122
8266
  };
8123
8267
  }
8268
+ function buildRemoteWorkspaceFilesystemAgentTools(bundle, pathOptions) {
8269
+ const cwd = bundle.workspace.root;
8270
+ return [
8271
+ adaptPiTool(createReadToolDefinition(cwd, { operations: remoteWorkspaceReadOps(bundle.workspace, pathOptions) })),
8272
+ adaptPiTool(createWriteToolDefinition(cwd, { operations: remoteWorkspaceWriteOps(bundle.workspace, pathOptions) })),
8273
+ adaptPiTool(createEditToolDefinition(cwd, { operations: remoteWorkspaceEditOps(bundle.workspace, pathOptions) })),
8274
+ adaptPiTool(createFindToolDefinition(cwd, { operations: remoteWorkspaceFindOps(bundle.sandbox, bundle.workspace, pathOptions) })),
8275
+ { ...remoteWorkspaceGrepTool(bundle.sandbox, cwd, pathOptions), readinessRequirements: ["workspace-fs"] },
8276
+ adaptPiTool(createLsToolDefinition(cwd, { operations: remoteWorkspaceLsOps(bundle.workspace, pathOptions) }))
8277
+ ];
8278
+ }
8279
+
8280
+ // src/server/tools/filesystem/index.ts
8281
+ function isTextContent2(content) {
8282
+ return content.type === "text" && typeof content.text === "string";
8283
+ }
8284
+ function adaptPiTool2(piTool) {
8285
+ return {
8286
+ name: piTool.name,
8287
+ readinessRequirements: ["workspace-fs"],
8288
+ description: piTool.description,
8289
+ promptSnippet: piTool.promptSnippet,
8290
+ parameters: piTool.parameters,
8291
+ async execute(params, ctx) {
8292
+ const result = await piTool.execute(
8293
+ ctx.toolCallId,
8294
+ params,
8295
+ ctx.abortSignal,
8296
+ ctx.onUpdate ? (update) => {
8297
+ const text = update.content?.filter(isTextContent2).map((c) => c.text).join("");
8298
+ if (text) ctx.onUpdate?.(text);
8299
+ } : void 0,
8300
+ {}
8301
+ );
8302
+ const textContent = (result.content ?? []).filter(isTextContent2).map((c) => ({ type: "text", text: c.text }));
8303
+ return {
8304
+ content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
8305
+ isError: false,
8306
+ details: result.details
8307
+ };
8308
+ }
8309
+ };
8310
+ }
8311
+ function defaultFilesystemStrategyForBundle(bundle) {
8312
+ return bundle.sandbox.placement === "remote" ? { kind: "remote-workspace" } : { kind: "host" };
8313
+ }
8124
8314
  function buildFilesystemAgentTools(bundle) {
8125
8315
  const cwd = bundle.workspace.root;
8126
- if (bundle.sandbox.provider === "vercel-sandbox" || bundle.sandbox.provider === "remote-worker") {
8127
- return [
8128
- adaptPiTool(createReadToolDefinition(cwd, { operations: vercelReadOps(bundle.workspace) })),
8129
- adaptPiTool(createWriteToolDefinition(cwd, { operations: vercelWriteOps(bundle.workspace) })),
8130
- adaptPiTool(createEditToolDefinition(cwd, { operations: vercelEditOps(bundle.workspace) })),
8131
- adaptPiTool(createFindToolDefinition(cwd, { operations: vercelFindOps(bundle.sandbox, bundle.workspace) })),
8132
- { ...vercelGrepTool(bundle.sandbox, cwd), readinessRequirements: ["workspace-fs"] },
8133
- adaptPiTool(createLsToolDefinition(cwd, { operations: vercelLsOps(bundle.workspace) }))
8134
- ];
8316
+ const strategy = bundle.filesystem ?? defaultFilesystemStrategyForBundle(bundle);
8317
+ if (strategy.kind === "remote-workspace") {
8318
+ return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions);
8135
8319
  }
8136
8320
  const storageRoot = getRuntimeBundleStorageRoot(bundle);
8137
8321
  const ops = boundFs(storageRoot, { runtimeRoot: cwd });
8138
8322
  return [
8139
- adaptPiTool(createReadToolDefinition(cwd, { operations: ops.read })),
8140
- adaptPiTool(createWriteToolDefinition(cwd, { operations: ops.write })),
8141
- adaptPiTool(createEditToolDefinition(cwd, { operations: ops.edit })),
8142
- adaptPiTool(createFindToolDefinition(cwd, { operations: ops.find })),
8143
- adaptPiTool(createGrepToolDefinition2(cwd, { operations: ops.grep })),
8144
- adaptPiTool(createLsToolDefinition(cwd, { operations: ops.ls }))
8323
+ adaptPiTool2(createReadToolDefinition2(cwd, { operations: ops.read })),
8324
+ adaptPiTool2(createWriteToolDefinition2(cwd, { operations: ops.write })),
8325
+ adaptPiTool2(createEditToolDefinition2(cwd, { operations: ops.edit })),
8326
+ adaptPiTool2(createFindToolDefinition2(cwd, { operations: ops.find })),
8327
+ adaptPiTool2(createGrepToolDefinition2(cwd, { operations: ops.grep })),
8328
+ adaptPiTool2(createLsToolDefinition2(cwd, { operations: ops.ls }))
8145
8329
  ];
8146
8330
  }
8147
8331
 
8148
8332
  // src/server/tools/harness/index.ts
8149
- import {
8150
- createBashToolDefinition,
8151
- createLocalBashOperations
8152
- } from "@mariozechner/pi-coding-agent";
8333
+ import { unlink as unlink4 } from "fs/promises";
8334
+ import { createBashToolDefinition } from "@mariozechner/pi-coding-agent";
8153
8335
 
8154
8336
  // src/server/catalog/toolReadiness.ts
8155
8337
  var WORKSPACE_PREPARING_MESSAGE = "Workspace is still preparing. Try again in a moment.";
@@ -8227,11 +8409,13 @@ function wrapToolForReadiness(tool, checkReadiness) {
8227
8409
  };
8228
8410
  }
8229
8411
 
8230
- // src/server/tools/harness/index.ts
8231
- function shellEscape2(s) {
8232
- return `'${s.replace(/'/g, "'\\''")}'`;
8233
- }
8234
- function mergeRuntimeEnv(runtime, commandEnv) {
8412
+ // src/server/tools/harness/bashToolOptions.ts
8413
+ import {
8414
+ createLocalBashOperations
8415
+ } from "@mariozechner/pi-coding-agent";
8416
+
8417
+ // src/server/runtime/env.ts
8418
+ function mergeRuntimeProvisioningEnv(runtime, commandEnv) {
8235
8419
  const current = runtime?.getCurrent?.() ?? runtime;
8236
8420
  if (!current?.env && !current?.pathEntries?.length) return commandEnv;
8237
8421
  const merged = {
@@ -8244,7 +8428,38 @@ function mergeRuntimeEnv(runtime, commandEnv) {
8244
8428
  if (pathParts.length > 0) merged.PATH = pathParts.join(":");
8245
8429
  return merged;
8246
8430
  }
8247
- function bwrapSpawnHook(workspaceRoot, runtime) {
8431
+
8432
+ // src/server/tools/operations/remoteSandbox.ts
8433
+ var REMOTE_SAFE_DEFAULT_PATH = "/usr/local/bin:/usr/bin:/bin";
8434
+ function mergeRemoteBashRuntimeEnv(runtime, executionRuntimeEnv, defaultPath) {
8435
+ const { PATH: executionPath, ...executionEnv } = executionRuntimeEnv ?? {};
8436
+ return mergeRuntimeProvisioningEnv(runtime, {
8437
+ ...executionEnv,
8438
+ PATH: executionPath ? `${executionPath}:${defaultPath}` : defaultPath
8439
+ });
8440
+ }
8441
+ function remoteSandboxBashOps(sandbox, opts = {}) {
8442
+ return {
8443
+ exec(command, cwd, { onData, signal, timeout, env }) {
8444
+ const effectiveEnv = opts.mergeEnv ? opts.mergeEnv(env) : opts.runtime || opts.executionRuntimeEnv ? mergeRemoteBashRuntimeEnv(opts.runtime, opts.executionRuntimeEnv, opts.defaultPath ?? REMOTE_SAFE_DEFAULT_PATH) : env;
8445
+ const filteredEnv2 = effectiveEnv ? Object.fromEntries(Object.entries(effectiveEnv).filter((e) => e[1] != null)) : void 0;
8446
+ return sandbox.exec(command, {
8447
+ cwd,
8448
+ env: filteredEnv2,
8449
+ signal,
8450
+ timeoutMs: timeout ? timeout * 1e3 : void 0,
8451
+ onStdout: (chunk) => onData(Buffer.from(chunk)),
8452
+ onStderr: (chunk) => onData(Buffer.from(chunk))
8453
+ }).then((result) => ({ exitCode: result.exitCode }));
8454
+ }
8455
+ };
8456
+ }
8457
+
8458
+ // src/server/tools/harness/bashToolOptions.ts
8459
+ function shellEscape2(s) {
8460
+ return `'${s.replace(/'/g, "'\\''")}'`;
8461
+ }
8462
+ function bwrapSpawnHook(workspaceRoot, runtime, sandboxRoot = "/workspace") {
8248
8463
  const args = buildBwrapArgs(workspaceRoot);
8249
8464
  const bwrapPrefix = ["bwrap", ...args].map(shellEscape2).join(" ");
8250
8465
  return (context) => ({
@@ -8252,50 +8467,94 @@ function bwrapSpawnHook(workspaceRoot, runtime) {
8252
8467
  command: `${bwrapPrefix} bash -lc ${shellEscape2(context.command)}`,
8253
8468
  env: withWorkspacePythonEnv({
8254
8469
  workspaceRoot,
8255
- env: mergeRuntimeEnv(runtime, context.env),
8256
- sandboxRoot: "/workspace"
8470
+ env: mergeRuntimeProvisioningEnv(runtime, context.env),
8471
+ sandboxRoot
8257
8472
  })
8258
8473
  });
8259
8474
  }
8260
- function directSpawnHook(workspaceRoot, runtime) {
8475
+ function directSpawnHook(workspaceRoot, runtime, preserveHostHome = true) {
8261
8476
  return (context) => ({
8262
8477
  ...context,
8263
8478
  env: withWorkspacePythonEnv({
8264
8479
  workspaceRoot,
8265
- env: mergeRuntimeEnv(runtime, context.env),
8266
- preserveHostHome: true
8480
+ env: mergeRuntimeProvisioningEnv(runtime, context.env),
8481
+ preserveHostHome
8267
8482
  })
8268
8483
  });
8269
8484
  }
8270
- var VERCEL_SAFE_DEFAULT_PATH = "/vercel/runtimes/node24/bin:/vercel/runtimes/node22/bin:/usr/local/bin:/usr/bin:/bin";
8271
- function bashOptionsForMode(bundle, runtime) {
8272
- switch (bundle.sandbox.provider) {
8273
- case "vercel-sandbox":
8274
- case "remote-worker":
8275
- return {
8276
- operations: vercelBashOps(bundle.sandbox, {
8277
- // The pi bash tool's env may include the host process env. Never
8278
- // forward host secrets into a remote sandbox; provide only the
8279
- // provisioned runtime env plus a conservative remote PATH tail.
8280
- mergeEnv: () => mergeRuntimeEnv(runtime, { PATH: VERCEL_SAFE_DEFAULT_PATH })
8281
- })
8282
- };
8283
- case "bwrap": {
8284
- const storageRoot = getRuntimeBundleStorageRoot(bundle);
8285
- return {
8286
- operations: createLocalBashOperations(),
8287
- spawnHook: bwrapSpawnHook(storageRoot, runtime)
8288
- };
8289
- }
8290
- default: {
8291
- const storageRoot = getRuntimeBundleStorageRoot(bundle);
8292
- return {
8293
- operations: createLocalBashOperations(),
8294
- spawnHook: directSpawnHook(storageRoot, runtime)
8295
- };
8485
+ function localBashOperationsWithRuntimeEnv(bundle) {
8486
+ const local = createLocalBashOperations();
8487
+ return {
8488
+ async exec(command, cwd, options) {
8489
+ const runtimeEnv = await bundle.getRuntimeEnv?.();
8490
+ return local.exec(command, cwd, {
8491
+ ...options,
8492
+ env: { ...options.env ?? {}, ...runtimeEnv ?? {} }
8493
+ });
8296
8494
  }
8495
+ };
8496
+ }
8497
+ function hostBashToolOptions(bundle, runtime, strategy) {
8498
+ const storageRoot = getRuntimeBundleStorageRoot(bundle);
8499
+ return {
8500
+ operations: localBashOperationsWithRuntimeEnv(bundle),
8501
+ spawnHook: directSpawnHook(storageRoot, runtime, strategy.preserveHostHome ?? true)
8502
+ };
8503
+ }
8504
+ function localSandboxBashToolOptions(bundle, runtime, strategy) {
8505
+ const storageRoot = getRuntimeBundleStorageRoot(bundle);
8506
+ return {
8507
+ // localBashOperationsWithRuntimeEnv() injects bundle.getRuntimeEnv()
8508
+ // into the outer shell env before the spawned sandbox shell command runs,
8509
+ // so bridge runtime env reaches local sandboxed commands without relying
8510
+ // on provisioning PATH/env alone.
8511
+ operations: localBashOperationsWithRuntimeEnv(bundle),
8512
+ spawnHook: bwrapSpawnHook(storageRoot, runtime, strategy.sandboxRoot)
8513
+ };
8514
+ }
8515
+ function remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy) {
8516
+ return {
8517
+ operations: remoteSandboxBashOps(bundle.sandbox, {
8518
+ defaultPath: strategy.defaultPath,
8519
+ runtime,
8520
+ executionRuntimeEnv
8521
+ })
8522
+ };
8523
+ }
8524
+ function defaultBashStrategyForBundle(bundle) {
8525
+ return bundle.sandbox.placement === "remote" ? { kind: "remote" } : { kind: "host", preserveHostHome: true };
8526
+ }
8527
+ function createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv) {
8528
+ const strategy = bundle.bash ?? defaultBashStrategyForBundle(bundle);
8529
+ switch (strategy.kind) {
8530
+ case "host":
8531
+ return hostBashToolOptions(bundle, runtime, strategy);
8532
+ case "local-sandbox":
8533
+ return localSandboxBashToolOptions(bundle, runtime, strategy);
8534
+ case "remote":
8535
+ return remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy);
8297
8536
  }
8298
8537
  }
8538
+
8539
+ // src/server/tools/harness/index.ts
8540
+ function bashOptionsForBundle(bundle, runtime, executionRuntimeEnv) {
8541
+ return createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv);
8542
+ }
8543
+ function runtimeSecretValues(env) {
8544
+ return Object.entries(env ?? {}).filter(([key, value]) => key !== "PATH" && /TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY/i.test(key) && value.length > 0).map(([, value]) => value);
8545
+ }
8546
+ function redactSecretsInString(text, secrets) {
8547
+ return secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), text);
8548
+ }
8549
+ function redactSecrets(value, secrets) {
8550
+ if (secrets.length === 0) return value;
8551
+ if (typeof value === "string") return redactSecretsInString(value, secrets);
8552
+ if (Array.isArray(value)) return value.map((item) => redactSecrets(item, secrets));
8553
+ if (!value || typeof value !== "object") return value;
8554
+ return Object.fromEntries(
8555
+ Object.entries(value).map(([key, child]) => [key, redactSecrets(child, secrets)])
8556
+ );
8557
+ }
8299
8558
  function isRuntimeReady(readiness) {
8300
8559
  return readiness === void 0 || readiness === true || typeof readiness === "object" && readiness !== null && readiness.ready === true;
8301
8560
  }
@@ -8313,23 +8572,38 @@ function runtimeRequirementForFailure(text) {
8313
8572
  if (/(?:^|\n)(?:[^\n:]+:\s*)?(?:line \d+:\s*)?[A-Za-z0-9_.-]+: command not found/i.test(text)) return "runtime-dependencies";
8314
8573
  return void 0;
8315
8574
  }
8316
- function adaptPiTool2(piTool, runtime) {
8575
+ function runtimeNotReadyFromState(requirement, readiness) {
8576
+ return runtimeNotReadyToolResult(
8577
+ requirement,
8578
+ readiness && typeof readiness === "object" && readiness.ready === false ? readiness : { ready: false, state: "preparing", retryable: true }
8579
+ );
8580
+ }
8581
+ function adaptPiTool3(bundle, runtime) {
8582
+ const template = createBashToolDefinition(bundle.workspace.root, bashOptionsForBundle(bundle, runtime));
8317
8583
  return {
8318
- name: piTool.name,
8319
- description: piTool.description,
8320
- promptSnippet: piTool.promptSnippet,
8321
- parameters: piTool.parameters,
8584
+ name: template.name,
8585
+ description: template.description,
8586
+ promptSnippet: template.promptSnippet,
8587
+ parameters: template.parameters,
8322
8588
  readinessRequirements: ["sandbox-exec"],
8323
8589
  async execute(params, ctx) {
8324
8590
  const command = typeof params.command === "string" ? params.command : "";
8325
8591
  const readiness = runtime?.getReadiness?.();
8326
8592
  const commandRuntimeRequirement = command ? runtimeRequirementForCommand(command) : void 0;
8327
8593
  if (commandRuntimeRequirement && !isRuntimeReady(readiness)) {
8328
- return runtimeNotReadyToolResult(
8329
- commandRuntimeRequirement,
8330
- readiness && typeof readiness === "object" && readiness.ready === false ? readiness : { ready: false, state: "preparing", retryable: true }
8331
- );
8594
+ return runtimeNotReadyFromState(commandRuntimeRequirement, readiness);
8332
8595
  }
8596
+ const runtimeEnv = await bundle.getRuntimeEnv?.();
8597
+ const secrets = runtimeSecretValues(runtimeEnv);
8598
+ const executionBundle = runtimeEnv === void 0 ? bundle : {
8599
+ ...bundle,
8600
+ getRuntimeEnv: async () => runtimeEnv
8601
+ };
8602
+ const piTool = createBashToolDefinition(
8603
+ bundle.workspace.root,
8604
+ bashOptionsForBundle(executionBundle, runtime, runtimeEnv)
8605
+ );
8606
+ let emittedRedactionNotice = false;
8333
8607
  let result;
8334
8608
  try {
8335
8609
  result = await piTool.execute(
@@ -8338,7 +8612,14 @@ function adaptPiTool2(piTool, runtime) {
8338
8612
  ctx.abortSignal,
8339
8613
  ctx.onUpdate ? (update) => {
8340
8614
  const text2 = update.content.filter((c) => c.type === "text").map((c) => c.text).join("");
8341
- ctx.onUpdate(text2);
8615
+ if (secrets.length === 0) {
8616
+ ctx.onUpdate(text2);
8617
+ return;
8618
+ }
8619
+ if (!emittedRedactionNotice) {
8620
+ emittedRedactionNotice = true;
8621
+ ctx.onUpdate("[streaming output redacted while runtime secrets are in scope]");
8622
+ }
8342
8623
  } : void 0,
8343
8624
  {}
8344
8625
  );
@@ -8347,27 +8628,29 @@ function adaptPiTool2(piTool, runtime) {
8347
8628
  const latestReadiness2 = runtime?.getReadiness?.();
8348
8629
  const failureRuntimeRequirement2 = runtimeRequirementForFailure(message);
8349
8630
  if (command && failureRuntimeRequirement2 && !isRuntimeReady(latestReadiness2)) {
8350
- return runtimeNotReadyToolResult(
8351
- failureRuntimeRequirement2,
8352
- latestReadiness2 && typeof latestReadiness2 === "object" && latestReadiness2.ready === false ? latestReadiness2 : { ready: false, state: "preparing", retryable: true }
8353
- );
8631
+ return runtimeNotReadyFromState(failureRuntimeRequirement2, latestReadiness2);
8354
8632
  }
8355
- throw error;
8633
+ return {
8634
+ content: [{ type: "text", text: redactSecretsInString(message, secrets) }],
8635
+ isError: true,
8636
+ details: {}
8637
+ };
8356
8638
  }
8357
- const textContent = (result.content ?? []).filter((c) => c.type === "text").map((c) => ({ type: "text", text: c.text }));
8639
+ if (secrets.length > 0 && result.details && typeof result.details === "object" && "fullOutputPath" in result.details && typeof result.details.fullOutputPath === "string") {
8640
+ await unlink4(result.details.fullOutputPath).catch(() => void 0);
8641
+ delete result.details.fullOutputPath;
8642
+ }
8643
+ const textContent = (result.content ?? []).filter((c) => c.type === "text").map((c) => ({ type: "text", text: redactSecretsInString(c.text, secrets) }));
8358
8644
  const text = textContent.map((part) => part.text).join("\n");
8359
8645
  const latestReadiness = runtime?.getReadiness?.();
8360
8646
  const failureRuntimeRequirement = runtimeRequirementForFailure(text);
8361
8647
  if (command && failureRuntimeRequirement && !isRuntimeReady(latestReadiness)) {
8362
- return runtimeNotReadyToolResult(
8363
- failureRuntimeRequirement,
8364
- latestReadiness && typeof latestReadiness === "object" && latestReadiness.ready === false ? latestReadiness : { ready: false, state: "preparing", retryable: true }
8365
- );
8648
+ return runtimeNotReadyFromState(failureRuntimeRequirement, latestReadiness);
8366
8649
  }
8367
8650
  return {
8368
8651
  content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
8369
8652
  isError: Boolean(result.isError),
8370
- details: result.details
8653
+ details: redactSecrets(result.details, secrets)
8371
8654
  };
8372
8655
  }
8373
8656
  };
@@ -8427,7 +8710,7 @@ function createExecuteIsolatedCodeTool(sandbox) {
8427
8710
  }
8428
8711
  function buildHarnessAgentTools(bundle, runtime) {
8429
8712
  const tools = [
8430
- adaptPiTool2(createBashToolDefinition(bundle.workspace.root, bashOptionsForMode(bundle, runtime)), runtime)
8713
+ adaptPiTool3(bundle, runtime)
8431
8714
  ];
8432
8715
  if (bundle.sandbox.capabilities.includes("isolated-code")) {
8433
8716
  tools.push(createExecuteIsolatedCodeTool(bundle.sandbox));
@@ -9727,7 +10010,7 @@ function gitRoutes(app, opts, done) {
9727
10010
  done();
9728
10011
  }
9729
10012
 
9730
- // src/server/sandbox/vercel-sandbox/readyStatus.ts
10013
+ // src/server/runtime/readyStatus.ts
9731
10014
  function defaultCapabilities(sandboxReady, harnessReady) {
9732
10015
  return {
9733
10016
  chat: { state: harnessReady ? "ready" : "preparing" },
@@ -9829,6 +10112,21 @@ var ReadyStatusTracker = class {
9829
10112
  }
9830
10113
  };
9831
10114
 
10115
+ // src/server/runtime/modeReadiness.ts
10116
+ function createRuntimeReadyStatusTracker(modeAdapter, opts) {
10117
+ const readiness = modeAdapter.readiness;
10118
+ const tracker = new ReadyStatusTracker({
10119
+ sandboxReady: readiness?.initialSandboxReady ?? true,
10120
+ harnessReady: opts.harnessReady,
10121
+ capabilities: {
10122
+ ...readiness?.initialWorkspaceReadiness ? { workspace: readiness.initialWorkspaceReadiness } : {},
10123
+ ...opts.capabilities ?? {}
10124
+ }
10125
+ });
10126
+ readiness?.onTrackerCreated?.(tracker);
10127
+ return tracker;
10128
+ }
10129
+
9832
10130
  // src/server/pi-chat/piChatHistory.ts
9833
10131
  function isRecord(value) {
9834
10132
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -11879,11 +12177,24 @@ async function createAgentApp(opts = {}) {
11879
12177
  const app = Fastify({ logger: opts.logger ?? true, bodyLimit: 16 * 1024 * 1024 });
11880
12178
  const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
11881
12179
  const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode);
11882
- const runtimeBundle = await modeAdapter.create({
12180
+ let runtimeBundle = await modeAdapter.create({
11883
12181
  workspaceRoot,
11884
12182
  sessionId,
11885
12183
  templatePath
11886
12184
  });
12185
+ if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
12186
+ runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
12187
+ workspaceId: sessionId,
12188
+ workspaceRoot,
12189
+ runtimeMode: resolvedMode,
12190
+ runtimeBundle
12191
+ }, opts.runtimeEnvContributions, opts.telemetry);
12192
+ }
12193
+ await opts.runtimeProvisioner?.({
12194
+ workspaceRoot,
12195
+ runtimeMode: resolvedMode,
12196
+ runtimeBundle
12197
+ });
11887
12198
  const pluginTools = [];
11888
12199
  const externalPluginsEnabled = opts.externalPlugins !== false;
11889
12200
  if (externalPluginsEnabled && modeAdapter.workspaceFsCapability === "strong") {
@@ -11940,13 +12251,9 @@ async function createAgentApp(opts = {}) {
11940
12251
  });
11941
12252
  harnessRef = harness;
11942
12253
  const sessionChangesTracker = new InMemorySessionChangesTracker();
11943
- const readyTracker = new ReadyStatusTracker({
11944
- sandboxReady: resolvedMode !== "vercel-sandbox",
12254
+ const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
11945
12255
  harnessReady: true
11946
12256
  });
11947
- if (resolvedMode === "vercel-sandbox") {
11948
- queueMicrotask(() => readyTracker.markSandboxReady());
11949
- }
11950
12257
  app.addHook(
11951
12258
  "onRequest",
11952
12259
  createAuthMiddleware({
@@ -11963,10 +12270,7 @@ async function createAgentApp(opts = {}) {
11963
12270
  await app.register(treeRoutes, { workspace: runtimeBundle.workspace });
11964
12271
  await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
11965
12272
  await app.register(gitRoutes, {
11966
- getWorkspaceRoot: () => {
11967
- if (runtimeBundle.sandbox.provider === "remote-worker") return void 0;
11968
- return getRuntimeBundleStorageRoot(runtimeBundle);
11969
- }
12273
+ getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle)
11970
12274
  });
11971
12275
  const piChatService = new HarnessPiChatService({
11972
12276
  harness,
@@ -12148,8 +12452,8 @@ function buildUploadAgentTools(bundle) {
12148
12452
  const contentType = contentTypeFromExt(filePath);
12149
12453
  const ext = extForUpload2(filePath, contentType);
12150
12454
  const base = basenameForUpload2(filePath);
12151
- const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
12152
- const destPath = `${dir}/${base}-${unique}.${ext}`;
12455
+ const unique2 = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
12456
+ const destPath = `${dir}/${base}-${unique2}.${ext}`;
12153
12457
  await workspace.mkdir(dir, { recursive: true });
12154
12458
  if (workspace.writeBinaryFile) {
12155
12459
  await workspace.writeBinaryFile(destPath, bytes);
@@ -12179,7 +12483,6 @@ function buildUploadAgentTools(bundle) {
12179
12483
  var DEFAULT_VERSION2 = "0.1.0-dev";
12180
12484
  var DEFAULT_WORKSPACE_ID3 = "default";
12181
12485
  var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
12182
- var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
12183
12486
  function pluginNameFromPath(path4) {
12184
12487
  const fileName = basename3(path4);
12185
12488
  if (fileName.endsWith(".mjs")) return fileName.slice(0, -4);
@@ -12198,15 +12501,6 @@ function getAvailableModelProviders() {
12198
12501
  new Set(availableModels.map((model) => model.provider))
12199
12502
  ).sort((a, b) => a.localeCompare(b));
12200
12503
  }
12201
- function selectRuntimeModeAdapter(mode, sandboxHandleStore) {
12202
- if (mode === "vercel-sandbox" && sandboxHandleStore) {
12203
- return createVercelSandboxModeAdapter({
12204
- store: sandboxHandleStore,
12205
- orphanGuardMaxIdleMs: null
12206
- });
12207
- }
12208
- return resolveMode(mode);
12209
- }
12210
12504
  function getRequestWorkspaceId(request) {
12211
12505
  return request.workspaceContext?.workspaceId ?? DEFAULT_WORKSPACE_ID3;
12212
12506
  }
@@ -12226,27 +12520,11 @@ function isWorkspaceAgnosticAgentRequest(request, options) {
12226
12520
  if (pathname === "/api/v1/ready-status") return !options?.readyStatusWorkspaceScoped;
12227
12521
  return pathname === "/health" || pathname === "/ready" || pathname === "/api/v1/agent/models";
12228
12522
  }
12229
- function extractHttpStatus2(error) {
12230
- const statusCode = error?.statusCode;
12231
- if (typeof statusCode === "number") return statusCode;
12232
- const status = error?.status;
12233
- if (typeof status === "number") return status;
12234
- const responseStatus = error?.response?.status;
12235
- return typeof responseStatus === "number" ? responseStatus : null;
12236
- }
12237
12523
  function normalizeSessionNamespace(value) {
12238
12524
  if (typeof value !== "string") return void 0;
12239
12525
  const trimmed = value.trim();
12240
12526
  return trimmed.length > 0 ? trimmed : void 0;
12241
12527
  }
12242
- function isExpiredSandboxRuntimeError(error) {
12243
- const code = error?.code;
12244
- if (code === ErrorCode.enum.SANDBOX_EXPIRED) return true;
12245
- const status = extractHttpStatus2(error);
12246
- if (status === 404 || status === 410) return true;
12247
- const message = error instanceof Error ? error.message : String(error);
12248
- return /status code (404|410) is not ok/i.test(message);
12249
- }
12250
12528
  function createHttpError(code, message, details = {}) {
12251
12529
  const error = new Error(message);
12252
12530
  error.code = code;
@@ -12301,7 +12579,7 @@ var registerAgentRoutes = async (app, opts) => {
12301
12579
  const sessionId = opts.sessionId ?? DEFAULT_WORKSPACE_ID3;
12302
12580
  const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
12303
12581
  const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
12304
- const modeAdapter = opts.runtimeModeAdapter ?? selectRuntimeModeAdapter(resolvedMode, opts.sandboxHandleStore);
12582
+ const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode, { sandboxHandleStore: opts.sandboxHandleStore });
12305
12583
  app.addHook("onClose", async () => {
12306
12584
  await modeAdapter.dispose?.();
12307
12585
  });
@@ -12373,9 +12651,7 @@ var registerAgentRoutes = async (app, opts) => {
12373
12651
  requestId: request?.id,
12374
12652
  telemetry: opts.telemetry
12375
12653
  };
12376
- const runtimeLayout = getBoringAgentRuntimePaths(
12377
- resolvedMode === "vercel-sandbox" ? VERCEL_SANDBOX_WORKSPACE_ROOT : scope.root
12378
- );
12654
+ const runtimeLayout = getBoringAgentRuntimePaths(modeAdapter.getRuntimeLayoutRoot?.(modeCtx) ?? scope.root);
12379
12655
  return await opts.provisionRuntime({
12380
12656
  workspaceId,
12381
12657
  workspaceRoot: scope.root,
@@ -12403,19 +12679,22 @@ var registerAgentRoutes = async (app, opts) => {
12403
12679
  retryable: true
12404
12680
  } : { state: "ready" };
12405
12681
  let provisioningGeneration = 0;
12406
- const runtimeBundle = await modeAdapter.create(modeCtx);
12407
- const readyTracker = new ReadyStatusTracker({
12408
- sandboxReady: resolvedMode !== "vercel-sandbox",
12682
+ let runtimeBundle = await modeAdapter.create(modeCtx);
12683
+ if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
12684
+ runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
12685
+ workspaceId,
12686
+ workspaceRoot: root,
12687
+ runtimeMode: resolvedMode,
12688
+ runtimeBundle
12689
+ }, opts.runtimeEnvContributions, opts.telemetry);
12690
+ }
12691
+ const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
12409
12692
  harnessReady: false,
12410
12693
  capabilities: {
12411
12694
  chat: { state: "preparing" },
12412
- workspace: { state: resolvedMode !== "vercel-sandbox" ? "ready" : "preparing" },
12413
12695
  runtimeDependencies
12414
12696
  }
12415
12697
  });
12416
- if (resolvedMode === "vercel-sandbox") {
12417
- queueMicrotask(() => readyTracker.markSandboxReady());
12418
- }
12419
12698
  let binding;
12420
12699
  const updateRuntimeDependencies = (next) => {
12421
12700
  runtimeDependencies = next;
@@ -12633,7 +12912,7 @@ var registerAgentRoutes = async (app, opts) => {
12633
12912
  }
12634
12913
  async function recreateRuntimeBinding(workspaceId, scope) {
12635
12914
  runtimeBindings.delete(scope.key);
12636
- evictSandboxHandleCacheForWorkspace(workspaceId);
12915
+ modeAdapter.evictCachedRuntime?.({ workspaceId });
12637
12916
  const created = createRuntimeBindingEntry(workspaceId, scope);
12638
12917
  runtimeBindings.set(scope.key, created);
12639
12918
  evictRuntimeBindings();
@@ -12647,23 +12926,23 @@ var registerAgentRoutes = async (app, opts) => {
12647
12926
  }
12648
12927
  }
12649
12928
  async function ensureRuntimeBindingReady(workspaceId, scope, binding) {
12650
- if (resolvedMode !== "vercel-sandbox") return binding;
12929
+ const healthCheck = modeAdapter.cachedBindingHealthCheck;
12930
+ if (!healthCheck) return binding;
12651
12931
  const now = Date.now();
12652
- if (binding.lastHealthCheckMs !== void 0 && now - binding.lastHealthCheckMs < VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS) {
12932
+ const intervalMs = healthCheck.intervalMs ?? 15e3;
12933
+ if (binding.lastHealthCheckMs !== void 0 && now - binding.lastHealthCheckMs < intervalMs) {
12653
12934
  return binding;
12654
12935
  }
12655
- try {
12656
- await binding.runtimeBundle.workspace.stat(".");
12936
+ const result = await healthCheck.check({ runtimeBundle: binding.runtimeBundle, workspaceId });
12937
+ if (result.state === "ok") {
12657
12938
  binding.lastHealthCheckMs = now;
12658
12939
  return binding;
12659
- } catch (error) {
12660
- if (!isExpiredSandboxRuntimeError(error)) throw error;
12661
- app.log.warn({
12662
- err: error,
12663
- workspaceId
12664
- }, "[sandbox] cached runtime expired; recreating from persisted handle");
12665
- return await recreateRuntimeBinding(workspaceId, scope);
12666
12940
  }
12941
+ app.log.warn({
12942
+ err: result.error,
12943
+ workspaceId
12944
+ }, result.message ?? "[runtime] cached runtime invalid; recreating");
12945
+ return await recreateRuntimeBinding(workspaceId, scope);
12667
12946
  }
12668
12947
  const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
12669
12948
  const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
@@ -12761,11 +13040,7 @@ var registerAgentRoutes = async (app, opts) => {
12761
13040
  getFileSearch: async (request) => (await getBindingForRequest(request)).runtimeBundle.fileSearch
12762
13041
  });
12763
13042
  await app.register(gitRoutes, {
12764
- getWorkspaceRoot: async (request) => {
12765
- const bundle = (await getBindingForRequest(request)).runtimeBundle;
12766
- if (bundle.sandbox.provider === "remote-worker") return void 0;
12767
- return getRuntimeBundleStorageRoot(bundle);
12768
- }
13043
+ getWorkspaceRoot: async (request) => getOptionalRuntimeBundleStorageRoot((await getBindingForRequest(request)).runtimeBundle)
12769
13044
  });
12770
13045
  await app.register(piChatRoutes, {
12771
13046
  getService: async (request) => {