@hachej/boring-agent 0.1.54 → 0.1.56

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";
@@ -6053,24 +6175,26 @@ import {
6053
6175
  parseSessionEntries,
6054
6176
  CURRENT_SESSION_VERSION
6055
6177
  } from "@mariozechner/pi-coding-agent";
6056
- function sessionBaseDir() {
6178
+ function sessionBaseDir(explicitRoot) {
6179
+ const explicit = explicitRoot?.trim();
6180
+ if (explicit) return resolve7(explicit);
6057
6181
  const configured = getEnv(SESSION_ROOT_ENV)?.trim();
6058
6182
  return configured ? resolve7(configured) : join10(homedir3(), ".pi", "agent", "sessions");
6059
6183
  }
6060
- function defaultSessionDir(cwd) {
6184
+ function defaultSessionDir(cwd, explicitRoot) {
6061
6185
  const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
6062
- return join10(sessionBaseDir(), safePath);
6186
+ return join10(sessionBaseDir(explicitRoot), safePath);
6063
6187
  }
6064
6188
  var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
6065
6189
  var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
6066
6190
  var SESSION_ROOT_ENV = "BORING_AGENT_SESSION_ROOT";
6067
6191
  var SUMMARY_PREFIX_BYTES = 64 * 1024;
6068
- function sessionDirForNamespace(namespace) {
6192
+ function sessionDirForNamespace(namespace, explicitRoot) {
6069
6193
  const safeNamespace = namespace.trim();
6070
6194
  if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
6071
6195
  throw new Error("session namespace must contain only letters, numbers, underscores, and dashes");
6072
6196
  }
6073
- return join10(sessionBaseDir(), safeNamespace);
6197
+ return join10(sessionBaseDir(explicitRoot), safeNamespace);
6074
6198
  }
6075
6199
  function normalizeListOptions(options) {
6076
6200
  return {
@@ -6090,7 +6214,7 @@ var PiSessionStore = class {
6090
6214
  this.sessionDir = options;
6091
6215
  return;
6092
6216
  }
6093
- this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace) : defaultSessionDir(options?.storageCwd ?? cwd));
6217
+ this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace, options.sessionRoot) : defaultSessionDir(options?.storageCwd ?? cwd, options?.sessionRoot));
6094
6218
  }
6095
6219
  getSessionDir() {
6096
6220
  return this.sessionDir;
@@ -6273,10 +6397,34 @@ var PiSessionStore = class {
6273
6397
  }
6274
6398
  }
6275
6399
  async loadPiSessionFile(_ctx, sessionId) {
6400
+ if (!SAFE_ID.test(sessionId)) return null;
6276
6401
  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);
6402
+ const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
6403
+ let filepath = direct;
6404
+ let content;
6405
+ try {
6406
+ content = await readFile9(direct, "utf-8");
6407
+ } catch {
6408
+ const files = await readdir7(this.sessionDir).catch(() => []);
6409
+ const match = files.find(
6410
+ (f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
6411
+ );
6412
+ if (!match) return null;
6413
+ filepath = join10(this.sessionDir, match);
6414
+ content = await readFile9(filepath, "utf-8");
6415
+ }
6416
+ const entries = safeParseEntries(content);
6417
+ const linkedPiFile = extractPiSessionFilePath(entries);
6418
+ if (linkedPiFile) return linkedPiFile;
6419
+ if (!isTimestampNamedPiSessionFile(filepath, sessionId)) return null;
6420
+ const existingWrapper = await this.findWrapperReferencingNativeSession(filepath);
6421
+ if (existingWrapper) {
6422
+ const wrapperSessionId = await this.readSessionFileId(existingWrapper);
6423
+ if (wrapperSessionId !== sessionId) return null;
6424
+ const wrapperEntries = parseJsonlPrefixEntries(await readJsonlPrefix(existingWrapper));
6425
+ return extractPiSessionFilePath(wrapperEntries);
6426
+ }
6427
+ return await this.ensureWrapperForNativeSession(sessionId, filepath);
6280
6428
  } catch {
6281
6429
  return null;
6282
6430
  }
@@ -7065,6 +7213,7 @@ function createPiCodingAgentHarness(opts) {
7065
7213
  const pi = withPiHarnessDefaults(opts.pi);
7066
7214
  const sessionStore = new PiSessionStore(opts.runtimeCwd ?? opts.cwd, {
7067
7215
  sessionNamespace: opts.sessionNamespace,
7216
+ sessionRoot: opts.sessionRoot,
7068
7217
  sessionDir: opts.sessionDir,
7069
7218
  storageCwd: opts.cwd
7070
7219
  });
@@ -7182,8 +7331,8 @@ function createPiCodingAgentHarness(opts) {
7182
7331
  const { session: piSession } = await createAgentSession({
7183
7332
  cwd: runtimeCwd,
7184
7333
  // 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.
7334
+ // adapted tool catalog active. Do NOT pass an explicit empty tool-name
7335
+ // allowlist: in the current Pi SDK that disables custom tools too.
7187
7336
  noTools: "builtin",
7188
7337
  customTools: adaptToolsForPi(opts.tools, input.sessionId, opts.telemetry),
7189
7338
  model,
@@ -7450,12 +7599,12 @@ async function loadPlugins(options) {
7450
7599
 
7451
7600
  // src/server/tools/filesystem/index.ts
7452
7601
  import {
7453
- createEditToolDefinition,
7454
- createFindToolDefinition,
7602
+ createEditToolDefinition as createEditToolDefinition2,
7603
+ createFindToolDefinition as createFindToolDefinition2,
7455
7604
  createGrepToolDefinition as createGrepToolDefinition2,
7456
- createLsToolDefinition,
7457
- createReadToolDefinition,
7458
- createWriteToolDefinition
7605
+ createLsToolDefinition as createLsToolDefinition2,
7606
+ createReadToolDefinition as createReadToolDefinition2,
7607
+ createWriteToolDefinition as createWriteToolDefinition2
7459
7608
  } from "@mariozechner/pi-coding-agent";
7460
7609
 
7461
7610
  // src/server/tools/operations/bound.ts
@@ -7596,7 +7745,7 @@ function boundFs(workspaceRoot, opts = {}) {
7596
7745
  }
7597
7746
  return absolutePath;
7598
7747
  };
7599
- const toRuntimePath2 = (absolutePath) => {
7748
+ const toRuntimePath = (absolutePath) => {
7600
7749
  if (!shouldMapRuntimeRoot || !runtimeRoot) return absolutePath;
7601
7750
  const rel = relative10(workspaceRoot, absolutePath);
7602
7751
  if (rel === "") return runtimeRoot;
@@ -7662,7 +7811,7 @@ function boundFs(workspaceRoot, opts = {}) {
7662
7811
  await assertWithinWorkspace(workspaceRoot, storageCwd);
7663
7812
  const matches = [];
7664
7813
  await walkMatches(storageCwd, storageCwd, pattern, options.ignore, options.limit, matches);
7665
- return matches.map(toRuntimePath2);
7814
+ return matches.map(toRuntimePath);
7666
7815
  }
7667
7816
  };
7668
7817
  const grep = {
@@ -7702,20 +7851,28 @@ function boundFs(workspaceRoot, opts = {}) {
7702
7851
  return { read, write, edit, find, grep, ls };
7703
7852
  }
7704
7853
 
7705
- // src/server/tools/operations/vercel.ts
7854
+ // src/server/tools/filesystem/remoteWorkspaceTools.ts
7855
+ import {
7856
+ createEditToolDefinition,
7857
+ createFindToolDefinition,
7858
+ createLsToolDefinition,
7859
+ createReadToolDefinition,
7860
+ createWriteToolDefinition
7861
+ } from "@mariozechner/pi-coding-agent";
7862
+
7863
+ // src/server/tools/operations/remoteWorkspace.ts
7706
7864
  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));
7865
+ function unique(values) {
7866
+ return Array.from(new Set(values));
7867
+ }
7868
+ function rootsFor(workspace, opts = {}) {
7869
+ return unique([workspace.root, ...opts.rootAliases ?? []]);
7713
7870
  }
7714
7871
  function isOutsideWorkspaceRel(rel) {
7715
7872
  return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute8(rel);
7716
7873
  }
7717
- function toRelPath(workspace, absolutePath) {
7718
- for (const root of rootAliases(workspace)) {
7874
+ function toRelPath(workspace, absolutePath, opts = {}) {
7875
+ for (const root of rootsFor(workspace, opts)) {
7719
7876
  const rel = relative11(root, absolutePath);
7720
7877
  if (!isOutsideWorkspaceRel(rel)) return rel;
7721
7878
  }
@@ -7732,113 +7889,92 @@ function toRelPath(workspace, absolutePath) {
7732
7889
  `path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${workspace.root}`
7733
7890
  );
7734
7891
  }
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
- };
7892
+ function shellEscape(s) {
7893
+ return `'${s.replace(/'/g, "'\\''")}'`;
7894
+ }
7895
+ function findPredicate(pattern) {
7896
+ const isPathShaped = pattern.includes("/") || pattern.includes("**");
7897
+ if (!isPathShaped) return `-name ${shellEscape(pattern)}`;
7898
+ let translated = pattern.replaceAll("**", "*").replace(/^\/+/, "");
7899
+ if (!translated.startsWith("*")) translated = `*${translated}`;
7900
+ return `-path ${shellEscape(translated)}`;
7901
+ }
7902
+ function findIgnoreArgs(cwd, ignore) {
7903
+ return ignore.map((pattern) => `! -path ${shellEscape(`${cwd}/${pattern.replace(/^\/+/, "")}/*`)}`).join(" ");
7750
7904
  }
7751
- function vercelReadOps(workspace) {
7905
+ function fallbackFindCommand(pattern, cwd, options) {
7906
+ return [
7907
+ "find",
7908
+ shellEscape(cwd),
7909
+ "-maxdepth 20",
7910
+ "-type f",
7911
+ findIgnoreArgs(cwd, options.ignore),
7912
+ findPredicate(pattern),
7913
+ `| head -n ${Math.max(1, Math.trunc(options.limit))}`
7914
+ ].filter(Boolean).join(" ");
7915
+ }
7916
+ function isFdMissing(result) {
7917
+ if (result.exitCode !== 127) return false;
7918
+ const stderr = Buffer.from(result.stderr).toString("utf-8");
7919
+ return /\bfd: (?:not found|command not found)\b/i.test(stderr);
7920
+ }
7921
+ function remotePath(value, opts) {
7922
+ return opts.toRemotePath?.(value) ?? value;
7923
+ }
7924
+ function runtimePath(value, opts) {
7925
+ return opts.toRuntimePath?.(value) ?? value;
7926
+ }
7927
+ function sanitizeErrorText(value, opts) {
7928
+ return opts.sanitizeErrorText?.(value) ?? value;
7929
+ }
7930
+ function remoteWorkspaceReadOps(workspace, opts = {}) {
7752
7931
  return {
7753
7932
  async readFile(absolutePath) {
7754
- const rel = toRelPath(workspace, absolutePath);
7933
+ const rel = toRelPath(workspace, absolutePath, opts);
7755
7934
  const content = await workspace.readFile(rel);
7756
7935
  return Buffer.from(content, "utf-8");
7757
7936
  },
7758
7937
  async access(absolutePath) {
7759
- const rel = toRelPath(workspace, absolutePath);
7938
+ const rel = toRelPath(workspace, absolutePath, opts);
7760
7939
  await workspace.stat(rel);
7761
7940
  }
7762
7941
  };
7763
7942
  }
7764
- function vercelWriteOps(workspace) {
7943
+ function remoteWorkspaceWriteOps(workspace, opts = {}) {
7765
7944
  return {
7766
7945
  async writeFile(absolutePath, content) {
7767
- const rel = toRelPath(workspace, absolutePath);
7946
+ const rel = toRelPath(workspace, absolutePath, opts);
7768
7947
  await workspace.writeFile(rel, content);
7769
7948
  },
7770
7949
  async mkdir(dir) {
7771
- const rel = toRelPath(workspace, dir);
7950
+ const rel = toRelPath(workspace, dir, opts);
7772
7951
  await workspace.mkdir(rel, { recursive: true });
7773
7952
  }
7774
7953
  };
7775
7954
  }
7776
- function vercelEditOps(workspace) {
7955
+ function remoteWorkspaceEditOps(workspace, opts = {}) {
7777
7956
  return {
7778
7957
  async readFile(absolutePath) {
7779
- const rel = toRelPath(workspace, absolutePath);
7958
+ const rel = toRelPath(workspace, absolutePath, opts);
7780
7959
  const content = await workspace.readFile(rel);
7781
7960
  return Buffer.from(content, "utf-8");
7782
7961
  },
7783
7962
  async writeFile(absolutePath, content) {
7784
- const rel = toRelPath(workspace, absolutePath);
7963
+ const rel = toRelPath(workspace, absolutePath, opts);
7785
7964
  await workspace.writeFile(rel, content);
7786
7965
  },
7787
7966
  async access(absolutePath) {
7788
- const rel = toRelPath(workspace, absolutePath);
7967
+ const rel = toRelPath(workspace, absolutePath, opts);
7789
7968
  await workspace.stat(rel);
7790
7969
  }
7791
7970
  };
7792
7971
  }
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) {
7972
+ function remoteWorkspaceFindOps(sandbox, workspace, opts = {}) {
7837
7973
  return {
7838
7974
  async exists(absolutePath) {
7839
7975
  if (workspace) {
7840
7976
  try {
7841
- const rel = toRelPath(workspace, absolutePath);
7977
+ const rel = toRelPath(workspace, absolutePath, opts);
7842
7978
  await workspace.stat(rel);
7843
7979
  return true;
7844
7980
  } catch {
@@ -7851,11 +7987,9 @@ function vercelFindOps(sandbox, workspace) {
7851
7987
  return result.exitCode === 0;
7852
7988
  },
7853
7989
  async glob(pattern, cwd, options) {
7854
- const remoteCwd = toRemotePath(cwd);
7990
+ const remoteCwd = remotePath(cwd, opts);
7855
7991
  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
- }
7992
+ for (const ig of options.ignore) args.push("--exclude", ig);
7859
7993
  args.push(pattern, remoteCwd);
7860
7994
  let result = await sandbox.exec(args.map(shellEscape).join(" "), {
7861
7995
  timeoutMs: 3e4,
@@ -7868,19 +8002,19 @@ function vercelFindOps(sandbox, workspace) {
7868
8002
  });
7869
8003
  }
7870
8004
  if (result.exitCode !== 0 && result.exitCode !== 1) {
7871
- const stderr = sanitizeRuntimeText(Buffer.from(result.stderr).toString("utf-8").trim());
8005
+ const stderr = sanitizeErrorText(Buffer.from(result.stderr).toString("utf-8").trim(), opts);
7872
8006
  throw new Error(`file search failed (exit ${result.exitCode}): ${stderr}`);
7873
8007
  }
7874
8008
  const stdout = Buffer.from(result.stdout).toString("utf-8");
7875
- return stdout.split("\n").filter(Boolean).map(toRuntimePath);
8009
+ return stdout.split("\n").filter(Boolean).map((value) => runtimePath(value, opts));
7876
8010
  }
7877
8011
  };
7878
8012
  }
7879
- function vercelLsOps(workspace) {
8013
+ function remoteWorkspaceLsOps(workspace, opts = {}) {
7880
8014
  return {
7881
8015
  async exists(absolutePath) {
7882
8016
  try {
7883
- const rel = toRelPath(workspace, absolutePath);
8017
+ const rel = toRelPath(workspace, absolutePath, opts);
7884
8018
  await workspace.stat(rel);
7885
8019
  return true;
7886
8020
  } catch {
@@ -7888,22 +8022,18 @@ function vercelLsOps(workspace) {
7888
8022
  }
7889
8023
  },
7890
8024
  async stat(absolutePath) {
7891
- const rel = toRelPath(workspace, absolutePath);
7892
- const s = await workspace.stat(rel);
7893
- return { isDirectory: () => s.kind === "dir" };
8025
+ const rel = toRelPath(workspace, absolutePath, opts);
8026
+ const stat11 = await workspace.stat(rel);
8027
+ return { isDirectory: () => stat11.kind === "dir" };
7894
8028
  },
7895
8029
  async readdir(absolutePath) {
7896
- const rel = toRelPath(workspace, absolutePath);
7897
- const entries = await workspace.readdir(rel);
7898
- return entries.map((e) => e.name);
8030
+ const rel = toRelPath(workspace, absolutePath, opts);
8031
+ return (await workspace.readdir(rel)).map((entry) => entry.name);
7899
8032
  }
7900
8033
  };
7901
8034
  }
7902
- function shellEscape(arg) {
7903
- return `'${arg.replace(/'/g, "'\\''")}'`;
7904
- }
7905
8035
 
7906
- // src/server/tools/vercelGrepTool.ts
8036
+ // src/server/tools/remoteWorkspaceGrepTool.ts
7907
8037
  import {
7908
8038
  createGrepToolDefinition,
7909
8039
  formatSize,
@@ -7927,7 +8057,7 @@ function decode(bytes) {
7927
8057
  return decoder.decode(bytes);
7928
8058
  }
7929
8059
 
7930
- // src/server/tools/vercelGrepTool.ts
8060
+ // src/server/tools/remoteWorkspaceGrepTool.ts
7931
8061
  var PI_GREP_TOOL = createGrepToolDefinition("/");
7932
8062
  var DEFAULT_LIMIT2 = 100;
7933
8063
  var GREP_MAX_LINE_LENGTH = 500;
@@ -7943,7 +8073,7 @@ function numberParam(value, fallback, min = 1) {
7943
8073
  function optionalStringParam(value) {
7944
8074
  return typeof value === "string" && value.length > 0 ? value : void 0;
7945
8075
  }
7946
- function buildRgCommand(params) {
8076
+ function buildRgCommand(params, searchPath) {
7947
8077
  const args = ["--json", "--line-number", "--color=never", "--hidden"];
7948
8078
  if (params.ignoreCase === true) args.push("--ignore-case");
7949
8079
  if (params.literal === true) args.push("--fixed-strings");
@@ -7952,11 +8082,10 @@ function buildRgCommand(params) {
7952
8082
  const context = numberParam(params.context, 0, 0);
7953
8083
  if (context > 0) args.push("--context", String(context));
7954
8084
  const pattern = params.pattern;
7955
- const searchPath = optionalStringParam(params.path) ?? ".";
7956
8085
  args.push("--", quoteShell(pattern), quoteShell(searchPath));
7957
8086
  return `rg ${args.join(" ")}`;
7958
8087
  }
7959
- function parseRgJson(stdout, limit) {
8088
+ function parseRgJson(stdout, limit, pathOptions) {
7960
8089
  const outputLines = [];
7961
8090
  let matchCount = 0;
7962
8091
  let matchLimitReached = false;
@@ -7989,7 +8118,7 @@ function parseRgJson(stdout, limit) {
7989
8118
  const { text, wasTruncated } = truncateLine(sanitized);
7990
8119
  if (wasTruncated) linesTruncated = true;
7991
8120
  const separator = isMatch ? ":" : "-";
7992
- outputLines.push(`${filePath}${separator}${lineNumber}${separator} ${text}`);
8121
+ outputLines.push(`${pathOptions?.toRuntimePath?.(filePath) ?? filePath}${separator}${lineNumber}${separator} ${text}`);
7993
8122
  }
7994
8123
  return { outputLines, matchCount, matchLimitReached, linesTruncated };
7995
8124
  }
@@ -8010,6 +8139,29 @@ function syntheticExecTruncation(output) {
8010
8139
  maxBytes: GREP_MAX_OUTPUT_BYTES
8011
8140
  };
8012
8141
  }
8142
+ function isOutsideWorkspace(rel) {
8143
+ return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || rel.startsWith("/");
8144
+ }
8145
+ function isUnderRoot(root, path4) {
8146
+ const rel = relative12(root, path4);
8147
+ return !isOutsideWorkspace(rel);
8148
+ }
8149
+ function normalizeSearchPath(rawPath, workspaceRoot, pathOptions) {
8150
+ const path4 = optionalStringParam(rawPath) ?? ".";
8151
+ if (!workspaceRoot) return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
8152
+ if (!path4.startsWith("/")) {
8153
+ const resolved = resolve10(workspaceRoot, path4);
8154
+ const rel = relative12(workspaceRoot, resolved);
8155
+ if (isOutsideWorkspace(rel)) return { ok: false, message: `path "${path4}" is outside workspace` };
8156
+ return { ok: true, path: path4 };
8157
+ }
8158
+ const roots = [workspaceRoot, ...pathOptions?.rootAliases ?? []];
8159
+ if (!roots.some((root) => isUnderRoot(root, path4))) return { ok: false, message: `path "${path4}" is outside workspace` };
8160
+ return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
8161
+ }
8162
+ function sanitizeErrorMessage(message, pathOptions) {
8163
+ return pathOptions?.sanitizeErrorText?.(message) ?? message;
8164
+ }
8013
8165
  function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
8014
8166
  if (parsed.matchCount === 0) {
8015
8167
  return {
@@ -8047,7 +8199,7 @@ function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
8047
8199
  details: Object.keys(details).length > 0 ? details : void 0
8048
8200
  };
8049
8201
  }
8050
- function vercelGrepTool(sandbox, workspaceRoot) {
8202
+ function remoteWorkspaceGrepTool(sandbox, workspaceRoot, pathOptions) {
8051
8203
  return {
8052
8204
  name: PI_GREP_TOOL.name,
8053
8205
  description: PI_GREP_TOOL.description,
@@ -8061,36 +8213,31 @@ function vercelGrepTool(sandbox, workspaceRoot) {
8061
8213
  if (typeof params.pattern !== "string" || params.pattern.length === 0) {
8062
8214
  return makeError("pattern is required");
8063
8215
  }
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
- }
8216
+ const searchPath = normalizeSearchPath(params.path, workspaceRoot, pathOptions);
8217
+ if (!searchPath.ok) return makeError(searchPath.message);
8071
8218
  try {
8072
- const result = await sandbox.exec(buildRgCommand(params), {
8219
+ const result = await sandbox.exec(buildRgCommand(params, searchPath.path), {
8073
8220
  signal: ctx.abortSignal,
8074
8221
  timeoutMs: GREP_TIMEOUT_MS,
8075
8222
  maxOutputBytes: GREP_MAX_OUTPUT_BYTES
8076
8223
  });
8077
8224
  if (result.exitCode !== 0 && result.exitCode !== 1) {
8078
- const stderr = decode(result.stderr).trim();
8225
+ const stderr = sanitizeErrorMessage(decode(result.stderr).trim(), pathOptions);
8079
8226
  const message = stderr || `ripgrep exited with code ${result.exitCode}`;
8080
8227
  return makeError(`grep failed: ${message}`);
8081
8228
  }
8082
8229
  const limit = numberParam(params.limit, DEFAULT_LIMIT2);
8083
- const parsed = parseRgJson(decode(result.stdout), limit);
8230
+ const parsed = parseRgJson(decode(result.stdout), limit, pathOptions);
8084
8231
  return buildSuccessResult(parsed, limit, result.truncated);
8085
8232
  } catch (error) {
8086
- const message = error instanceof Error ? error.message : "unknown error";
8233
+ const message = error instanceof Error ? sanitizeErrorMessage(error.message, pathOptions) : "unknown error";
8087
8234
  return makeError(`grep failed: ${message}`);
8088
8235
  }
8089
8236
  }
8090
8237
  };
8091
8238
  }
8092
8239
 
8093
- // src/server/tools/filesystem/index.ts
8240
+ // src/server/tools/filesystem/remoteWorkspaceTools.ts
8094
8241
  function isTextContent(content) {
8095
8242
  return content.type === "text" && typeof content.text === "string";
8096
8243
  }
@@ -8121,35 +8268,73 @@ function adaptPiTool(piTool) {
8121
8268
  }
8122
8269
  };
8123
8270
  }
8271
+ function buildRemoteWorkspaceFilesystemAgentTools(bundle, pathOptions) {
8272
+ const cwd = bundle.workspace.root;
8273
+ return [
8274
+ adaptPiTool(createReadToolDefinition(cwd, { operations: remoteWorkspaceReadOps(bundle.workspace, pathOptions) })),
8275
+ adaptPiTool(createWriteToolDefinition(cwd, { operations: remoteWorkspaceWriteOps(bundle.workspace, pathOptions) })),
8276
+ adaptPiTool(createEditToolDefinition(cwd, { operations: remoteWorkspaceEditOps(bundle.workspace, pathOptions) })),
8277
+ adaptPiTool(createFindToolDefinition(cwd, { operations: remoteWorkspaceFindOps(bundle.sandbox, bundle.workspace, pathOptions) })),
8278
+ { ...remoteWorkspaceGrepTool(bundle.sandbox, cwd, pathOptions), readinessRequirements: ["workspace-fs"] },
8279
+ adaptPiTool(createLsToolDefinition(cwd, { operations: remoteWorkspaceLsOps(bundle.workspace, pathOptions) }))
8280
+ ];
8281
+ }
8282
+
8283
+ // src/server/tools/filesystem/index.ts
8284
+ function isTextContent2(content) {
8285
+ return content.type === "text" && typeof content.text === "string";
8286
+ }
8287
+ function adaptPiTool2(piTool) {
8288
+ return {
8289
+ name: piTool.name,
8290
+ readinessRequirements: ["workspace-fs"],
8291
+ description: piTool.description,
8292
+ promptSnippet: piTool.promptSnippet,
8293
+ parameters: piTool.parameters,
8294
+ async execute(params, ctx) {
8295
+ const result = await piTool.execute(
8296
+ ctx.toolCallId,
8297
+ params,
8298
+ ctx.abortSignal,
8299
+ ctx.onUpdate ? (update) => {
8300
+ const text = update.content?.filter(isTextContent2).map((c) => c.text).join("");
8301
+ if (text) ctx.onUpdate?.(text);
8302
+ } : void 0,
8303
+ {}
8304
+ );
8305
+ const textContent = (result.content ?? []).filter(isTextContent2).map((c) => ({ type: "text", text: c.text }));
8306
+ return {
8307
+ content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
8308
+ isError: false,
8309
+ details: result.details
8310
+ };
8311
+ }
8312
+ };
8313
+ }
8314
+ function defaultFilesystemStrategyForBundle(bundle) {
8315
+ return bundle.sandbox.placement === "remote" ? { kind: "remote-workspace" } : { kind: "host" };
8316
+ }
8124
8317
  function buildFilesystemAgentTools(bundle) {
8125
8318
  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
- ];
8319
+ const strategy = bundle.filesystem ?? defaultFilesystemStrategyForBundle(bundle);
8320
+ if (strategy.kind === "remote-workspace") {
8321
+ return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions);
8135
8322
  }
8136
8323
  const storageRoot = getRuntimeBundleStorageRoot(bundle);
8137
8324
  const ops = boundFs(storageRoot, { runtimeRoot: cwd });
8138
8325
  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 }))
8326
+ adaptPiTool2(createReadToolDefinition2(cwd, { operations: ops.read })),
8327
+ adaptPiTool2(createWriteToolDefinition2(cwd, { operations: ops.write })),
8328
+ adaptPiTool2(createEditToolDefinition2(cwd, { operations: ops.edit })),
8329
+ adaptPiTool2(createFindToolDefinition2(cwd, { operations: ops.find })),
8330
+ adaptPiTool2(createGrepToolDefinition2(cwd, { operations: ops.grep })),
8331
+ adaptPiTool2(createLsToolDefinition2(cwd, { operations: ops.ls }))
8145
8332
  ];
8146
8333
  }
8147
8334
 
8148
8335
  // src/server/tools/harness/index.ts
8149
- import {
8150
- createBashToolDefinition,
8151
- createLocalBashOperations
8152
- } from "@mariozechner/pi-coding-agent";
8336
+ import { unlink as unlink4 } from "fs/promises";
8337
+ import { createBashToolDefinition } from "@mariozechner/pi-coding-agent";
8153
8338
 
8154
8339
  // src/server/catalog/toolReadiness.ts
8155
8340
  var WORKSPACE_PREPARING_MESSAGE = "Workspace is still preparing. Try again in a moment.";
@@ -8227,11 +8412,13 @@ function wrapToolForReadiness(tool, checkReadiness) {
8227
8412
  };
8228
8413
  }
8229
8414
 
8230
- // src/server/tools/harness/index.ts
8231
- function shellEscape2(s) {
8232
- return `'${s.replace(/'/g, "'\\''")}'`;
8233
- }
8234
- function mergeRuntimeEnv(runtime, commandEnv) {
8415
+ // src/server/tools/harness/bashToolOptions.ts
8416
+ import {
8417
+ createLocalBashOperations
8418
+ } from "@mariozechner/pi-coding-agent";
8419
+
8420
+ // src/server/runtime/env.ts
8421
+ function mergeRuntimeProvisioningEnv(runtime, commandEnv) {
8235
8422
  const current = runtime?.getCurrent?.() ?? runtime;
8236
8423
  if (!current?.env && !current?.pathEntries?.length) return commandEnv;
8237
8424
  const merged = {
@@ -8244,7 +8431,38 @@ function mergeRuntimeEnv(runtime, commandEnv) {
8244
8431
  if (pathParts.length > 0) merged.PATH = pathParts.join(":");
8245
8432
  return merged;
8246
8433
  }
8247
- function bwrapSpawnHook(workspaceRoot, runtime) {
8434
+
8435
+ // src/server/tools/operations/remoteSandbox.ts
8436
+ var REMOTE_SAFE_DEFAULT_PATH = "/usr/local/bin:/usr/bin:/bin";
8437
+ function mergeRemoteBashRuntimeEnv(runtime, executionRuntimeEnv, defaultPath) {
8438
+ const { PATH: executionPath, ...executionEnv } = executionRuntimeEnv ?? {};
8439
+ return mergeRuntimeProvisioningEnv(runtime, {
8440
+ ...executionEnv,
8441
+ PATH: executionPath ? `${executionPath}:${defaultPath}` : defaultPath
8442
+ });
8443
+ }
8444
+ function remoteSandboxBashOps(sandbox, opts = {}) {
8445
+ return {
8446
+ exec(command, cwd, { onData, signal, timeout, env }) {
8447
+ const effectiveEnv = opts.mergeEnv ? opts.mergeEnv(env) : opts.runtime || opts.executionRuntimeEnv ? mergeRemoteBashRuntimeEnv(opts.runtime, opts.executionRuntimeEnv, opts.defaultPath ?? REMOTE_SAFE_DEFAULT_PATH) : env;
8448
+ const filteredEnv2 = effectiveEnv ? Object.fromEntries(Object.entries(effectiveEnv).filter((e) => e[1] != null)) : void 0;
8449
+ return sandbox.exec(command, {
8450
+ cwd,
8451
+ env: filteredEnv2,
8452
+ signal,
8453
+ timeoutMs: timeout ? timeout * 1e3 : void 0,
8454
+ onStdout: (chunk) => onData(Buffer.from(chunk)),
8455
+ onStderr: (chunk) => onData(Buffer.from(chunk))
8456
+ }).then((result) => ({ exitCode: result.exitCode }));
8457
+ }
8458
+ };
8459
+ }
8460
+
8461
+ // src/server/tools/harness/bashToolOptions.ts
8462
+ function shellEscape2(s) {
8463
+ return `'${s.replace(/'/g, "'\\''")}'`;
8464
+ }
8465
+ function bwrapSpawnHook(workspaceRoot, runtime, sandboxRoot = "/workspace") {
8248
8466
  const args = buildBwrapArgs(workspaceRoot);
8249
8467
  const bwrapPrefix = ["bwrap", ...args].map(shellEscape2).join(" ");
8250
8468
  return (context) => ({
@@ -8252,50 +8470,94 @@ function bwrapSpawnHook(workspaceRoot, runtime) {
8252
8470
  command: `${bwrapPrefix} bash -lc ${shellEscape2(context.command)}`,
8253
8471
  env: withWorkspacePythonEnv({
8254
8472
  workspaceRoot,
8255
- env: mergeRuntimeEnv(runtime, context.env),
8256
- sandboxRoot: "/workspace"
8473
+ env: mergeRuntimeProvisioningEnv(runtime, context.env),
8474
+ sandboxRoot
8257
8475
  })
8258
8476
  });
8259
8477
  }
8260
- function directSpawnHook(workspaceRoot, runtime) {
8478
+ function directSpawnHook(workspaceRoot, runtime, preserveHostHome = true) {
8261
8479
  return (context) => ({
8262
8480
  ...context,
8263
8481
  env: withWorkspacePythonEnv({
8264
8482
  workspaceRoot,
8265
- env: mergeRuntimeEnv(runtime, context.env),
8266
- preserveHostHome: true
8483
+ env: mergeRuntimeProvisioningEnv(runtime, context.env),
8484
+ preserveHostHome
8267
8485
  })
8268
8486
  });
8269
8487
  }
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
- };
8488
+ function localBashOperationsWithRuntimeEnv(bundle) {
8489
+ const local = createLocalBashOperations();
8490
+ return {
8491
+ async exec(command, cwd, options) {
8492
+ const runtimeEnv = await bundle.getRuntimeEnv?.();
8493
+ return local.exec(command, cwd, {
8494
+ ...options,
8495
+ env: { ...options.env ?? {}, ...runtimeEnv ?? {} }
8496
+ });
8296
8497
  }
8498
+ };
8499
+ }
8500
+ function hostBashToolOptions(bundle, runtime, strategy) {
8501
+ const storageRoot = getRuntimeBundleStorageRoot(bundle);
8502
+ return {
8503
+ operations: localBashOperationsWithRuntimeEnv(bundle),
8504
+ spawnHook: directSpawnHook(storageRoot, runtime, strategy.preserveHostHome ?? true)
8505
+ };
8506
+ }
8507
+ function localSandboxBashToolOptions(bundle, runtime, strategy) {
8508
+ const storageRoot = getRuntimeBundleStorageRoot(bundle);
8509
+ return {
8510
+ // localBashOperationsWithRuntimeEnv() injects bundle.getRuntimeEnv()
8511
+ // into the outer shell env before the spawned sandbox shell command runs,
8512
+ // so bridge runtime env reaches local sandboxed commands without relying
8513
+ // on provisioning PATH/env alone.
8514
+ operations: localBashOperationsWithRuntimeEnv(bundle),
8515
+ spawnHook: bwrapSpawnHook(storageRoot, runtime, strategy.sandboxRoot)
8516
+ };
8517
+ }
8518
+ function remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy) {
8519
+ return {
8520
+ operations: remoteSandboxBashOps(bundle.sandbox, {
8521
+ defaultPath: strategy.defaultPath,
8522
+ runtime,
8523
+ executionRuntimeEnv
8524
+ })
8525
+ };
8526
+ }
8527
+ function defaultBashStrategyForBundle(bundle) {
8528
+ return bundle.sandbox.placement === "remote" ? { kind: "remote" } : { kind: "host", preserveHostHome: true };
8529
+ }
8530
+ function createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv) {
8531
+ const strategy = bundle.bash ?? defaultBashStrategyForBundle(bundle);
8532
+ switch (strategy.kind) {
8533
+ case "host":
8534
+ return hostBashToolOptions(bundle, runtime, strategy);
8535
+ case "local-sandbox":
8536
+ return localSandboxBashToolOptions(bundle, runtime, strategy);
8537
+ case "remote":
8538
+ return remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy);
8297
8539
  }
8298
8540
  }
8541
+
8542
+ // src/server/tools/harness/index.ts
8543
+ function bashOptionsForBundle(bundle, runtime, executionRuntimeEnv) {
8544
+ return createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv);
8545
+ }
8546
+ function runtimeSecretValues(env) {
8547
+ 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);
8548
+ }
8549
+ function redactSecretsInString(text, secrets) {
8550
+ return secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), text);
8551
+ }
8552
+ function redactSecrets(value, secrets) {
8553
+ if (secrets.length === 0) return value;
8554
+ if (typeof value === "string") return redactSecretsInString(value, secrets);
8555
+ if (Array.isArray(value)) return value.map((item) => redactSecrets(item, secrets));
8556
+ if (!value || typeof value !== "object") return value;
8557
+ return Object.fromEntries(
8558
+ Object.entries(value).map(([key, child]) => [key, redactSecrets(child, secrets)])
8559
+ );
8560
+ }
8299
8561
  function isRuntimeReady(readiness) {
8300
8562
  return readiness === void 0 || readiness === true || typeof readiness === "object" && readiness !== null && readiness.ready === true;
8301
8563
  }
@@ -8313,23 +8575,38 @@ function runtimeRequirementForFailure(text) {
8313
8575
  if (/(?:^|\n)(?:[^\n:]+:\s*)?(?:line \d+:\s*)?[A-Za-z0-9_.-]+: command not found/i.test(text)) return "runtime-dependencies";
8314
8576
  return void 0;
8315
8577
  }
8316
- function adaptPiTool2(piTool, runtime) {
8578
+ function runtimeNotReadyFromState(requirement, readiness) {
8579
+ return runtimeNotReadyToolResult(
8580
+ requirement,
8581
+ readiness && typeof readiness === "object" && readiness.ready === false ? readiness : { ready: false, state: "preparing", retryable: true }
8582
+ );
8583
+ }
8584
+ function adaptPiTool3(bundle, runtime) {
8585
+ const template = createBashToolDefinition(bundle.workspace.root, bashOptionsForBundle(bundle, runtime));
8317
8586
  return {
8318
- name: piTool.name,
8319
- description: piTool.description,
8320
- promptSnippet: piTool.promptSnippet,
8321
- parameters: piTool.parameters,
8587
+ name: template.name,
8588
+ description: template.description,
8589
+ promptSnippet: template.promptSnippet,
8590
+ parameters: template.parameters,
8322
8591
  readinessRequirements: ["sandbox-exec"],
8323
8592
  async execute(params, ctx) {
8324
8593
  const command = typeof params.command === "string" ? params.command : "";
8325
8594
  const readiness = runtime?.getReadiness?.();
8326
8595
  const commandRuntimeRequirement = command ? runtimeRequirementForCommand(command) : void 0;
8327
8596
  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
- );
8597
+ return runtimeNotReadyFromState(commandRuntimeRequirement, readiness);
8332
8598
  }
8599
+ const runtimeEnv = await bundle.getRuntimeEnv?.();
8600
+ const secrets = runtimeSecretValues(runtimeEnv);
8601
+ const executionBundle = runtimeEnv === void 0 ? bundle : {
8602
+ ...bundle,
8603
+ getRuntimeEnv: async () => runtimeEnv
8604
+ };
8605
+ const piTool = createBashToolDefinition(
8606
+ bundle.workspace.root,
8607
+ bashOptionsForBundle(executionBundle, runtime, runtimeEnv)
8608
+ );
8609
+ let emittedRedactionNotice = false;
8333
8610
  let result;
8334
8611
  try {
8335
8612
  result = await piTool.execute(
@@ -8338,7 +8615,14 @@ function adaptPiTool2(piTool, runtime) {
8338
8615
  ctx.abortSignal,
8339
8616
  ctx.onUpdate ? (update) => {
8340
8617
  const text2 = update.content.filter((c) => c.type === "text").map((c) => c.text).join("");
8341
- ctx.onUpdate(text2);
8618
+ if (secrets.length === 0) {
8619
+ ctx.onUpdate(text2);
8620
+ return;
8621
+ }
8622
+ if (!emittedRedactionNotice) {
8623
+ emittedRedactionNotice = true;
8624
+ ctx.onUpdate("[streaming output redacted while runtime secrets are in scope]");
8625
+ }
8342
8626
  } : void 0,
8343
8627
  {}
8344
8628
  );
@@ -8347,27 +8631,29 @@ function adaptPiTool2(piTool, runtime) {
8347
8631
  const latestReadiness2 = runtime?.getReadiness?.();
8348
8632
  const failureRuntimeRequirement2 = runtimeRequirementForFailure(message);
8349
8633
  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
- );
8634
+ return runtimeNotReadyFromState(failureRuntimeRequirement2, latestReadiness2);
8354
8635
  }
8355
- throw error;
8636
+ return {
8637
+ content: [{ type: "text", text: redactSecretsInString(message, secrets) }],
8638
+ isError: true,
8639
+ details: {}
8640
+ };
8356
8641
  }
8357
- const textContent = (result.content ?? []).filter((c) => c.type === "text").map((c) => ({ type: "text", text: c.text }));
8642
+ if (secrets.length > 0 && result.details && typeof result.details === "object" && "fullOutputPath" in result.details && typeof result.details.fullOutputPath === "string") {
8643
+ await unlink4(result.details.fullOutputPath).catch(() => void 0);
8644
+ delete result.details.fullOutputPath;
8645
+ }
8646
+ const textContent = (result.content ?? []).filter((c) => c.type === "text").map((c) => ({ type: "text", text: redactSecretsInString(c.text, secrets) }));
8358
8647
  const text = textContent.map((part) => part.text).join("\n");
8359
8648
  const latestReadiness = runtime?.getReadiness?.();
8360
8649
  const failureRuntimeRequirement = runtimeRequirementForFailure(text);
8361
8650
  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
- );
8651
+ return runtimeNotReadyFromState(failureRuntimeRequirement, latestReadiness);
8366
8652
  }
8367
8653
  return {
8368
8654
  content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
8369
8655
  isError: Boolean(result.isError),
8370
- details: result.details
8656
+ details: redactSecrets(result.details, secrets)
8371
8657
  };
8372
8658
  }
8373
8659
  };
@@ -8427,7 +8713,7 @@ function createExecuteIsolatedCodeTool(sandbox) {
8427
8713
  }
8428
8714
  function buildHarnessAgentTools(bundle, runtime) {
8429
8715
  const tools = [
8430
- adaptPiTool2(createBashToolDefinition(bundle.workspace.root, bashOptionsForMode(bundle, runtime)), runtime)
8716
+ adaptPiTool3(bundle, runtime)
8431
8717
  ];
8432
8718
  if (bundle.sandbox.capabilities.includes("isolated-code")) {
8433
8719
  tools.push(createExecuteIsolatedCodeTool(bundle.sandbox));
@@ -9727,7 +10013,7 @@ function gitRoutes(app, opts, done) {
9727
10013
  done();
9728
10014
  }
9729
10015
 
9730
- // src/server/sandbox/vercel-sandbox/readyStatus.ts
10016
+ // src/server/runtime/readyStatus.ts
9731
10017
  function defaultCapabilities(sandboxReady, harnessReady) {
9732
10018
  return {
9733
10019
  chat: { state: harnessReady ? "ready" : "preparing" },
@@ -9829,6 +10115,21 @@ var ReadyStatusTracker = class {
9829
10115
  }
9830
10116
  };
9831
10117
 
10118
+ // src/server/runtime/modeReadiness.ts
10119
+ function createRuntimeReadyStatusTracker(modeAdapter, opts) {
10120
+ const readiness = modeAdapter.readiness;
10121
+ const tracker = new ReadyStatusTracker({
10122
+ sandboxReady: readiness?.initialSandboxReady ?? true,
10123
+ harnessReady: opts.harnessReady,
10124
+ capabilities: {
10125
+ ...readiness?.initialWorkspaceReadiness ? { workspace: readiness.initialWorkspaceReadiness } : {},
10126
+ ...opts.capabilities ?? {}
10127
+ }
10128
+ });
10129
+ readiness?.onTrackerCreated?.(tracker);
10130
+ return tracker;
10131
+ }
10132
+
9832
10133
  // src/server/pi-chat/piChatHistory.ts
9833
10134
  function isRecord(value) {
9834
10135
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -11879,11 +12180,24 @@ async function createAgentApp(opts = {}) {
11879
12180
  const app = Fastify({ logger: opts.logger ?? true, bodyLimit: 16 * 1024 * 1024 });
11880
12181
  const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
11881
12182
  const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode);
11882
- const runtimeBundle = await modeAdapter.create({
12183
+ let runtimeBundle = await modeAdapter.create({
11883
12184
  workspaceRoot,
11884
12185
  sessionId,
11885
12186
  templatePath
11886
12187
  });
12188
+ if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
12189
+ runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
12190
+ workspaceId: sessionId,
12191
+ workspaceRoot,
12192
+ runtimeMode: resolvedMode,
12193
+ runtimeBundle
12194
+ }, opts.runtimeEnvContributions, opts.telemetry);
12195
+ }
12196
+ await opts.runtimeProvisioner?.({
12197
+ workspaceRoot,
12198
+ runtimeMode: resolvedMode,
12199
+ runtimeBundle
12200
+ });
11887
12201
  const pluginTools = [];
11888
12202
  const externalPluginsEnabled = opts.externalPlugins !== false;
11889
12203
  if (externalPluginsEnabled && modeAdapter.workspaceFsCapability === "strong") {
@@ -11940,13 +12254,9 @@ async function createAgentApp(opts = {}) {
11940
12254
  });
11941
12255
  harnessRef = harness;
11942
12256
  const sessionChangesTracker = new InMemorySessionChangesTracker();
11943
- const readyTracker = new ReadyStatusTracker({
11944
- sandboxReady: resolvedMode !== "vercel-sandbox",
12257
+ const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
11945
12258
  harnessReady: true
11946
12259
  });
11947
- if (resolvedMode === "vercel-sandbox") {
11948
- queueMicrotask(() => readyTracker.markSandboxReady());
11949
- }
11950
12260
  app.addHook(
11951
12261
  "onRequest",
11952
12262
  createAuthMiddleware({
@@ -11963,10 +12273,7 @@ async function createAgentApp(opts = {}) {
11963
12273
  await app.register(treeRoutes, { workspace: runtimeBundle.workspace });
11964
12274
  await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
11965
12275
  await app.register(gitRoutes, {
11966
- getWorkspaceRoot: () => {
11967
- if (runtimeBundle.sandbox.provider === "remote-worker") return void 0;
11968
- return getRuntimeBundleStorageRoot(runtimeBundle);
11969
- }
12276
+ getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle)
11970
12277
  });
11971
12278
  const piChatService = new HarnessPiChatService({
11972
12279
  harness,
@@ -12148,8 +12455,8 @@ function buildUploadAgentTools(bundle) {
12148
12455
  const contentType = contentTypeFromExt(filePath);
12149
12456
  const ext = extForUpload2(filePath, contentType);
12150
12457
  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}`;
12458
+ const unique2 = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
12459
+ const destPath = `${dir}/${base}-${unique2}.${ext}`;
12153
12460
  await workspace.mkdir(dir, { recursive: true });
12154
12461
  if (workspace.writeBinaryFile) {
12155
12462
  await workspace.writeBinaryFile(destPath, bytes);
@@ -12179,7 +12486,6 @@ function buildUploadAgentTools(bundle) {
12179
12486
  var DEFAULT_VERSION2 = "0.1.0-dev";
12180
12487
  var DEFAULT_WORKSPACE_ID3 = "default";
12181
12488
  var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
12182
- var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
12183
12489
  function pluginNameFromPath(path4) {
12184
12490
  const fileName = basename3(path4);
12185
12491
  if (fileName.endsWith(".mjs")) return fileName.slice(0, -4);
@@ -12198,15 +12504,6 @@ function getAvailableModelProviders() {
12198
12504
  new Set(availableModels.map((model) => model.provider))
12199
12505
  ).sort((a, b) => a.localeCompare(b));
12200
12506
  }
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
12507
  function getRequestWorkspaceId(request) {
12211
12508
  return request.workspaceContext?.workspaceId ?? DEFAULT_WORKSPACE_ID3;
12212
12509
  }
@@ -12226,27 +12523,11 @@ function isWorkspaceAgnosticAgentRequest(request, options) {
12226
12523
  if (pathname === "/api/v1/ready-status") return !options?.readyStatusWorkspaceScoped;
12227
12524
  return pathname === "/health" || pathname === "/ready" || pathname === "/api/v1/agent/models";
12228
12525
  }
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
12526
  function normalizeSessionNamespace(value) {
12238
12527
  if (typeof value !== "string") return void 0;
12239
12528
  const trimmed = value.trim();
12240
12529
  return trimmed.length > 0 ? trimmed : void 0;
12241
12530
  }
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
12531
  function createHttpError(code, message, details = {}) {
12251
12532
  const error = new Error(message);
12252
12533
  error.code = code;
@@ -12301,7 +12582,7 @@ var registerAgentRoutes = async (app, opts) => {
12301
12582
  const sessionId = opts.sessionId ?? DEFAULT_WORKSPACE_ID3;
12302
12583
  const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
12303
12584
  const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
12304
- const modeAdapter = opts.runtimeModeAdapter ?? selectRuntimeModeAdapter(resolvedMode, opts.sandboxHandleStore);
12585
+ const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode, { sandboxHandleStore: opts.sandboxHandleStore });
12305
12586
  app.addHook("onClose", async () => {
12306
12587
  await modeAdapter.dispose?.();
12307
12588
  });
@@ -12373,9 +12654,7 @@ var registerAgentRoutes = async (app, opts) => {
12373
12654
  requestId: request?.id,
12374
12655
  telemetry: opts.telemetry
12375
12656
  };
12376
- const runtimeLayout = getBoringAgentRuntimePaths(
12377
- resolvedMode === "vercel-sandbox" ? VERCEL_SANDBOX_WORKSPACE_ROOT : scope.root
12378
- );
12657
+ const runtimeLayout = getBoringAgentRuntimePaths(modeAdapter.getRuntimeLayoutRoot?.(modeCtx) ?? scope.root);
12379
12658
  return await opts.provisionRuntime({
12380
12659
  workspaceId,
12381
12660
  workspaceRoot: scope.root,
@@ -12403,19 +12682,22 @@ var registerAgentRoutes = async (app, opts) => {
12403
12682
  retryable: true
12404
12683
  } : { state: "ready" };
12405
12684
  let provisioningGeneration = 0;
12406
- const runtimeBundle = await modeAdapter.create(modeCtx);
12407
- const readyTracker = new ReadyStatusTracker({
12408
- sandboxReady: resolvedMode !== "vercel-sandbox",
12685
+ let runtimeBundle = await modeAdapter.create(modeCtx);
12686
+ if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
12687
+ runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
12688
+ workspaceId,
12689
+ workspaceRoot: root,
12690
+ runtimeMode: resolvedMode,
12691
+ runtimeBundle
12692
+ }, opts.runtimeEnvContributions, opts.telemetry);
12693
+ }
12694
+ const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
12409
12695
  harnessReady: false,
12410
12696
  capabilities: {
12411
12697
  chat: { state: "preparing" },
12412
- workspace: { state: resolvedMode !== "vercel-sandbox" ? "ready" : "preparing" },
12413
12698
  runtimeDependencies
12414
12699
  }
12415
12700
  });
12416
- if (resolvedMode === "vercel-sandbox") {
12417
- queueMicrotask(() => readyTracker.markSandboxReady());
12418
- }
12419
12701
  let binding;
12420
12702
  const updateRuntimeDependencies = (next) => {
12421
12703
  runtimeDependencies = next;
@@ -12555,6 +12837,7 @@ var registerAgentRoutes = async (app, opts) => {
12555
12837
  tools,
12556
12838
  cwd: root,
12557
12839
  sessionNamespace: scope.sessionNamespace,
12840
+ sessionRoot: opts.sessionRoot,
12558
12841
  systemPromptAppend: opts.systemPromptAppend,
12559
12842
  systemPromptDynamic: opts.getSystemPromptDynamic ? () => opts.getSystemPromptDynamic?.({ workspaceId, workspaceRoot: root }) : opts.systemPromptDynamic,
12560
12843
  telemetry: opts.telemetry
@@ -12633,7 +12916,7 @@ var registerAgentRoutes = async (app, opts) => {
12633
12916
  }
12634
12917
  async function recreateRuntimeBinding(workspaceId, scope) {
12635
12918
  runtimeBindings.delete(scope.key);
12636
- evictSandboxHandleCacheForWorkspace(workspaceId);
12919
+ modeAdapter.evictCachedRuntime?.({ workspaceId });
12637
12920
  const created = createRuntimeBindingEntry(workspaceId, scope);
12638
12921
  runtimeBindings.set(scope.key, created);
12639
12922
  evictRuntimeBindings();
@@ -12647,23 +12930,23 @@ var registerAgentRoutes = async (app, opts) => {
12647
12930
  }
12648
12931
  }
12649
12932
  async function ensureRuntimeBindingReady(workspaceId, scope, binding) {
12650
- if (resolvedMode !== "vercel-sandbox") return binding;
12933
+ const healthCheck = modeAdapter.cachedBindingHealthCheck;
12934
+ if (!healthCheck) return binding;
12651
12935
  const now = Date.now();
12652
- if (binding.lastHealthCheckMs !== void 0 && now - binding.lastHealthCheckMs < VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS) {
12936
+ const intervalMs = healthCheck.intervalMs ?? 15e3;
12937
+ if (binding.lastHealthCheckMs !== void 0 && now - binding.lastHealthCheckMs < intervalMs) {
12653
12938
  return binding;
12654
12939
  }
12655
- try {
12656
- await binding.runtimeBundle.workspace.stat(".");
12940
+ const result = await healthCheck.check({ runtimeBundle: binding.runtimeBundle, workspaceId });
12941
+ if (result.state === "ok") {
12657
12942
  binding.lastHealthCheckMs = now;
12658
12943
  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
12944
  }
12945
+ app.log.warn({
12946
+ err: result.error,
12947
+ workspaceId
12948
+ }, result.message ?? "[runtime] cached runtime invalid; recreating");
12949
+ return await recreateRuntimeBinding(workspaceId, scope);
12667
12950
  }
12668
12951
  const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
12669
12952
  const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
@@ -12688,6 +12971,7 @@ var registerAgentRoutes = async (app, opts) => {
12688
12971
  if (cached) return cached;
12689
12972
  const store = new PiSessionStore(scope.root, {
12690
12973
  sessionNamespace: scope.sessionNamespace,
12974
+ sessionRoot: opts.sessionRoot,
12691
12975
  storageCwd: scope.root
12692
12976
  });
12693
12977
  earlySessionStores.set(scope.key, store);
@@ -12761,11 +13045,7 @@ var registerAgentRoutes = async (app, opts) => {
12761
13045
  getFileSearch: async (request) => (await getBindingForRequest(request)).runtimeBundle.fileSearch
12762
13046
  });
12763
13047
  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
- }
13048
+ getWorkspaceRoot: async (request) => getOptionalRuntimeBundleStorageRoot((await getBindingForRequest(request)).runtimeBundle)
12769
13049
  });
12770
13050
  await app.register(piChatRoutes, {
12771
13051
  getService: async (request) => {