@warlock.js/ai-workspace 5.45.0 → 5.47.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 (48) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +2 -0
  3. package/cjs/index.cjs +514 -54
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/backends/local.d.mts.map +1 -1
  6. package/esm/backends/local.mjs +24 -1
  7. package/esm/backends/local.mjs.map +1 -1
  8. package/esm/contracts/index.d.mts +4 -4
  9. package/esm/contracts/tool-io.type.d.mts +6 -0
  10. package/esm/contracts/tool-io.type.d.mts.map +1 -1
  11. package/esm/contracts/workspace-backend.contract.d.mts +20 -0
  12. package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
  13. package/esm/contracts/workspace-edit.type.d.mts +30 -0
  14. package/esm/contracts/workspace-edit.type.d.mts.map +1 -1
  15. package/esm/contracts/workspace-ops.contract.d.mts +7 -2
  16. package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
  17. package/esm/contracts/workspace-policy.type.d.mts +23 -0
  18. package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
  19. package/esm/contracts/workspace.contract.d.mts +4 -1
  20. package/esm/contracts/workspace.contract.d.mts.map +1 -1
  21. package/esm/hooks/shell-output.mjs +113 -0
  22. package/esm/hooks/shell-output.mjs.map +1 -0
  23. package/esm/index.d.mts +4 -4
  24. package/esm/ops.d.mts.map +1 -1
  25. package/esm/ops.mjs +108 -40
  26. package/esm/ops.mjs.map +1 -1
  27. package/esm/policy/shell-check.d.mts +2 -0
  28. package/esm/policy/shell-check.d.mts.map +1 -1
  29. package/esm/policy/shell-check.mjs +59 -5
  30. package/esm/policy/shell-check.mjs.map +1 -1
  31. package/esm/search/walk.mjs +209 -0
  32. package/esm/search/walk.mjs.map +1 -0
  33. package/esm/shell-jobs.d.mts +1 -1
  34. package/esm/shell-jobs.d.mts.map +1 -1
  35. package/esm/shell-jobs.mjs +1 -1
  36. package/esm/shell-jobs.mjs.map +1 -1
  37. package/esm/tools/glob.mjs +5 -4
  38. package/esm/tools/glob.mjs.map +1 -1
  39. package/esm/tools/grep.mjs +3 -2
  40. package/esm/tools/grep.mjs.map +1 -1
  41. package/esm/tools/run-shell.d.mts.map +1 -1
  42. package/esm/tools/run-shell.mjs +2 -1
  43. package/esm/tools/run-shell.mjs.map +1 -1
  44. package/esm/workspace.mjs +2 -2
  45. package/esm/workspace.mjs.map +1 -1
  46. package/llms-full.txt +36 -17
  47. package/package.json +3 -3
  48. package/skills/use-a-workspace/SKILL.md +36 -17
package/esm/index.d.mts CHANGED
@@ -2,9 +2,9 @@ import { WorkspaceEditMatchTier, WorkspaceEditMatching } from "./edit/match.mjs"
2
2
  import { WorkspaceOutputKeep, WorkspaceOutputPolicy, WorkspaceTruncation } from "./contracts/workspace-output.type.mjs";
3
3
  import { ApplyPatchFileResult, ApplyPatchInput, ApplyPatchResult, EditFileInput, EditFileResult, ExecOptions, GlobInput, GlobResult, GrepInput, GrepMatch, GrepResult, ListDirEntry, ListDirEntryType, ListDirInput, ListDirOptions, ListDirResult, MultiEditInput, MultiEditOperation, MultiEditResult, ReadFileInput, ReadFileResult, RunShellBackgroundResult, RunShellInput, RunShellResult, RunTestsInput, ShellJobInfo, ShellJobStatus, ShellKillInput, ShellOutputInput, ShellOutputOptions, ShellOutputResult, StartShellOptions, TodoListResult, TodoWriteInput, WorkspaceTodo, WorkspaceTodoInput, WorkspaceTodoStatus, WorkspaceToolName, WriteFileInput, WriteFileResult } from "./contracts/tool-io.type.mjs";
4
4
  import { WorkspaceCommandDecision, WorkspaceCommandRule, WorkspaceShellAsk, WorkspaceShellAskDecision, WorkspaceShellAskReason, WorkspaceShellAskRequest, WorkspaceShellClassification, WorkspaceShellOnAsk } from "./contracts/shell-approval.type.mjs";
5
- import { WorkspaceAfterEditEvent, WorkspaceEditLineEndings, WorkspaceEditPolicy, WorkspaceEditTool, WorkspaceHooks } from "./contracts/workspace-edit.type.mjs";
6
- import { WorkspaceAllowCommand, WorkspaceBackendType, WorkspaceBackgroundPolicy, WorkspaceCommandCheckContext, WorkspaceCommandPattern, WorkspaceListPolicy, WorkspacePolicy, WorkspaceReadPolicy, WorkspaceShellInterpreter, WorkspaceShellPolicy } from "./contracts/workspace-policy.type.mjs";
7
- import { WorkspaceBackend, WorkspaceBackendExecOptions, WorkspaceBackendExecResult, WorkspaceBackendProcess, WorkspaceBackendStartOptions, WorkspaceBackendStat, WorkspaceOutputStream } from "./contracts/workspace-backend.contract.mjs";
5
+ import { WorkspaceAfterEditEvent, WorkspaceEditLineEndings, WorkspaceEditPolicy, WorkspaceEditTool, WorkspaceHooks, WorkspaceShellOutputEvent } from "./contracts/workspace-edit.type.mjs";
6
+ import { WorkspaceAllowCommand, WorkspaceBackendType, WorkspaceBackgroundPolicy, WorkspaceCommandCheckContext, WorkspaceCommandPattern, WorkspaceListPolicy, WorkspacePolicy, WorkspaceReadPolicy, WorkspaceSearchPolicy, WorkspaceShellInterpreter, WorkspaceShellPolicy } from "./contracts/workspace-policy.type.mjs";
7
+ import { WorkspaceBackend, WorkspaceBackendDirEntry, WorkspaceBackendExecOptions, WorkspaceBackendExecResult, WorkspaceBackendProcess, WorkspaceBackendStartOptions, WorkspaceBackendStat, WorkspaceOutputStream } from "./contracts/workspace-backend.contract.mjs";
8
8
  import { WorkspaceOps } from "./contracts/workspace-ops.contract.mjs";
9
9
  import { Workspace, WorkspaceTools } from "./contracts/workspace.contract.mjs";
10
10
  import "./contracts/index.mjs";
@@ -34,5 +34,5 @@ import { TodoStore } from "./todos.mjs";
34
34
  import { makeTodoReadTool, makeTodoWriteTool } from "./tools/todo.mjs";
35
35
  import { makeWriteFileTool } from "./tools/write-file.mjs";
36
36
  import { workspace } from "./workspace.mjs";
37
- export { type ApplyPatchFileResult, type ApplyPatchInput, type ApplyPatchResult, type ArgvAnalysis, type EditFileInput, type EditFileResult, type ExecOptions, type FilePatch, type FilePatchKind, type GlobInput, type GlobResult, type GrepInput, type GrepMatch, type GrepResult, type ListDirEntry, type ListDirEntryType, type ListDirInput, type ListDirOptions, type ListDirResult, type MakeGlobToolOptions, type MakeGrepToolOptions, type MakeRunShellToolOptions, type MakeRunTestsToolOptions, type MockBackendSeed, type MockExecResult, type MultiEditInput, type MultiEditOperation, type MultiEditResult, type PatchHunk, type PatchLine, type PowerShellEdition, type ReadFileInput, type ReadFileResult, type ResolvedPath, type ResolvedShell, type ResolvedShellInterpreter, type RunShellBackgroundResult, type RunShellInput, type RunShellResult, type RunTestsInput, type ShellHost, type ShellJobInfo, type ShellJobStatus, type ShellKillInput, type ShellOutputInput, type ShellOutputOptions, type ShellOutputResult, type StartShellOptions, type TodoListResult, TodoStore, type TodoWriteInput, type Workspace, type WorkspaceAfterEditEvent, type WorkspaceAllowCommand, type WorkspaceBackend, type WorkspaceBackendExecOptions, type WorkspaceBackendExecResult, type WorkspaceBackendProcess, type WorkspaceBackendStartOptions, type WorkspaceBackendStat, type WorkspaceBackendType, type WorkspaceBackgroundPolicy, type WorkspaceCommandCheckContext, type WorkspaceCommandDecision, type WorkspaceCommandPattern, type WorkspaceCommandRule, WorkspaceEditError, type WorkspaceEditErrorOptions, type WorkspaceEditFailure, type WorkspaceEditLineEndings, type WorkspaceEditMatchTier, type WorkspaceEditMatching, type WorkspaceEditPolicy, type WorkspaceEditTool, type WorkspaceHooks, WorkspaceJobError, type WorkspaceJobErrorOptions, type WorkspaceJobFailure, type WorkspaceListPolicy, type WorkspaceOps, type WorkspaceOutputKeep, type WorkspaceOutputPolicy, type WorkspaceOutputStream, type WorkspacePolicy, WorkspacePolicyError, type WorkspacePolicyErrorOptions, type WorkspacePolicyViolation, type WorkspaceReadPolicy, type WorkspaceShellAsk, type WorkspaceShellAskDecision, type WorkspaceShellAskReason, type WorkspaceShellAskRequest, type WorkspaceShellCheck, type WorkspaceShellClassification, type WorkspaceShellInterpreter, type WorkspaceShellOnAsk, type WorkspaceShellPath, type WorkspaceShellPathAccess, type WorkspaceShellPolicy, type WorkspaceShellRefusalReason, type WorkspaceTodo, type WorkspaceTodoInput, type WorkspaceTodoStatus, type WorkspaceToolName, type WorkspaceTools, type WorkspaceTruncation, type WriteFileInput, type WriteFileResult, analyzeArgvCommand, applyHunks, applyPatchWithOps, buildEnv, checkShellCommand, classifyShellCommand, createLocalBackend, createMockBackend, createOps, isCommandAllowed, makeApplyPatchTool, makeEditFileTool, makeGlobTool, makeGrepTool, makeListDirTool, makeMultiEditTool, makeReadFileTool, makeRunShellTool, makeRunTestsTool, makeShellKillTool, makeShellOutputTool, makeTodoReadTool, makeTodoWriteTool, makeWriteFileTool, parsePatch, resolveInJail, resolveShell, tokenizeCommand, workspace };
37
+ export { type ApplyPatchFileResult, type ApplyPatchInput, type ApplyPatchResult, type ArgvAnalysis, type EditFileInput, type EditFileResult, type ExecOptions, type FilePatch, type FilePatchKind, type GlobInput, type GlobResult, type GrepInput, type GrepMatch, type GrepResult, type ListDirEntry, type ListDirEntryType, type ListDirInput, type ListDirOptions, type ListDirResult, type MakeGlobToolOptions, type MakeGrepToolOptions, type MakeRunShellToolOptions, type MakeRunTestsToolOptions, type MockBackendSeed, type MockExecResult, type MultiEditInput, type MultiEditOperation, type MultiEditResult, type PatchHunk, type PatchLine, type PowerShellEdition, type ReadFileInput, type ReadFileResult, type ResolvedPath, type ResolvedShell, type ResolvedShellInterpreter, type RunShellBackgroundResult, type RunShellInput, type RunShellResult, type RunTestsInput, type ShellHost, type ShellJobInfo, type ShellJobStatus, type ShellKillInput, type ShellOutputInput, type ShellOutputOptions, type ShellOutputResult, type StartShellOptions, type TodoListResult, TodoStore, type TodoWriteInput, type Workspace, type WorkspaceAfterEditEvent, type WorkspaceAllowCommand, type WorkspaceBackend, type WorkspaceBackendDirEntry, type WorkspaceBackendExecOptions, type WorkspaceBackendExecResult, type WorkspaceBackendProcess, type WorkspaceBackendStartOptions, type WorkspaceBackendStat, type WorkspaceBackendType, type WorkspaceBackgroundPolicy, type WorkspaceCommandCheckContext, type WorkspaceCommandDecision, type WorkspaceCommandPattern, type WorkspaceCommandRule, WorkspaceEditError, type WorkspaceEditErrorOptions, type WorkspaceEditFailure, type WorkspaceEditLineEndings, type WorkspaceEditMatchTier, type WorkspaceEditMatching, type WorkspaceEditPolicy, type WorkspaceEditTool, type WorkspaceHooks, WorkspaceJobError, type WorkspaceJobErrorOptions, type WorkspaceJobFailure, type WorkspaceListPolicy, type WorkspaceOps, type WorkspaceOutputKeep, type WorkspaceOutputPolicy, type WorkspaceOutputStream, type WorkspacePolicy, WorkspacePolicyError, type WorkspacePolicyErrorOptions, type WorkspacePolicyViolation, type WorkspaceReadPolicy, type WorkspaceSearchPolicy, type WorkspaceShellAsk, type WorkspaceShellAskDecision, type WorkspaceShellAskReason, type WorkspaceShellAskRequest, type WorkspaceShellCheck, type WorkspaceShellClassification, type WorkspaceShellInterpreter, type WorkspaceShellOnAsk, type WorkspaceShellOutputEvent, type WorkspaceShellPath, type WorkspaceShellPathAccess, type WorkspaceShellPolicy, type WorkspaceShellRefusalReason, type WorkspaceTodo, type WorkspaceTodoInput, type WorkspaceTodoStatus, type WorkspaceToolName, type WorkspaceTools, type WorkspaceTruncation, type WriteFileInput, type WriteFileResult, analyzeArgvCommand, applyHunks, applyPatchWithOps, buildEnv, checkShellCommand, classifyShellCommand, createLocalBackend, createMockBackend, createOps, isCommandAllowed, makeApplyPatchTool, makeEditFileTool, makeGlobTool, makeGrepTool, makeListDirTool, makeMultiEditTool, makeReadFileTool, makeRunShellTool, makeRunTestsTool, makeShellKillTool, makeShellOutputTool, makeTodoReadTool, makeTodoWriteTool, makeWriteFileTool, parsePatch, resolveInJail, resolveShell, tokenizeCommand, workspace };
38
38
  import "./workspace.mjs";
package/esm/ops.d.mts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ops.d.mts","names":[],"sources":["../../../../../../../packages/ai-workspace/src/ops.ts"],"mappings":";;;;;;;;;;;;;;;;;iBA2MiB;;EAEf,MAAM;;EAEN,YAAY;;;;;;;;;;;;;;;;;;;;;wBA65BE,UACd,SAAS,kBACT,QAAQ,iBACR,UAAU,aACT"}
1
+ {"version":3,"file":"ops.d.mts","names":[],"sources":["../../../../../../../packages/ai-workspace/src/ops.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAqNiB;;EAEf,MAAM;;EAEN,YAAY;;;;;;;;;;;;;;;;;;;;;wBA69BE,UACd,SAAS,kBACT,QAAQ,iBACR,UAAU,aACT"}
package/esm/ops.mjs CHANGED
@@ -6,9 +6,11 @@ import { buildCommandEnv, resolveInJail } from "./policy/policy.mjs";
6
6
  import { executePatch } from "./patch/execute-patch.mjs";
7
7
  import { applyReplacement } from "./edit/apply-replacement.mjs";
8
8
  import { runAfterEdit } from "./hooks/after-edit.mjs";
9
+ import { ShellOutputStreamer } from "./hooks/shell-output.mjs";
9
10
  import { OutputSpiller, splitLines } from "./output/spill.mjs";
10
11
  import { refusalReason, samePattern } from "./policy/approval-helpers.mjs";
11
12
  import { ShellJobs } from "./shell-jobs.mjs";
13
+ import { DEFAULT_SEARCH_MAX_FILES, DEFAULT_SEARCH_SKIP_DIRS, staticGlobPrefix, walkFiles } from "./search/walk.mjs";
12
14
  import path from "node:path";
13
15
  import { fs } from "@warlock.js/fs";
14
16
 
@@ -183,20 +185,35 @@ var Ops = class {
183
185
  }
184
186
  }
185
187
  /**
186
- * Recursively collect every file under `absDir` as absolute paths,
187
- * via the backend's `list` (so it works over disk or the in-memory
188
- * mock). Directories are descended; files are accumulated.
188
+ * Collect the files a `glob` or `grep` should look at, starting at the
189
+ * folder the pattern is anchored to (see {@link staticGlobPrefix}).
190
+ * `denyPaths`, `search.skipDirs` and `.gitignore` are applied before a
191
+ * directory is listed.
189
192
  */
190
- async walkFiles(absDir) {
191
- const found = [];
192
- const entries = await this.backend.list(absDir);
193
- await Promise.all(entries.map(async (entry) => {
194
- if (await this.isDirectory(entry)) {
195
- const nested = await this.walkFiles(entry);
196
- found.push(...nested);
197
- } else found.push(entry);
198
- }));
199
- return found;
193
+ async searchFiles(jailRoot, pattern, signal) {
194
+ const prefix = pattern === void 0 ? "" : staticGlobPrefix(pattern);
195
+ const search = this.policy.search;
196
+ let startDir = jailRoot;
197
+ if (prefix !== "") try {
198
+ startDir = (await resolveInJail(this.policy, prefix)).absolutePath;
199
+ } catch (error) {
200
+ if (error instanceof WorkspacePolicyError) return {
201
+ files: [],
202
+ truncated: false
203
+ };
204
+ throw error;
205
+ }
206
+ return walkFiles({
207
+ backend: this.backend,
208
+ jailRoot,
209
+ startDir,
210
+ denyPaths: this.policy.denyPaths ?? [],
211
+ skipDirs: search?.skipDirs ?? DEFAULT_SEARCH_SKIP_DIRS,
212
+ gitignore: search?.gitignore ?? true,
213
+ maxFiles: search?.maxFiles ?? 5e4,
214
+ concurrency: Math.max(1, search?.concurrency ?? 16),
215
+ ...signal ? { signal } : {}
216
+ });
200
217
  }
201
218
  /** Whether an absolute path is a directory, by attempting to list it. */
202
219
  async isDirectory(absPath) {
@@ -435,14 +452,29 @@ var Ops = class {
435
452
  const timeoutMs = opts?.timeoutMs ?? shell?.timeoutMs;
436
453
  const maxOutputBytes = shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
437
454
  const interpreter = this.resolvedShell.interpreter;
438
- const result = await this.backend.exec(command, {
439
- cwd: this.policy.cwd,
440
- timeoutMs,
441
- env: buildCommandEnv(this.policy),
442
- ...signal ? { signal } : {},
443
- ...interpreter !== "none" ? { interpreter } : {},
444
- ...interpreter !== "none" && this.resolvedShell.file !== void 0 ? { interpreterPath: this.resolvedShell.file } : {}
445
- });
455
+ const env = buildCommandEnv(this.policy);
456
+ const live = ShellOutputStreamer.create(this.policy.hooks, {
457
+ command,
458
+ ...opts?.callId !== void 0 ? { callId: opts.callId } : {}
459
+ }, this.policy.shell?.env);
460
+ let result;
461
+ try {
462
+ result = await this.backend.exec(command, {
463
+ cwd: this.policy.cwd,
464
+ timeoutMs,
465
+ env,
466
+ ...live ? { onOutput: (stream, chunk) => live.push(stream, chunk) } : {},
467
+ ...signal ? { signal } : {},
468
+ ...interpreter !== "none" ? { interpreter } : {},
469
+ ...interpreter !== "none" && this.resolvedShell.file !== void 0 ? { interpreterPath: this.resolvedShell.file } : {}
470
+ });
471
+ if (live !== void 0 && !live.received) {
472
+ live.push("stdout", result.stdout);
473
+ live.push("stderr", result.stderr);
474
+ }
475
+ } finally {
476
+ live?.end();
477
+ }
446
478
  const stdout = await this.fitStream("run_shell-stdout", result.stdout, maxOutputBytes);
447
479
  const stderr = await this.fitStream("run_shell-stderr", result.stderr, maxOutputBytes);
448
480
  const cancelled = result.cancelled === true;
@@ -482,13 +514,24 @@ var Ops = class {
482
514
  retainBytes: shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
483
515
  maxJobs: background.maxJobs ?? 4,
484
516
  ...timeoutMs !== void 0 ? { timeoutMs } : {},
485
- launch: (onOutput) => start(command, {
486
- cwd: this.policy.cwd,
487
- env,
488
- ...interpreter !== "none" ? { interpreter } : {},
489
- ...interpreterPath !== void 0 ? { interpreterPath } : {},
490
- onOutput
491
- })
517
+ launch: (onOutput, jobId) => {
518
+ const live = ShellOutputStreamer.create(this.policy.hooks, {
519
+ command,
520
+ jobId
521
+ }, this.policy.shell?.env);
522
+ const handle = start(command, {
523
+ cwd: this.policy.cwd,
524
+ env,
525
+ ...interpreter !== "none" ? { interpreter } : {},
526
+ ...interpreterPath !== void 0 ? { interpreterPath } : {},
527
+ onOutput: (stream, chunk) => {
528
+ onOutput(stream, chunk);
529
+ live?.push(stream, chunk);
530
+ }
531
+ });
532
+ handle.done.then(() => live?.end());
533
+ return handle;
534
+ }
492
535
  });
493
536
  }
494
537
  async shellOutput(id, opts) {
@@ -623,9 +666,11 @@ var Ops = class {
623
666
  const flags = opts?.ignoreCase ? "i" : "";
624
667
  const regex = new RegExp(pattern, flags);
625
668
  const globRegex = opts?.glob ? globToRegExp(opts.glob) : void 0;
626
- const files = await this.walkFiles(jailRoot);
669
+ const walked = await this.searchFiles(jailRoot, opts?.glob, opts?.signal);
670
+ const files = walked.files.sort();
627
671
  const matches = [];
628
672
  for (const absFile of files) {
673
+ opts?.signal?.throwIfAborted();
629
674
  const relativePath = path.relative(jailRoot, absFile).split(path.sep).join("/");
630
675
  if (globRegex && !globRegex.test(relativePath)) continue;
631
676
  if (this.isDenied(relativePath)) continue;
@@ -645,14 +690,22 @@ var Ops = class {
645
690
  line: index + 1,
646
691
  text: line
647
692
  });
648
- if (matches.length >= DEFAULT_MAX_GREP_MATCHES) return this.fitMatches(matches);
693
+ if (matches.length >= DEFAULT_MAX_GREP_MATCHES) return this.fitMatches(matches, walked.truncated);
649
694
  }
650
695
  }
651
696
  }
652
- return this.fitMatches(matches);
697
+ return this.fitMatches(matches, walked.truncated);
653
698
  }
654
699
  /** Apply the `output` limits to a finished match list. */
655
- async fitMatches(matches) {
700
+ async fitMatches(matches, maxFilesReached) {
701
+ const result = await this.fitMatchesWithin(matches);
702
+ return maxFilesReached ? {
703
+ ...result,
704
+ truncated: true,
705
+ maxFilesReached: true
706
+ } : result;
707
+ }
708
+ async fitMatchesWithin(matches) {
656
709
  const fitted = await this.spiller.list("grep", matches, (match) => `${match.path}:${match.line}:${match.text}`, "head");
657
710
  return fitted.truncation === void 0 ? {
658
711
  matches,
@@ -665,27 +718,42 @@ var Ops = class {
665
718
  ...spilledField({ stdout: fitted.truncation })
666
719
  };
667
720
  }
668
- async globResult(pattern) {
669
- const all = await this.glob(pattern);
721
+ async globResult(pattern, opts) {
722
+ const { paths: all, maxFilesReached } = await this.globWalk(pattern, opts?.signal);
670
723
  const fitted = await this.spiller.list("glob", all, (entry) => entry, "head");
671
- return fitted.truncation === void 0 ? { paths: all } : {
724
+ const reached = maxFilesReached ? {
725
+ truncated: true,
726
+ maxFilesReached: true
727
+ } : {};
728
+ return fitted.truncation === void 0 ? {
729
+ paths: all,
730
+ ...reached
731
+ } : {
672
732
  paths: fitted.items,
673
733
  truncated: true,
674
734
  truncation: fitted.truncation,
675
- ...spilledField({ stdout: fitted.truncation })
735
+ ...spilledField({ stdout: fitted.truncation }),
736
+ ...reached
676
737
  };
677
738
  }
678
- async glob(pattern) {
739
+ async glob(pattern, opts) {
740
+ return (await this.globWalk(pattern, opts?.signal)).paths;
741
+ }
742
+ /** Match `pattern` against the walked files; sorted, with whether the walk hit `maxFiles`. */
743
+ async globWalk(pattern, signal) {
679
744
  const { absolutePath: jailRoot } = await resolveInJail(this.policy, ".");
680
745
  const regex = globToRegExp(pattern);
681
- const files = await this.walkFiles(jailRoot);
746
+ const walked = await this.searchFiles(jailRoot, pattern, signal);
682
747
  const matched = [];
683
- for (const absFile of files) {
748
+ for (const absFile of walked.files) {
684
749
  const relativePath = path.relative(jailRoot, absFile).split(path.sep).join("/");
685
750
  if (this.isDenied(relativePath)) continue;
686
751
  if (regex.test(relativePath)) matched.push(relativePath);
687
752
  }
688
- return matched.sort();
753
+ return {
754
+ paths: matched.sort(),
755
+ maxFilesReached: walked.truncated
756
+ };
689
757
  }
690
758
  async exists(inputPath) {
691
759
  const { absolutePath } = await resolveInJail(this.policy, inputPath);
package/esm/ops.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ops.mjs","names":[],"sources":["../../../../../../../packages/ai-workspace/src/ops.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fs } from \"@warlock.js/fs\";\nimport { applyReplacement, type AppliedReplacementOptions } from \"./edit/apply-replacement\";\nimport { WorkspaceEditError, WorkspacePolicyError } from \"./errors\";\nimport { runAfterEdit } from \"./hooks/after-edit\";\nimport { OutputSpiller, splitLines } from \"./output/spill\";\nimport { globToRegExp, isInside, matchesAnyGlob } from \"./policy/jail\";\nimport { buildCommandEnv, resolveInJail } from \"./policy/policy\";\nimport { refusalReason, samePattern } from \"./policy/approval-helpers\";\nimport { checkShellCommand } from \"./policy/shell-check\";\nimport { resolveShell, type ResolvedShell } from \"./policy/shell-interpreter\";\nimport { DEFAULT_MAX_BACKGROUND_JOBS, ShellJobs } from \"./shell-jobs\";\nimport { executePatch } from \"./patch/execute-patch\";\nimport type {\n ApplyPatchInput,\n ApplyPatchResult,\n EditFileInput,\n EditFileResult,\n ExecOptions,\n GrepMatch,\n GlobResult,\n GrepResult,\n ListDirEntry,\n ListDirEntryType,\n ListDirOptions,\n ListDirResult,\n MultiEditInput,\n MultiEditResult,\n ReadFileResult,\n RunShellResult,\n ShellJobInfo,\n ShellOutputOptions,\n ShellOutputResult,\n StartShellOptions,\n WorkspaceBackend,\n WorkspaceCommandPattern,\n WorkspaceOps,\n WorkspacePolicy,\n WorkspaceShellAsk,\n WorkspaceShellOnAsk,\n WorkspaceEditTool,\n WorkspaceTruncation,\n} from \"./contracts\";\n\n/** Default line window a read returns when the policy sets no `defaultLines`. */\nconst DEFAULT_READ_LINES = 2000;\n/** Hard ceiling on grep matches returned, so a broad pattern can't flood. */\nconst DEFAULT_MAX_GREP_MATCHES = 1000;\n/** Default per-command output byte cap when the policy sets none. */\nconst DEFAULT_MAX_OUTPUT_BYTES = 1_000_000;\n/** Default `list.ignore` when the policy sets none. */\nconst DEFAULT_LIST_IGNORE: readonly string[] = [\"node_modules\", \".git\"];\n/** Default deepest `depth` a listing may ask for. */\nconst DEFAULT_LIST_MAX_DEPTH = 5;\n/** Default most entries one listing returns. */\nconst DEFAULT_LIST_MAX_ENTRIES = 500;\n/** Most edits one `multiEdit` call may carry. */\nexport const MAX_MULTI_EDITS = 100;\n/**\n * Exit code reported for a command refused because its signal was already\n * aborted (the shell convention for an interrupted command). Callers should\n * branch on `cancelled`, not on this number.\n */\nconst CANCELLED_BEFORE_START_EXIT_CODE = 130;\n/**\n * Hard ceiling on `grep` pattern length. A model-controlled regex has no\n * legitimate reason to be this long; longer patterns are rejected outright\n * rather than compiled.\n */\nconst MAX_GREP_PATTERN_LENGTH = 200;\n/**\n * Hard ceiling on the number of characters of a single line handed to\n * `RegExp#test` during a `grep`. Catastrophic backtracking is exponential\n * in input length, so bounding the input scanned per call bounds the\n * worst-case time a single pathological line can cost — lines longer than\n * this are skipped rather than scanned.\n */\nconst MAX_GREP_LINE_SCAN_LENGTH = 2000;\n\n/** The `cat -n` prefix for a line: right-aligned number (min width 6) and a tab. */\nfunction linePrefix(lineNumber: number): string {\n return `${String(lineNumber).padStart(6, \" \")}\\t`;\n}\n\n/**\n * Clip `value` to at most `maxBytes` UTF-8 bytes without splitting a\n * character: the cut backs off over continuation bytes (`10xxxxxx`) to the\n * start of the character it would have split, so the result never carries\n * a replacement character.\n */\nfunction clipUtf8(value: string, maxBytes: number): { value: string; truncated: boolean } {\n const bytes = Buffer.from(value, \"utf8\");\n\n if (bytes.byteLength <= maxBytes) {\n return { value, truncated: false };\n }\n\n let cut = Math.max(0, maxBytes);\n\n while (cut > 0 && ((bytes[cut] ?? 0) & 0xc0) === 0x80) {\n cut--;\n }\n\n return { value: bytes.subarray(0, cut).toString(\"utf8\"), truncated: true };\n}\n\n/**\n * Number a window of lines `cat -n` style, keeping the joined output within\n * `maxBytes` UTF-8 bytes (prefixes and newlines included) when a cap is set.\n * A line that does not fit whole is clipped on a character boundary; it is\n * still counted as returned when its prefix and at least one character fit.\n */\nfunction numberWindow(\n lines: string[],\n startLine: number,\n maxBytes: number | undefined,\n): { content: string; returnedLines: number; bytesCapped: boolean } {\n const parts: string[] = [];\n let used = 0;\n\n for (let index = 0; index < lines.length; index++) {\n const prefix = linePrefix(startLine + index);\n const numbered = prefix + lines[index];\n const separator = parts.length > 0 ? 1 : 0;\n\n if (maxBytes === undefined) {\n parts.push(numbered);\n\n continue;\n }\n\n const cost = separator + Buffer.byteLength(numbered, \"utf8\");\n\n if (used + cost <= maxBytes) {\n parts.push(numbered);\n used += cost;\n\n continue;\n }\n\n const room = maxBytes - used - separator - Buffer.byteLength(prefix, \"utf8\");\n const clipped = room > 0 ? clipUtf8(lines[index] ?? \"\", room).value : \"\";\n\n if (clipped.length > 0) {\n parts.push(prefix + clipped);\n }\n\n return { content: parts.join(\"\\n\"), returnedLines: parts.length, bytesCapped: true };\n }\n\n return { content: parts.join(\"\\n\"), returnedLines: parts.length, bytesCapped: false };\n}\n\n/**\n * Clip a captured stream at `maxBytes` (measured in UTF-8 bytes), on a\n * character boundary. Returns the possibly-clipped string plus whether\n * clipping occurred.\n */\nfunction capOutput(value: string, maxBytes: number): { value: string; truncated: boolean } {\n return clipUtf8(value, maxBytes);\n}\n\n/**\n * A quantifier: `+`, `*`, `?`, or a `{m,n}` bound.\n */\nconst QUANTIFIER_SOURCE = String.raw`[+*?]|\\{\\d*,?\\d*\\}`;\n\n/**\n * Heuristic catastrophic-backtracking detector: flags a quantified group\n * that itself contains a quantifier (e.g. `(a+)+`, `(a*)*`, `(.+)*`) —\n * the classic exponential-blowup shape. Not a full regex-safety analyzer\n * (it won't catch every ReDoS shape, e.g. quantified alternation like\n * `(a|a)+`), but it rejects the shape an agent is most likely to emit,\n * intentionally or via prompt injection.\n */\nconst NESTED_QUANTIFIER_PATTERN = new RegExp(\n String.raw`\\([^()]*(?:${QUANTIFIER_SOURCE})[^()]*\\)(?:${QUANTIFIER_SOURCE})`,\n);\n\n/**\n * Whether `pattern` is safe enough to compile and run against workspace\n * content: within the length cap and free of the nested-quantifier shape\n * that causes catastrophic regex backtracking (ReDoS).\n */\nfunction isSafeGrepPattern(pattern: string): boolean {\n if (pattern.length > MAX_GREP_PATTERN_LENGTH) {\n return false;\n }\n\n return !NESTED_QUANTIFIER_PATTERN.test(pattern);\n}\n\n/**\n * The internal, single-instance implementation of {@link WorkspaceOps}.\n * Holds the backend + policy and is the one place the jail, command\n * gating, read caps, and the read-before-edit guard are enforced — both\n * the agent-facing tools and the human-facing direct methods funnel\n * through this object, so there is exactly one set of rules.\n */\n/**\n * State an ops layer shares with the other projections of the same\n * workspace (`readonly()`, `scope()`): the background commands it owns.\n */\nexport interface OpsRuntime {\n /** The workspace's background commands. */\n jobs: ShellJobs;\n /** Patterns the host approved with `remember`, shared by every view of the workspace. */\n approvals?: WorkspaceCommandPattern[];\n}\n\nclass Ops implements WorkspaceOps {\n /**\n * Tail of the mutation chain per resolved absolute path. Writes, edits and\n * removes on the same path run one after another, so two tool calls the\n * agent dispatches concurrently cannot both read the same content, both\n * pass the stale-hash guard and silently drop one edit: the second one\n * re-reads after the first has written.\n */\n private readonly pathLocks = new Map<string, Promise<void>>();\n\n /** The shell `policy.shell.interpreter` resolves to on this host, resolved once. */\n private readonly resolvedShell: ResolvedShell;\n\n /** Patterns approved with `remember` (shared through the runtime). */\n private readonly approvals: WorkspaceCommandPattern[];\n\n /** Saves oversized output to files when `output.spillDir` is set. */\n private readonly spiller: OutputSpiller;\n\n public constructor(\n private readonly backend: WorkspaceBackend,\n private readonly policy: WorkspacePolicy,\n private readonly runtime: OpsRuntime,\n ) {\n this.resolvedShell = resolveShell(policy.shell);\n this.approvals = runtime.approvals ?? [];\n this.spiller = new OutputSpiller(backend, policy);\n }\n\n /** The edit-policy settings applied to every replacement. */\n private editOptions(): AppliedReplacementOptions {\n const edit = this.policy.edit;\n\n return {\n matching: edit?.matching ?? \"tiered\",\n missHintLines: edit?.missHintLines ?? 12,\n lineEndings: edit?.lineEndings ?? \"preserve\",\n ...(this.policy.read?.maxBytes !== undefined\n ? { hintMaxBytes: this.policy.read.maxBytes }\n : {}),\n };\n }\n\n /** Run `afterEdit` for a call that wrote `paths`; `undefined` when there is no output. */\n private diagnosticsFor(\n tool: WorkspaceEditTool,\n paths: readonly string[],\n ): Promise<string | undefined> {\n return runAfterEdit(this.policy, { paths, tool });\n }\n\n /** Run `task` after every earlier mutation of `absolutePath` has settled. */\n private async withPathLock<T>(absolutePath: string, task: () => Promise<T>): Promise<T> {\n const previous = this.pathLocks.get(absolutePath) ?? Promise.resolve();\n let release: () => void = () => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n const tail = previous.then(() => current);\n\n this.pathLocks.set(absolutePath, tail);\n\n try {\n await previous;\n\n return await task();\n } finally {\n release();\n\n if (this.pathLocks.get(absolutePath) === tail) {\n this.pathLocks.delete(absolutePath);\n }\n }\n }\n\n /**\n * Recursively collect every file under `absDir` as absolute paths,\n * via the backend's `list` (so it works over disk or the in-memory\n * mock). Directories are descended; files are accumulated.\n */\n private async walkFiles(absDir: string): Promise<string[]> {\n const found: string[] = [];\n const entries = await this.backend.list(absDir);\n\n await Promise.all(\n entries.map(async (entry) => {\n // A child is a directory iff listing it succeeds; the backend\n // throws/returns for a file. Probe via `exists` + a list guard.\n const isDir = await this.isDirectory(entry);\n\n if (isDir) {\n const nested = await this.walkFiles(entry);\n found.push(...nested);\n } else {\n found.push(entry);\n }\n }),\n );\n\n return found;\n }\n\n /** Whether an absolute path is a directory, by attempting to list it. */\n private async isDirectory(absPath: string): Promise<boolean> {\n try {\n await this.backend.list(absPath);\n\n return true;\n } catch {\n return false;\n }\n }\n\n public async readFile(\n inputPath: string,\n opts?: { offset?: number; limit?: number },\n ): Promise<ReadFileResult> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath, \"read\");\n const raw = await this.backend.readFile(absolutePath);\n\n // Hash the full content the backend returned (SHA-256 via\n // @warlock.js/fs), not the returned window and not the on-disk file —\n // the mock backend keeps content in memory, and the stale-edit guard\n // later hashes the backend's content too, so it must be the SAME\n // source of truth whatever window or byte cap applied.\n const hash = fs.hash.string(raw);\n\n const lines = raw.split(\"\\n\");\n const totalLines = lines.length;\n\n const offset = Math.max(1, opts?.offset ?? 1);\n const limit = opts?.limit ?? this.policy.read?.defaultLines ?? DEFAULT_READ_LINES;\n const maxBytes = this.policy.read?.maxBytes;\n\n const startIndex = offset - 1;\n const window = lines.slice(startIndex, startIndex + Math.max(0, limit));\n const { content, returnedLines, bytesCapped } = numberWindow(\n window,\n offset,\n maxBytes === undefined ? undefined : Math.max(0, Math.floor(maxBytes)),\n );\n const endLine = offset + returnedLines - 1;\n const spillDir = this.spiller.spillDir;\n let spilled: { content: string; truncation: WorkspaceTruncation } | undefined;\n\n // A read of a spill file is never spilled again; it is already paged.\n if (bytesCapped && spillDir !== undefined && !isInside(absolutePath, spillDir)) {\n const full = numberWindow(window, offset, undefined).content;\n const file = await this.spiller.save(\"read_file\", full);\n\n if (file !== undefined) {\n spilled = {\n content: `${content}\\n[showing lines 1-${returnedLines} of ${window.length}; full output saved to ${file}]`,\n truncation: {\n shown: { lines: returnedLines },\n total: { lines: window.length, bytes: Buffer.byteLength(full, \"utf8\") },\n path: file,\n },\n };\n }\n }\n\n return {\n content: spilled?.content ?? content,\n ...(spilled !== undefined ? { truncation: spilled.truncation } : {}),\n ...(spilled?.truncation.path !== undefined ? { spilledTo: [spilled.truncation.path] } : {}),\n startLine: offset,\n endLine,\n totalLines,\n truncated: bytesCapped || endLine < totalLines,\n bytesCapped,\n totalBytes: Buffer.byteLength(raw, \"utf8\"),\n hash,\n };\n }\n\n public async writeFile(\n inputPath: string,\n content: string,\n ): Promise<{ hash: string; bytesWritten: number; diagnostics?: string }> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, inputPath);\n\n const written = await this.withPathLock(absolutePath, async () => {\n // Ensure the parent directory exists before writing the leaf.\n const parent = path.dirname(absolutePath);\n await this.backend.mkdir(parent);\n\n await this.backend.writeFile(absolutePath, content);\n\n return {\n hash: fs.hash.string(content),\n bytesWritten: Buffer.byteLength(content, \"utf8\"),\n };\n });\n const diagnostics = await this.diagnosticsFor(\"write_file\", [relativePath || inputPath]);\n\n return { ...written, ...(diagnostics !== undefined ? { diagnostics } : {}) };\n }\n\n public async editFile(input: EditFileInput): Promise<EditFileResult> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);\n\n const result = await this.withPathLock(absolutePath, () =>\n this.applyEdit(input, absolutePath, relativePath),\n );\n const diagnostics = await this.diagnosticsFor(\"edit_file\", [result.path]);\n\n return diagnostics === undefined ? result : { ...result, diagnostics };\n }\n\n /** The read-check-replace-write of {@link editFile}, run under its path lock. */\n private async applyEdit(\n input: EditFileInput,\n absolutePath: string,\n relativePath: string,\n ): Promise<EditFileResult> {\n const current = await this.backend.readFile(absolutePath);\n const currentHash = fs.hash.string(current);\n\n // Stale-hash guard: if the caller pinned a hash and the file moved\n // underneath them, refuse so they re-read before clobbering.\n if (input.expectHash !== undefined && input.expectHash !== currentHash) {\n throw new WorkspaceEditError(\n `File \"${input.path}\" changed since it was read; the edit is stale.`,\n {\n type: \"stale-hash\",\n path: relativePath || input.path,\n expectedHash: input.expectHash,\n actualHash: currentHash,\n },\n );\n }\n\n const {\n content: updated,\n replacements,\n matchTier,\n } = applyReplacement(\n current,\n input,\n input.path,\n relativePath || input.path,\n this.editOptions(),\n );\n\n await this.backend.writeFile(absolutePath, updated);\n\n return {\n path: relativePath || input.path,\n replacements,\n hash: fs.hash.string(updated),\n ...(matchTier !== undefined ? { matchTier } : {}),\n };\n }\n\n public async multiEdit(input: MultiEditInput): Promise<MultiEditResult> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);\n const shownPath = relativePath || input.path;\n\n if (input.edits.length === 0 || input.edits.length > MAX_MULTI_EDITS) {\n throw new RangeError(\n `A multi-edit needs between 1 and ${MAX_MULTI_EDITS} edits (got ${input.edits.length}).`,\n );\n }\n\n const result = await this.withPathLock(absolutePath, async () => {\n const current = await this.backend.readFile(absolutePath);\n const currentHash = fs.hash.string(current);\n\n if (input.expectHash !== undefined && input.expectHash !== currentHash) {\n throw new WorkspaceEditError(\n `File \"${input.path}\" changed since it was read; none of the edits were applied.`,\n {\n type: \"stale-hash\",\n path: shownPath,\n expectedHash: input.expectHash,\n actualHash: currentHash,\n },\n );\n }\n\n // Apply every edit in memory first; one failure leaves the file untouched.\n let content = current;\n let replacements = 0;\n let matchTier: MultiEditResult[\"matchTier\"];\n const options = this.editOptions();\n\n input.edits.forEach((edit, editIndex) => {\n const applied = applyReplacement(content, edit, input.path, shownPath, options, editIndex);\n\n content = applied.content;\n replacements += applied.replacements;\n\n if (applied.matchTier === \"whitespace\" || matchTier === undefined) {\n matchTier = applied.matchTier ?? matchTier;\n }\n });\n\n await this.backend.writeFile(absolutePath, content);\n\n return {\n path: shownPath,\n edits: input.edits.length,\n replacements,\n hash: fs.hash.string(content),\n ...(matchTier !== undefined ? { matchTier } : {}),\n };\n });\n const diagnostics = await this.diagnosticsFor(\"multi_edit\", [shownPath]);\n\n return diagnostics === undefined ? result : { ...result, diagnostics };\n }\n\n public async applyPatch(input: ApplyPatchInput): Promise<ApplyPatchResult> {\n const result = await this.runPatch(input);\n\n if (result.dryRun) {\n return result;\n }\n\n const paths = result.files.filter((file) => file.action !== \"deleted\").map((file) => file.path);\n const diagnostics = await this.diagnosticsFor(\"apply_patch\", paths);\n\n return diagnostics === undefined ? result : { ...result, diagnostics };\n }\n\n private runPatch(input: ApplyPatchInput): Promise<ApplyPatchResult> {\n return executePatch(input, {\n resolve: (inputPath) => resolveInJail(this.policy, inputPath),\n exists: (absolutePath) => this.backend.exists(absolutePath),\n stat: this.backend.stat\n ? (absolutePath) =>\n (this.backend.stat as NonNullable<WorkspaceBackend[\"stat\"]>)(absolutePath)\n : undefined,\n read: (absolutePath) => this.backend.readFile(absolutePath),\n write: async (absolutePath, content) => {\n await this.backend.mkdir(path.dirname(absolutePath));\n await this.backend.writeFile(absolutePath, content);\n },\n remove: (absolutePath) => this.backend.remove(absolutePath),\n withLock: (key, task) => this.withPathLock(key, task),\n display: (absolutePath) => path.relative(this.policy.cwd, absolutePath),\n });\n }\n\n /**\n * Check a command line against the shell policy: every command in it\n * (argv or shell mode), then — shell mode — every redirection target and\n * `cd` directory against the jail with symlinks resolved.\n */\n private async assertCommandAllowed(command: string, signal?: AbortSignal): Promise<void> {\n const verdict = checkShellCommand(this.policy, command, {\n remembered: this.approvals,\n resolved: this.resolvedShell,\n });\n const onAsk = this.policy.shell?.onAsk;\n const needsApproval =\n !verdict.allowed && verdict.reason === \"approval-required\" && verdict.asks.length > 0;\n\n if (!verdict.allowed && !(needsApproval && onAsk !== undefined)) {\n throw new WorkspacePolicyError(\n verdict.message ?? `Command is not permitted by the workspace shell policy: ${command}`,\n {\n type: \"denied-command\",\n command,\n ...(verdict.reason !== undefined ? { reason: verdict.reason } : {}),\n ...(verdict.executable !== undefined ? { executable: verdict.executable } : {}),\n ...(verdict.path !== undefined ? { path: verdict.path } : {}),\n },\n );\n }\n\n for (const target of verdict.paths) {\n try {\n await resolveInJail(this.policy, target.path);\n } catch (error) {\n if (!(error instanceof WorkspacePolicyError)) {\n throw error;\n }\n\n throw new WorkspacePolicyError(\n target.access === \"cwd\"\n ? `\\`cd ${target.target}\\` resolves (through a symlink) outside the workspace jail or onto a denied path.`\n : `The redirection target \\`${target.target}\\` resolves (through a symlink) outside the workspace jail or onto a denied path.`,\n { type: \"denied-command\", reason: \"path-escape\", command, path: target.target },\n );\n }\n }\n\n if (this.runtime.jobs.isDisposed) {\n throw new WorkspacePolicyError(\n \"The workspace has been disposed; it cannot run new commands.\",\n { type: \"denied-command\", reason: \"not-allowed\", command },\n );\n }\n\n if (needsApproval && onAsk !== undefined) {\n await this.askForApproval(command, verdict.asks, onAsk, signal);\n }\n }\n\n /** Put the commands that need approval to `shell.onAsk`; refuse the line unless it says yes. */\n private async askForApproval(\n command: string,\n asks: WorkspaceShellAsk[],\n onAsk: WorkspaceShellOnAsk,\n signal: AbortSignal | undefined,\n ): Promise<void> {\n let decision: Awaited<ReturnType<WorkspaceShellOnAsk>> | undefined;\n\n try {\n decision = await onAsk({ command, asks, ...(signal ? { signal } : {}) });\n } catch {\n decision = undefined;\n }\n\n if (decision?.allow !== true) {\n const reason = refusalReason(decision?.reason);\n\n throw new WorkspacePolicyError(\n `Running ${asks.map((ask) => `\\`${ask.argv.join(\" \")}\\``).join(\", \")} was not approved${reason}`,\n { type: \"denied-command\", reason: \"approval-denied\", command },\n );\n }\n\n if (decision.remember !== undefined) {\n const patterns = Array.isArray(decision.remember) ? decision.remember : [decision.remember];\n\n for (const pattern of patterns as WorkspaceCommandPattern[]) {\n if (!this.approvals.some((entry) => samePattern(entry, pattern))) {\n this.approvals.push(pattern);\n }\n }\n }\n }\n\n public async exec(command: string, opts?: ExecOptions): Promise<RunShellResult> {\n await this.assertCommandAllowed(command, opts?.signal);\n\n const signal = opts?.signal;\n\n // Refuse an already-cancelled invocation here, so no backend — built-in\n // or custom — ever starts a command whose caller has given up on it.\n if (signal?.aborted) {\n return {\n exitCode: CANCELLED_BEFORE_START_EXIT_CODE,\n stdout: \"\",\n stderr: \"Command was not executed: the invocation was cancelled before it started.\",\n truncated: false,\n timedOut: false,\n cancelled: true,\n };\n }\n\n const shell = this.policy.shell;\n const timeoutMs = opts?.timeoutMs ?? shell?.timeoutMs;\n const maxOutputBytes = shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;\n const interpreter = this.resolvedShell.interpreter;\n\n const result = await this.backend.exec(command, {\n cwd: this.policy.cwd,\n timeoutMs,\n env: buildCommandEnv(this.policy),\n ...(signal ? { signal } : {}),\n ...(interpreter !== \"none\" ? { interpreter } : {}),\n ...(interpreter !== \"none\" && this.resolvedShell.file !== undefined\n ? { interpreterPath: this.resolvedShell.file }\n : {}),\n });\n\n const stdout = await this.fitStream(\"run_shell-stdout\", result.stdout, maxOutputBytes);\n const stderr = await this.fitStream(\"run_shell-stderr\", result.stderr, maxOutputBytes);\n const cancelled = result.cancelled === true;\n const truncation = streamTruncation(stdout.truncation, stderr.truncation);\n\n return {\n exitCode: result.exitCode,\n stdout: stdout.value,\n stderr: stderr.value,\n truncated: stdout.truncated || stderr.truncated,\n timedOut: result.timedOut && !cancelled,\n cancelled,\n ...(truncation !== undefined ? { truncation } : {}),\n ...spilledField(truncation),\n };\n }\n\n public async startShell(command: string, opts?: StartShellOptions): Promise<ShellJobInfo> {\n const background = this.policy.shell?.background;\n\n if (background === undefined) {\n throw new WorkspacePolicyError(\n \"Background commands are not enabled for this workspace. Run the command without background, or ask for shell.background to be enabled.\",\n { type: \"denied-command\", reason: \"not-allowed\", command },\n );\n }\n\n await this.assertCommandAllowed(command);\n\n const start = this.backend.start?.bind(this.backend);\n\n if (start === undefined) {\n throw new WorkspacePolicyError(\"This workspace backend cannot run background commands.\", {\n type: \"denied-command\",\n reason: \"not-allowed\",\n command,\n });\n }\n\n const shell = this.policy.shell;\n const interpreter = this.resolvedShell.interpreter;\n const interpreterPath = interpreter !== \"none\" ? this.resolvedShell.file : undefined;\n const timeoutMs = lowerOf(background.timeoutMs, opts?.timeoutMs);\n const env = buildCommandEnv(this.policy);\n\n return this.runtime.jobs.start({\n command,\n retainBytes: shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxJobs: background.maxJobs ?? DEFAULT_MAX_BACKGROUND_JOBS,\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n launch: (onOutput) =>\n start(command, {\n cwd: this.policy.cwd,\n env,\n ...(interpreter !== \"none\" ? { interpreter } : {}),\n ...(interpreterPath !== undefined ? { interpreterPath } : {}),\n onOutput,\n }),\n });\n }\n\n public async shellOutput(id: string, opts?: ShellOutputOptions): Promise<ShellOutputResult> {\n const result = await this.runtime.jobs.output(id, opts);\n\n if (!this.spiller.enabled) {\n return result;\n }\n\n const stdout = await this.spiller.text(`shell_output-${id}-stdout`, result.stdout, \"head+tail\");\n const stderr = await this.spiller.text(`shell_output-${id}-stderr`, result.stderr, \"head+tail\");\n const truncation = streamTruncation(stdout.truncation, stderr.truncation);\n\n return truncation === undefined\n ? result\n : {\n ...result,\n stdout: stdout.value,\n stderr: stderr.value,\n truncated: true,\n truncation,\n ...spilledField(truncation),\n };\n }\n\n /**\n * Fit one captured stream to the limits: spilled to a file when\n * `output.spillDir` is set, otherwise clipped at `maxBytes` as always.\n */\n private async fitStream(\n label: string,\n value: string,\n maxBytes: number,\n ): Promise<{ value: string; truncated: boolean; truncation?: WorkspaceTruncation }> {\n if (this.spiller.enabled) {\n const cap = this.policy.shell?.maxOutputBytes;\n const spilled = await this.spiller.text(label, value, \"head+tail\", cap);\n\n if (spilled.truncation !== undefined) {\n return { value: spilled.value, truncated: true, truncation: spilled.truncation };\n }\n }\n\n const capped = capOutput(value, maxBytes);\n\n if (!capped.truncated) {\n return capped;\n }\n\n return {\n ...capped,\n truncation: {\n shown: { lines: splitLines(capped.value).length },\n total: { lines: splitLines(value).length, bytes: Buffer.byteLength(value, \"utf8\") },\n },\n };\n }\n\n public shellKill(id: string): Promise<ShellJobInfo> {\n return this.runtime.jobs.kill(id);\n }\n\n public listShells(): ShellJobInfo[] {\n return this.runtime.jobs.list();\n }\n\n public async dispose(): Promise<void> {\n await this.runtime.jobs.dispose();\n await this.spiller.dispose();\n }\n\n public async listDir(inputPath = \".\", opts?: ListDirOptions): Promise<ListDirResult> {\n const { absolutePath: root, relativePath: rootRelative } = await resolveInJail(\n this.policy,\n inputPath,\n );\n const { absolutePath: jailRoot } = await resolveInJail(this.policy, \".\");\n const listPolicy = this.policy.list;\n const maxDepth = Math.max(1, Math.floor(listPolicy?.maxDepth ?? DEFAULT_LIST_MAX_DEPTH));\n const depth = Math.min(Math.max(1, Math.floor(opts?.depth ?? 1)), maxDepth);\n const maxEntries = Math.max(1, Math.floor(listPolicy?.maxEntries ?? DEFAULT_LIST_MAX_ENTRIES));\n const ignore = [...(listPolicy?.ignore ?? DEFAULT_LIST_IGNORE), ...(opts?.ignore ?? [])];\n const insideCwd = isInside(root, jailRoot);\n\n if (!(await this.isDirectory(root))) {\n throw new Error(`\"${inputPath}\" is not a directory in the workspace.`);\n }\n\n const entries: ListDirEntry[] = [];\n let level: string[] = [root];\n let truncated = false;\n\n for (let currentDepth = 1; currentDepth <= depth && level.length > 0; currentDepth++) {\n const nextLevel: string[] = [];\n\n for (const directory of level) {\n let children: string[];\n\n try {\n children = await this.backend.list(directory);\n } catch {\n continue;\n }\n\n children.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));\n\n for (const child of children) {\n const relativeToJail = insideCwd\n ? path.relative(jailRoot, child).split(path.sep).join(\"/\")\n : child;\n\n if (\n insideCwd &&\n (this.isDenied(relativeToJail) || matchesAnyGlob(relativeToJail, ignore))\n ) {\n continue;\n }\n\n const relativeToRoot = path.relative(root, child).split(path.sep).join(\"/\");\n\n if (!insideCwd && matchesAnyGlob(relativeToRoot, ignore)) {\n continue;\n }\n\n if (entries.length >= maxEntries) {\n truncated = true;\n\n break;\n }\n\n const described = await this.describe(child);\n\n entries.push({\n path: relativeToJail,\n type: described.type,\n ...(described.size !== undefined ? { size: described.size } : {}),\n depth: currentDepth,\n });\n\n if (described.type === \"directory\") {\n nextLevel.push(child);\n }\n }\n\n if (truncated) {\n break;\n }\n }\n\n if (truncated) {\n break;\n }\n\n level = nextLevel;\n }\n\n return { path: insideCwd ? rootRelative || \".\" : root, entries, truncated };\n }\n\n /** Kind and size of a listed entry; symlinks are reported, never followed. */\n private async describe(absPath: string): Promise<{ type: ListDirEntryType; size?: number }> {\n if (this.backend.stat) {\n try {\n const stats = await this.backend.stat(absPath);\n\n return stats.type === \"file\" ? { type: \"file\", size: stats.size } : { type: stats.type };\n } catch {\n return { type: \"other\" };\n }\n }\n\n // No `stat`: a child is a directory iff it can be listed, and it must\n // still resolve inside the jail before it is descended into.\n if (await this.isDirectory(absPath)) {\n try {\n await resolveInJail(this.policy, absPath);\n\n return { type: \"directory\" };\n } catch {\n return { type: \"other\" };\n }\n }\n\n return { type: \"file\" };\n }\n\n public async grep(\n pattern: string,\n opts?: { glob?: string; ignoreCase?: boolean },\n ): Promise<GrepResult> {\n if (!isSafeGrepPattern(pattern)) {\n throw new WorkspacePolicyError(\n `Grep pattern is too long or too likely to cause catastrophic regex backtracking: ${pattern}`,\n { type: \"unsafe-pattern\", pattern },\n );\n }\n\n const { absolutePath: jailRoot } = await resolveInJail(this.policy, \".\");\n const flags = opts?.ignoreCase ? \"i\" : \"\";\n const regex = new RegExp(pattern, flags);\n const globRegex = opts?.glob ? globToRegExp(opts.glob) : undefined;\n\n const files = await this.walkFiles(jailRoot);\n const matches: GrepMatch[] = [];\n\n for (const absFile of files) {\n const relativePath = path.relative(jailRoot, absFile).split(path.sep).join(\"/\");\n\n if (globRegex && !globRegex.test(relativePath)) {\n continue;\n }\n\n // Skip files the deny list would block (e.g. `.git/**`).\n if (this.isDenied(relativePath)) {\n continue;\n }\n\n let content: string;\n\n try {\n content = await this.backend.readFile(absFile);\n } catch {\n // Unreadable entry (vanished, binary handle) — skip, don't fail.\n continue;\n }\n\n const lines = content.split(\"\\n\");\n\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? \"\";\n\n // Cap the input size a single `RegExp#test` call scans: backtracking\n // cost is exponential in input length, so this bounds the worst-case\n // time even a pathological (but length/shape-allowed) pattern can\n // burn on any one line.\n if (line.length > MAX_GREP_LINE_SCAN_LENGTH) {\n continue;\n }\n\n if (regex.test(line)) {\n matches.push({ path: relativePath, line: index + 1, text: line });\n\n if (matches.length >= DEFAULT_MAX_GREP_MATCHES) {\n return this.fitMatches(matches);\n }\n }\n }\n }\n\n return this.fitMatches(matches);\n }\n\n /** Apply the `output` limits to a finished match list. */\n private async fitMatches(matches: GrepMatch[]): Promise<GrepResult> {\n const fitted = await this.spiller.list(\n \"grep\",\n matches,\n (match) => `${match.path}:${match.line}:${match.text}`,\n \"head\",\n );\n\n return fitted.truncation === undefined\n ? { matches, total: matches.length }\n : {\n matches: fitted.items,\n total: matches.length,\n truncated: true,\n truncation: fitted.truncation,\n ...spilledField({ stdout: fitted.truncation }),\n };\n }\n\n public async globResult(pattern: string): Promise<GlobResult> {\n const all = await this.glob(pattern);\n const fitted = await this.spiller.list(\"glob\", all, (entry) => entry, \"head\");\n\n return fitted.truncation === undefined\n ? { paths: all }\n : {\n paths: fitted.items,\n truncated: true,\n truncation: fitted.truncation,\n ...spilledField({ stdout: fitted.truncation }),\n };\n }\n\n public async glob(pattern: string): Promise<string[]> {\n const { absolutePath: jailRoot } = await resolveInJail(this.policy, \".\");\n const regex = globToRegExp(pattern);\n\n const files = await this.walkFiles(jailRoot);\n const matched: string[] = [];\n\n for (const absFile of files) {\n const relativePath = path.relative(jailRoot, absFile).split(path.sep).join(\"/\");\n\n if (this.isDenied(relativePath)) {\n continue;\n }\n\n if (regex.test(relativePath)) {\n matched.push(relativePath);\n }\n }\n\n return matched.sort();\n }\n\n public async exists(inputPath: string): Promise<boolean> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n return this.backend.exists(absolutePath);\n }\n\n public async mkdir(inputPath: string): Promise<void> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n await this.backend.mkdir(absolutePath);\n }\n\n public async remove(inputPath: string): Promise<void> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n await this.withPathLock(absolutePath, () => this.backend.remove(absolutePath));\n }\n\n /** Whether a `/`-separated relative path hits the policy deny list. */\n private isDenied(relativePath: string): boolean {\n const denyPaths = this.policy.denyPaths;\n\n return (\n denyPaths !== undefined && denyPaths.length > 0 && matchesAnyGlob(relativePath, denyPaths)\n );\n }\n}\n\n/** `{ spilledTo }` for every spill file named in the truncation metadata, or nothing. */\nfunction spilledField(\n truncation: { stdout?: WorkspaceTruncation; stderr?: WorkspaceTruncation } | undefined,\n): { spilledTo?: string[] } {\n const paths = [truncation?.stdout?.path, truncation?.stderr?.path].filter(\n (file): file is string => file !== undefined,\n );\n\n return paths.length > 0 ? { spilledTo: paths } : {};\n}\n\n/** Combine per-stream truncation metadata; `undefined` when neither stream was clipped. */\nfunction streamTruncation(\n stdout: WorkspaceTruncation | undefined,\n stderr: WorkspaceTruncation | undefined,\n): { stdout?: WorkspaceTruncation; stderr?: WorkspaceTruncation } | undefined {\n if (stdout === undefined && stderr === undefined) {\n return undefined;\n }\n\n return {\n ...(stdout !== undefined ? { stdout } : {}),\n ...(stderr !== undefined ? { stderr } : {}),\n };\n}\n\n/** The lower of two optional limits (either may be absent). */\nfunction lowerOf(\n policyLimit: number | undefined,\n requested: number | undefined,\n): number | undefined {\n if (requested === undefined || requested <= 0) {\n return policyLimit;\n }\n\n return policyLimit === undefined ? requested : Math.min(policyLimit, requested);\n}\n\n/**\n * Create the policy-enforced operation layer over a backend.\n *\n * The returned {@link WorkspaceOps} is the single seam both the\n * agent-facing `.tools.*` factories and the human-facing direct methods\n * delegate to — one jail, one command-gate, one read-before-edit guard,\n * regardless of caller. Path inputs are workspace-relative and resolved\n * against `policy.cwd`; escapes and denied commands surface as typed\n * {@link WorkspacePolicyError} / {@link WorkspaceEditError}.\n *\n * @param backend - The dumb IO executor (local disk or in-memory mock).\n * @param policy - The policy that bounds every operation.\n * @param runtime - State shared with other views of the same workspace\n * (its background commands); a fresh one when omitted.\n *\n * @example\n * const ops = createOps(localBackend, { cwd: \"/srv/api\", shell: { allow: [\"npm\"] } });\n * const { content, hash } = await ops.readFile(\"src/index.ts\");\n */\nexport function createOps(\n backend: WorkspaceBackend,\n policy: WorkspacePolicy,\n runtime?: OpsRuntime,\n): WorkspaceOps {\n return new Ops(backend, policy, runtime ?? { jobs: new ShellJobs() });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ops.mjs","names":[],"sources":["../../../../../../../packages/ai-workspace/src/ops.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fs } from \"@warlock.js/fs\";\nimport { applyReplacement, type AppliedReplacementOptions } from \"./edit/apply-replacement\";\nimport { WorkspaceEditError, WorkspacePolicyError } from \"./errors\";\nimport { runAfterEdit } from \"./hooks/after-edit\";\nimport { ShellOutputStreamer } from \"./hooks/shell-output\";\nimport { OutputSpiller, splitLines } from \"./output/spill\";\nimport { globToRegExp, isInside, matchesAnyGlob } from \"./policy/jail\";\nimport { buildCommandEnv, resolveInJail } from \"./policy/policy\";\nimport { refusalReason, samePattern } from \"./policy/approval-helpers\";\nimport { checkShellCommand } from \"./policy/shell-check\";\nimport { resolveShell, type ResolvedShell } from \"./policy/shell-interpreter\";\nimport { DEFAULT_MAX_BACKGROUND_JOBS, ShellJobs } from \"./shell-jobs\";\nimport { executePatch } from \"./patch/execute-patch\";\nimport {\n DEFAULT_SEARCH_CONCURRENCY,\n DEFAULT_SEARCH_MAX_FILES,\n DEFAULT_SEARCH_SKIP_DIRS,\n staticGlobPrefix,\n walkFiles,\n type WalkResult,\n} from \"./search/walk\";\nimport type {\n ApplyPatchInput,\n ApplyPatchResult,\n EditFileInput,\n EditFileResult,\n ExecOptions,\n WorkspaceBackendExecResult,\n GrepMatch,\n GlobResult,\n GrepResult,\n ListDirEntry,\n ListDirEntryType,\n ListDirOptions,\n ListDirResult,\n MultiEditInput,\n MultiEditResult,\n ReadFileResult,\n RunShellResult,\n ShellJobInfo,\n ShellOutputOptions,\n ShellOutputResult,\n StartShellOptions,\n WorkspaceBackend,\n WorkspaceCommandPattern,\n WorkspaceOps,\n WorkspacePolicy,\n WorkspaceShellAsk,\n WorkspaceShellOnAsk,\n WorkspaceEditTool,\n WorkspaceTruncation,\n} from \"./contracts\";\n\n/** Default line window a read returns when the policy sets no `defaultLines`. */\nconst DEFAULT_READ_LINES = 2000;\n/** Hard ceiling on grep matches returned, so a broad pattern can't flood. */\nconst DEFAULT_MAX_GREP_MATCHES = 1000;\n/** Default per-command output byte cap when the policy sets none. */\nconst DEFAULT_MAX_OUTPUT_BYTES = 1_000_000;\n/** Default `list.ignore` when the policy sets none. */\nconst DEFAULT_LIST_IGNORE: readonly string[] = [\"node_modules\", \".git\"];\n/** Default deepest `depth` a listing may ask for. */\nconst DEFAULT_LIST_MAX_DEPTH = 5;\n/** Default most entries one listing returns. */\nconst DEFAULT_LIST_MAX_ENTRIES = 500;\n/** Most edits one `multiEdit` call may carry. */\nexport const MAX_MULTI_EDITS = 100;\n/**\n * Exit code reported for a command refused because its signal was already\n * aborted (the shell convention for an interrupted command). Callers should\n * branch on `cancelled`, not on this number.\n */\nconst CANCELLED_BEFORE_START_EXIT_CODE = 130;\n/**\n * Hard ceiling on `grep` pattern length. A model-controlled regex has no\n * legitimate reason to be this long; longer patterns are rejected outright\n * rather than compiled.\n */\nconst MAX_GREP_PATTERN_LENGTH = 200;\n/**\n * Hard ceiling on the number of characters of a single line handed to\n * `RegExp#test` during a `grep`. Catastrophic backtracking is exponential\n * in input length, so bounding the input scanned per call bounds the\n * worst-case time a single pathological line can cost — lines longer than\n * this are skipped rather than scanned.\n */\nconst MAX_GREP_LINE_SCAN_LENGTH = 2000;\n\n/** The `cat -n` prefix for a line: right-aligned number (min width 6) and a tab. */\nfunction linePrefix(lineNumber: number): string {\n return `${String(lineNumber).padStart(6, \" \")}\\t`;\n}\n\n/**\n * Clip `value` to at most `maxBytes` UTF-8 bytes without splitting a\n * character: the cut backs off over continuation bytes (`10xxxxxx`) to the\n * start of the character it would have split, so the result never carries\n * a replacement character.\n */\nfunction clipUtf8(value: string, maxBytes: number): { value: string; truncated: boolean } {\n const bytes = Buffer.from(value, \"utf8\");\n\n if (bytes.byteLength <= maxBytes) {\n return { value, truncated: false };\n }\n\n let cut = Math.max(0, maxBytes);\n\n while (cut > 0 && ((bytes[cut] ?? 0) & 0xc0) === 0x80) {\n cut--;\n }\n\n return { value: bytes.subarray(0, cut).toString(\"utf8\"), truncated: true };\n}\n\n/**\n * Number a window of lines `cat -n` style, keeping the joined output within\n * `maxBytes` UTF-8 bytes (prefixes and newlines included) when a cap is set.\n * A line that does not fit whole is clipped on a character boundary; it is\n * still counted as returned when its prefix and at least one character fit.\n */\nfunction numberWindow(\n lines: string[],\n startLine: number,\n maxBytes: number | undefined,\n): { content: string; returnedLines: number; bytesCapped: boolean } {\n const parts: string[] = [];\n let used = 0;\n\n for (let index = 0; index < lines.length; index++) {\n const prefix = linePrefix(startLine + index);\n const numbered = prefix + lines[index];\n const separator = parts.length > 0 ? 1 : 0;\n\n if (maxBytes === undefined) {\n parts.push(numbered);\n\n continue;\n }\n\n const cost = separator + Buffer.byteLength(numbered, \"utf8\");\n\n if (used + cost <= maxBytes) {\n parts.push(numbered);\n used += cost;\n\n continue;\n }\n\n const room = maxBytes - used - separator - Buffer.byteLength(prefix, \"utf8\");\n const clipped = room > 0 ? clipUtf8(lines[index] ?? \"\", room).value : \"\";\n\n if (clipped.length > 0) {\n parts.push(prefix + clipped);\n }\n\n return { content: parts.join(\"\\n\"), returnedLines: parts.length, bytesCapped: true };\n }\n\n return { content: parts.join(\"\\n\"), returnedLines: parts.length, bytesCapped: false };\n}\n\n/**\n * Clip a captured stream at `maxBytes` (measured in UTF-8 bytes), on a\n * character boundary. Returns the possibly-clipped string plus whether\n * clipping occurred.\n */\nfunction capOutput(value: string, maxBytes: number): { value: string; truncated: boolean } {\n return clipUtf8(value, maxBytes);\n}\n\n/**\n * A quantifier: `+`, `*`, `?`, or a `{m,n}` bound.\n */\nconst QUANTIFIER_SOURCE = String.raw`[+*?]|\\{\\d*,?\\d*\\}`;\n\n/**\n * Heuristic catastrophic-backtracking detector: flags a quantified group\n * that itself contains a quantifier (e.g. `(a+)+`, `(a*)*`, `(.+)*`) —\n * the classic exponential-blowup shape. Not a full regex-safety analyzer\n * (it won't catch every ReDoS shape, e.g. quantified alternation like\n * `(a|a)+`), but it rejects the shape an agent is most likely to emit,\n * intentionally or via prompt injection.\n */\nconst NESTED_QUANTIFIER_PATTERN = new RegExp(\n String.raw`\\([^()]*(?:${QUANTIFIER_SOURCE})[^()]*\\)(?:${QUANTIFIER_SOURCE})`,\n);\n\n/**\n * Whether `pattern` is safe enough to compile and run against workspace\n * content: within the length cap and free of the nested-quantifier shape\n * that causes catastrophic regex backtracking (ReDoS).\n */\nfunction isSafeGrepPattern(pattern: string): boolean {\n if (pattern.length > MAX_GREP_PATTERN_LENGTH) {\n return false;\n }\n\n return !NESTED_QUANTIFIER_PATTERN.test(pattern);\n}\n\n/**\n * The internal, single-instance implementation of {@link WorkspaceOps}.\n * Holds the backend + policy and is the one place the jail, command\n * gating, read caps, and the read-before-edit guard are enforced — both\n * the agent-facing tools and the human-facing direct methods funnel\n * through this object, so there is exactly one set of rules.\n */\n/**\n * State an ops layer shares with the other projections of the same\n * workspace (`readonly()`, `scope()`): the background commands it owns.\n */\nexport interface OpsRuntime {\n /** The workspace's background commands. */\n jobs: ShellJobs;\n /** Patterns the host approved with `remember`, shared by every view of the workspace. */\n approvals?: WorkspaceCommandPattern[];\n}\n\nclass Ops implements WorkspaceOps {\n /**\n * Tail of the mutation chain per resolved absolute path. Writes, edits and\n * removes on the same path run one after another, so two tool calls the\n * agent dispatches concurrently cannot both read the same content, both\n * pass the stale-hash guard and silently drop one edit: the second one\n * re-reads after the first has written.\n */\n private readonly pathLocks = new Map<string, Promise<void>>();\n\n /** The shell `policy.shell.interpreter` resolves to on this host, resolved once. */\n private readonly resolvedShell: ResolvedShell;\n\n /** Patterns approved with `remember` (shared through the runtime). */\n private readonly approvals: WorkspaceCommandPattern[];\n\n /** Saves oversized output to files when `output.spillDir` is set. */\n private readonly spiller: OutputSpiller;\n\n public constructor(\n private readonly backend: WorkspaceBackend,\n private readonly policy: WorkspacePolicy,\n private readonly runtime: OpsRuntime,\n ) {\n this.resolvedShell = resolveShell(policy.shell);\n this.approvals = runtime.approvals ?? [];\n this.spiller = new OutputSpiller(backend, policy);\n }\n\n /** The edit-policy settings applied to every replacement. */\n private editOptions(): AppliedReplacementOptions {\n const edit = this.policy.edit;\n\n return {\n matching: edit?.matching ?? \"tiered\",\n missHintLines: edit?.missHintLines ?? 12,\n lineEndings: edit?.lineEndings ?? \"preserve\",\n ...(this.policy.read?.maxBytes !== undefined\n ? { hintMaxBytes: this.policy.read.maxBytes }\n : {}),\n };\n }\n\n /** Run `afterEdit` for a call that wrote `paths`; `undefined` when there is no output. */\n private diagnosticsFor(\n tool: WorkspaceEditTool,\n paths: readonly string[],\n ): Promise<string | undefined> {\n return runAfterEdit(this.policy, { paths, tool });\n }\n\n /** Run `task` after every earlier mutation of `absolutePath` has settled. */\n private async withPathLock<T>(absolutePath: string, task: () => Promise<T>): Promise<T> {\n const previous = this.pathLocks.get(absolutePath) ?? Promise.resolve();\n let release: () => void = () => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n const tail = previous.then(() => current);\n\n this.pathLocks.set(absolutePath, tail);\n\n try {\n await previous;\n\n return await task();\n } finally {\n release();\n\n if (this.pathLocks.get(absolutePath) === tail) {\n this.pathLocks.delete(absolutePath);\n }\n }\n }\n\n /**\n * Collect the files a `glob` or `grep` should look at, starting at the\n * folder the pattern is anchored to (see {@link staticGlobPrefix}).\n * `denyPaths`, `search.skipDirs` and `.gitignore` are applied before a\n * directory is listed.\n */\n private async searchFiles(\n jailRoot: string,\n pattern: string | undefined,\n signal: AbortSignal | undefined,\n ): Promise<WalkResult> {\n const prefix = pattern === undefined ? \"\" : staticGlobPrefix(pattern);\n const search = this.policy.search;\n let startDir = jailRoot;\n\n if (prefix !== \"\") {\n try {\n startDir = (await resolveInJail(this.policy, prefix)).absolutePath;\n } catch (error) {\n if (error instanceof WorkspacePolicyError) {\n return { files: [], truncated: false };\n }\n\n throw error;\n }\n }\n\n return walkFiles({\n backend: this.backend,\n jailRoot,\n startDir,\n denyPaths: this.policy.denyPaths ?? [],\n skipDirs: search?.skipDirs ?? DEFAULT_SEARCH_SKIP_DIRS,\n gitignore: search?.gitignore ?? true,\n maxFiles: search?.maxFiles ?? DEFAULT_SEARCH_MAX_FILES,\n concurrency: Math.max(1, search?.concurrency ?? DEFAULT_SEARCH_CONCURRENCY),\n ...(signal ? { signal } : {}),\n });\n }\n\n /** Whether an absolute path is a directory, by attempting to list it. */\n private async isDirectory(absPath: string): Promise<boolean> {\n try {\n await this.backend.list(absPath);\n\n return true;\n } catch {\n return false;\n }\n }\n\n public async readFile(\n inputPath: string,\n opts?: { offset?: number; limit?: number },\n ): Promise<ReadFileResult> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath, \"read\");\n const raw = await this.backend.readFile(absolutePath);\n\n // Hash the full content the backend returned (SHA-256 via\n // @warlock.js/fs), not the returned window and not the on-disk file —\n // the mock backend keeps content in memory, and the stale-edit guard\n // later hashes the backend's content too, so it must be the SAME\n // source of truth whatever window or byte cap applied.\n const hash = fs.hash.string(raw);\n\n const lines = raw.split(\"\\n\");\n const totalLines = lines.length;\n\n const offset = Math.max(1, opts?.offset ?? 1);\n const limit = opts?.limit ?? this.policy.read?.defaultLines ?? DEFAULT_READ_LINES;\n const maxBytes = this.policy.read?.maxBytes;\n\n const startIndex = offset - 1;\n const window = lines.slice(startIndex, startIndex + Math.max(0, limit));\n const { content, returnedLines, bytesCapped } = numberWindow(\n window,\n offset,\n maxBytes === undefined ? undefined : Math.max(0, Math.floor(maxBytes)),\n );\n const endLine = offset + returnedLines - 1;\n const spillDir = this.spiller.spillDir;\n let spilled: { content: string; truncation: WorkspaceTruncation } | undefined;\n\n // A read of a spill file is never spilled again; it is already paged.\n if (bytesCapped && spillDir !== undefined && !isInside(absolutePath, spillDir)) {\n const full = numberWindow(window, offset, undefined).content;\n const file = await this.spiller.save(\"read_file\", full);\n\n if (file !== undefined) {\n spilled = {\n content: `${content}\\n[showing lines 1-${returnedLines} of ${window.length}; full output saved to ${file}]`,\n truncation: {\n shown: { lines: returnedLines },\n total: { lines: window.length, bytes: Buffer.byteLength(full, \"utf8\") },\n path: file,\n },\n };\n }\n }\n\n return {\n content: spilled?.content ?? content,\n ...(spilled !== undefined ? { truncation: spilled.truncation } : {}),\n ...(spilled?.truncation.path !== undefined ? { spilledTo: [spilled.truncation.path] } : {}),\n startLine: offset,\n endLine,\n totalLines,\n truncated: bytesCapped || endLine < totalLines,\n bytesCapped,\n totalBytes: Buffer.byteLength(raw, \"utf8\"),\n hash,\n };\n }\n\n public async writeFile(\n inputPath: string,\n content: string,\n ): Promise<{ hash: string; bytesWritten: number; diagnostics?: string }> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, inputPath);\n\n const written = await this.withPathLock(absolutePath, async () => {\n // Ensure the parent directory exists before writing the leaf.\n const parent = path.dirname(absolutePath);\n await this.backend.mkdir(parent);\n\n await this.backend.writeFile(absolutePath, content);\n\n return {\n hash: fs.hash.string(content),\n bytesWritten: Buffer.byteLength(content, \"utf8\"),\n };\n });\n const diagnostics = await this.diagnosticsFor(\"write_file\", [relativePath || inputPath]);\n\n return { ...written, ...(diagnostics !== undefined ? { diagnostics } : {}) };\n }\n\n public async editFile(input: EditFileInput): Promise<EditFileResult> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);\n\n const result = await this.withPathLock(absolutePath, () =>\n this.applyEdit(input, absolutePath, relativePath),\n );\n const diagnostics = await this.diagnosticsFor(\"edit_file\", [result.path]);\n\n return diagnostics === undefined ? result : { ...result, diagnostics };\n }\n\n /** The read-check-replace-write of {@link editFile}, run under its path lock. */\n private async applyEdit(\n input: EditFileInput,\n absolutePath: string,\n relativePath: string,\n ): Promise<EditFileResult> {\n const current = await this.backend.readFile(absolutePath);\n const currentHash = fs.hash.string(current);\n\n // Stale-hash guard: if the caller pinned a hash and the file moved\n // underneath them, refuse so they re-read before clobbering.\n if (input.expectHash !== undefined && input.expectHash !== currentHash) {\n throw new WorkspaceEditError(\n `File \"${input.path}\" changed since it was read; the edit is stale.`,\n {\n type: \"stale-hash\",\n path: relativePath || input.path,\n expectedHash: input.expectHash,\n actualHash: currentHash,\n },\n );\n }\n\n const {\n content: updated,\n replacements,\n matchTier,\n } = applyReplacement(\n current,\n input,\n input.path,\n relativePath || input.path,\n this.editOptions(),\n );\n\n await this.backend.writeFile(absolutePath, updated);\n\n return {\n path: relativePath || input.path,\n replacements,\n hash: fs.hash.string(updated),\n ...(matchTier !== undefined ? { matchTier } : {}),\n };\n }\n\n public async multiEdit(input: MultiEditInput): Promise<MultiEditResult> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);\n const shownPath = relativePath || input.path;\n\n if (input.edits.length === 0 || input.edits.length > MAX_MULTI_EDITS) {\n throw new RangeError(\n `A multi-edit needs between 1 and ${MAX_MULTI_EDITS} edits (got ${input.edits.length}).`,\n );\n }\n\n const result = await this.withPathLock(absolutePath, async () => {\n const current = await this.backend.readFile(absolutePath);\n const currentHash = fs.hash.string(current);\n\n if (input.expectHash !== undefined && input.expectHash !== currentHash) {\n throw new WorkspaceEditError(\n `File \"${input.path}\" changed since it was read; none of the edits were applied.`,\n {\n type: \"stale-hash\",\n path: shownPath,\n expectedHash: input.expectHash,\n actualHash: currentHash,\n },\n );\n }\n\n // Apply every edit in memory first; one failure leaves the file untouched.\n let content = current;\n let replacements = 0;\n let matchTier: MultiEditResult[\"matchTier\"];\n const options = this.editOptions();\n\n input.edits.forEach((edit, editIndex) => {\n const applied = applyReplacement(content, edit, input.path, shownPath, options, editIndex);\n\n content = applied.content;\n replacements += applied.replacements;\n\n if (applied.matchTier === \"whitespace\" || matchTier === undefined) {\n matchTier = applied.matchTier ?? matchTier;\n }\n });\n\n await this.backend.writeFile(absolutePath, content);\n\n return {\n path: shownPath,\n edits: input.edits.length,\n replacements,\n hash: fs.hash.string(content),\n ...(matchTier !== undefined ? { matchTier } : {}),\n };\n });\n const diagnostics = await this.diagnosticsFor(\"multi_edit\", [shownPath]);\n\n return diagnostics === undefined ? result : { ...result, diagnostics };\n }\n\n public async applyPatch(input: ApplyPatchInput): Promise<ApplyPatchResult> {\n const result = await this.runPatch(input);\n\n if (result.dryRun) {\n return result;\n }\n\n const paths = result.files.filter((file) => file.action !== \"deleted\").map((file) => file.path);\n const diagnostics = await this.diagnosticsFor(\"apply_patch\", paths);\n\n return diagnostics === undefined ? result : { ...result, diagnostics };\n }\n\n private runPatch(input: ApplyPatchInput): Promise<ApplyPatchResult> {\n return executePatch(input, {\n resolve: (inputPath) => resolveInJail(this.policy, inputPath),\n exists: (absolutePath) => this.backend.exists(absolutePath),\n stat: this.backend.stat\n ? (absolutePath) =>\n (this.backend.stat as NonNullable<WorkspaceBackend[\"stat\"]>)(absolutePath)\n : undefined,\n read: (absolutePath) => this.backend.readFile(absolutePath),\n write: async (absolutePath, content) => {\n await this.backend.mkdir(path.dirname(absolutePath));\n await this.backend.writeFile(absolutePath, content);\n },\n remove: (absolutePath) => this.backend.remove(absolutePath),\n withLock: (key, task) => this.withPathLock(key, task),\n display: (absolutePath) => path.relative(this.policy.cwd, absolutePath),\n });\n }\n\n /**\n * Check a command line against the shell policy: every command in it\n * (argv or shell mode), then — shell mode — every redirection target and\n * `cd` directory against the jail with symlinks resolved.\n */\n private async assertCommandAllowed(command: string, signal?: AbortSignal): Promise<void> {\n const verdict = checkShellCommand(this.policy, command, {\n remembered: this.approvals,\n resolved: this.resolvedShell,\n });\n const onAsk = this.policy.shell?.onAsk;\n const needsApproval =\n !verdict.allowed && verdict.reason === \"approval-required\" && verdict.asks.length > 0;\n\n if (!verdict.allowed && !(needsApproval && onAsk !== undefined)) {\n throw new WorkspacePolicyError(\n verdict.message ?? `Command is not permitted by the workspace shell policy: ${command}`,\n {\n type: \"denied-command\",\n command,\n ...(verdict.reason !== undefined ? { reason: verdict.reason } : {}),\n ...(verdict.executable !== undefined ? { executable: verdict.executable } : {}),\n ...(verdict.path !== undefined ? { path: verdict.path } : {}),\n },\n );\n }\n\n for (const target of verdict.paths) {\n try {\n await resolveInJail(this.policy, target.path);\n } catch (error) {\n if (!(error instanceof WorkspacePolicyError)) {\n throw error;\n }\n\n throw new WorkspacePolicyError(\n target.access === \"cwd\"\n ? `\\`cd ${target.target}\\` resolves (through a symlink) outside the workspace jail or onto a denied path.`\n : `The redirection target \\`${target.target}\\` resolves (through a symlink) outside the workspace jail or onto a denied path.`,\n { type: \"denied-command\", reason: \"path-escape\", command, path: target.target },\n );\n }\n }\n\n if (this.runtime.jobs.isDisposed) {\n throw new WorkspacePolicyError(\n \"The workspace has been disposed; it cannot run new commands.\",\n { type: \"denied-command\", reason: \"not-allowed\", command },\n );\n }\n\n if (needsApproval && onAsk !== undefined) {\n await this.askForApproval(command, verdict.asks, onAsk, signal);\n }\n }\n\n /** Put the commands that need approval to `shell.onAsk`; refuse the line unless it says yes. */\n private async askForApproval(\n command: string,\n asks: WorkspaceShellAsk[],\n onAsk: WorkspaceShellOnAsk,\n signal: AbortSignal | undefined,\n ): Promise<void> {\n let decision: Awaited<ReturnType<WorkspaceShellOnAsk>> | undefined;\n\n try {\n decision = await onAsk({ command, asks, ...(signal ? { signal } : {}) });\n } catch {\n decision = undefined;\n }\n\n if (decision?.allow !== true) {\n const reason = refusalReason(decision?.reason);\n\n throw new WorkspacePolicyError(\n `Running ${asks.map((ask) => `\\`${ask.argv.join(\" \")}\\``).join(\", \")} was not approved${reason}`,\n { type: \"denied-command\", reason: \"approval-denied\", command },\n );\n }\n\n if (decision.remember !== undefined) {\n const patterns = Array.isArray(decision.remember) ? decision.remember : [decision.remember];\n\n for (const pattern of patterns as WorkspaceCommandPattern[]) {\n if (!this.approvals.some((entry) => samePattern(entry, pattern))) {\n this.approvals.push(pattern);\n }\n }\n }\n }\n\n public async exec(command: string, opts?: ExecOptions): Promise<RunShellResult> {\n await this.assertCommandAllowed(command, opts?.signal);\n\n const signal = opts?.signal;\n\n // Refuse an already-cancelled invocation here, so no backend — built-in\n // or custom — ever starts a command whose caller has given up on it.\n if (signal?.aborted) {\n return {\n exitCode: CANCELLED_BEFORE_START_EXIT_CODE,\n stdout: \"\",\n stderr: \"Command was not executed: the invocation was cancelled before it started.\",\n truncated: false,\n timedOut: false,\n cancelled: true,\n };\n }\n\n const shell = this.policy.shell;\n const timeoutMs = opts?.timeoutMs ?? shell?.timeoutMs;\n const maxOutputBytes = shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;\n const interpreter = this.resolvedShell.interpreter;\n\n const env = buildCommandEnv(this.policy);\n const live = ShellOutputStreamer.create(\n this.policy.hooks,\n { command, ...(opts?.callId !== undefined ? { callId: opts.callId } : {}) },\n this.policy.shell?.env,\n );\n\n let result: WorkspaceBackendExecResult;\n\n try {\n result = await this.backend.exec(command, {\n cwd: this.policy.cwd,\n timeoutMs,\n env,\n ...(live ? { onOutput: (stream, chunk) => live.push(stream, chunk) } : {}),\n ...(signal ? { signal } : {}),\n ...(interpreter !== \"none\" ? { interpreter } : {}),\n ...(interpreter !== \"none\" && this.resolvedShell.file !== undefined\n ? { interpreterPath: this.resolvedShell.file }\n : {}),\n });\n\n // A backend that cannot stream hands over its capture at the end.\n if (live !== undefined && !live.received) {\n live.push(\"stdout\", result.stdout);\n live.push(\"stderr\", result.stderr);\n }\n } finally {\n live?.end();\n }\n\n const stdout = await this.fitStream(\"run_shell-stdout\", result.stdout, maxOutputBytes);\n const stderr = await this.fitStream(\"run_shell-stderr\", result.stderr, maxOutputBytes);\n const cancelled = result.cancelled === true;\n const truncation = streamTruncation(stdout.truncation, stderr.truncation);\n\n return {\n exitCode: result.exitCode,\n stdout: stdout.value,\n stderr: stderr.value,\n truncated: stdout.truncated || stderr.truncated,\n timedOut: result.timedOut && !cancelled,\n cancelled,\n ...(truncation !== undefined ? { truncation } : {}),\n ...spilledField(truncation),\n };\n }\n\n public async startShell(command: string, opts?: StartShellOptions): Promise<ShellJobInfo> {\n const background = this.policy.shell?.background;\n\n if (background === undefined) {\n throw new WorkspacePolicyError(\n \"Background commands are not enabled for this workspace. Run the command without background, or ask for shell.background to be enabled.\",\n { type: \"denied-command\", reason: \"not-allowed\", command },\n );\n }\n\n await this.assertCommandAllowed(command);\n\n const start = this.backend.start?.bind(this.backend);\n\n if (start === undefined) {\n throw new WorkspacePolicyError(\"This workspace backend cannot run background commands.\", {\n type: \"denied-command\",\n reason: \"not-allowed\",\n command,\n });\n }\n\n const shell = this.policy.shell;\n const interpreter = this.resolvedShell.interpreter;\n const interpreterPath = interpreter !== \"none\" ? this.resolvedShell.file : undefined;\n const timeoutMs = lowerOf(background.timeoutMs, opts?.timeoutMs);\n const env = buildCommandEnv(this.policy);\n\n return this.runtime.jobs.start({\n command,\n retainBytes: shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxJobs: background.maxJobs ?? DEFAULT_MAX_BACKGROUND_JOBS,\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n launch: (onOutput, jobId) => {\n const live = ShellOutputStreamer.create(\n this.policy.hooks,\n { command, jobId },\n this.policy.shell?.env,\n );\n const handle = start(command, {\n cwd: this.policy.cwd,\n env,\n ...(interpreter !== \"none\" ? { interpreter } : {}),\n ...(interpreterPath !== undefined ? { interpreterPath } : {}),\n onOutput: (stream, chunk) => {\n onOutput(stream, chunk);\n live?.push(stream, chunk);\n },\n });\n\n void handle.done.then(() => live?.end());\n\n return handle;\n },\n });\n }\n\n public async shellOutput(id: string, opts?: ShellOutputOptions): Promise<ShellOutputResult> {\n const result = await this.runtime.jobs.output(id, opts);\n\n if (!this.spiller.enabled) {\n return result;\n }\n\n const stdout = await this.spiller.text(`shell_output-${id}-stdout`, result.stdout, \"head+tail\");\n const stderr = await this.spiller.text(`shell_output-${id}-stderr`, result.stderr, \"head+tail\");\n const truncation = streamTruncation(stdout.truncation, stderr.truncation);\n\n return truncation === undefined\n ? result\n : {\n ...result,\n stdout: stdout.value,\n stderr: stderr.value,\n truncated: true,\n truncation,\n ...spilledField(truncation),\n };\n }\n\n /**\n * Fit one captured stream to the limits: spilled to a file when\n * `output.spillDir` is set, otherwise clipped at `maxBytes` as always.\n */\n private async fitStream(\n label: string,\n value: string,\n maxBytes: number,\n ): Promise<{ value: string; truncated: boolean; truncation?: WorkspaceTruncation }> {\n if (this.spiller.enabled) {\n const cap = this.policy.shell?.maxOutputBytes;\n const spilled = await this.spiller.text(label, value, \"head+tail\", cap);\n\n if (spilled.truncation !== undefined) {\n return { value: spilled.value, truncated: true, truncation: spilled.truncation };\n }\n }\n\n const capped = capOutput(value, maxBytes);\n\n if (!capped.truncated) {\n return capped;\n }\n\n return {\n ...capped,\n truncation: {\n shown: { lines: splitLines(capped.value).length },\n total: { lines: splitLines(value).length, bytes: Buffer.byteLength(value, \"utf8\") },\n },\n };\n }\n\n public shellKill(id: string): Promise<ShellJobInfo> {\n return this.runtime.jobs.kill(id);\n }\n\n public listShells(): ShellJobInfo[] {\n return this.runtime.jobs.list();\n }\n\n public async dispose(): Promise<void> {\n await this.runtime.jobs.dispose();\n await this.spiller.dispose();\n }\n\n public async listDir(inputPath = \".\", opts?: ListDirOptions): Promise<ListDirResult> {\n const { absolutePath: root, relativePath: rootRelative } = await resolveInJail(\n this.policy,\n inputPath,\n );\n const { absolutePath: jailRoot } = await resolveInJail(this.policy, \".\");\n const listPolicy = this.policy.list;\n const maxDepth = Math.max(1, Math.floor(listPolicy?.maxDepth ?? DEFAULT_LIST_MAX_DEPTH));\n const depth = Math.min(Math.max(1, Math.floor(opts?.depth ?? 1)), maxDepth);\n const maxEntries = Math.max(1, Math.floor(listPolicy?.maxEntries ?? DEFAULT_LIST_MAX_ENTRIES));\n const ignore = [...(listPolicy?.ignore ?? DEFAULT_LIST_IGNORE), ...(opts?.ignore ?? [])];\n const insideCwd = isInside(root, jailRoot);\n\n if (!(await this.isDirectory(root))) {\n throw new Error(`\"${inputPath}\" is not a directory in the workspace.`);\n }\n\n const entries: ListDirEntry[] = [];\n let level: string[] = [root];\n let truncated = false;\n\n for (let currentDepth = 1; currentDepth <= depth && level.length > 0; currentDepth++) {\n const nextLevel: string[] = [];\n\n for (const directory of level) {\n let children: string[];\n\n try {\n children = await this.backend.list(directory);\n } catch {\n continue;\n }\n\n children.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));\n\n for (const child of children) {\n const relativeToJail = insideCwd\n ? path.relative(jailRoot, child).split(path.sep).join(\"/\")\n : child;\n\n if (\n insideCwd &&\n (this.isDenied(relativeToJail) || matchesAnyGlob(relativeToJail, ignore))\n ) {\n continue;\n }\n\n const relativeToRoot = path.relative(root, child).split(path.sep).join(\"/\");\n\n if (!insideCwd && matchesAnyGlob(relativeToRoot, ignore)) {\n continue;\n }\n\n if (entries.length >= maxEntries) {\n truncated = true;\n\n break;\n }\n\n const described = await this.describe(child);\n\n entries.push({\n path: relativeToJail,\n type: described.type,\n ...(described.size !== undefined ? { size: described.size } : {}),\n depth: currentDepth,\n });\n\n if (described.type === \"directory\") {\n nextLevel.push(child);\n }\n }\n\n if (truncated) {\n break;\n }\n }\n\n if (truncated) {\n break;\n }\n\n level = nextLevel;\n }\n\n return { path: insideCwd ? rootRelative || \".\" : root, entries, truncated };\n }\n\n /** Kind and size of a listed entry; symlinks are reported, never followed. */\n private async describe(absPath: string): Promise<{ type: ListDirEntryType; size?: number }> {\n if (this.backend.stat) {\n try {\n const stats = await this.backend.stat(absPath);\n\n return stats.type === \"file\" ? { type: \"file\", size: stats.size } : { type: stats.type };\n } catch {\n return { type: \"other\" };\n }\n }\n\n // No `stat`: a child is a directory iff it can be listed, and it must\n // still resolve inside the jail before it is descended into.\n if (await this.isDirectory(absPath)) {\n try {\n await resolveInJail(this.policy, absPath);\n\n return { type: \"directory\" };\n } catch {\n return { type: \"other\" };\n }\n }\n\n return { type: \"file\" };\n }\n\n public async grep(\n pattern: string,\n opts?: { glob?: string; ignoreCase?: boolean; signal?: AbortSignal },\n ): Promise<GrepResult> {\n if (!isSafeGrepPattern(pattern)) {\n throw new WorkspacePolicyError(\n `Grep pattern is too long or too likely to cause catastrophic regex backtracking: ${pattern}`,\n { type: \"unsafe-pattern\", pattern },\n );\n }\n\n const { absolutePath: jailRoot } = await resolveInJail(this.policy, \".\");\n const flags = opts?.ignoreCase ? \"i\" : \"\";\n const regex = new RegExp(pattern, flags);\n const globRegex = opts?.glob ? globToRegExp(opts.glob) : undefined;\n\n const walked = await this.searchFiles(jailRoot, opts?.glob, opts?.signal);\n const files = walked.files.sort();\n const matches: GrepMatch[] = [];\n\n for (const absFile of files) {\n opts?.signal?.throwIfAborted();\n\n const relativePath = path.relative(jailRoot, absFile).split(path.sep).join(\"/\");\n\n if (globRegex && !globRegex.test(relativePath)) {\n continue;\n }\n\n // Skip files the deny list would block (e.g. `.git/**`).\n if (this.isDenied(relativePath)) {\n continue;\n }\n\n let content: string;\n\n try {\n content = await this.backend.readFile(absFile);\n } catch {\n // Unreadable entry (vanished, binary handle) — skip, don't fail.\n continue;\n }\n\n const lines = content.split(\"\\n\");\n\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? \"\";\n\n // Cap the input size a single `RegExp#test` call scans: backtracking\n // cost is exponential in input length, so this bounds the worst-case\n // time even a pathological (but length/shape-allowed) pattern can\n // burn on any one line.\n if (line.length > MAX_GREP_LINE_SCAN_LENGTH) {\n continue;\n }\n\n if (regex.test(line)) {\n matches.push({ path: relativePath, line: index + 1, text: line });\n\n if (matches.length >= DEFAULT_MAX_GREP_MATCHES) {\n return this.fitMatches(matches, walked.truncated);\n }\n }\n }\n }\n\n return this.fitMatches(matches, walked.truncated);\n }\n\n /** Apply the `output` limits to a finished match list. */\n private async fitMatches(matches: GrepMatch[], maxFilesReached: boolean): Promise<GrepResult> {\n const result = await this.fitMatchesWithin(matches);\n\n return maxFilesReached ? { ...result, truncated: true, maxFilesReached: true } : result;\n }\n\n private async fitMatchesWithin(matches: GrepMatch[]): Promise<GrepResult> {\n const fitted = await this.spiller.list(\n \"grep\",\n matches,\n (match) => `${match.path}:${match.line}:${match.text}`,\n \"head\",\n );\n\n return fitted.truncation === undefined\n ? { matches, total: matches.length }\n : {\n matches: fitted.items,\n total: matches.length,\n truncated: true,\n truncation: fitted.truncation,\n ...spilledField({ stdout: fitted.truncation }),\n };\n }\n\n public async globResult(pattern: string, opts?: { signal?: AbortSignal }): Promise<GlobResult> {\n const { paths: all, maxFilesReached } = await this.globWalk(pattern, opts?.signal);\n const fitted = await this.spiller.list(\"glob\", all, (entry) => entry, \"head\");\n const reached = maxFilesReached ? { truncated: true, maxFilesReached: true } : {};\n\n return fitted.truncation === undefined\n ? { paths: all, ...reached }\n : {\n paths: fitted.items,\n truncated: true,\n truncation: fitted.truncation,\n ...spilledField({ stdout: fitted.truncation }),\n ...reached,\n };\n }\n\n public async glob(pattern: string, opts?: { signal?: AbortSignal }): Promise<string[]> {\n return (await this.globWalk(pattern, opts?.signal)).paths;\n }\n\n /** Match `pattern` against the walked files; sorted, with whether the walk hit `maxFiles`. */\n private async globWalk(\n pattern: string,\n signal: AbortSignal | undefined,\n ): Promise<{ paths: string[]; maxFilesReached: boolean }> {\n const { absolutePath: jailRoot } = await resolveInJail(this.policy, \".\");\n const regex = globToRegExp(pattern);\n const walked = await this.searchFiles(jailRoot, pattern, signal);\n const matched: string[] = [];\n\n for (const absFile of walked.files) {\n const relativePath = path.relative(jailRoot, absFile).split(path.sep).join(\"/\");\n\n if (this.isDenied(relativePath)) {\n continue;\n }\n\n if (regex.test(relativePath)) {\n matched.push(relativePath);\n }\n }\n\n return { paths: matched.sort(), maxFilesReached: walked.truncated };\n }\n\n public async exists(inputPath: string): Promise<boolean> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n return this.backend.exists(absolutePath);\n }\n\n public async mkdir(inputPath: string): Promise<void> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n await this.backend.mkdir(absolutePath);\n }\n\n public async remove(inputPath: string): Promise<void> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n await this.withPathLock(absolutePath, () => this.backend.remove(absolutePath));\n }\n\n /** Whether a `/`-separated relative path hits the policy deny list. */\n private isDenied(relativePath: string): boolean {\n const denyPaths = this.policy.denyPaths;\n\n return (\n denyPaths !== undefined && denyPaths.length > 0 && matchesAnyGlob(relativePath, denyPaths)\n );\n }\n}\n\n/** `{ spilledTo }` for every spill file named in the truncation metadata, or nothing. */\nfunction spilledField(\n truncation: { stdout?: WorkspaceTruncation; stderr?: WorkspaceTruncation } | undefined,\n): { spilledTo?: string[] } {\n const paths = [truncation?.stdout?.path, truncation?.stderr?.path].filter(\n (file): file is string => file !== undefined,\n );\n\n return paths.length > 0 ? { spilledTo: paths } : {};\n}\n\n/** Combine per-stream truncation metadata; `undefined` when neither stream was clipped. */\nfunction streamTruncation(\n stdout: WorkspaceTruncation | undefined,\n stderr: WorkspaceTruncation | undefined,\n): { stdout?: WorkspaceTruncation; stderr?: WorkspaceTruncation } | undefined {\n if (stdout === undefined && stderr === undefined) {\n return undefined;\n }\n\n return {\n ...(stdout !== undefined ? { stdout } : {}),\n ...(stderr !== undefined ? { stderr } : {}),\n };\n}\n\n/** The lower of two optional limits (either may be absent). */\nfunction lowerOf(\n policyLimit: number | undefined,\n requested: number | undefined,\n): number | undefined {\n if (requested === undefined || requested <= 0) {\n return policyLimit;\n }\n\n return policyLimit === undefined ? requested : Math.min(policyLimit, requested);\n}\n\n/**\n * Create the policy-enforced operation layer over a backend.\n *\n * The returned {@link WorkspaceOps} is the single seam both the\n * agent-facing `.tools.*` factories and the human-facing direct methods\n * delegate to — one jail, one command-gate, one read-before-edit guard,\n * regardless of caller. Path inputs are workspace-relative and resolved\n * against `policy.cwd`; escapes and denied commands surface as typed\n * {@link WorkspacePolicyError} / {@link WorkspaceEditError}.\n *\n * @param backend - The dumb IO executor (local disk or in-memory mock).\n * @param policy - The policy that bounds every operation.\n * @param runtime - State shared with other views of the same workspace\n * (its background commands); a fresh one when omitted.\n *\n * @example\n * const ops = createOps(localBackend, { cwd: \"/srv/api\", shell: { allow: [\"npm\"] } });\n * const { content, hash } = await ops.readFile(\"src/index.ts\");\n */\nexport function createOps(\n backend: WorkspaceBackend,\n policy: WorkspacePolicy,\n runtime?: OpsRuntime,\n): WorkspaceOps {\n return new Ops(backend, policy, runtime ?? { jobs: new ShellJobs() });\n}\n"],"mappings":"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"}
@@ -49,6 +49,8 @@ export interface ShellCheckOptions {
49
49
  host?: ShellHost;
50
50
  /** The already-resolved shell, to skip resolving it again. */
51
51
  resolved?: ResolvedShell;
52
+ /** The platform the lines run on. Default: `host.platform`, else the running one. */
53
+ platform?: NodeJS.Platform;
52
54
  }
53
55
  /**
54
56
  * The shell policy's verdict on a command line, without touching the
@@ -1 +1 @@
1
- {"version":3,"file":"shell-check.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-check.ts"],"mappings":";;;;;;;YAkCY;;iBAGK;;EAEf;;EAEA,QAAQ;;EAER;;;;;;iBAOe;;EAEf;;EAEA,SAAS;;EAET;;EAEA;;EAEA;;;;;;EAMA,MAAM;;EAEN;;EAEA,OAAO;;;iBA0QQ;;EAEf,sBAAsB;;EAEtB,OAAO;;EAEP,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAsgCG,kBACd,QAAQ,iBACR,iBACA,UAAU,oBACT;;;;;;;;;;;;;wBAyHa,qBACd,iBACA,SAAS,KAAK,2BACd,OAAO,YACN"}
1
+ {"version":3,"file":"shell-check.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/policy/shell-check.ts"],"mappings":";;;;;;;YAkCY;;iBAGK;;EAEf;;EAEA,QAAQ;;EAER;;;;;;iBAOe;;EAEf;;EAEA,SAAS;;EAET;;EAEA;;EAEA;;;;;;EAMA,MAAM;;EAEN;;EAEA,OAAO;;;iBA0QQ;;EAEf,sBAAsB;;EAEtB,OAAO;;EAEP,WAAW;;EAEX,WAAW,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAykCJ,kBACd,QAAQ,iBACR,iBACA,UAAU,oBACT;;;;;;;;;;;;;wBAyJa,qBACd,iBACA,SAAS,KAAK,2BACd,OAAO,YACN"}