@mastra/code-sdk 1.15.0-alpha.8 → 1.15.0

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.
Files changed (143) hide show
  1. package/dist/acp/agent.js +2 -1
  2. package/dist/acp/agent.js.map +1 -1
  3. package/dist/acp/event-mapper.js +0 -2
  4. package/dist/acp/event-mapper.js.map +1 -1
  5. package/dist/acp.js +1 -2
  6. package/dist/agent-connections/registry.js.map +1 -1
  7. package/dist/agent-connections/signal-provider.js.map +1 -1
  8. package/dist/agent-connections/state-processor.js.map +1 -1
  9. package/dist/agent-connections/tools.js +26 -24
  10. package/dist/agent-connections/tools.js.map +1 -1
  11. package/dist/agents/background-completion.d.ts +15 -2
  12. package/dist/agents/background-completion.d.ts.map +1 -1
  13. package/dist/agents/background-completion.js +19 -2
  14. package/dist/agents/background-completion.js.map +1 -1
  15. package/dist/agents/instructions.js +3 -2
  16. package/dist/agents/instructions.js.map +1 -1
  17. package/dist/agents/mastracode-gateway.js +35 -27
  18. package/dist/agents/mastracode-gateway.js.map +1 -1
  19. package/dist/agents/memory.d.ts.map +1 -1
  20. package/dist/agents/memory.js +17 -3
  21. package/dist/agents/memory.js.map +1 -1
  22. package/dist/agents/prompts/agent-instructions.js +1 -1
  23. package/dist/agents/prompts/agent-instructions.js.map +1 -1
  24. package/dist/agents/prompts/index.js +3 -2
  25. package/dist/agents/prompts/index.js.map +1 -1
  26. package/dist/agents/prompts/plan.js +2 -1
  27. package/dist/agents/prompts/plan.js.map +1 -1
  28. package/dist/agents/sandbox-filesystem.d.ts +28 -0
  29. package/dist/agents/sandbox-filesystem.d.ts.map +1 -1
  30. package/dist/agents/sandbox-filesystem.js +81 -2
  31. package/dist/agents/sandbox-filesystem.js.map +1 -1
  32. package/dist/agents/tools.d.ts.map +1 -1
  33. package/dist/agents/tools.js +18 -12
  34. package/dist/agents/tools.js.map +1 -1
  35. package/dist/agents/workspace.js +2 -1
  36. package/dist/agents/workspace.js.map +1 -1
  37. package/dist/auth/account-rotation-processor.js +2 -1
  38. package/dist/auth/account-rotation-processor.js.map +1 -1
  39. package/dist/auth/providers/anthropic.js +2 -2
  40. package/dist/auth/providers/anthropic.js.map +1 -1
  41. package/dist/auth/providers/github-copilot.js +1 -1
  42. package/dist/auth/providers/github-copilot.js.map +1 -1
  43. package/dist/auth/providers/openai-codex.js +3 -2
  44. package/dist/auth/providers/openai-codex.js.map +1 -1
  45. package/dist/auth/providers/xai.js +1 -1
  46. package/dist/auth/providers/xai.js.map +1 -1
  47. package/dist/clipboard/index.js +5 -4
  48. package/dist/clipboard/index.js.map +1 -1
  49. package/dist/evals/context-builder.js +1 -3
  50. package/dist/evals/context-builder.js.map +1 -1
  51. package/dist/evals/scorers/outcome.js +2 -1
  52. package/dist/evals/scorers/outcome.js.map +1 -1
  53. package/dist/factory/remote-controller.js.map +1 -1
  54. package/dist/factory/remote-session.d.ts +1 -1
  55. package/dist/factory/remote-session.d.ts.map +1 -1
  56. package/dist/factory/remote-session.js +15 -13
  57. package/dist/factory/remote-session.js.map +1 -1
  58. package/dist/headless/cli.js +2 -1
  59. package/dist/headless/cli.js.map +1 -1
  60. package/dist/headless/run-mc.js +9 -9
  61. package/dist/headless/run-mc.js.map +1 -1
  62. package/dist/hooks/executor.js +3 -1
  63. package/dist/hooks/executor.js.map +1 -1
  64. package/dist/hooks/manager.js +40 -26
  65. package/dist/hooks/manager.js.map +1 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +38 -21
  68. package/dist/index.js.map +1 -1
  69. package/dist/knowledge-inspector.js +2 -2
  70. package/dist/knowledge-inspector.js.map +1 -1
  71. package/dist/lsp/server.js +4 -2
  72. package/dist/lsp/server.js.map +1 -1
  73. package/dist/mcp/manager.d.ts.map +1 -1
  74. package/dist/mcp/manager.js +14 -2
  75. package/dist/mcp/manager.js.map +1 -1
  76. package/dist/onboarding/settings.js +5 -2
  77. package/dist/onboarding/settings.js.map +1 -1
  78. package/dist/platform/index.js +5 -3
  79. package/dist/platform/index.js.map +1 -1
  80. package/dist/plugins/loader.js +3 -1
  81. package/dist/plugins/loader.js.map +1 -1
  82. package/dist/plugins/manager.js +2 -1
  83. package/dist/plugins/manager.js.map +1 -1
  84. package/dist/plugins/package-link.js.map +1 -1
  85. package/dist/plugins/scaffold.js +4 -2
  86. package/dist/plugins/scaffold.js.map +1 -1
  87. package/dist/plugins/signal-lane.js.map +1 -1
  88. package/dist/process-memory-diagnostics.js +2 -1
  89. package/dist/process-memory-diagnostics.js.map +1 -1
  90. package/dist/providers/amazon-bedrock-gateway.js +2 -1
  91. package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
  92. package/dist/providers/amazon-bedrock.js +2 -2
  93. package/dist/providers/amazon-bedrock.js.map +1 -1
  94. package/dist/providers/claude-max.js +13 -9
  95. package/dist/providers/claude-max.js.map +1 -1
  96. package/dist/providers/github-copilot-gateway.js.map +1 -1
  97. package/dist/providers/github-copilot.js +13 -12
  98. package/dist/providers/github-copilot.js.map +1 -1
  99. package/dist/providers/openai-codex.js +14 -11
  100. package/dist/providers/openai-codex.js.map +1 -1
  101. package/dist/providers/provider-thinking.js.map +1 -1
  102. package/dist/providers/thinking-request.js +1 -1
  103. package/dist/providers/thinking-request.js.map +1 -1
  104. package/dist/schedules/run-script.js +1 -1
  105. package/dist/schedules/run-script.js.map +1 -1
  106. package/dist/schedules/scheduler.js.map +1 -1
  107. package/dist/tools/request-sandbox-access.js +7 -5
  108. package/dist/tools/request-sandbox-access.js.map +1 -1
  109. package/dist/tools/utils.js +3 -1
  110. package/dist/tools/utils.js.map +1 -1
  111. package/dist/tools/web-search.d.ts +13 -11
  112. package/dist/tools/web-search.d.ts.map +1 -1
  113. package/dist/tools/web-search.js +21 -15
  114. package/dist/tools/web-search.js.map +1 -1
  115. package/dist/utils/debug-log.js +2 -2
  116. package/dist/utils/debug-log.js.map +1 -1
  117. package/dist/utils/gateway-sync.js +1 -1
  118. package/dist/utils/gateway-sync.js.map +1 -1
  119. package/dist/utils/local-observability.d.ts +1 -1
  120. package/dist/utils/local-observability.d.ts.map +1 -1
  121. package/dist/utils/local-observability.js +1 -1
  122. package/dist/utils/local-observability.js.map +1 -1
  123. package/dist/utils/prune-cli.js +10 -8
  124. package/dist/utils/prune-cli.js.map +1 -1
  125. package/dist/utils/signals-pubsub.js.map +1 -1
  126. package/dist/utils/slash-command-processor.js +1 -1
  127. package/dist/utils/slash-command-processor.js.map +1 -1
  128. package/dist/utils/stdin-pipe.js +1 -1
  129. package/dist/utils/stdin-pipe.js.map +1 -1
  130. package/dist/utils/storage-factory.d.ts.map +1 -1
  131. package/dist/utils/storage-factory.js +1 -1
  132. package/dist/utils/storage-factory.js.map +1 -1
  133. package/dist/utils/storage-maintenance.js +1 -1
  134. package/dist/utils/storage-maintenance.js.map +1 -1
  135. package/dist/utils/update-check.js +2 -1
  136. package/dist/utils/update-check.js.map +1 -1
  137. package/dist/utils/wake-session.d.ts +1 -1
  138. package/dist/utils/wake-session.d.ts.map +1 -1
  139. package/dist/utils/wake-session.js +8 -1
  140. package/dist/utils/wake-session.js.map +1 -1
  141. package/dist/workflows/register-primitives.js +1 -1
  142. package/dist/workflows/register-primitives.js.map +1 -1
  143. package/package.json +19 -19
@@ -8,6 +8,9 @@ import { DirectoryNotFoundError, FileExistsError, FileNotFoundError, IsDirectory
8
8
  * (e.g. a Railway VM) rather than on the server host. File operations are
9
9
  * implemented by shelling out through the sandbox's `executeCommand`, so the
10
10
  * agent's file tools and command tools share one VM and one view of the repo.
11
+ * When the sandbox exposes its own `filesystem`, file operations use it
12
+ * instead, which takes one round trip rather than several commands; grep and
13
+ * walks still run as shell commands.
11
14
  *
12
15
  * Paths are workspace-relative (`/src/foo.ts`) and resolve under the sandbox
13
16
  * working directory (`basePath`). A traversal guard rejects any path that
@@ -104,9 +107,13 @@ var SandboxFilesystem = class SandboxFilesystem {
104
107
  workdirSource;
105
108
  resolvedBase;
106
109
  resolvingBase;
110
+ verifySymlinks;
111
+ /** `check:absolutePath` keys that passed while the entry existed. */
112
+ verified = /* @__PURE__ */ new Set();
107
113
  constructor(options) {
108
114
  this.sandbox = options.sandbox;
109
115
  this.workdirSource = options.workdir;
116
+ this.verifySymlinks = options.verifySymlinks ?? false;
110
117
  if (typeof options.workdir === "string") this.resolvedBase = options.workdir;
111
118
  this.id = options.id ?? (typeof options.workdir === "string" ? `sandbox-fs:${options.sandbox.id}:${options.workdir}` : `sandbox-fs:${options.sandbox.id}`);
112
119
  }
@@ -182,10 +189,11 @@ var SandboxFilesystem = class SandboxFilesystem {
182
189
  `[ -n "$rp" ] || exit 1`,
183
190
  `printf '%s\\n%s' "$root" "$rp"`
184
191
  ].join("\n"));
185
- if (result.exitCode === EXIT_NOT_FOUND) return;
192
+ if (result.exitCode === EXIT_NOT_FOUND) return false;
186
193
  const [root, real] = result.stdout.split("\n").map((s) => s.trim());
187
194
  if (result.exitCode !== 0 || !root || !real) throw new Error(`Unable to verify path stays within workspace root: ${inputPath}`);
188
195
  if (real !== root && !real.startsWith(`${root}/`)) throw new Error(`Path escapes workspace root (symlink): ${inputPath}`);
196
+ return true;
189
197
  }
190
198
  /**
191
199
  * Guard for write destinations. The lexical guard catches `..`, but a symlink
@@ -195,9 +203,22 @@ var SandboxFilesystem = class SandboxFilesystem {
195
203
  * escape vector (e.g. `link -> /etc` then writing `link/passwd`).
196
204
  */
197
205
  async assertContainedDest(abs, inputPath) {
198
- await this.assertContainedRealpath(abs, inputPath);
206
+ const exists = await this.assertContainedRealpath(abs, inputPath);
199
207
  const parent = posix.dirname(abs);
200
208
  if (parent && parent !== abs) await this.assertContainedRealpath(parent, inputPath);
209
+ return exists;
210
+ }
211
+ /**
212
+ * Before a native filesystem call, run the containment check the shell path
213
+ * runs for that operation, when `verifySymlinks` is on. A pass is remembered
214
+ * only for an entry that exists: a missing path may later appear as a
215
+ * symlink that leads outside the workdir.
216
+ */
217
+ async verifyNative(check, abs, inputPath) {
218
+ if (!this.verifySymlinks) return;
219
+ const key = `${check}:${abs}`;
220
+ if (this.verified.has(key)) return;
221
+ if (check === "dest" ? await this.assertContainedDest(abs, inputPath) : await this.assertContainedRealpath(check === "parent" ? posix.dirname(abs) : abs, inputPath)) this.verified.add(key);
201
222
  }
202
223
  async execOk(script, context) {
203
224
  const result = await this.exec(script);
@@ -206,6 +227,11 @@ var SandboxFilesystem = class SandboxFilesystem {
206
227
  }
207
228
  async readFile(path, options) {
208
229
  const abs = await this.resolveAsync(path);
230
+ const native = this.sandbox.filesystem;
231
+ if (native) {
232
+ await this.verifyNative("target", abs, path);
233
+ return native.readFile(abs, options);
234
+ }
209
235
  await this.assertContainedRealpath(abs, path);
210
236
  const result = await this.exec(`if [ -d ${shellQuote(abs)} ]; then exit ${EXIT_IS_DIRECTORY}; elif [ ! -e ${shellQuote(abs)} ]; then exit ${EXIT_NOT_FOUND}; fi; base64 < ${shellQuote(abs)}`);
211
237
  if (result.exitCode === EXIT_IS_DIRECTORY) throw new IsDirectoryError(path);
@@ -217,6 +243,11 @@ var SandboxFilesystem = class SandboxFilesystem {
217
243
  }
218
244
  async writeFile(path, content, options) {
219
245
  const abs = await this.resolveAsync(path);
246
+ const native = this.sandbox.filesystem;
247
+ if (native) {
248
+ await this.verifyNative("dest", abs, path);
249
+ return native.writeFile(abs, content, options);
250
+ }
220
251
  await this.assertContainedDest(abs, path);
221
252
  const b64 = toBuffer(content).toString("base64");
222
253
  const dir = posix.dirname(abs);
@@ -231,12 +262,22 @@ var SandboxFilesystem = class SandboxFilesystem {
231
262
  }
232
263
  async appendFile(path, content) {
233
264
  const abs = await this.resolveAsync(path);
265
+ const native = this.sandbox.filesystem;
266
+ if (native) {
267
+ await this.verifyNative("dest", abs, path);
268
+ return native.appendFile(abs, content);
269
+ }
234
270
  await this.assertContainedDest(abs, path);
235
271
  const b64 = toBuffer(content).toString("base64");
236
272
  await this.execOk(`mkdir -p ${shellQuote(posix.dirname(abs))} && printf %s ${shellQuote(b64)} | base64 -d >> ${shellQuote(abs)}`, `appendFile ${path}`);
237
273
  }
238
274
  async deleteFile(path, options) {
239
275
  const abs = await this.resolveAsync(path);
276
+ const native = this.sandbox.filesystem;
277
+ if (native) {
278
+ await this.verifyNative("parent", abs, path);
279
+ return native.deleteFile(abs, options);
280
+ }
240
281
  await this.assertContainedRealpath(posix.dirname(abs), path);
241
282
  if (options?.force) {
242
283
  await this.execOk(`rm -f ${shellQuote(abs)}`, `deleteFile ${path}`);
@@ -249,6 +290,12 @@ var SandboxFilesystem = class SandboxFilesystem {
249
290
  async copyFile(src, dest, options) {
250
291
  const srcAbs = await this.resolveAsync(src);
251
292
  const destAbs = await this.resolveAsync(dest);
293
+ const native = this.sandbox.filesystem;
294
+ if (native) {
295
+ await this.verifyNative("target", srcAbs, src);
296
+ await this.verifyNative("dest", destAbs, dest);
297
+ return native.copyFile(srcAbs, destAbs, options);
298
+ }
252
299
  await this.assertContainedRealpath(srcAbs, src);
253
300
  await this.assertContainedDest(destAbs, dest);
254
301
  const recursive = options?.recursive ? "-r " : "";
@@ -280,6 +327,12 @@ var SandboxFilesystem = class SandboxFilesystem {
280
327
  async moveFile(src, dest, options) {
281
328
  const srcAbs = await this.resolveAsync(src);
282
329
  const destAbs = await this.resolveAsync(dest);
330
+ const native = this.sandbox.filesystem;
331
+ if (native) {
332
+ await this.verifyNative("target", srcAbs, src);
333
+ await this.verifyNative("dest", destAbs, dest);
334
+ return native.moveFile(srcAbs, destAbs, options);
335
+ }
283
336
  await this.assertContainedRealpath(srcAbs, src);
284
337
  await this.assertContainedDest(destAbs, dest);
285
338
  if (options?.overwrite === false) {
@@ -302,12 +355,22 @@ var SandboxFilesystem = class SandboxFilesystem {
302
355
  }
303
356
  async mkdir(path, options) {
304
357
  const abs = await this.resolveAsync(path);
358
+ const native = this.sandbox.filesystem;
359
+ if (native) {
360
+ await this.verifyNative("dest", abs, path);
361
+ return native.mkdir(abs, options);
362
+ }
305
363
  await this.assertContainedDest(abs, path);
306
364
  const flag = options?.recursive === false ? "" : "-p ";
307
365
  await this.execOk(`mkdir ${flag}${shellQuote(abs)}`, `mkdir ${path}`);
308
366
  }
309
367
  async rmdir(path, options) {
310
368
  const abs = await this.resolveAsync(path);
369
+ const native = this.sandbox.filesystem;
370
+ if (native) {
371
+ await this.verifyNative("parent", abs, path);
372
+ return native.rmdir(abs, options);
373
+ }
311
374
  await this.assertContainedRealpath(posix.dirname(abs), path);
312
375
  if (options?.recursive) {
313
376
  const force = options?.force ? "-f " : "";
@@ -318,6 +381,11 @@ var SandboxFilesystem = class SandboxFilesystem {
318
381
  }
319
382
  async readdir(path, options) {
320
383
  const abs = await this.resolveAsync(path);
384
+ const native = this.sandbox.filesystem;
385
+ if (native) {
386
+ await this.verifyNative("target", abs, path);
387
+ return native.readdir(abs, options);
388
+ }
321
389
  await this.assertContainedRealpath(abs, path);
322
390
  if (options?.recursive) {
323
391
  const result = await this.exec(`test -d ${shellQuote(abs)} && find ${shellQuote(abs)} -mindepth 1 ${options.maxDepth ? `-maxdepth ${Number(options.maxDepth)} ` : ""}2>/dev/null | while IFS= read -r f; do if [ -d "$f" ]; then printf 'd\\t%s\\n' "$f"; else printf 'f\\t%s\\n' "$f"; fi; done`);
@@ -673,10 +741,21 @@ var SandboxFilesystem = class SandboxFilesystem {
673
741
  }
674
742
  async exists(path) {
675
743
  const abs = await this.resolveAsync(path);
744
+ const native = this.sandbox.filesystem;
745
+ if (native) return native.exists(abs);
676
746
  return (await this.exec(`test -e ${shellQuote(abs)}`)).exitCode === 0;
677
747
  }
678
748
  async stat(path) {
679
749
  const abs = await this.resolveAsync(path);
750
+ const native = this.sandbox.filesystem;
751
+ if (native) {
752
+ await this.verifyNative("target", abs, path);
753
+ return {
754
+ ...await native.stat(abs),
755
+ name: posix.basename(abs),
756
+ path: `/${posix.relative(this.basePath, abs)}`
757
+ };
758
+ }
680
759
  await this.assertContainedRealpath(abs, path);
681
760
  const result = await this.exec(`stat -c '%F|%s|%Y|%W' ${shellQuote(abs)} 2>/dev/null || stat -f '%HT|%z|%m|%B' ${shellQuote(abs)}`);
682
761
  if (result.exitCode !== 0) throw new FileNotFoundError(path);
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-filesystem.js","names":["posixPath"],"sources":["../../src/agents/sandbox-filesystem.ts"],"sourcesContent":["/**\n * SandboxFilesystem\n *\n * A `WorkspaceFilesystem` that stores files inside a remote `MastraSandbox`\n * (e.g. a Railway VM) rather than on the server host. File operations are\n * implemented by shelling out through the sandbox's `executeCommand`, so the\n * agent's file tools and command tools share one VM and one view of the repo.\n *\n * Paths are workspace-relative (`/src/foo.ts`) and resolve under the sandbox\n * working directory (`basePath`). A traversal guard rejects any path that\n * escapes the workdir, mirroring `LocalFilesystem`'s contained mode.\n *\n * Reads/writes use base64 over the wire so binary content survives the shell.\n */\n\nimport { posix as posixPath } from 'node:path';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemGrepMatch,\n FilesystemGrepOptions,\n FilesystemGrepResult,\n FilesystemInfo,\n ListOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WalkEntry,\n WalkOptions,\n WorkspaceFilesystem,\n WriteOptions,\n} from '@mastra/core/workspace';\nimport {\n DirectoryNotFoundError,\n FileExistsError,\n FileNotFoundError,\n IsDirectoryError,\n NotDirectoryError,\n UnsupportedGrepPatternError,\n} from '@mastra/core/workspace';\n\n/**\n * Sentinel exit codes used by guard clauses that run before the real command,\n * so shell failures can be mapped to typed filesystem errors.\n */\nconst EXIT_NOT_FOUND = 20;\nconst EXIT_IS_DIRECTORY = 21;\nconst EXIT_EXISTS = 22;\nconst EXIT_NOT_DIRECTORY = 23;\n\n/** Minimal command result shape we depend on. */\nexport interface SandboxCommandResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/** Minimal sandbox surface the filesystem needs. */\nexport interface SandboxExec {\n readonly id: string;\n executeCommand(\n command: string,\n args?: string[],\n options?: { timeout?: number; maxRetainedBytes?: number },\n ): Promise<SandboxCommandResult>;\n}\n\nexport interface SandboxFilesystemOptions {\n /** Live sandbox to run commands in. */\n sandbox: SandboxExec;\n /**\n * Absolute path inside the sandbox that is the workspace root — or a lazy\n * resolver for it. The resolver form exists for sandboxes whose workspace\n * root is only knowable once the VM is running (e.g. `$HOME/<repo>` under\n * a provider-chosen home dir): it is awaited on the first file operation\n * (which itself may lazily start the VM) and the result is memoized.\n */\n workdir: string | (() => Promise<string> | string);\n /** Optional stable id; defaults to a sandbox-derived id. */\n id?: string;\n}\n\n/** Default per-command deadline so a hung sandbox can't block file tools forever. */\nconst COMMAND_TIMEOUT_MS = 30_000;\n\n/**\n * Explain why a native grep command failed so callers can tell a timeout or\n * broken toolchain apart from a pattern the engine cannot express.\n */\nfunction describeGrepFailure(result: SandboxCommandResult): string {\n if (result.exitCode === 124) return `timed out after ${COMMAND_TIMEOUT_MS / 1000}s`;\n const stderrLine = result.stderr\n .split('\\n')\n .map(line => line.trim())\n .find(Boolean);\n const detail = stderrLine ? `: ${stderrLine.length > 200 ? `${stderrLine.slice(0, 200)}…` : stderrLine}` : '';\n return `exit code ${result.exitCode}${detail}`;\n}\n\n/** Single-quote a string for safe POSIX shell interpolation. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\ninterface SearchTools {\n rg: boolean;\n gnuGrep: boolean;\n git: boolean;\n /** `grep -P` works, and so does `git grep -P` when git is present. */\n pcre: boolean;\n}\n\n/** One command that reports which search tools the sandbox has, one name per line. */\nconst SEARCH_TOOLS_PROBE =\n 'command -v rg >/dev/null 2>&1 && echo rg; ' +\n \"grep --version 2>/dev/null | head -n 1 | grep -q 'GNU grep' && echo gnu-grep; \" +\n \"printf 'a1\\\\n' | grep -qP '\\\\d(?=)' 2>/dev/null && echo grep-pcre; \" +\n 'if command -v git >/dev/null 2>&1; then echo git; ' +\n // git can be built without PCRE; check it on a throwaway repository.\n 'd=$(mktemp -d 2>/dev/null) && { ' +\n \"(cd \\\"$d\\\" && git init -q && printf 'a1\\\\n' >f && git grep -q --untracked -P -e '\\\\d(?=)') >/dev/null 2>&1 && echo git-pcre; \" +\n 'rm -rf \"$d\"; }; fi; true';\n\nfunction isInsideNodeModules(path: string): boolean {\n return path.split('/').includes('node_modules');\n}\n\n/** Lines a grep engine printed for one file: matches plus surrounding context. */\ninterface GrepFileLines {\n matches: FilesystemGrepMatch[];\n linesByNumber: Map<number, string>;\n}\n\nfunction fileLines(files: Map<string, GrepFileLines>, filePath: string): GrepFileLines {\n let state = files.get(filePath);\n if (!state) {\n state = { matches: [], linesByNumber: new Map() };\n files.set(filePath, state);\n }\n return state;\n}\n\n/** Turn per-file lines into results relative to `abs`, attaching before/after context. */\nfunction toGrepResults(files: Map<string, GrepFileLines>, abs: string, contextLines: number): FilesystemGrepResult[] {\n const results: FilesystemGrepResult[] = [];\n for (const [filePath, state] of files) {\n if (state.matches.length === 0) continue;\n const rel = posixPath.relative(abs, filePath) || posixPath.basename(filePath);\n const matches = state.matches.map(match => {\n if (contextLines <= 0) return match;\n const before: string[] = [];\n for (let n = match.line - 1; n >= Math.max(1, match.line - contextLines); n--) {\n const text = state.linesByNumber.get(n);\n if (text === undefined) break;\n before.unshift(text);\n }\n const after: string[] = [];\n for (let n = match.line + 1; n <= match.line + contextLines; n++) {\n const text = state.linesByNumber.get(n);\n if (text === undefined) break;\n after.push(text);\n }\n return { ...match, before, after };\n });\n results.push({ path: rel, matches });\n }\n return results;\n}\n\nfunction isFileContentString(content: FileContent): content is string {\n return typeof content === 'string';\n}\n\nfunction toBuffer(content: FileContent): Buffer {\n if (isFileContentString(content)) return Buffer.from(content, 'utf8');\n return Buffer.from(content);\n}\n\nexport class SandboxFilesystem implements WorkspaceFilesystem {\n readonly id: string;\n readonly name = 'SandboxFilesystem';\n readonly provider = 'sandbox';\n status: ProviderStatus = 'ready';\n\n private readonly sandbox: SandboxExec;\n private readonly workdirSource: string | (() => Promise<string> | string);\n private resolvedBase?: string;\n private resolvingBase?: Promise<string>;\n\n constructor(options: SandboxFilesystemOptions) {\n this.sandbox = options.sandbox;\n this.workdirSource = options.workdir;\n if (typeof options.workdir === 'string') this.resolvedBase = options.workdir;\n // Include the workdir when known: one sandbox can back several\n // filesystems rooted at different checkouts, and each needs a distinct\n // id. Lazy-workdir callers pass an explicit id.\n this.id =\n options.id ??\n (typeof options.workdir === 'string'\n ? `sandbox-fs:${options.sandbox.id}:${options.workdir}`\n : `sandbox-fs:${options.sandbox.id}`);\n }\n\n /** The resolved workspace root; empty until a lazy workdir first resolves. */\n get basePath(): string {\n return this.resolvedBase ?? '';\n }\n\n /** Await (and memoize) the workspace root, resolving a lazy workdir once. */\n private async base(): Promise<string> {\n if (this.resolvedBase) return this.resolvedBase;\n const source = this.workdirSource;\n if (typeof source === 'string') return (this.resolvedBase = source);\n this.resolvingBase ??= Promise.resolve()\n .then(source)\n .then(resolved => {\n if (!resolved) throw new Error('Sandbox workspace root resolution returned an empty path');\n return (this.resolvedBase = resolved);\n })\n .finally(() => {\n this.resolvingBase = undefined;\n });\n return this.resolvingBase;\n }\n\n // ── Path handling ──────────────────────────────────────────────────────\n\n /**\n * Resolve a workspace path to an absolute path inside the sandbox, enforcing\n * that it stays within the workdir. Awaits the workspace root first, which\n * for a lazy workdir may start the VM.\n */\n private async resolveAsync(inputPath: string): Promise<string> {\n return this.resolveAgainst(await this.base(), inputPath);\n }\n\n /**\n * Resolve a workspace path against a known base, enforcing that it stays\n * within the workdir.\n *\n * Accepts both workspace-relative paths (`src/foo.ts`, `/src/foo.ts`) and\n * absolute sandbox paths that already live under the workdir — the agent's\n * prompt advertises the workdir as its working directory, so tools are\n * routinely called with fully-qualified paths like `<workdir>/src/foo.ts`.\n */\n private resolveAgainst(basePath: string, inputPath: string): string {\n const base = posixPath.normalize(basePath);\n const normalizedInput = posixPath.normalize(inputPath);\n const rel =\n normalizedInput === base\n ? ''\n : normalizedInput.startsWith(`${base}/`)\n ? normalizedInput.slice(base.length + 1)\n : inputPath.startsWith('/')\n ? inputPath.slice(1)\n : inputPath;\n const resolved = posixPath.normalize(posixPath.join(base, rel));\n if (resolved !== base && !resolved.startsWith(`${base}/`)) {\n throw new Error(`Path escapes workspace root: ${inputPath}`);\n }\n return resolved;\n }\n\n resolveAbsolutePath(inputPath: string): string | undefined {\n // Sync interface: a lazy workdir that has not resolved yet has no\n // absolute form to offer.\n if (!this.resolvedBase) return undefined;\n return this.resolveAgainst(this.resolvedBase, inputPath);\n }\n\n // ── Command helper ─────────────────────────────────────────────────────\n\n private async exec(script: string): Promise<SandboxCommandResult> {\n // File reads come back as base64 on stdout and listings/grep are parsed\n // line by line, so dropping the oldest output would corrupt results.\n return this.sandbox.executeCommand('sh', ['-c', script], {\n timeout: COMMAND_TIMEOUT_MS,\n maxRetainedBytes: Infinity,\n });\n }\n\n /**\n * Lexical guard catches `..` traversal, but a symlink inside the workdir can\n * still point outside it. After resolving a path that refers to an existing\n * entry, verify its realpath is still contained in the workdir.\n *\n * Canonicalization tries `realpath`, then `readlink -f` (GNU/busybox), then\n * `cd && pwd -P` for directories — covering GNU hosts, macOS/BSD, and\n * busybox. If the path exists but cannot be canonicalized we fail CLOSED:\n * returning without a check would let a symlink bypass containment.\n */\n private async assertContainedRealpath(abs: string, inputPath: string): Promise<void> {\n const result = await this.exec(\n [\n `p=${shellQuote(abs)}`,\n `if [ ! -e \"$p\" ] && [ ! -L \"$p\" ]; then exit ${EXIT_NOT_FOUND}; fi`,\n // The workdir itself may contain symlinked components (/tmp on macOS),\n // so canonicalize it as the comparison root.\n `root=$(cd ${shellQuote(this.basePath)} 2>/dev/null && pwd -P)`,\n `[ -n \"$root\" ] || exit 1`,\n `rp=$(realpath \"$p\" 2>/dev/null) || rp=$(readlink -f \"$p\" 2>/dev/null) || { [ -d \"$p\" ] && rp=$(cd \"$p\" 2>/dev/null && pwd -P); }`,\n `[ -n \"$rp\" ] || exit 1`,\n `printf '%s\\\\n%s' \"$root\" \"$rp\"`,\n ].join('\\n'),\n );\n // Path doesn't exist yet: nothing to canonicalize (writes to a fresh leaf\n // are covered by assertContainedDest checking the parent directory).\n if (result.exitCode === EXIT_NOT_FOUND) return;\n const [root, real] = result.stdout.split('\\n').map(s => s.trim());\n if (result.exitCode !== 0 || !root || !real) {\n throw new Error(`Unable to verify path stays within workspace root: ${inputPath}`);\n }\n if (real !== root && !real.startsWith(`${root}/`)) {\n throw new Error(`Path escapes workspace root (symlink): ${inputPath}`);\n }\n }\n\n /**\n * Guard for write destinations. The lexical guard catches `..`, but a symlink\n * inside the workdir can redirect a write outside it. For an existing target\n * we check its realpath; for a not-yet-existing target we check the realpath\n * of its nearest existing ancestor directory, since a symlinked parent is the\n * escape vector (e.g. `link -> /etc` then writing `link/passwd`).\n */\n private async assertContainedDest(abs: string, inputPath: string): Promise<void> {\n // First check the target itself (covers overwriting an existing symlink).\n await this.assertContainedRealpath(abs, inputPath);\n // Then check the parent directory's realpath; readlink -f resolves the\n // nearest existing ancestor when the leaf doesn't exist yet.\n const parent = posixPath.dirname(abs);\n if (parent && parent !== abs) {\n await this.assertContainedRealpath(parent, inputPath);\n }\n }\n\n private async execOk(script: string, context: string): Promise<SandboxCommandResult> {\n const result = await this.exec(script);\n if (result.exitCode !== 0) {\n throw new Error(`${context} failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`);\n }\n return result;\n }\n\n // ── File operations ────────────────────────────────────────────────────\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedRealpath(abs, path);\n // Guard clauses first: redirecting from a directory \"succeeds\" with empty\n // output on some shells, so classify before reading.\n const result = await this.exec(\n `if [ -d ${shellQuote(abs)} ]; then exit ${EXIT_IS_DIRECTORY}; elif [ ! -e ${shellQuote(abs)} ]; then exit ${EXIT_NOT_FOUND}; fi; base64 < ${shellQuote(abs)}`,\n );\n if (result.exitCode === EXIT_IS_DIRECTORY) throw new IsDirectoryError(path);\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(path);\n if (result.exitCode !== 0) {\n throw new Error(`readFile ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n const buffer = Buffer.from(result.stdout.replace(/\\s/g, ''), 'base64');\n if (options?.encoding) {\n return buffer.toString(options.encoding);\n }\n return buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n const dir = posixPath.dirname(abs);\n const mkdir = options?.recursive === false ? '' : `mkdir -p ${shellQuote(dir)} && `;\n if (options?.overwrite === false) {\n // `set -C` (noclobber) makes the redirect itself the exclusivity check —\n // no exists() pre-check that could race with a concurrent writer.\n const result = await this.exec(\n `${mkdir}{ (set -C; printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}) 2>/dev/null || { [ -e ${shellQuote(abs)} ] && exit ${EXIT_EXISTS} || exit 1; }; }`,\n );\n if (result.exitCode === EXIT_EXISTS) throw new FileExistsError(path);\n if (result.exitCode !== 0) {\n throw new Error(`writeFile ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n return;\n }\n await this.execOk(`${mkdir}printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`, `writeFile ${path}`);\n }\n\n async appendFile(path: string, content: FileContent): Promise<void> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n await this.execOk(\n `mkdir -p ${shellQuote(posixPath.dirname(abs))} && printf %s ${shellQuote(b64)} | base64 -d >> ${shellQuote(abs)}`,\n `appendFile ${path}`,\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n const abs = await this.resolveAsync(path);\n // Contain the parent's realpath: deleting `link/file` where `link` points\n // outside the workdir must fail, while deleting a symlink entry itself\n // (which lives inside the workdir) stays allowed.\n await this.assertContainedRealpath(posixPath.dirname(abs), path);\n if (options?.force) {\n // `rm -f` already succeeds for a missing file, but still fails for\n // directories and permission errors — surface those.\n await this.execOk(`rm -f ${shellQuote(abs)}`, `deleteFile ${path}`);\n return;\n }\n const result = await this.exec(\n `if [ ! -e ${shellQuote(abs)} ]; then exit ${EXIT_NOT_FOUND}; fi; rm ${shellQuote(abs)}`,\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(path);\n if (result.exitCode !== 0) {\n throw new Error(`deleteFile ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = await this.resolveAsync(src);\n const destAbs = await this.resolveAsync(dest);\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n const recursive = options?.recursive ? '-r ' : '';\n if (options?.overwrite === false) {\n // Atomic no-clobber: directories claim the destination with an exclusive\n // mkdir; files copy to a temp name then hardlink into place (link(2)\n // fails if the destination exists). No racy exists() pre-check.\n const result = await this.exec(\n [\n `src=${shellQuote(srcAbs)}`,\n `dest=${shellQuote(destAbs)}`,\n `if [ ! -e \"$src\" ] && [ ! -L \"$src\" ]; then exit ${EXIT_NOT_FOUND}; fi`,\n `mkdir -p ${shellQuote(posixPath.dirname(destAbs))} || exit 1`,\n `if [ -d \"$src\" ]; then`,\n ` mkdir \"$dest\" 2>/dev/null || exit ${EXIT_EXISTS}`,\n ` cp -R \"$src\"/. \"$dest\"/`,\n `else`,\n ` tmp=\"$dest.__cptmp$$\"`,\n ` cp \"$src\" \"$tmp\" || exit 1`,\n ` ln \"$tmp\" \"$dest\" 2>/dev/null || { rm -f \"$tmp\"; [ -e \"$dest\" ] && exit ${EXIT_EXISTS} || exit 1; }`,\n ` rm -f \"$tmp\"`,\n `fi`,\n ].join('\\n'),\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode === EXIT_EXISTS) throw new FileExistsError(dest);\n if (result.exitCode !== 0) {\n throw new Error(`copyFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n return;\n }\n const result = await this.exec(\n `if [ ! -e ${shellQuote(srcAbs)} ]; then exit ${EXIT_NOT_FOUND}; fi; mkdir -p ${shellQuote(posixPath.dirname(destAbs))} && cp ${recursive}${shellQuote(srcAbs)} ${shellQuote(destAbs)}`,\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode !== 0) {\n throw new Error(`copyFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = await this.resolveAsync(src);\n const destAbs = await this.resolveAsync(dest);\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n if (options?.overwrite === false) {\n // `mv -n` exits 0 even when it skips, so detect a skipped move by the\n // source surviving. The no-clobber rename itself is atomic; no racy\n // exists() pre-check.\n const result = await this.exec(\n [\n `src=${shellQuote(srcAbs)}`,\n `dest=${shellQuote(destAbs)}`,\n `if [ ! -e \"$src\" ] && [ ! -L \"$src\" ]; then exit ${EXIT_NOT_FOUND}; fi`,\n `mkdir -p ${shellQuote(posixPath.dirname(destAbs))} || exit 1`,\n `mv -n \"$src\" \"$dest\" 2>/dev/null || exit 1`,\n `if [ -e \"$src\" ] || [ -L \"$src\" ]; then exit ${EXIT_EXISTS}; fi`,\n ].join('\\n'),\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode === EXIT_EXISTS) throw new FileExistsError(dest);\n if (result.exitCode !== 0) {\n throw new Error(`moveFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n return;\n }\n const result = await this.exec(\n `if [ ! -e ${shellQuote(srcAbs)} ]; then exit ${EXIT_NOT_FOUND}; fi; mkdir -p ${shellQuote(posixPath.dirname(destAbs))} && mv ${shellQuote(srcAbs)} ${shellQuote(destAbs)}`,\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode !== 0) {\n throw new Error(`moveFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n }\n\n // ── Directory operations ───────────────────────────────────────────────\n\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedDest(abs, path);\n const flag = options?.recursive === false ? '' : '-p ';\n await this.execOk(`mkdir ${flag}${shellQuote(abs)}`, `mkdir ${path}`);\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n const abs = await this.resolveAsync(path);\n // Same parent containment as deleteFile — `rm -r` through a symlinked\n // parent would otherwise delete outside the workspace.\n await this.assertContainedRealpath(posixPath.dirname(abs), path);\n if (options?.recursive) {\n const force = options?.force ? '-f ' : '';\n await this.execOk(`rm -r ${force}${shellQuote(abs)}`, `rmdir ${path}`);\n return;\n }\n const result = await this.exec(`rmdir ${shellQuote(abs)}`);\n if (result.exitCode !== 0 && !options?.force) {\n throw new Error(`Directory not empty or not found: ${path}`);\n }\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedRealpath(abs, path);\n if (options?.recursive) {\n // Recursive listing emitting \"type\\tpath\". `find -printf` is GNU-only\n // (fails on macOS/BSD hosts backing a local sandbox), so classify each\n // entry with a portable shell loop instead.\n const result = await this.exec(\n `test -d ${shellQuote(abs)} && find ${shellQuote(abs)} -mindepth 1 ${options.maxDepth ? `-maxdepth ${Number(options.maxDepth)} ` : ''}2>/dev/null | while IFS= read -r f; do if [ -d \"$f\" ]; then printf 'd\\\\t%s\\\\n' \"$f\"; else printf 'f\\\\t%s\\\\n' \"$f\"; fi; done`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseFindOutput(result.stdout, abs, options);\n }\n // Non-recursive: list with name + type via a portable loop. Use printf,\n // not echo — bash-as-/bin/sh (macOS local sandboxes) does not expand \\t\n // in echo arguments.\n const result = await this.exec(\n `cd ${shellQuote(abs)} 2>/dev/null && for f in * .[!.]*; do [ -e \"$f\" ] || continue; if [ -d \"$f\" ]; then printf 'd\\\\t%s\\\\n' \"$f\"; else printf 'f\\\\t%s\\\\n' \"$f\"; fi; done`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseListOutput(result.stdout, options);\n }\n\n private parseListOutput(stdout: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const name = line.slice(tab + 1);\n if (!name || name === '.' || name === '..') continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private parseFindOutput(stdout: string, base: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const fullPath = line.slice(tab + 1);\n const name = posixPath.relative(base, fullPath);\n if (!name) continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private matchesExtension(name: string, extension?: string | string[]): boolean {\n if (!extension) return true;\n const exts = Array.isArray(extension) ? extension : [extension];\n return exts.some(ext => name.endsWith(ext));\n }\n\n // ── Native walk / grep capabilities ────────────────────────────────────\n\n /**\n * Walk the tree in a single sandbox command instead of one readdir round\n * trip per directory. Uses `find` with a portable classification loop\n * (`find -printf` is GNU-only and fails on macOS/BSD hosts). `find` does\n * not follow symlinked directories by default, matching the host-side\n * walker's no-recursion-into-symlinks behavior.\n */\n async walk(path: string, options?: WalkOptions): Promise<WalkEntry[]> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedRealpath(abs, path);\n const maxDepth =\n options?.maxDepth !== undefined && Number.isFinite(options.maxDepth)\n ? `-maxdepth ${Math.max(0, Math.floor(options.maxDepth))} `\n : '';\n // Prune hidden entries in-sandbox when not requested, so a huge hidden\n // tree (e.g. `.git`) doesn't inflate the response. `.gitignore` files\n // still come back when the caller applies them.\n const keepGitignore = options?.includeGitignoreFiles ? `! \\\\( -type f -name .gitignore \\\\) ` : '';\n const hiddenPrune = options?.includeHidden ? '' : `\\\\( -name '.*' ${keepGitignore}\\\\) -prune -o `;\n // Skip directories like node_modules entirely instead of listing them\n // only for the caller to filter them out.\n const skipNames = options?.skipDirectoryNames?.filter(Boolean) ?? [];\n const skipPrune = skipNames.length\n ? `\\\\( -type d \\\\( ${skipNames.map(name => `-name ${shellQuote(name)}`).join(' -o ')} \\\\) \\\\) -prune -o `\n : '';\n // Emit \"flag\\tpath[\\ttarget]\" — flag is d/f for regular entries, D/F for\n // symlinks (with the link target as a third field).\n const script =\n `root=${shellQuote(abs)}\\n` +\n `[ -e \"$root\" ] || [ -L \"$root\" ] || exit ${EXIT_NOT_FOUND}\\n` +\n `[ -d \"$root\" ] || exit ${EXIT_NOT_DIRECTORY}\\n` +\n `find \"$root\" -mindepth 1 ${maxDepth}${hiddenPrune}${skipPrune}-print 2>/dev/null | while IFS= read -r f; do ` +\n `if [ -L \"$f\" ]; then if [ -d \"$f\" ]; then t=D; else t=F; fi; printf '%s\\\\t%s\\\\t%s\\\\n' \"$t\" \"$f\" \"$(readlink \"$f\")\"; ` +\n `elif [ -d \"$f\" ]; then printf 'd\\\\t%s\\\\n' \"$f\"; else printf 'f\\\\t%s\\\\n' \"$f\"; fi; done`;\n const result = await this.exec(script);\n if (result.exitCode === EXIT_NOT_FOUND) throw new DirectoryNotFoundError(path);\n if (result.exitCode === EXIT_NOT_DIRECTORY) throw new NotDirectoryError(path);\n if (result.exitCode !== 0) {\n throw new Error(`walk ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n const entries: WalkEntry[] = [];\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const flag = line.slice(0, tab);\n const isSymlink = flag === 'D' || flag === 'F';\n let fullPath = line.slice(tab + 1);\n let symlinkTarget: string | undefined;\n if (isSymlink) {\n const tab2 = fullPath.lastIndexOf('\\t');\n if (tab2 >= 0) {\n symlinkTarget = fullPath.slice(tab2 + 1) || undefined;\n fullPath = fullPath.slice(0, tab2);\n }\n }\n const rel = posixPath.relative(abs, fullPath);\n if (!rel || rel.startsWith('..')) continue;\n entries.push({\n name: posixPath.basename(rel),\n type: flag === 'd' || flag === 'D' ? 'directory' : 'file',\n ...(isSymlink ? { isSymlink: true, ...(symlinkTarget ? { symlinkTarget } : {}) } : {}),\n path: rel,\n });\n }\n return entries;\n }\n\n /**\n * Content search executed inside the sandbox in one command, using the best\n * engine the sandbox has: ripgrep, then GNU grep (with git choosing files\n * inside repositories), then POSIX `grep -rnE`. Patterns the grep engines\n * can't express (PCRE-style classes, word boundaries, lookarounds,\n * non-greedy quantifiers) throw {@link UnsupportedGrepPatternError} so\n * callers use their host-side walk.\n *\n * ripgrep and GNU grep apply every `.gitignore` in the tree, relative to the\n * directory that holds it, and skip `node_modules` unless the search path\n * is inside one. Callers still apply their own hidden/glob filtering.\n */\n async grep(options: FilesystemGrepOptions): Promise<FilesystemGrepResult[]> {\n const abs = await this.resolveAsync(options.path);\n await this.assertContainedRealpath(abs, options.path);\n const tools = await this.searchTools();\n if (tools.rg) return this.grepWithRipgrep(abs, options);\n if (tools.gnuGrep) return this.grepWithGnuGrep(abs, options, tools);\n return this.grepWithPosixGrep(abs, options);\n }\n\n private toolsCheck: Promise<SearchTools> | undefined;\n\n private searchTools(): Promise<SearchTools> {\n const check = (this.toolsCheck ??= this.exec(SEARCH_TOOLS_PROBE).then(result => {\n if (result.exitCode !== 0) throw new Error(`search tool probe exited with ${result.exitCode}`);\n const found = new Set(result.stdout.split('\\n').map(line => line.trim()));\n const git = found.has('git');\n return {\n rg: found.has('rg'),\n gnuGrep: found.has('gnu-grep'),\n git,\n pcre: found.has('grep-pcre') && (!git || found.has('git-pcre')),\n };\n }));\n // Only a successful probe is cached; a failed one (e.g. the sandbox wasn't\n // ready) is retried on the next search instead of pinning the slow path.\n return check.catch(() => {\n if (this.toolsCheck === check) this.toolsCheck = undefined;\n return { rg: false, gnuGrep: false, git: false, pcre: false };\n });\n }\n\n private async grepWithRipgrep(abs: string, options: FilesystemGrepOptions): Promise<FilesystemGrepResult[]> {\n const args = [\n // --no-require-git applies .gitignore files outside a git checkout too\n // (e.g. a multi-repo root). A path passed explicitly is searched even\n // when ignored, so targeting `dist/` or `node_modules/x` still works.\n 'rg --json --hidden --no-require-git',\n // .git is filtered host-side too; exclude it here so its object files\n // don't inflate the response.\n `-g ${shellQuote('!.git/**')}`,\n isInsideNodeModules(abs) ? '' : `-g ${shellQuote('!**/node_modules/**')}`,\n options.caseSensitive ? '' : '-i',\n options.maxCountPerFile !== undefined ? `-m ${Math.max(1, Math.floor(options.maxCountPerFile))}` : '',\n options.contextLines ? `-C ${Math.max(0, Math.floor(options.contextLines))}` : '',\n `-e ${shellQuote(options.pattern)}`,\n shellQuote(abs),\n ]\n .filter(Boolean)\n .join(' ');\n const result = await this.exec(args);\n // rg exits 1 when there are no matches, 2 on any error. A bad pattern\n // produces no output at all, but a per-file error (e.g. an unreadable\n // file) also yields exit 2 while still emitting matches for every other\n // file. Keep those partial results; only treat \"error with no matches\"\n // as an unsupported pattern so the caller falls back to the host walk.\n if (result.exitCode === 1) return [];\n const results = this.parseRipgrepJson(result.stdout, abs, options);\n if (result.exitCode !== 0 && results.length === 0) {\n throw new UnsupportedGrepPatternError(options.pattern, describeGrepFailure(result));\n }\n return results;\n }\n\n private parseRipgrepJson(stdout: string, abs: string, options: FilesystemGrepOptions): FilesystemGrepResult[] {\n const files = new Map<string, GrepFileLines>();\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n let event: any;\n try {\n event = JSON.parse(line);\n } catch {\n continue;\n }\n if (event.type !== 'match' && event.type !== 'context') continue;\n const filePath: string | undefined = event.data?.path?.text;\n const lineNumber: number | undefined = event.data?.line_number;\n if (!filePath || !lineNumber) continue;\n const state = fileLines(files, filePath);\n const text: string = (event.data?.lines?.text ?? '').replace(/\\r?\\n$/, '');\n state.linesByNumber.set(lineNumber, text);\n if (event.type === 'match') {\n // rg submatch offsets are byte offsets into the line; convert to a\n // UTF-16 (JS string) index for the capability contract.\n const byteStart: number = event.data?.submatches?.[0]?.start ?? 0;\n const column = Buffer.from(text, 'utf8').subarray(0, byteStart).toString('utf8').length;\n state.matches.push({ line: lineNumber, column, text });\n }\n }\n return this.applyTotalCap(toGrepResults(files, abs, options.contextLines ?? 0), options.maxTotalMatches);\n }\n\n /**\n * Patterns whose meaning differs between POSIX ERE and JS RegExp. The grep\n * runs with ERE but match columns are recomputed with JS, so anything that\n * only one side understands (PCRE classes, lookarounds, lazy quantifiers,\n * POSIX bracket classes, GNU word anchors) must fall back to the host walk.\n */\n private static readonly ERE_UNSUPPORTED = /\\\\[dDwWsSbB<>]|\\(\\?|[*+?}]\\?|\\[:[a-z]+:\\]/;\n\n /**\n * Escapes that `grep -P` (PCRE) and JS RegExp (without the `u` flag) both\n * accept but read differently, e.g. `\\A` is \"start of subject\" in PCRE and\n * a literal `A` in JS, plus POSIX bracket classes. PCRE-only syntax that JS\n * rejects (inline flags, possessive quantifiers, atomic groups) already\n * fails the JS compile below.\n */\n private static readonly PCRE_UNSUPPORTED = /\\\\[AzZGKQEhHvVRXNCpPe]|\\[:[a-z]+:\\]/;\n\n /** Reject patterns grep can't run with the same meaning as JS; return the JS regex used for columns. */\n private static grepRegex(options: FilesystemGrepOptions, pcre = false): RegExp {\n if (\n pcre\n ? SandboxFilesystem.PCRE_UNSUPPORTED.test(options.pattern)\n : SandboxFilesystem.ERE_UNSUPPORTED.test(options.pattern)\n ) {\n throw new UnsupportedGrepPatternError(\n options.pattern,\n pcre ? 'grep -P reads this syntax differently from JavaScript' : 'POSIX ERE cannot express this syntax',\n );\n }\n try {\n return new RegExp(options.pattern, options.caseSensitive ? '' : 'i');\n } catch {\n throw new UnsupportedGrepPatternError(options.pattern, 'invalid regular expression');\n }\n }\n\n /**\n * GNU grep without ripgrep. Inside a git checkout, `git grep` picks the\n * files (tracked plus untracked, every `.gitignore` applied) and GNU grep\n * prints them with context. Outside a checkout (e.g. a multi-repo root),\n * each child repository is searched the same way and plain grep covers the\n * remaining files, skipping `node_modules`. `--null` ends each file name\n * with NUL, so match (`N:`) and context (`N-`) lines parse unambiguously.\n * When grep and git support it, patterns run as PCRE (`-P`), so `\\d`,\n * `\\w`, `\\s`, `\\b`, and lookarounds don't need the host walk.\n */\n private async grepWithGnuGrep(\n abs: string,\n options: FilesystemGrepOptions,\n tools: SearchTools,\n ): Promise<FilesystemGrepResult[]> {\n const hasGit = tools.git;\n const mode = tools.pcre ? '-P' : '-E';\n const jsRegex = SandboxFilesystem.grepRegex(options, tools.pcre);\n const contextLines = Math.max(0, Math.floor(options.contextLines ?? 0));\n const grepFlags = [\n `-HnI${mode.slice(1)} --null`,\n options.caseSensitive ? '' : '-i',\n options.maxCountPerFile !== undefined ? `-m ${Math.max(1, Math.floor(options.maxCountPerFile))}` : '',\n contextLines > 0 ? `-C ${contextLines}` : '',\n ]\n .filter(Boolean)\n .join(' ');\n const skipNodeModules = !isInsideNodeModules(abs);\n const nodeModulesPathspec = skipNodeModules ? ` ${shellQuote(':(exclude,glob)**/node_modules/**')}` : '';\n const pruneNames = skipNodeModules ? '-name .git -o -name node_modules' : '-name .git';\n const script = [\n `pat=${shellQuote(options.pattern)}`,\n `root=${shellQuote(abs)}`,\n // xargs exit codes can't tell \"no match\" from \"bad pattern\"; check first.\n `grep ${mode} -e \"$pat\" </dev/null >/dev/null 2>&1`,\n 'if [ $? -eq 2 ]; then echo \"grep: invalid regular expression\" >&2; exit 2; fi',\n 'tmp=$(mktemp -d) || exit 1',\n `trap 'rm -rf \"$tmp\"' EXIT`,\n `search_files() { xargs -0 -r grep ${grepFlags} -e \"$pat\" -- 2>/dev/null; }`,\n // A line starting with NUL names the directory later paths are relative to.\n 'search_repo() {',\n ` git -C \"$1\" grep -l -z -I --untracked ${options.caseSensitive ? '' : '-i '}${mode} -e \"$pat\" -- .${nodeModulesPathspec} >\"$tmp/files\" 2>/dev/null`,\n ' [ $? -gt 1 ] && return 1',\n ` printf '\\\\0%s\\\\n' \"$1\"`,\n ' (cd \"$1\" && search_files) <\"$tmp/files\"',\n '}',\n ...(hasGit\n ? [\n 'if git -C \"$root\" rev-parse --is-inside-work-tree >/dev/null 2>&1 && ! git -C \"$root\" check-ignore -q . 2>/dev/null; then',\n ' search_repo \"$root\" && exit 0',\n 'fi',\n `find \"$root\" -mindepth 1 -maxdepth 3 \\\\( ${skipNodeModules ? '-name node_modules -prune' : '-false'} \\\\) -o \\\\( -name .git -prune -print \\\\) >\"$tmp/repos\" 2>/dev/null`,\n ': >\"$tmp/done\"',\n 'while IFS= read -r g; do',\n ' d=${g%/.git}',\n ' search_repo \"$d\" && printf \\'%s\\\\n\\' \"$d\" >>\"$tmp/done\"',\n 'done <\"$tmp/repos\"',\n 'set --',\n 'while IFS= read -r d; do set -- \"$@\" -o -path \"$d\"; done <\"$tmp/done\"',\n ]\n : ['set --']),\n `printf '\\\\0%s\\\\n' \"$root\"`,\n `find \"$root\" -mindepth 1 \\\\( ${pruneNames} \"$@\" \\\\) -prune -o -type f -print0 2>/dev/null | search_files`,\n 'exit 0',\n ].join('\\n');\n const result = await this.exec(script);\n if (result.exitCode !== 0) {\n throw new UnsupportedGrepPatternError(options.pattern, describeGrepFailure(result));\n }\n const files = new Map<string, GrepFileLines>();\n let base = abs;\n for (const line of result.stdout.split('\\n')) {\n if (!line || line === '--') continue;\n if (line.startsWith('\\0')) {\n base = line.slice(1);\n continue;\n }\n const nul = line.indexOf('\\0');\n if (nul <= 0) continue;\n const parsed = /^(\\d+)([:-])(.*)$/s.exec(line.slice(nul + 1));\n if (!parsed) continue;\n const lineNumber = Number(parsed[1]);\n const text = parsed[3]!;\n const state = fileLines(files, posixPath.resolve(base, line.slice(0, nul)));\n state.linesByNumber.set(lineNumber, text);\n if (parsed[2] !== ':') continue;\n const column = jsRegex.exec(text)?.index;\n if (column === undefined) {\n throw new UnsupportedGrepPatternError(options.pattern, 'POSIX ERE and JS RegExp disagree on this pattern');\n }\n state.matches.push({ line: lineNumber, column, text });\n }\n return this.applyTotalCap(toGrepResults(files, abs, contextLines), options.maxTotalMatches);\n }\n\n private async grepWithPosixGrep(abs: string, options: FilesystemGrepOptions): Promise<FilesystemGrepResult[]> {\n const jsRegex = SandboxFilesystem.grepRegex(options);\n // Reconstructing per-match context from `grep -C` text output is not\n // reliable (separator lines are ambiguous); let the host walk handle it.\n if (options.contextLines) {\n throw new UnsupportedGrepPatternError(options.pattern, 'context lines are not supported by POSIX grep');\n }\n const args = [\n 'grep -rnIE',\n // .git is filtered host-side too; skip it so object files are not scanned.\n '--exclude-dir=.git',\n options.caseSensitive ? '' : '-i',\n options.maxCountPerFile !== undefined ? `-m ${Math.max(1, Math.floor(options.maxCountPerFile))}` : '',\n '--',\n shellQuote(options.pattern),\n shellQuote(abs),\n ]\n .filter(Boolean)\n .join(' ');\n const result = await this.exec(args);\n if (result.exitCode === 1) return [];\n // Like rg, grep exits 2 on per-file errors (e.g. an unreadable file) while\n // still printing matches for the rest; keep those partial results.\n if (result.exitCode !== 0 && !(result.exitCode === 2 && result.stdout.trim())) {\n throw new UnsupportedGrepPatternError(options.pattern, describeGrepFailure(result));\n }\n // grep -n has no column output; recompute with the JS regex host-side so\n // columns are UTF-16 indices, consistent with the fallback implementation.\n const byFile = new Map<string, FilesystemGrepMatch[]>();\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const parsed = /^(.*?):(\\d+):(.*)$/.exec(line);\n if (!parsed) continue;\n const rel = posixPath.relative(abs, parsed[1]!) || posixPath.basename(parsed[1]!);\n const text = parsed[3]!;\n const column = jsRegex.exec(text)?.index;\n // grep matched this line but the JS regex did not: the two dialects\n // disagree on this pattern, so the columns would be wrong. Fall back.\n if (column === undefined) {\n throw new UnsupportedGrepPatternError(options.pattern, 'POSIX ERE and JS RegExp disagree on this pattern');\n }\n let matches = byFile.get(rel);\n if (!matches) {\n matches = [];\n byFile.set(rel, matches);\n }\n matches.push({ line: Number(parsed[2]), column, text });\n }\n const results = [...byFile.entries()].map(([path, matches]) => ({ path, matches }));\n return this.applyTotalCap(results, options.maxTotalMatches);\n }\n\n private applyTotalCap(results: FilesystemGrepResult[], maxTotal?: number): FilesystemGrepResult[] {\n if (maxTotal === undefined) return results;\n const capped: FilesystemGrepResult[] = [];\n let total = 0;\n for (const file of results) {\n if (total >= maxTotal) break;\n const remaining = maxTotal - total;\n const matches = file.matches.slice(0, remaining);\n total += matches.length;\n capped.push({ path: file.path, matches });\n }\n return capped;\n }\n\n // ── Path / metadata ────────────────────────────────────────────────────\n\n async exists(path: string): Promise<boolean> {\n const abs = await this.resolveAsync(path);\n const result = await this.exec(`test -e ${shellQuote(abs)}`);\n return result.exitCode === 0;\n }\n\n async stat(path: string): Promise<FileStat> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedRealpath(abs, path);\n // GNU stat: %F=type, %s=size, %Y=mtime (epoch seconds), %W=birth (or -1).\n // BSD/macOS stat (local sandbox hosts) rejects `-c`; fall back to its\n // `-f` format with the same field order (%HT=type, %z=size, %m=mtime,\n // %B=birth). Delimit with `|` — neither stat interprets `\\t` escapes in\n // its format string.\n const result = await this.exec(\n `stat -c '%F|%s|%Y|%W' ${shellQuote(abs)} 2>/dev/null || stat -f '%HT|%z|%m|%B' ${shellQuote(abs)}`,\n );\n if (result.exitCode !== 0) {\n throw new FileNotFoundError(path);\n }\n const [kind, sizeStr, mtimeStr, ctimeStr] = result.stdout.trim().split('|');\n const type = kind && kind.toLowerCase().includes('directory') ? 'directory' : 'file';\n const size = Number(sizeStr) || 0;\n const mtime = Number(mtimeStr) || 0;\n const ctime = Number(ctimeStr);\n return {\n name: posixPath.basename(abs),\n path: `/${posixPath.relative(this.basePath, abs)}`,\n type,\n size: type === 'directory' ? 0 : size,\n modifiedAt: new Date(mtime * 1000),\n createdAt: new Date((ctime > 0 ? ctime : mtime) * 1000),\n };\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────────\n\n async init(): Promise<void> {\n await this.execOk(`mkdir -p ${shellQuote(await this.base())}`, 'init workdir');\n }\n\n async destroy(): Promise<void> {\n // The sandbox lifecycle is owned by the caller; nothing to tear down here.\n }\n\n async isReady(): Promise<boolean> {\n const result = await this.exec(`test -d ${shellQuote(await this.base())}`);\n return result.exitCode === 0;\n }\n\n getInfo(): FilesystemInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n metadata: { basePath: this.basePath, sandboxId: this.sandbox.id },\n };\n }\n\n getInstructions(): string {\n return `Files are stored in a remote sandbox at ${this.basePath}. Use absolute workspace paths like /src/index.ts. All reads, writes and commands run inside the same sandbox.`;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sandbox-filesystem.js","names":["posixPath"],"sources":["../../src/agents/sandbox-filesystem.ts"],"sourcesContent":["/**\n * SandboxFilesystem\n *\n * A `WorkspaceFilesystem` that stores files inside a remote `MastraSandbox`\n * (e.g. a Railway VM) rather than on the server host. File operations are\n * implemented by shelling out through the sandbox's `executeCommand`, so the\n * agent's file tools and command tools share one VM and one view of the repo.\n * When the sandbox exposes its own `filesystem`, file operations use it\n * instead, which takes one round trip rather than several commands; grep and\n * walks still run as shell commands.\n *\n * Paths are workspace-relative (`/src/foo.ts`) and resolve under the sandbox\n * working directory (`basePath`). A traversal guard rejects any path that\n * escapes the workdir, mirroring `LocalFilesystem`'s contained mode.\n *\n * Reads/writes use base64 over the wire so binary content survives the shell.\n */\n\nimport { posix as posixPath } from 'node:path';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemGrepMatch,\n FilesystemGrepOptions,\n FilesystemGrepResult,\n FilesystemInfo,\n ListOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WalkEntry,\n WalkOptions,\n WorkspaceFilesystem,\n WriteOptions,\n} from '@mastra/core/workspace';\nimport {\n DirectoryNotFoundError,\n FileExistsError,\n FileNotFoundError,\n IsDirectoryError,\n NotDirectoryError,\n UnsupportedGrepPatternError,\n} from '@mastra/core/workspace';\n\n/**\n * Sentinel exit codes used by guard clauses that run before the real command,\n * so shell failures can be mapped to typed filesystem errors.\n */\nconst EXIT_NOT_FOUND = 20;\nconst EXIT_IS_DIRECTORY = 21;\nconst EXIT_EXISTS = 22;\nconst EXIT_NOT_DIRECTORY = 23;\n\n/** Minimal command result shape we depend on. */\nexport interface SandboxCommandResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/** Minimal sandbox surface the filesystem needs. */\nexport interface SandboxExec {\n readonly id: string;\n /**\n * The sandbox's own file API, when it has one (`WorkspaceSandbox.filesystem`).\n * File operations go through it instead of shell commands.\n */\n readonly filesystem?: WorkspaceFilesystem;\n executeCommand(\n command: string,\n args?: string[],\n options?: { timeout?: number; maxRetainedBytes?: number },\n ): Promise<SandboxCommandResult>;\n}\n\nexport interface SandboxFilesystemOptions {\n /** Live sandbox to run commands in. */\n sandbox: SandboxExec;\n /**\n * Absolute path inside the sandbox that is the workspace root — or a lazy\n * resolver for it. The resolver form exists for sandboxes whose workspace\n * root is only knowable once the VM is running (e.g. `$HOME/<repo>` under\n * a provider-chosen home dir): it is awaited on the first file operation\n * (which itself may lazily start the VM) and the result is memoized.\n */\n workdir: string | (() => Promise<string> | string);\n /** Optional stable id; defaults to a sandbox-derived id. */\n id?: string;\n /**\n * Check that symlinks don't lead outside the workdir when using the\n * sandbox's native filesystem. Off by default: native file APIs follow\n * symlinks, and inside a sandbox the agent can read anything with a command\n * anyway. Turn it on when files are served to someone other than the agent.\n *\n * Each path is checked once per instance, so keep such instances\n * short-lived. Without a native filesystem the shell path always checks.\n */\n verifySymlinks?: boolean;\n}\n\ntype ContainmentCheck = 'target' | 'dest' | 'parent';\n\n/** Default per-command deadline so a hung sandbox can't block file tools forever. */\nconst COMMAND_TIMEOUT_MS = 30_000;\n\n/**\n * Explain why a native grep command failed so callers can tell a timeout or\n * broken toolchain apart from a pattern the engine cannot express.\n */\nfunction describeGrepFailure(result: SandboxCommandResult): string {\n if (result.exitCode === 124) return `timed out after ${COMMAND_TIMEOUT_MS / 1000}s`;\n const stderrLine = result.stderr\n .split('\\n')\n .map(line => line.trim())\n .find(Boolean);\n const detail = stderrLine ? `: ${stderrLine.length > 200 ? `${stderrLine.slice(0, 200)}…` : stderrLine}` : '';\n return `exit code ${result.exitCode}${detail}`;\n}\n\n/** Single-quote a string for safe POSIX shell interpolation. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\ninterface SearchTools {\n rg: boolean;\n gnuGrep: boolean;\n git: boolean;\n /** `grep -P` works, and so does `git grep -P` when git is present. */\n pcre: boolean;\n}\n\n/** One command that reports which search tools the sandbox has, one name per line. */\nconst SEARCH_TOOLS_PROBE =\n 'command -v rg >/dev/null 2>&1 && echo rg; ' +\n \"grep --version 2>/dev/null | head -n 1 | grep -q 'GNU grep' && echo gnu-grep; \" +\n \"printf 'a1\\\\n' | grep -qP '\\\\d(?=)' 2>/dev/null && echo grep-pcre; \" +\n 'if command -v git >/dev/null 2>&1; then echo git; ' +\n // git can be built without PCRE; check it on a throwaway repository.\n 'd=$(mktemp -d 2>/dev/null) && { ' +\n \"(cd \\\"$d\\\" && git init -q && printf 'a1\\\\n' >f && git grep -q --untracked -P -e '\\\\d(?=)') >/dev/null 2>&1 && echo git-pcre; \" +\n 'rm -rf \"$d\"; }; fi; true';\n\nfunction isInsideNodeModules(path: string): boolean {\n return path.split('/').includes('node_modules');\n}\n\n/** Lines a grep engine printed for one file: matches plus surrounding context. */\ninterface GrepFileLines {\n matches: FilesystemGrepMatch[];\n linesByNumber: Map<number, string>;\n}\n\nfunction fileLines(files: Map<string, GrepFileLines>, filePath: string): GrepFileLines {\n let state = files.get(filePath);\n if (!state) {\n state = { matches: [], linesByNumber: new Map() };\n files.set(filePath, state);\n }\n return state;\n}\n\n/** Turn per-file lines into results relative to `abs`, attaching before/after context. */\nfunction toGrepResults(files: Map<string, GrepFileLines>, abs: string, contextLines: number): FilesystemGrepResult[] {\n const results: FilesystemGrepResult[] = [];\n for (const [filePath, state] of files) {\n if (state.matches.length === 0) continue;\n const rel = posixPath.relative(abs, filePath) || posixPath.basename(filePath);\n const matches = state.matches.map(match => {\n if (contextLines <= 0) return match;\n const before: string[] = [];\n for (let n = match.line - 1; n >= Math.max(1, match.line - contextLines); n--) {\n const text = state.linesByNumber.get(n);\n if (text === undefined) break;\n before.unshift(text);\n }\n const after: string[] = [];\n for (let n = match.line + 1; n <= match.line + contextLines; n++) {\n const text = state.linesByNumber.get(n);\n if (text === undefined) break;\n after.push(text);\n }\n return { ...match, before, after };\n });\n results.push({ path: rel, matches });\n }\n return results;\n}\n\nfunction isFileContentString(content: FileContent): content is string {\n return typeof content === 'string';\n}\n\nfunction toBuffer(content: FileContent): Buffer {\n if (isFileContentString(content)) return Buffer.from(content, 'utf8');\n return Buffer.from(content);\n}\n\nexport class SandboxFilesystem implements WorkspaceFilesystem {\n readonly id: string;\n readonly name = 'SandboxFilesystem';\n readonly provider = 'sandbox';\n status: ProviderStatus = 'ready';\n\n private readonly sandbox: SandboxExec;\n private readonly workdirSource: string | (() => Promise<string> | string);\n private resolvedBase?: string;\n private resolvingBase?: Promise<string>;\n private readonly verifySymlinks: boolean;\n /** `check:absolutePath` keys that passed while the entry existed. */\n private readonly verified = new Set<string>();\n\n constructor(options: SandboxFilesystemOptions) {\n this.sandbox = options.sandbox;\n this.workdirSource = options.workdir;\n this.verifySymlinks = options.verifySymlinks ?? false;\n if (typeof options.workdir === 'string') this.resolvedBase = options.workdir;\n // Include the workdir when known: one sandbox can back several\n // filesystems rooted at different checkouts, and each needs a distinct\n // id. Lazy-workdir callers pass an explicit id.\n this.id =\n options.id ??\n (typeof options.workdir === 'string'\n ? `sandbox-fs:${options.sandbox.id}:${options.workdir}`\n : `sandbox-fs:${options.sandbox.id}`);\n }\n\n /** The resolved workspace root; empty until a lazy workdir first resolves. */\n get basePath(): string {\n return this.resolvedBase ?? '';\n }\n\n /** Await (and memoize) the workspace root, resolving a lazy workdir once. */\n private async base(): Promise<string> {\n if (this.resolvedBase) return this.resolvedBase;\n const source = this.workdirSource;\n if (typeof source === 'string') return (this.resolvedBase = source);\n this.resolvingBase ??= Promise.resolve()\n .then(source)\n .then(resolved => {\n if (!resolved) throw new Error('Sandbox workspace root resolution returned an empty path');\n return (this.resolvedBase = resolved);\n })\n .finally(() => {\n this.resolvingBase = undefined;\n });\n return this.resolvingBase;\n }\n\n // ── Path handling ──────────────────────────────────────────────────────\n\n /**\n * Resolve a workspace path to an absolute path inside the sandbox, enforcing\n * that it stays within the workdir. Awaits the workspace root first, which\n * for a lazy workdir may start the VM.\n */\n private async resolveAsync(inputPath: string): Promise<string> {\n return this.resolveAgainst(await this.base(), inputPath);\n }\n\n /**\n * Resolve a workspace path against a known base, enforcing that it stays\n * within the workdir.\n *\n * Accepts both workspace-relative paths (`src/foo.ts`, `/src/foo.ts`) and\n * absolute sandbox paths that already live under the workdir — the agent's\n * prompt advertises the workdir as its working directory, so tools are\n * routinely called with fully-qualified paths like `<workdir>/src/foo.ts`.\n */\n private resolveAgainst(basePath: string, inputPath: string): string {\n const base = posixPath.normalize(basePath);\n const normalizedInput = posixPath.normalize(inputPath);\n const rel =\n normalizedInput === base\n ? ''\n : normalizedInput.startsWith(`${base}/`)\n ? normalizedInput.slice(base.length + 1)\n : inputPath.startsWith('/')\n ? inputPath.slice(1)\n : inputPath;\n const resolved = posixPath.normalize(posixPath.join(base, rel));\n if (resolved !== base && !resolved.startsWith(`${base}/`)) {\n throw new Error(`Path escapes workspace root: ${inputPath}`);\n }\n return resolved;\n }\n\n resolveAbsolutePath(inputPath: string): string | undefined {\n // Sync interface: a lazy workdir that has not resolved yet has no\n // absolute form to offer.\n if (!this.resolvedBase) return undefined;\n return this.resolveAgainst(this.resolvedBase, inputPath);\n }\n\n // ── Command helper ─────────────────────────────────────────────────────\n\n private async exec(script: string): Promise<SandboxCommandResult> {\n // File reads come back as base64 on stdout and listings/grep are parsed\n // line by line, so dropping the oldest output would corrupt results.\n return this.sandbox.executeCommand('sh', ['-c', script], {\n timeout: COMMAND_TIMEOUT_MS,\n maxRetainedBytes: Infinity,\n });\n }\n\n /**\n * Lexical guard catches `..` traversal, but a symlink inside the workdir can\n * still point outside it. After resolving a path that refers to an existing\n * entry, verify its realpath is still contained in the workdir.\n *\n * Canonicalization tries `realpath`, then `readlink -f` (GNU/busybox), then\n * `cd && pwd -P` for directories — covering GNU hosts, macOS/BSD, and\n * busybox. If the path exists but cannot be canonicalized we fail CLOSED:\n * returning without a check would let a symlink bypass containment.\n */\n private async assertContainedRealpath(abs: string, inputPath: string): Promise<boolean> {\n const result = await this.exec(\n [\n `p=${shellQuote(abs)}`,\n `if [ ! -e \"$p\" ] && [ ! -L \"$p\" ]; then exit ${EXIT_NOT_FOUND}; fi`,\n // The workdir itself may contain symlinked components (/tmp on macOS),\n // so canonicalize it as the comparison root.\n `root=$(cd ${shellQuote(this.basePath)} 2>/dev/null && pwd -P)`,\n `[ -n \"$root\" ] || exit 1`,\n `rp=$(realpath \"$p\" 2>/dev/null) || rp=$(readlink -f \"$p\" 2>/dev/null) || { [ -d \"$p\" ] && rp=$(cd \"$p\" 2>/dev/null && pwd -P); }`,\n `[ -n \"$rp\" ] || exit 1`,\n `printf '%s\\\\n%s' \"$root\" \"$rp\"`,\n ].join('\\n'),\n );\n // Path doesn't exist yet: nothing to canonicalize (writes to a fresh leaf\n // are covered by assertContainedDest checking the parent directory).\n if (result.exitCode === EXIT_NOT_FOUND) return false;\n const [root, real] = result.stdout.split('\\n').map(s => s.trim());\n if (result.exitCode !== 0 || !root || !real) {\n throw new Error(`Unable to verify path stays within workspace root: ${inputPath}`);\n }\n if (real !== root && !real.startsWith(`${root}/`)) {\n throw new Error(`Path escapes workspace root (symlink): ${inputPath}`);\n }\n return true;\n }\n\n /**\n * Guard for write destinations. The lexical guard catches `..`, but a symlink\n * inside the workdir can redirect a write outside it. For an existing target\n * we check its realpath; for a not-yet-existing target we check the realpath\n * of its nearest existing ancestor directory, since a symlinked parent is the\n * escape vector (e.g. `link -> /etc` then writing `link/passwd`).\n */\n private async assertContainedDest(abs: string, inputPath: string): Promise<boolean> {\n // First check the target itself (covers overwriting an existing symlink).\n const exists = await this.assertContainedRealpath(abs, inputPath);\n // Then check the parent directory's realpath; readlink -f resolves the\n // nearest existing ancestor when the leaf doesn't exist yet.\n const parent = posixPath.dirname(abs);\n if (parent && parent !== abs) {\n await this.assertContainedRealpath(parent, inputPath);\n }\n return exists;\n }\n\n /**\n * Before a native filesystem call, run the containment check the shell path\n * runs for that operation, when `verifySymlinks` is on. A pass is remembered\n * only for an entry that exists: a missing path may later appear as a\n * symlink that leads outside the workdir.\n */\n private async verifyNative(check: ContainmentCheck, abs: string, inputPath: string): Promise<void> {\n if (!this.verifySymlinks) return;\n const key = `${check}:${abs}`;\n if (this.verified.has(key)) return;\n const existed =\n check === 'dest'\n ? await this.assertContainedDest(abs, inputPath)\n : await this.assertContainedRealpath(check === 'parent' ? posixPath.dirname(abs) : abs, inputPath);\n if (existed) this.verified.add(key);\n }\n\n private async execOk(script: string, context: string): Promise<SandboxCommandResult> {\n const result = await this.exec(script);\n if (result.exitCode !== 0) {\n throw new Error(`${context} failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`);\n }\n return result;\n }\n\n // ── File operations ────────────────────────────────────────────────────\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('target', abs, path);\n return native.readFile(abs, options);\n }\n await this.assertContainedRealpath(abs, path);\n // Guard clauses first: redirecting from a directory \"succeeds\" with empty\n // output on some shells, so classify before reading.\n const result = await this.exec(\n `if [ -d ${shellQuote(abs)} ]; then exit ${EXIT_IS_DIRECTORY}; elif [ ! -e ${shellQuote(abs)} ]; then exit ${EXIT_NOT_FOUND}; fi; base64 < ${shellQuote(abs)}`,\n );\n if (result.exitCode === EXIT_IS_DIRECTORY) throw new IsDirectoryError(path);\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(path);\n if (result.exitCode !== 0) {\n throw new Error(`readFile ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n const buffer = Buffer.from(result.stdout.replace(/\\s/g, ''), 'base64');\n if (options?.encoding) {\n return buffer.toString(options.encoding);\n }\n return buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('dest', abs, path);\n return native.writeFile(abs, content, options);\n }\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n const dir = posixPath.dirname(abs);\n const mkdir = options?.recursive === false ? '' : `mkdir -p ${shellQuote(dir)} && `;\n if (options?.overwrite === false) {\n // `set -C` (noclobber) makes the redirect itself the exclusivity check —\n // no exists() pre-check that could race with a concurrent writer.\n const result = await this.exec(\n `${mkdir}{ (set -C; printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}) 2>/dev/null || { [ -e ${shellQuote(abs)} ] && exit ${EXIT_EXISTS} || exit 1; }; }`,\n );\n if (result.exitCode === EXIT_EXISTS) throw new FileExistsError(path);\n if (result.exitCode !== 0) {\n throw new Error(`writeFile ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n return;\n }\n await this.execOk(`${mkdir}printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`, `writeFile ${path}`);\n }\n\n async appendFile(path: string, content: FileContent): Promise<void> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('dest', abs, path);\n return native.appendFile(abs, content);\n }\n await this.assertContainedDest(abs, path);\n const b64 = toBuffer(content).toString('base64');\n await this.execOk(\n `mkdir -p ${shellQuote(posixPath.dirname(abs))} && printf %s ${shellQuote(b64)} | base64 -d >> ${shellQuote(abs)}`,\n `appendFile ${path}`,\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('parent', abs, path);\n return native.deleteFile(abs, options);\n }\n // Contain the parent's realpath: deleting `link/file` where `link` points\n // outside the workdir must fail, while deleting a symlink entry itself\n // (which lives inside the workdir) stays allowed.\n await this.assertContainedRealpath(posixPath.dirname(abs), path);\n if (options?.force) {\n // `rm -f` already succeeds for a missing file, but still fails for\n // directories and permission errors — surface those.\n await this.execOk(`rm -f ${shellQuote(abs)}`, `deleteFile ${path}`);\n return;\n }\n const result = await this.exec(\n `if [ ! -e ${shellQuote(abs)} ]; then exit ${EXIT_NOT_FOUND}; fi; rm ${shellQuote(abs)}`,\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(path);\n if (result.exitCode !== 0) {\n throw new Error(`deleteFile ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = await this.resolveAsync(src);\n const destAbs = await this.resolveAsync(dest);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('target', srcAbs, src);\n await this.verifyNative('dest', destAbs, dest);\n return native.copyFile(srcAbs, destAbs, options);\n }\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n const recursive = options?.recursive ? '-r ' : '';\n if (options?.overwrite === false) {\n // Atomic no-clobber: directories claim the destination with an exclusive\n // mkdir; files copy to a temp name then hardlink into place (link(2)\n // fails if the destination exists). No racy exists() pre-check.\n const result = await this.exec(\n [\n `src=${shellQuote(srcAbs)}`,\n `dest=${shellQuote(destAbs)}`,\n `if [ ! -e \"$src\" ] && [ ! -L \"$src\" ]; then exit ${EXIT_NOT_FOUND}; fi`,\n `mkdir -p ${shellQuote(posixPath.dirname(destAbs))} || exit 1`,\n `if [ -d \"$src\" ]; then`,\n ` mkdir \"$dest\" 2>/dev/null || exit ${EXIT_EXISTS}`,\n ` cp -R \"$src\"/. \"$dest\"/`,\n `else`,\n ` tmp=\"$dest.__cptmp$$\"`,\n ` cp \"$src\" \"$tmp\" || exit 1`,\n ` ln \"$tmp\" \"$dest\" 2>/dev/null || { rm -f \"$tmp\"; [ -e \"$dest\" ] && exit ${EXIT_EXISTS} || exit 1; }`,\n ` rm -f \"$tmp\"`,\n `fi`,\n ].join('\\n'),\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode === EXIT_EXISTS) throw new FileExistsError(dest);\n if (result.exitCode !== 0) {\n throw new Error(`copyFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n return;\n }\n const result = await this.exec(\n `if [ ! -e ${shellQuote(srcAbs)} ]; then exit ${EXIT_NOT_FOUND}; fi; mkdir -p ${shellQuote(posixPath.dirname(destAbs))} && cp ${recursive}${shellQuote(srcAbs)} ${shellQuote(destAbs)}`,\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode !== 0) {\n throw new Error(`copyFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n const srcAbs = await this.resolveAsync(src);\n const destAbs = await this.resolveAsync(dest);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('target', srcAbs, src);\n await this.verifyNative('dest', destAbs, dest);\n return native.moveFile(srcAbs, destAbs, options);\n }\n await this.assertContainedRealpath(srcAbs, src);\n await this.assertContainedDest(destAbs, dest);\n if (options?.overwrite === false) {\n // `mv -n` exits 0 even when it skips, so detect a skipped move by the\n // source surviving. The no-clobber rename itself is atomic; no racy\n // exists() pre-check.\n const result = await this.exec(\n [\n `src=${shellQuote(srcAbs)}`,\n `dest=${shellQuote(destAbs)}`,\n `if [ ! -e \"$src\" ] && [ ! -L \"$src\" ]; then exit ${EXIT_NOT_FOUND}; fi`,\n `mkdir -p ${shellQuote(posixPath.dirname(destAbs))} || exit 1`,\n `mv -n \"$src\" \"$dest\" 2>/dev/null || exit 1`,\n `if [ -e \"$src\" ] || [ -L \"$src\" ]; then exit ${EXIT_EXISTS}; fi`,\n ].join('\\n'),\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode === EXIT_EXISTS) throw new FileExistsError(dest);\n if (result.exitCode !== 0) {\n throw new Error(`moveFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n return;\n }\n const result = await this.exec(\n `if [ ! -e ${shellQuote(srcAbs)} ]; then exit ${EXIT_NOT_FOUND}; fi; mkdir -p ${shellQuote(posixPath.dirname(destAbs))} && mv ${shellQuote(srcAbs)} ${shellQuote(destAbs)}`,\n );\n if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(src);\n if (result.exitCode !== 0) {\n throw new Error(`moveFile ${src} -> ${dest} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n }\n\n // ── Directory operations ───────────────────────────────────────────────\n\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('dest', abs, path);\n return native.mkdir(abs, options);\n }\n await this.assertContainedDest(abs, path);\n const flag = options?.recursive === false ? '' : '-p ';\n await this.execOk(`mkdir ${flag}${shellQuote(abs)}`, `mkdir ${path}`);\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('parent', abs, path);\n return native.rmdir(abs, options);\n }\n // Same parent containment as deleteFile — `rm -r` through a symlinked\n // parent would otherwise delete outside the workspace.\n await this.assertContainedRealpath(posixPath.dirname(abs), path);\n if (options?.recursive) {\n const force = options?.force ? '-f ' : '';\n await this.execOk(`rm -r ${force}${shellQuote(abs)}`, `rmdir ${path}`);\n return;\n }\n const result = await this.exec(`rmdir ${shellQuote(abs)}`);\n if (result.exitCode !== 0 && !options?.force) {\n throw new Error(`Directory not empty or not found: ${path}`);\n }\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('target', abs, path);\n return native.readdir(abs, options);\n }\n await this.assertContainedRealpath(abs, path);\n if (options?.recursive) {\n // Recursive listing emitting \"type\\tpath\". `find -printf` is GNU-only\n // (fails on macOS/BSD hosts backing a local sandbox), so classify each\n // entry with a portable shell loop instead.\n const result = await this.exec(\n `test -d ${shellQuote(abs)} && find ${shellQuote(abs)} -mindepth 1 ${options.maxDepth ? `-maxdepth ${Number(options.maxDepth)} ` : ''}2>/dev/null | while IFS= read -r f; do if [ -d \"$f\" ]; then printf 'd\\\\t%s\\\\n' \"$f\"; else printf 'f\\\\t%s\\\\n' \"$f\"; fi; done`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseFindOutput(result.stdout, abs, options);\n }\n // Non-recursive: list with name + type via a portable loop. Use printf,\n // not echo — bash-as-/bin/sh (macOS local sandboxes) does not expand \\t\n // in echo arguments.\n const result = await this.exec(\n `cd ${shellQuote(abs)} 2>/dev/null && for f in * .[!.]*; do [ -e \"$f\" ] || continue; if [ -d \"$f\" ]; then printf 'd\\\\t%s\\\\n' \"$f\"; else printf 'f\\\\t%s\\\\n' \"$f\"; fi; done`,\n );\n if (result.exitCode !== 0) throw new Error(`Directory not found: ${path}`);\n return this.parseListOutput(result.stdout, options);\n }\n\n private parseListOutput(stdout: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const name = line.slice(tab + 1);\n if (!name || name === '.' || name === '..') continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private parseFindOutput(stdout: string, base: string, options?: ListOptions): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const type = line.slice(0, tab) === 'd' ? 'directory' : 'file';\n const fullPath = line.slice(tab + 1);\n const name = posixPath.relative(base, fullPath);\n if (!name) continue;\n if (type === 'file' && !this.matchesExtension(name, options?.extension)) continue;\n entries.push({ name, type });\n }\n return entries;\n }\n\n private matchesExtension(name: string, extension?: string | string[]): boolean {\n if (!extension) return true;\n const exts = Array.isArray(extension) ? extension : [extension];\n return exts.some(ext => name.endsWith(ext));\n }\n\n // ── Native walk / grep capabilities ────────────────────────────────────\n\n /**\n * Walk the tree in a single sandbox command instead of one readdir round\n * trip per directory. Uses `find` with a portable classification loop\n * (`find -printf` is GNU-only and fails on macOS/BSD hosts). `find` does\n * not follow symlinked directories by default, matching the host-side\n * walker's no-recursion-into-symlinks behavior.\n */\n async walk(path: string, options?: WalkOptions): Promise<WalkEntry[]> {\n const abs = await this.resolveAsync(path);\n await this.assertContainedRealpath(abs, path);\n const maxDepth =\n options?.maxDepth !== undefined && Number.isFinite(options.maxDepth)\n ? `-maxdepth ${Math.max(0, Math.floor(options.maxDepth))} `\n : '';\n // Prune hidden entries in-sandbox when not requested, so a huge hidden\n // tree (e.g. `.git`) doesn't inflate the response. `.gitignore` files\n // still come back when the caller applies them.\n const keepGitignore = options?.includeGitignoreFiles ? `! \\\\( -type f -name .gitignore \\\\) ` : '';\n const hiddenPrune = options?.includeHidden ? '' : `\\\\( -name '.*' ${keepGitignore}\\\\) -prune -o `;\n // Skip directories like node_modules entirely instead of listing them\n // only for the caller to filter them out.\n const skipNames = options?.skipDirectoryNames?.filter(Boolean) ?? [];\n const skipPrune = skipNames.length\n ? `\\\\( -type d \\\\( ${skipNames.map(name => `-name ${shellQuote(name)}`).join(' -o ')} \\\\) \\\\) -prune -o `\n : '';\n // Emit \"flag\\tpath[\\ttarget]\" — flag is d/f for regular entries, D/F for\n // symlinks (with the link target as a third field).\n const script =\n `root=${shellQuote(abs)}\\n` +\n `[ -e \"$root\" ] || [ -L \"$root\" ] || exit ${EXIT_NOT_FOUND}\\n` +\n `[ -d \"$root\" ] || exit ${EXIT_NOT_DIRECTORY}\\n` +\n `find \"$root\" -mindepth 1 ${maxDepth}${hiddenPrune}${skipPrune}-print 2>/dev/null | while IFS= read -r f; do ` +\n `if [ -L \"$f\" ]; then if [ -d \"$f\" ]; then t=D; else t=F; fi; printf '%s\\\\t%s\\\\t%s\\\\n' \"$t\" \"$f\" \"$(readlink \"$f\")\"; ` +\n `elif [ -d \"$f\" ]; then printf 'd\\\\t%s\\\\n' \"$f\"; else printf 'f\\\\t%s\\\\n' \"$f\"; fi; done`;\n const result = await this.exec(script);\n if (result.exitCode === EXIT_NOT_FOUND) throw new DirectoryNotFoundError(path);\n if (result.exitCode === EXIT_NOT_DIRECTORY) throw new NotDirectoryError(path);\n if (result.exitCode !== 0) {\n throw new Error(`walk ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n }\n const entries: WalkEntry[] = [];\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const tab = line.indexOf('\\t');\n if (tab < 0) continue;\n const flag = line.slice(0, tab);\n const isSymlink = flag === 'D' || flag === 'F';\n let fullPath = line.slice(tab + 1);\n let symlinkTarget: string | undefined;\n if (isSymlink) {\n const tab2 = fullPath.lastIndexOf('\\t');\n if (tab2 >= 0) {\n symlinkTarget = fullPath.slice(tab2 + 1) || undefined;\n fullPath = fullPath.slice(0, tab2);\n }\n }\n const rel = posixPath.relative(abs, fullPath);\n if (!rel || rel.startsWith('..')) continue;\n entries.push({\n name: posixPath.basename(rel),\n type: flag === 'd' || flag === 'D' ? 'directory' : 'file',\n ...(isSymlink ? { isSymlink: true, ...(symlinkTarget ? { symlinkTarget } : {}) } : {}),\n path: rel,\n });\n }\n return entries;\n }\n\n /**\n * Content search executed inside the sandbox in one command, using the best\n * engine the sandbox has: ripgrep, then GNU grep (with git choosing files\n * inside repositories), then POSIX `grep -rnE`. Patterns the grep engines\n * can't express (PCRE-style classes, word boundaries, lookarounds,\n * non-greedy quantifiers) throw {@link UnsupportedGrepPatternError} so\n * callers use their host-side walk.\n *\n * ripgrep and GNU grep apply every `.gitignore` in the tree, relative to the\n * directory that holds it, and skip `node_modules` unless the search path\n * is inside one. Callers still apply their own hidden/glob filtering.\n */\n async grep(options: FilesystemGrepOptions): Promise<FilesystemGrepResult[]> {\n const abs = await this.resolveAsync(options.path);\n await this.assertContainedRealpath(abs, options.path);\n const tools = await this.searchTools();\n if (tools.rg) return this.grepWithRipgrep(abs, options);\n if (tools.gnuGrep) return this.grepWithGnuGrep(abs, options, tools);\n return this.grepWithPosixGrep(abs, options);\n }\n\n private toolsCheck: Promise<SearchTools> | undefined;\n\n private searchTools(): Promise<SearchTools> {\n const check = (this.toolsCheck ??= this.exec(SEARCH_TOOLS_PROBE).then(result => {\n if (result.exitCode !== 0) throw new Error(`search tool probe exited with ${result.exitCode}`);\n const found = new Set(result.stdout.split('\\n').map(line => line.trim()));\n const git = found.has('git');\n return {\n rg: found.has('rg'),\n gnuGrep: found.has('gnu-grep'),\n git,\n pcre: found.has('grep-pcre') && (!git || found.has('git-pcre')),\n };\n }));\n // Only a successful probe is cached; a failed one (e.g. the sandbox wasn't\n // ready) is retried on the next search instead of pinning the slow path.\n return check.catch(() => {\n if (this.toolsCheck === check) this.toolsCheck = undefined;\n return { rg: false, gnuGrep: false, git: false, pcre: false };\n });\n }\n\n private async grepWithRipgrep(abs: string, options: FilesystemGrepOptions): Promise<FilesystemGrepResult[]> {\n const args = [\n // --no-require-git applies .gitignore files outside a git checkout too\n // (e.g. a multi-repo root). A path passed explicitly is searched even\n // when ignored, so targeting `dist/` or `node_modules/x` still works.\n 'rg --json --hidden --no-require-git',\n // .git is filtered host-side too; exclude it here so its object files\n // don't inflate the response.\n `-g ${shellQuote('!.git/**')}`,\n isInsideNodeModules(abs) ? '' : `-g ${shellQuote('!**/node_modules/**')}`,\n options.caseSensitive ? '' : '-i',\n options.maxCountPerFile !== undefined ? `-m ${Math.max(1, Math.floor(options.maxCountPerFile))}` : '',\n options.contextLines ? `-C ${Math.max(0, Math.floor(options.contextLines))}` : '',\n `-e ${shellQuote(options.pattern)}`,\n shellQuote(abs),\n ]\n .filter(Boolean)\n .join(' ');\n const result = await this.exec(args);\n // rg exits 1 when there are no matches, 2 on any error. A bad pattern\n // produces no output at all, but a per-file error (e.g. an unreadable\n // file) also yields exit 2 while still emitting matches for every other\n // file. Keep those partial results; only treat \"error with no matches\"\n // as an unsupported pattern so the caller falls back to the host walk.\n if (result.exitCode === 1) return [];\n const results = this.parseRipgrepJson(result.stdout, abs, options);\n if (result.exitCode !== 0 && results.length === 0) {\n throw new UnsupportedGrepPatternError(options.pattern, describeGrepFailure(result));\n }\n return results;\n }\n\n private parseRipgrepJson(stdout: string, abs: string, options: FilesystemGrepOptions): FilesystemGrepResult[] {\n const files = new Map<string, GrepFileLines>();\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n let event: any;\n try {\n event = JSON.parse(line);\n } catch {\n continue;\n }\n if (event.type !== 'match' && event.type !== 'context') continue;\n const filePath: string | undefined = event.data?.path?.text;\n const lineNumber: number | undefined = event.data?.line_number;\n if (!filePath || !lineNumber) continue;\n const state = fileLines(files, filePath);\n const text: string = (event.data?.lines?.text ?? '').replace(/\\r?\\n$/, '');\n state.linesByNumber.set(lineNumber, text);\n if (event.type === 'match') {\n // rg submatch offsets are byte offsets into the line; convert to a\n // UTF-16 (JS string) index for the capability contract.\n const byteStart: number = event.data?.submatches?.[0]?.start ?? 0;\n const column = Buffer.from(text, 'utf8').subarray(0, byteStart).toString('utf8').length;\n state.matches.push({ line: lineNumber, column, text });\n }\n }\n return this.applyTotalCap(toGrepResults(files, abs, options.contextLines ?? 0), options.maxTotalMatches);\n }\n\n /**\n * Patterns whose meaning differs between POSIX ERE and JS RegExp. The grep\n * runs with ERE but match columns are recomputed with JS, so anything that\n * only one side understands (PCRE classes, lookarounds, lazy quantifiers,\n * POSIX bracket classes, GNU word anchors) must fall back to the host walk.\n */\n private static readonly ERE_UNSUPPORTED = /\\\\[dDwWsSbB<>]|\\(\\?|[*+?}]\\?|\\[:[a-z]+:\\]/;\n\n /**\n * Escapes that `grep -P` (PCRE) and JS RegExp (without the `u` flag) both\n * accept but read differently, e.g. `\\A` is \"start of subject\" in PCRE and\n * a literal `A` in JS, plus POSIX bracket classes. PCRE-only syntax that JS\n * rejects (inline flags, possessive quantifiers, atomic groups) already\n * fails the JS compile below.\n */\n private static readonly PCRE_UNSUPPORTED = /\\\\[AzZGKQEhHvVRXNCpPe]|\\[:[a-z]+:\\]/;\n\n /** Reject patterns grep can't run with the same meaning as JS; return the JS regex used for columns. */\n private static grepRegex(options: FilesystemGrepOptions, pcre = false): RegExp {\n if (\n pcre\n ? SandboxFilesystem.PCRE_UNSUPPORTED.test(options.pattern)\n : SandboxFilesystem.ERE_UNSUPPORTED.test(options.pattern)\n ) {\n throw new UnsupportedGrepPatternError(\n options.pattern,\n pcre ? 'grep -P reads this syntax differently from JavaScript' : 'POSIX ERE cannot express this syntax',\n );\n }\n try {\n return new RegExp(options.pattern, options.caseSensitive ? '' : 'i');\n } catch {\n throw new UnsupportedGrepPatternError(options.pattern, 'invalid regular expression');\n }\n }\n\n /**\n * GNU grep without ripgrep. Inside a git checkout, `git grep` picks the\n * files (tracked plus untracked, every `.gitignore` applied) and GNU grep\n * prints them with context. Outside a checkout (e.g. a multi-repo root),\n * each child repository is searched the same way and plain grep covers the\n * remaining files, skipping `node_modules`. `--null` ends each file name\n * with NUL, so match (`N:`) and context (`N-`) lines parse unambiguously.\n * When grep and git support it, patterns run as PCRE (`-P`), so `\\d`,\n * `\\w`, `\\s`, `\\b`, and lookarounds don't need the host walk.\n */\n private async grepWithGnuGrep(\n abs: string,\n options: FilesystemGrepOptions,\n tools: SearchTools,\n ): Promise<FilesystemGrepResult[]> {\n const hasGit = tools.git;\n const mode = tools.pcre ? '-P' : '-E';\n const jsRegex = SandboxFilesystem.grepRegex(options, tools.pcre);\n const contextLines = Math.max(0, Math.floor(options.contextLines ?? 0));\n const grepFlags = [\n `-HnI${mode.slice(1)} --null`,\n options.caseSensitive ? '' : '-i',\n options.maxCountPerFile !== undefined ? `-m ${Math.max(1, Math.floor(options.maxCountPerFile))}` : '',\n contextLines > 0 ? `-C ${contextLines}` : '',\n ]\n .filter(Boolean)\n .join(' ');\n const skipNodeModules = !isInsideNodeModules(abs);\n const nodeModulesPathspec = skipNodeModules ? ` ${shellQuote(':(exclude,glob)**/node_modules/**')}` : '';\n const pruneNames = skipNodeModules ? '-name .git -o -name node_modules' : '-name .git';\n const script = [\n `pat=${shellQuote(options.pattern)}`,\n `root=${shellQuote(abs)}`,\n // xargs exit codes can't tell \"no match\" from \"bad pattern\"; check first.\n `grep ${mode} -e \"$pat\" </dev/null >/dev/null 2>&1`,\n 'if [ $? -eq 2 ]; then echo \"grep: invalid regular expression\" >&2; exit 2; fi',\n 'tmp=$(mktemp -d) || exit 1',\n `trap 'rm -rf \"$tmp\"' EXIT`,\n `search_files() { xargs -0 -r grep ${grepFlags} -e \"$pat\" -- 2>/dev/null; }`,\n // A line starting with NUL names the directory later paths are relative to.\n 'search_repo() {',\n ` git -C \"$1\" grep -l -z -I --untracked ${options.caseSensitive ? '' : '-i '}${mode} -e \"$pat\" -- .${nodeModulesPathspec} >\"$tmp/files\" 2>/dev/null`,\n ' [ $? -gt 1 ] && return 1',\n ` printf '\\\\0%s\\\\n' \"$1\"`,\n ' (cd \"$1\" && search_files) <\"$tmp/files\"',\n '}',\n ...(hasGit\n ? [\n 'if git -C \"$root\" rev-parse --is-inside-work-tree >/dev/null 2>&1 && ! git -C \"$root\" check-ignore -q . 2>/dev/null; then',\n ' search_repo \"$root\" && exit 0',\n 'fi',\n `find \"$root\" -mindepth 1 -maxdepth 3 \\\\( ${skipNodeModules ? '-name node_modules -prune' : '-false'} \\\\) -o \\\\( -name .git -prune -print \\\\) >\"$tmp/repos\" 2>/dev/null`,\n ': >\"$tmp/done\"',\n 'while IFS= read -r g; do',\n ' d=${g%/.git}',\n ' search_repo \"$d\" && printf \\'%s\\\\n\\' \"$d\" >>\"$tmp/done\"',\n 'done <\"$tmp/repos\"',\n 'set --',\n 'while IFS= read -r d; do set -- \"$@\" -o -path \"$d\"; done <\"$tmp/done\"',\n ]\n : ['set --']),\n `printf '\\\\0%s\\\\n' \"$root\"`,\n `find \"$root\" -mindepth 1 \\\\( ${pruneNames} \"$@\" \\\\) -prune -o -type f -print0 2>/dev/null | search_files`,\n 'exit 0',\n ].join('\\n');\n const result = await this.exec(script);\n if (result.exitCode !== 0) {\n throw new UnsupportedGrepPatternError(options.pattern, describeGrepFailure(result));\n }\n const files = new Map<string, GrepFileLines>();\n let base = abs;\n for (const line of result.stdout.split('\\n')) {\n if (!line || line === '--') continue;\n if (line.startsWith('\\0')) {\n base = line.slice(1);\n continue;\n }\n const nul = line.indexOf('\\0');\n if (nul <= 0) continue;\n const parsed = /^(\\d+)([:-])(.*)$/s.exec(line.slice(nul + 1));\n if (!parsed) continue;\n const lineNumber = Number(parsed[1]);\n const text = parsed[3]!;\n const state = fileLines(files, posixPath.resolve(base, line.slice(0, nul)));\n state.linesByNumber.set(lineNumber, text);\n if (parsed[2] !== ':') continue;\n const column = jsRegex.exec(text)?.index;\n if (column === undefined) {\n throw new UnsupportedGrepPatternError(options.pattern, 'POSIX ERE and JS RegExp disagree on this pattern');\n }\n state.matches.push({ line: lineNumber, column, text });\n }\n return this.applyTotalCap(toGrepResults(files, abs, contextLines), options.maxTotalMatches);\n }\n\n private async grepWithPosixGrep(abs: string, options: FilesystemGrepOptions): Promise<FilesystemGrepResult[]> {\n const jsRegex = SandboxFilesystem.grepRegex(options);\n // Reconstructing per-match context from `grep -C` text output is not\n // reliable (separator lines are ambiguous); let the host walk handle it.\n if (options.contextLines) {\n throw new UnsupportedGrepPatternError(options.pattern, 'context lines are not supported by POSIX grep');\n }\n const args = [\n 'grep -rnIE',\n // .git is filtered host-side too; skip it so object files are not scanned.\n '--exclude-dir=.git',\n options.caseSensitive ? '' : '-i',\n options.maxCountPerFile !== undefined ? `-m ${Math.max(1, Math.floor(options.maxCountPerFile))}` : '',\n '--',\n shellQuote(options.pattern),\n shellQuote(abs),\n ]\n .filter(Boolean)\n .join(' ');\n const result = await this.exec(args);\n if (result.exitCode === 1) return [];\n // Like rg, grep exits 2 on per-file errors (e.g. an unreadable file) while\n // still printing matches for the rest; keep those partial results.\n if (result.exitCode !== 0 && !(result.exitCode === 2 && result.stdout.trim())) {\n throw new UnsupportedGrepPatternError(options.pattern, describeGrepFailure(result));\n }\n // grep -n has no column output; recompute with the JS regex host-side so\n // columns are UTF-16 indices, consistent with the fallback implementation.\n const byFile = new Map<string, FilesystemGrepMatch[]>();\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const parsed = /^(.*?):(\\d+):(.*)$/.exec(line);\n if (!parsed) continue;\n const rel = posixPath.relative(abs, parsed[1]!) || posixPath.basename(parsed[1]!);\n const text = parsed[3]!;\n const column = jsRegex.exec(text)?.index;\n // grep matched this line but the JS regex did not: the two dialects\n // disagree on this pattern, so the columns would be wrong. Fall back.\n if (column === undefined) {\n throw new UnsupportedGrepPatternError(options.pattern, 'POSIX ERE and JS RegExp disagree on this pattern');\n }\n let matches = byFile.get(rel);\n if (!matches) {\n matches = [];\n byFile.set(rel, matches);\n }\n matches.push({ line: Number(parsed[2]), column, text });\n }\n const results = [...byFile.entries()].map(([path, matches]) => ({ path, matches }));\n return this.applyTotalCap(results, options.maxTotalMatches);\n }\n\n private applyTotalCap(results: FilesystemGrepResult[], maxTotal?: number): FilesystemGrepResult[] {\n if (maxTotal === undefined) return results;\n const capped: FilesystemGrepResult[] = [];\n let total = 0;\n for (const file of results) {\n if (total >= maxTotal) break;\n const remaining = maxTotal - total;\n const matches = file.matches.slice(0, remaining);\n total += matches.length;\n capped.push({ path: file.path, matches });\n }\n return capped;\n }\n\n // ── Path / metadata ────────────────────────────────────────────────────\n\n async exists(path: string): Promise<boolean> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) return native.exists(abs);\n const result = await this.exec(`test -e ${shellQuote(abs)}`);\n return result.exitCode === 0;\n }\n\n async stat(path: string): Promise<FileStat> {\n const abs = await this.resolveAsync(path);\n const native = this.sandbox.filesystem;\n if (native) {\n await this.verifyNative('target', abs, path);\n const info = await native.stat(abs);\n return { ...info, name: posixPath.basename(abs), path: `/${posixPath.relative(this.basePath, abs)}` };\n }\n await this.assertContainedRealpath(abs, path);\n // GNU stat: %F=type, %s=size, %Y=mtime (epoch seconds), %W=birth (or -1).\n // BSD/macOS stat (local sandbox hosts) rejects `-c`; fall back to its\n // `-f` format with the same field order (%HT=type, %z=size, %m=mtime,\n // %B=birth). Delimit with `|` — neither stat interprets `\\t` escapes in\n // its format string.\n const result = await this.exec(\n `stat -c '%F|%s|%Y|%W' ${shellQuote(abs)} 2>/dev/null || stat -f '%HT|%z|%m|%B' ${shellQuote(abs)}`,\n );\n if (result.exitCode !== 0) {\n throw new FileNotFoundError(path);\n }\n const [kind, sizeStr, mtimeStr, ctimeStr] = result.stdout.trim().split('|');\n const type = kind && kind.toLowerCase().includes('directory') ? 'directory' : 'file';\n const size = Number(sizeStr) || 0;\n const mtime = Number(mtimeStr) || 0;\n const ctime = Number(ctimeStr);\n return {\n name: posixPath.basename(abs),\n path: `/${posixPath.relative(this.basePath, abs)}`,\n type,\n size: type === 'directory' ? 0 : size,\n modifiedAt: new Date(mtime * 1000),\n createdAt: new Date((ctime > 0 ? ctime : mtime) * 1000),\n };\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────────\n\n async init(): Promise<void> {\n await this.execOk(`mkdir -p ${shellQuote(await this.base())}`, 'init workdir');\n }\n\n async destroy(): Promise<void> {\n // The sandbox lifecycle is owned by the caller; nothing to tear down here.\n }\n\n async isReady(): Promise<boolean> {\n const result = await this.exec(`test -d ${shellQuote(await this.base())}`);\n return result.exitCode === 0;\n }\n\n getInfo(): FilesystemInfo {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n metadata: { basePath: this.basePath, sandboxId: this.sandbox.id },\n };\n }\n\n getInstructions(): string {\n return `Files are stored in a remote sandbox at ${this.basePath}. Use absolute workspace paths like /src/index.ts. All reads, writes and commands run inside the same sandbox.`;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/agents/tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAA+B,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,KAAK,EACV,uBAAuB,EACvB,yBAAyB,EACzB,sBAAsB,EACtB,uBAAuB,EACvB,8BAA8B,EAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAWlD,4DAA4D;AAC5D,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC1D,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAOxB,qBAAa,wBAAyB,SAAQ,oBAAoB;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;YAI5C,uBAAuB;IAQ/B,kBAAkB,CAAC,KAAK,EAAE,uBAAuB;IAIjD,iBAAiB,CAAC,KAAK,EAAE,sBAAsB;IAI/C,oBAAoB,CAAC,KAAK,EAAE,yBAAyB;IAIrD,eAAe,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;IAIvD,kBAAkB,CAAC,KAAK,EAAE,uBAAuB;IAIxC,yBAAyB,CAAC,KAAK,EAAE,8BAA8B;IAIxE,mBAAmB;CAG1B;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvF,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAoCrH;AAED,KAAK,mBAAmB,GACpB,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC,GACpC,CAAC,CAAC,GAAG,EAAE;IACL,cAAc,EAAE,cAAc,CAAC;CAChC,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;AAEhG,wBAAgB,kBAAkB,CAChC,UAAU,CAAC,EAAE,UAAU,EACvB,UAAU,CAAC,EAAE,mBAAmB,EAChC,aAAa,CAAC,EAAE,MAAM,EAAE,EACxB,OAAO,CAAC,EAAE,oBAAoB,EAC9B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EACtC,sBAAsB,UAAQ,EAC9B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAEP,qBAE7B;IACD,cAAc,EAAE,cAAc,CAAC;CAChC,KAAG,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAgGrC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/agents/tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAA+B,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,KAAK,EACV,uBAAuB,EACvB,yBAAyB,EACzB,sBAAsB,EACtB,uBAAuB,EACvB,8BAA8B,EAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAWlD,4DAA4D;AAC5D,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC1D,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAOxB,qBAAa,wBAAyB,SAAQ,oBAAoB;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;YAI5C,uBAAuB;IAQ/B,kBAAkB,CAAC,KAAK,EAAE,uBAAuB;IAIjD,iBAAiB,CAAC,KAAK,EAAE,sBAAsB;IAI/C,oBAAoB,CAAC,KAAK,EAAE,yBAAyB;IAIrD,eAAe,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;IAIvD,kBAAkB,CAAC,KAAK,EAAE,uBAAuB;IAIxC,yBAAyB,CAAC,KAAK,EAAE,8BAA8B;IAIxE,mBAAmB;CAG1B;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvF,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAoCrH;AAED,KAAK,mBAAmB,GACpB,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC,GACpC,CAAC,CAAC,GAAG,EAAE;IACL,cAAc,EAAE,cAAc,CAAC;CAChC,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;AAEhG,wBAAgB,kBAAkB,CAChC,UAAU,CAAC,EAAE,UAAU,EACvB,UAAU,CAAC,EAAE,mBAAmB,EAChC,aAAa,CAAC,EAAE,MAAM,EAAE,EACxB,OAAO,CAAC,EAAE,oBAAoB,EAC9B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EACtC,sBAAsB,UAAQ,EAC9B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAEP,qBAE7B;IACD,cAAc,EAAE,cAAc,CAAC;CAChC,KAAG,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CA0GrC"}
@@ -96,13 +96,12 @@ function createDynamicTools(mcpManager, extraTools, disabledTools, storage, plug
96
96
  if (builtinTools) Object.assign(tools, builtinTools);
97
97
  if (storage) tools[MC_TOOLS.NOTIFICATION_INBOX] = createNotificationInboxTool({ storage: new LazyNotificationsStorage(storage) });
98
98
  const configuredWebTools = createConfiguredWebTools();
99
- if (configuredWebTools) Object.assign(tools, configuredWebTools);
100
- else if (isAnthropicModel) tools.web_search = createAnthropic({}).tools.webSearch_20250305();
101
- else if (isOpenAIModel) tools.web_search = createOpenAI({}).tools.webSearch();
102
- if (mcpManager) {
103
- const mcpTools = mcpManager.getTools();
104
- Object.assign(tools, mcpTools);
99
+ if (!configuredWebTools) {
100
+ if (isAnthropicModel) tools.web_search = createAnthropic({}).tools.webSearch_20250305();
101
+ else if (isOpenAIModel) tools.web_search = createOpenAI({}).tools.webSearch();
105
102
  }
103
+ const mcpTools = mcpManager?.getTools();
104
+ if (mcpTools) Object.assign(tools, mcpTools);
106
105
  const finish = (resolvedExtra) => {
107
106
  if (resolvedExtra) {
108
107
  for (const [name, tool] of Object.entries(resolvedExtra)) if (tool && !(name in tools)) tools[name] = tool;
@@ -117,12 +116,19 @@ function createDynamicTools(mcpManager, extraTools, disabledTools, storage, plug
117
116
  }
118
117
  return tools;
119
118
  };
120
- if (typeof extraTools === "function") {
121
- const resolved = extraTools({ requestContext });
122
- if (resolved && typeof resolved.then === "function") return Promise.resolve(resolved).then(finish);
123
- return finish(resolved);
124
- }
125
- return finish(extraTools);
119
+ const resolveExtra = () => {
120
+ if (typeof extraTools === "function") {
121
+ const resolved = extraTools({ requestContext });
122
+ if (resolved && typeof resolved.then === "function") return Promise.resolve(resolved).then(finish);
123
+ return finish(resolved);
124
+ }
125
+ return finish(extraTools);
126
+ };
127
+ if (configuredWebTools) return configuredWebTools.then((webTools) => {
128
+ Object.assign(tools, webTools, mcpTools);
129
+ return resolveExtra();
130
+ });
131
+ return resolveExtra();
126
132
  };
127
133
  }
128
134
  //#endregion