@warlock.js/ai-workspace 5.44.0 → 5.45.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.
- package/CHANGELOG.md +13 -0
- package/cjs/index.cjs +101 -35
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/shell-approval.type.d.mts +11 -1
- package/esm/contracts/shell-approval.type.d.mts.map +1 -1
- package/esm/contracts/tool-io.type.d.mts +10 -0
- package/esm/contracts/tool-io.type.d.mts.map +1 -1
- package/esm/contracts/workspace.contract.d.mts +12 -1
- package/esm/contracts/workspace.contract.d.mts.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/ops.d.mts.map +1 -1
- package/esm/ops.mjs +27 -10
- package/esm/ops.mjs.map +1 -1
- package/esm/output/spill.mjs +4 -1
- package/esm/output/spill.mjs.map +1 -1
- package/esm/policy/approval-helpers.mjs +22 -0
- package/esm/policy/approval-helpers.mjs.map +1 -0
- package/esm/policy/powershell-parser.mjs +6 -4
- package/esm/policy/powershell-parser.mjs.map +1 -1
- package/esm/policy/shell-check.d.mts.map +1 -1
- package/esm/policy/shell-check.mjs +4 -4
- package/esm/policy/shell-check.mjs.map +1 -1
- package/esm/policy/shell-interpreter.d.mts +20 -2
- package/esm/policy/shell-interpreter.d.mts.map +1 -1
- package/esm/policy/shell-interpreter.mjs +26 -12
- package/esm/policy/shell-interpreter.mjs.map +1 -1
- package/esm/policy/shell-parser.d.mts +3 -1
- package/esm/policy/shell-parser.d.mts.map +1 -1
- package/esm/policy/shell-parser.mjs +1 -1
- package/esm/policy/shell-parser.mjs.map +1 -1
- package/esm/tools/run-shell.d.mts +1 -1
- package/esm/tools/run-shell.d.mts.map +1 -1
- package/esm/tools/run-shell.mjs +2 -1
- package/esm/tools/run-shell.mjs.map +1 -1
- package/esm/tools/run-tests.d.mts +1 -1
- package/esm/tools/run-tests.mjs +1 -1
- package/esm/tools/run-tests.mjs.map +1 -1
- package/esm/workspace.d.mts.map +1 -1
- package/esm/workspace.mjs +12 -1
- package/esm/workspace.mjs.map +1 -1
- package/llms-full.txt +23 -17
- package/package.json +3 -3
- package/skills/use-a-workspace/SKILL.md +23 -17
|
@@ -54,7 +54,17 @@ export interface WorkspaceShellAskRequest {
|
|
|
54
54
|
*/
|
|
55
55
|
export interface WorkspaceShellAskDecision {
|
|
56
56
|
allow: boolean;
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* One pattern or several (a line such as `git status -s && npm test` asks
|
|
59
|
+
* about two commands). Each is added once; list them with
|
|
60
|
+
* `workspace.shell.remembered()` and remove one with `forget(pattern)`.
|
|
61
|
+
*/
|
|
62
|
+
remember?: WorkspaceCommandPattern | readonly WorkspaceCommandPattern[];
|
|
63
|
+
/**
|
|
64
|
+
* Why the line was refused (used when `allow` is `false`). Appended to the
|
|
65
|
+
* refusal the model reads, cut at 500 characters with a `[truncated]` marker.
|
|
66
|
+
*/
|
|
67
|
+
reason?: string;
|
|
58
68
|
}
|
|
59
69
|
/**
|
|
60
70
|
* Decides on commands that need approval. A throw or a rejection refuses the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-approval.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/shell-approval.type.ts"],"mappings":";;;YAGY;;;;;;;iBAQK;;;;;;EAMf,OAAO;;EAEP,QAAQ;;EAER;;;YAIU;;;;;;;;iBASK;;EAEf;;EAEA,OAAO;;EAEP,QAAQ;;;iBAIO;;EAEf;;EAEA,eAAe;;EAEf,SAAS;;;;;;;;iBASM;EACf
|
|
1
|
+
{"version":3,"file":"shell-approval.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/shell-approval.type.ts"],"mappings":";;;YAGY;;;;;;;iBAQK;;;;;;EAMf,OAAO;;EAEP,QAAQ;;EAER;;;YAIU;;;;;;;;iBASK;;EAEf;;EAEA,OAAO;;EAEP,QAAQ;;;iBAIO;;EAEf;;EAEA,eAAe;;EAEf,SAAS;;;;;;;;iBASM;EACf;;;;;;EAMA,WAAW,mCAAmC;;;;;EAK9C;;;;;;;;;YAUU,uBACV,SAAS,6BACN,QAAQ;;;;;iBAMI;;EAEf;;EAEA"}
|
|
@@ -56,6 +56,8 @@ export interface ReadFileResult {
|
|
|
56
56
|
bytesCapped: boolean;
|
|
57
57
|
/** Set when `output.spillDir` is on and `bytesCapped`: where the full window was saved. */
|
|
58
58
|
truncation?: WorkspaceTruncation;
|
|
59
|
+
/** Set with `truncation.path`: the file holding the full window (absolute paths). */
|
|
60
|
+
spilledTo?: string[];
|
|
59
61
|
/** UTF-8 byte length of the full file. */
|
|
60
62
|
totalBytes: number;
|
|
61
63
|
/**
|
|
@@ -294,6 +296,8 @@ export interface ShellOutputResult extends ShellJobInfo {
|
|
|
294
296
|
stdout?: WorkspaceTruncation;
|
|
295
297
|
stderr?: WorkspaceTruncation;
|
|
296
298
|
};
|
|
299
|
+
/** Absolute paths of the files holding the complete output of each stream that spilled to disk. */
|
|
300
|
+
spilledTo?: string[];
|
|
297
301
|
}
|
|
298
302
|
/** Input to the `shell_kill` tool. */
|
|
299
303
|
export interface ShellKillInput {
|
|
@@ -317,6 +321,8 @@ export interface RunShellResult {
|
|
|
317
321
|
stdout?: WorkspaceTruncation;
|
|
318
322
|
stderr?: WorkspaceTruncation;
|
|
319
323
|
};
|
|
324
|
+
/** Absolute paths of the files holding the complete output of each stream that spilled to disk. */
|
|
325
|
+
spilledTo?: string[];
|
|
320
326
|
/**
|
|
321
327
|
* True when the invocation's `signal` aborted: before start (nothing ran)
|
|
322
328
|
* or mid-run (the process tree was killed). Never true together with
|
|
@@ -361,6 +367,8 @@ export interface GrepResult {
|
|
|
361
367
|
truncated?: boolean;
|
|
362
368
|
/** What was left out; set with `truncated`. */
|
|
363
369
|
truncation?: WorkspaceTruncation;
|
|
370
|
+
/** Absolute path of the file holding the complete output, when it spilled to disk. */
|
|
371
|
+
spilledTo?: string[];
|
|
364
372
|
}
|
|
365
373
|
/** Input to the `list_dir` tool. */
|
|
366
374
|
export interface ListDirInput {
|
|
@@ -413,6 +421,8 @@ export interface GlobResult {
|
|
|
413
421
|
truncated?: boolean;
|
|
414
422
|
/** What was left out; set with `truncated`. */
|
|
415
423
|
truncation?: WorkspaceTruncation;
|
|
424
|
+
/** Absolute path of the file holding the complete output, when it spilled to disk. */
|
|
425
|
+
spilledTo?: string[];
|
|
416
426
|
}
|
|
417
427
|
/** State of one todo item. */
|
|
418
428
|
export type WorkspaceTodoStatus = "pending" | "in_progress" | "completed";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-io.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/tool-io.type.ts"],"mappings":";;;;;;;;;;;;;;YAeY;;iBAiBK;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;;iBAee;;EAEf;;EAEA;;;;;EAKA;;EAEA;;;;;;EAMA;;EAEA;;EAEA,aAAa;;EAEb;;;;;;EAMA;;;;;;iBAOe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA,YAAY,QAAQ;;EAEpB;;;iBAIe;;EAEf;;EAEA;;EAEA;;;;;;;;iBASe;;EAEf;;EAEA,OAAO;;EAEP;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,YAAY,QAAQ;;EAEpB;;;;;;;iBAQe;;EAEf;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf,OAAO;;EAEP;;EAEA;;;iBAIe;;EAEf;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf;;;;;;EAMA,SAAS;;;iBAIM;;EAEf;;EAEA;;;;;EAKA;;;;;;;;;;;YAYU;;iBAGK;;EAEf;;EAEA;;EAEA,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;;;iBAIe,iCAAiC;;EAEhD;;;iBAIe;;;;;;EAMf;;;iBAIe;;EAEf;;;;;EAKA;;;;;EAKA;;;iBAIe;;EAEf;;EAEA;;EAEA,SAAS;;;iBAIM,0BAA0B;;EAEzC;;EAEA;;;;;EAKA;;;;;EAKA;;EAEA;;EAEA;IAAe,SAAS;IAAqB,SAAS
|
|
1
|
+
{"version":3,"file":"tool-io.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/tool-io.type.ts"],"mappings":";;;;;;;;;;;;;;YAeY;;iBAiBK;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;;iBAee;;EAEf;;EAEA;;;;;EAKA;;EAEA;;;;;;EAMA;;EAEA;;EAEA,aAAa;;EAEb;;EAEA;;;;;;EAMA;;;;;;iBAOe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA,YAAY,QAAQ;;EAEpB;;;iBAIe;;EAEf;;EAEA;;EAEA;;;;;;;;iBASe;;EAEf;;EAEA,OAAO;;EAEP;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,YAAY,QAAQ;;EAEpB;;;;;;;iBAQe;;EAEf;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf,OAAO;;EAEP;;EAEA;;;iBAIe;;EAEf;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf;;;;;;EAMA,SAAS;;;iBAIM;;EAEf;;EAEA;;;;;EAKA;;;;;;;;;;;YAYU;;iBAGK;;EAEf;;EAEA;;EAEA,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;;;iBAIe,iCAAiC;;EAEhD;;;iBAIe;;;;;;EAMf;;;iBAIe;;EAEf;;;;;EAKA;;;;;EAKA;;;iBAIe;;EAEf;;EAEA;;EAEA,SAAS;;;iBAIM,0BAA0B;;EAEzC;;EAEA;;;;;EAKA;;;;;EAKA;;EAEA;;EAEA;IAAe,SAAS;IAAqB,SAAS;;;EAEtD;;;iBAIe;;EAEf;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;IAAe,SAAS;IAAqB,SAAS;;;EAEtD;;;;;;EAMA;;;;;;;iBAQe;;EAEf;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf,SAAS;;EAET;;EAEA;;EAEA,aAAa;;EAEb;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;;YAIU;;iBAGK;;EAEf;;EAEA,MAAM;;EAEN;;EAEA;;;iBAIe;;EAEf;;EAEA,SAAS;;EAET;;;iBAIe;;EAEf;;;iBAIe;;EAEf;;EAEA;;EAEA,aAAa;;EAEb;;;YAIU;;iBAGK;;EAEf;;EAEA;;EAEA,QAAQ;;;iBAIO;;EAEf;;EAEA;;EAEA,QAAQ;;;iBAIO;;EAEf,OAAO;;;iBAIQ;;EAEf,OAAO;;EAEP,QAAQ,OAAO"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApplyPatchInput, ApplyPatchResult, EditFileInput, EditFileResult, ExecOptions, GrepResult, ListDirOptions, ListDirResult, MultiEditInput, MultiEditResult, ReadFileResult, RunShellResult, ShellJobInfo, ShellOutputOptions, ShellOutputResult, StartShellOptions, TodoListResult, WorkspaceTodoInput, WorkspaceToolName } from "./tool-io.type.mjs";
|
|
2
2
|
import { WorkspaceShellClassification } from "./shell-approval.type.mjs";
|
|
3
|
-
import { WorkspacePolicy } from "./workspace-policy.type.mjs";
|
|
3
|
+
import { WorkspaceCommandPattern, WorkspacePolicy } from "./workspace-policy.type.mjs";
|
|
4
4
|
import { ToolContract } from "@warlock.js/ai";
|
|
5
5
|
//#region ../../packages/ai-workspace/src/contracts/workspace.contract.d.ts
|
|
6
6
|
/**
|
|
@@ -116,6 +116,17 @@ export interface Workspace {
|
|
|
116
116
|
* cannot read with confidence returns `opaque` (why) and no commands.
|
|
117
117
|
*/
|
|
118
118
|
classify(line: string): WorkspaceShellClassification;
|
|
119
|
+
/**
|
|
120
|
+
* The command patterns approved so far with `onAsk`'s `remember`, shared
|
|
121
|
+
* by every view of the workspace (`readonly()`, `scope()`).
|
|
122
|
+
*/
|
|
123
|
+
remembered(): readonly WorkspaceCommandPattern[];
|
|
124
|
+
/**
|
|
125
|
+
* Remove a remembered pattern (a string by value, a RegExp by source and
|
|
126
|
+
* flags). Returns whether one was removed. A configured deny is
|
|
127
|
+
* unaffected either way.
|
|
128
|
+
*/
|
|
129
|
+
forget(pattern: WorkspaceCommandPattern): boolean;
|
|
119
130
|
};
|
|
120
131
|
/**
|
|
121
132
|
* Read a jailed file. `offset`/`limit` select a 1-based line window and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/workspace.contract.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"workspace.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/workspace.contract.ts"],"mappings":";;;;;;;;;;;;;;;iBAoCiB;;;;;;EAMf,OAAO;;EAEP,QAAQ,OAAO,sBAAsB;;EAErC,SAAS;IAAS;MAAkB;;EAEpC,SAAS;IAAS;MAAkB;;EAEpC,UAAU;IAAS;MAAkB;;EAErC,WAAW;IAAS;MAAkB;;EAEtC,UAAU;IAAS;MAAkB;;EAErC,QAAQ;IAAS;MAAkB;;EAEnC,SAAS;IAAS;MAAkB;;EAEpC,YAAY;IAAS;MAAkB;;EAEvC,UAAU;IAAS;MAAkB;;EAErC,SAAS;IAAS;IAAe;MAAqB;;EAEtD,KAAK;IAAS;MAAkB;;EAEhC,KAAK;IAAS;MAAkB;;EAEhC,UAAU;IAAS;MAAkB;;EAErC,SAAS;IAAS;MAAkB;;;;;;;;;;;;;;;;;;;;;;;;iBAyBrB;;WAEN,QAAQ;;WAGR,OAAO;;WAGP;;;;;;;IAOP,SAAS,eAAe;;;;;IAKxB,uBAAuB;;;;;;IAMvB,OAAO,SAAS;;;;;;;;EASlB,SAAS,cAAc;IAAS;IAAiB;MAAmB,QAAQ;;EAG5E,UAAU,cAAc,kBAAkB;IAAU;IAAc;;;EAGlE,SAAS,OAAO,gBAAgB,QAAQ;;EAGxC,UAAU,OAAO,iBAAiB,QAAQ;;EAG1C,WAAW,OAAO,kBAAkB,QAAQ;;EAG5C,QAAQ,eAAe,OAAO,iBAAiB,QAAQ;;;;;;EAOvD,KAAK,iBAAiB,OAAO,cAAc,QAAQ;;;;;;EAOnD,WAAW,iBAAiB,OAAO,oBAAoB,QAAQ;;EAG/D,YAAY,YAAY,OAAO,qBAAqB,QAAQ;;EAG5D,UAAU,aAAa,QAAQ;;EAG/B,cAAc;;EAGd,aAAa;;EAGb,WAAW,OAAO,uBAAuB;;;;;;;EAQzC,WAAW;;EAGX,KAAK,iBAAiB;IAAS;IAAe;MAAyB,QAAQ;;EAG/E,KAAK,kBAAkB;;EAGvB,OAAO,eAAe;;EAGtB,MAAM,eAAe;;EAGrB,OAAO,eAAe;;;;;;EAOtB,YAAY;;;;;;EAOZ,MAAM,iBAAiB"}
|
package/esm/index.d.mts
CHANGED
|
@@ -10,7 +10,7 @@ import { Workspace, WorkspaceTools } from "./contracts/workspace.contract.mjs";
|
|
|
10
10
|
import "./contracts/index.mjs";
|
|
11
11
|
import { WorkspaceEditError, WorkspaceEditErrorOptions, WorkspaceEditFailure, WorkspaceJobError, WorkspaceJobErrorOptions, WorkspaceJobFailure, WorkspacePolicyError, WorkspacePolicyErrorOptions, WorkspacePolicyViolation, WorkspaceShellRefusalReason } from "./errors.mjs";
|
|
12
12
|
import { ResolvedPath, buildEnv, isCommandAllowed, resolveInJail } from "./policy/policy.mjs";
|
|
13
|
-
import { ResolvedShell, ResolvedShellInterpreter, ShellHost, resolveShell } from "./policy/shell-interpreter.mjs";
|
|
13
|
+
import { PowerShellEdition, ResolvedShell, ResolvedShellInterpreter, ShellHost, resolveShell } from "./policy/shell-interpreter.mjs";
|
|
14
14
|
import { WorkspaceShellCheck, WorkspaceShellPath, WorkspaceShellPathAccess, checkShellCommand, classifyShellCommand } from "./policy/shell-check.mjs";
|
|
15
15
|
import { ArgvAnalysis, analyzeArgvCommand, tokenizeCommand } from "./policy/tokenize-command.mjs";
|
|
16
16
|
import { applyPatchWithOps } from "./patch/apply-patch-generic.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 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 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 };
|
|
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":";;;;;;;;;;;;;;;;;
|
|
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"}
|
package/esm/ops.mjs
CHANGED
|
@@ -7,6 +7,7 @@ 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
9
|
import { OutputSpiller, splitLines } from "./output/spill.mjs";
|
|
10
|
+
import { refusalReason, samePattern } from "./policy/approval-helpers.mjs";
|
|
10
11
|
import { ShellJobs } from "./shell-jobs.mjs";
|
|
11
12
|
import path from "node:path";
|
|
12
13
|
import { fs } from "@warlock.js/fs";
|
|
@@ -239,6 +240,7 @@ var Ops = class {
|
|
|
239
240
|
return {
|
|
240
241
|
content: spilled?.content ?? content,
|
|
241
242
|
...spilled !== void 0 ? { truncation: spilled.truncation } : {},
|
|
243
|
+
...spilled?.truncation.path !== void 0 ? { spilledTo: [spilled.truncation.path] } : {},
|
|
242
244
|
startLine: offset,
|
|
243
245
|
endLine,
|
|
244
246
|
totalLines,
|
|
@@ -405,12 +407,18 @@ var Ops = class {
|
|
|
405
407
|
} catch {
|
|
406
408
|
decision = void 0;
|
|
407
409
|
}
|
|
408
|
-
if (decision?.allow !== true)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
410
|
+
if (decision?.allow !== true) {
|
|
411
|
+
const reason = refusalReason(decision?.reason);
|
|
412
|
+
throw new WorkspacePolicyError(`Running ${asks.map((ask) => `\`${ask.argv.join(" ")}\``).join(", ")} was not approved${reason}`, {
|
|
413
|
+
type: "denied-command",
|
|
414
|
+
reason: "approval-denied",
|
|
415
|
+
command
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (decision.remember !== void 0) {
|
|
419
|
+
const patterns = Array.isArray(decision.remember) ? decision.remember : [decision.remember];
|
|
420
|
+
for (const pattern of patterns) if (!this.approvals.some((entry) => samePattern(entry, pattern))) this.approvals.push(pattern);
|
|
421
|
+
}
|
|
414
422
|
}
|
|
415
423
|
async exec(command, opts) {
|
|
416
424
|
await this.assertCommandAllowed(command, opts?.signal);
|
|
@@ -446,7 +454,8 @@ var Ops = class {
|
|
|
446
454
|
truncated: stdout.truncated || stderr.truncated,
|
|
447
455
|
timedOut: result.timedOut && !cancelled,
|
|
448
456
|
cancelled,
|
|
449
|
-
...truncation !== void 0 ? { truncation } : {}
|
|
457
|
+
...truncation !== void 0 ? { truncation } : {},
|
|
458
|
+
...spilledField(truncation)
|
|
450
459
|
};
|
|
451
460
|
}
|
|
452
461
|
async startShell(command, opts) {
|
|
@@ -493,7 +502,8 @@ var Ops = class {
|
|
|
493
502
|
stdout: stdout.value,
|
|
494
503
|
stderr: stderr.value,
|
|
495
504
|
truncated: true,
|
|
496
|
-
truncation
|
|
505
|
+
truncation,
|
|
506
|
+
...spilledField(truncation)
|
|
497
507
|
};
|
|
498
508
|
}
|
|
499
509
|
/**
|
|
@@ -651,7 +661,8 @@ var Ops = class {
|
|
|
651
661
|
matches: fitted.items,
|
|
652
662
|
total: matches.length,
|
|
653
663
|
truncated: true,
|
|
654
|
-
truncation: fitted.truncation
|
|
664
|
+
truncation: fitted.truncation,
|
|
665
|
+
...spilledField({ stdout: fitted.truncation })
|
|
655
666
|
};
|
|
656
667
|
}
|
|
657
668
|
async globResult(pattern) {
|
|
@@ -660,7 +671,8 @@ var Ops = class {
|
|
|
660
671
|
return fitted.truncation === void 0 ? { paths: all } : {
|
|
661
672
|
paths: fitted.items,
|
|
662
673
|
truncated: true,
|
|
663
|
-
truncation: fitted.truncation
|
|
674
|
+
truncation: fitted.truncation,
|
|
675
|
+
...spilledField({ stdout: fitted.truncation })
|
|
664
676
|
};
|
|
665
677
|
}
|
|
666
678
|
async glob(pattern) {
|
|
@@ -693,6 +705,11 @@ var Ops = class {
|
|
|
693
705
|
return denyPaths !== void 0 && denyPaths.length > 0 && matchesAnyGlob(relativePath, denyPaths);
|
|
694
706
|
}
|
|
695
707
|
};
|
|
708
|
+
/** `{ spilledTo }` for every spill file named in the truncation metadata, or nothing. */
|
|
709
|
+
function spilledField(truncation) {
|
|
710
|
+
const paths = [truncation?.stdout?.path, truncation?.stderr?.path].filter((file) => file !== void 0);
|
|
711
|
+
return paths.length > 0 ? { spilledTo: paths } : {};
|
|
712
|
+
}
|
|
696
713
|
/** Combine per-stream truncation metadata; `undefined` when neither stream was clipped. */
|
|
697
714
|
function streamTruncation(stdout, stderr) {
|
|
698
715
|
if (stdout === void 0 && stderr === void 0) return;
|
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 { 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 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 throw new WorkspacePolicyError(\n `Running ${asks.map((ask) => `\\`${ask.argv.join(\" \")}\\``).join(\", \")} was not approved.`,\n { type: \"denied-command\", reason: \"approval-denied\", command },\n );\n }\n\n if (decision.remember !== undefined && !this.approvals.includes(decision.remember)) {\n this.approvals.push(decision.remember);\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 };\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 : { ...result, stdout: stdout.value, stderr: stderr.value, truncated: true, truncation };\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 };\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 : { paths: fitted.items, truncated: true, truncation: fitted.truncation };\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/** 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 { 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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"}
|
package/esm/output/spill.mjs
CHANGED
|
@@ -161,7 +161,10 @@ var OutputSpiller = class {
|
|
|
161
161
|
const where = file === void 0 ? "the rest was dropped" : `full output saved to ${file}`;
|
|
162
162
|
const marker = `[showing ${tail.length === 0 ? `lines 1-${head.length}` : head.length === 0 ? `lines ${total - tail.length + 1}-${total}` : `lines 1-${head.length} and ${total - tail.length + 1}-${total}`} of ${total}; ${where}]`;
|
|
163
163
|
const parts = [...head.length > 0 ? [head.join("\n")] : [], marker];
|
|
164
|
-
if (tail.length > 0)
|
|
164
|
+
if (tail.length > 0) {
|
|
165
|
+
parts.push(tail.join("\n"));
|
|
166
|
+
if (file !== void 0) parts.push(`[full output saved to ${file}]`);
|
|
167
|
+
}
|
|
165
168
|
return {
|
|
166
169
|
value: parts.join("\n"),
|
|
167
170
|
truncation: {
|
package/esm/output/spill.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spill.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/output/spill.ts"],"sourcesContent":["import path from \"node:path\";\nimport type {\n WorkspaceBackend,\n WorkspaceOutputKeep,\n WorkspacePolicy,\n WorkspaceTruncation,\n} from \"../contracts\";\nimport { resolveSpillDir } from \"./spill-dir\";\n\nconst DEFAULT_MAX_LINES = 2000;\nconst DEFAULT_MAX_BYTES = 50 * 1024;\nconst DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;\nconst SPILL_NAME = /^spill-(\\d+)-[a-z0-9]+-[a-z0-9_-]+\\.txt$/;\n\n/** What spilling an output produced. */\nexport interface SpilledText {\n /** The text to return (marker included). */\n value: string;\n /** Set when anything was left out. */\n truncation?: WorkspaceTruncation;\n}\n\n/** What spilling a list of entries produced. */\nexport interface SpilledList<T> {\n items: T[];\n truncation?: WorkspaceTruncation;\n}\n\nfunction byteLength(value: string): number {\n return Buffer.byteLength(value, \"utf8\");\n}\n\n/** The first `maxBytes` bytes of `value`, cut on a character boundary. */\nfunction headOf(value: string, maxBytes: number): string {\n const bytes = Buffer.from(value, \"utf8\");\n\n if (bytes.byteLength <= maxBytes) {\n return value;\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 bytes.subarray(0, cut).toString(\"utf8\");\n}\n\n/** The last `maxBytes` bytes of `value`, cut on a character boundary. */\nfunction tailOf(value: string, maxBytes: number): string {\n const bytes = Buffer.from(value, \"utf8\");\n\n if (bytes.byteLength <= maxBytes) {\n return value;\n }\n\n let from = bytes.byteLength - Math.max(0, maxBytes);\n\n while (from < bytes.byteLength && ((bytes[from] ?? 0) & 0xc0) === 0x80) {\n from++;\n }\n\n return bytes.subarray(from).toString(\"utf8\");\n}\n\n/** Lines of `value`; a trailing line break does not add an empty line. */\nexport function splitLines(value: string): string[] {\n if (value === \"\") {\n return [];\n }\n\n const lines = value.split(\"\\n\");\n\n if (lines[lines.length - 1] === \"\") {\n lines.pop();\n }\n\n return lines;\n}\n\n/** Pick lines from the front within `lines` / `bytes`; one oversized line is clipped. */\nfunction takeHead(lines: string[], maxLines: number, maxBytes: number): string[] {\n const out: string[] = [];\n let used = 0;\n\n for (const line of lines) {\n if (out.length >= maxLines) {\n break;\n }\n\n const cost = byteLength(line) + 1;\n\n if (used + cost > maxBytes) {\n if (out.length === 0 && maxBytes > 0) {\n out.push(headOf(line, maxBytes));\n }\n\n break;\n }\n\n out.push(line);\n used += cost;\n }\n\n return out;\n}\n\n/** Pick lines from the back, same limits; the result is in file order. */\nfunction takeTail(lines: string[], maxLines: number, maxBytes: number): string[] {\n const out: string[] = [];\n let used = 0;\n\n for (let index = lines.length - 1; index >= 0; index--) {\n const line = lines[index] ?? \"\";\n\n if (out.length >= maxLines) {\n break;\n }\n\n const cost = byteLength(line) + 1;\n\n if (used + cost > maxBytes) {\n if (out.length === 0 && maxBytes > 0) {\n out.push(tailOf(line, maxBytes));\n }\n\n break;\n }\n\n out.unshift(line);\n used += cost;\n }\n\n return out;\n}\n\n/** Choose the lines to keep for `keep`; `headCount` lines come from the front. */\nfunction select(\n lines: string[],\n keep: WorkspaceOutputKeep,\n maxLines: number,\n maxBytes: number,\n): { head: string[]; tail: string[] } {\n if (keep === \"head\") {\n return { head: takeHead(lines, maxLines, maxBytes), tail: [] };\n }\n\n if (keep === \"tail\") {\n return { head: [], tail: takeTail(lines, maxLines, maxBytes) };\n }\n\n const headLines = Math.ceil(maxLines / 2);\n const headBytes = Math.ceil(maxBytes / 2);\n const head = takeHead(lines, headLines, headBytes);\n const rest = lines.slice(head.length);\n const tail = takeTail(rest, maxLines - head.length, maxBytes - headBytes);\n\n return { head, tail };\n}\n\n/**\n * Keeps oversized command, search and read output from flooding the model:\n * with `output.spillDir` set it saves the full output to a file and returns\n * the part the policy keeps plus a pointer to it. One instance per ops layer;\n * it remembers the files it wrote so `dispose()` can remove them.\n */\nexport class OutputSpiller {\n private readonly created: string[] = [];\n private readonly dir: string | undefined;\n\n public constructor(\n private readonly backend: WorkspaceBackend,\n private readonly policy: WorkspacePolicy,\n ) {\n this.dir = resolveSpillDir(policy);\n\n if (this.dir !== undefined && this.retentionMs > 0) {\n void this.sweep();\n }\n }\n\n /** True when `output.spillDir` is set. */\n public get enabled(): boolean {\n return this.dir !== undefined;\n }\n\n /** The spill directory, when spilling is on. */\n public get spillDir(): string | undefined {\n return this.dir;\n }\n\n private get retentionMs(): number {\n return this.policy.output?.retentionMs ?? DEFAULT_RETENTION_MS;\n }\n\n private limits(cap: number | undefined): { maxLines: number; maxBytes: number } {\n const configured = this.policy.output?.maxBytes ?? DEFAULT_MAX_BYTES;\n\n return {\n maxLines: Math.max(1, Math.floor(this.policy.output?.maxLines ?? DEFAULT_MAX_LINES)),\n maxBytes: Math.max(1, Math.floor(cap === undefined ? configured : Math.min(configured, cap))),\n };\n }\n\n /** Remove spill files older than the retention (only files named like ours). */\n public async sweep(): Promise<void> {\n if (this.dir === undefined) {\n return;\n }\n\n try {\n const now = Date.now();\n\n for (const file of await this.backend.list(this.dir)) {\n const match = SPILL_NAME.exec(path.basename(file));\n\n if (match !== null && now - Number(match[1]) > this.retentionMs) {\n await this.backend.remove(file).catch(() => undefined);\n }\n }\n } catch {\n // No spill directory yet, or it is not listable: nothing to clean.\n }\n }\n\n /** Write `content` to a new spill file and return its absolute path. */\n private async write(label: string, content: string): Promise<string | undefined> {\n if (this.dir === undefined) {\n return undefined;\n }\n\n try {\n if (this.retentionMs > 0) {\n await this.sweep();\n }\n\n const safe = label.toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\") || \"output\";\n const rand = Math.random().toString(36).slice(2, 8) || \"x\";\n const file = path.join(this.dir, `spill-${Date.now()}-${rand}-${safe}.txt`);\n\n await this.backend.mkdir(this.dir);\n await this.backend.writeFile(file, content);\n this.created.push(file);\n\n return file;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Fit `value` to the policy. Returns it untouched when it fits. Over the\n * limits with spilling on, the whole text is saved and the kept lines come\n * back with a `[showing lines … of …; full output saved to …]` marker.\n * `cap` is a tighter byte limit from elsewhere (`shell.maxOutputBytes`).\n */\n public async text(\n label: string,\n value: string,\n defaultKeep: WorkspaceOutputKeep,\n cap?: number,\n ): Promise<SpilledText> {\n if (this.dir === undefined) {\n return { value };\n }\n\n const { maxLines, maxBytes } = this.limits(cap);\n const lines = splitLines(value);\n const totalBytes = byteLength(value);\n\n if (lines.length <= maxLines && totalBytes <= maxBytes) {\n return { value };\n }\n\n const keep = this.policy.output?.keep ?? defaultKeep;\n const { head, tail } = select(lines, keep, maxLines, maxBytes);\n const file = await this.write(label, value);\n const shown = head.length + tail.length;\n const total = lines.length;\n const where = file === undefined ? \"the rest was dropped\" : `full output saved to ${file}`;\n const range =\n tail.length === 0\n ? `lines 1-${head.length}`\n : head.length === 0\n ? `lines ${total - tail.length + 1}-${total}`\n : `lines 1-${head.length} and ${total - tail.length + 1}-${total}`;\n const marker = `[showing ${range} of ${total}; ${where}]`;\n const parts = [...(head.length > 0 ? [head.join(\"\\n\")] : []), marker];\n\n if (tail.length > 0) {\n parts.push(tail.join(\"\\n\"));\n }\n\n return {\n value: parts.join(\"\\n\"),\n truncation: {\n shown: { lines: shown },\n total: { lines: total, bytes: totalBytes },\n ...(file !== undefined ? { path: file } : {}),\n },\n };\n }\n\n /**\n * Fit a list of entries (one line each when rendered) to the policy; the\n * rendered full list is what gets saved.\n */\n public async list<T>(\n label: string,\n items: T[],\n render: (item: T) => string,\n defaultKeep: WorkspaceOutputKeep,\n ): Promise<SpilledList<T>> {\n if (this.dir === undefined) {\n return { items };\n }\n\n const { maxLines, maxBytes } = this.limits(undefined);\n const lines = items.map(render);\n const totalBytes = lines.reduce((sum, line) => sum + byteLength(line) + 1, 0);\n\n if (items.length <= maxLines && totalBytes <= maxBytes) {\n return { items };\n }\n\n const keep = this.policy.output?.keep ?? defaultKeep;\n // Count how many whole entries fit from each end, then slice the items.\n const { head, tail } = select(lines, keep, maxLines, maxBytes);\n const headCount = head.length;\n const tailCount = tail.length;\n const file = await this.write(label, lines.join(\"\\n\") + \"\\n\");\n\n return {\n items: [...items.slice(0, headCount), ...items.slice(items.length - tailCount)],\n truncation: {\n shown: { lines: headCount + tailCount },\n total: { lines: items.length, bytes: totalBytes },\n ...(file !== undefined ? { path: file } : {}),\n },\n };\n }\n\n /** Save the complete text of an output that was already cut elsewhere. */\n public async save(label: string, content: string): Promise<string | undefined> {\n return this.write(label, content);\n }\n\n /** Remove expired files, and this run's own when `retentionMs` is `0`. */\n public async dispose(): Promise<void> {\n if (this.dir === undefined) {\n return;\n }\n\n if (this.retentionMs === 0) {\n for (const file of this.created.splice(0)) {\n await this.backend.remove(file).catch(() => undefined);\n }\n\n return;\n }\n\n await this.sweep();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spill.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/output/spill.ts"],"sourcesContent":["import path from \"node:path\";\nimport type {\n WorkspaceBackend,\n WorkspaceOutputKeep,\n WorkspacePolicy,\n WorkspaceTruncation,\n} from \"../contracts\";\nimport { resolveSpillDir } from \"./spill-dir\";\n\nconst DEFAULT_MAX_LINES = 2000;\nconst DEFAULT_MAX_BYTES = 50 * 1024;\nconst DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;\nconst SPILL_NAME = /^spill-(\\d+)-[a-z0-9]+-[a-z0-9_-]+\\.txt$/;\n\n/** What spilling an output produced. */\nexport interface SpilledText {\n /** The text to return (marker included). */\n value: string;\n /** Set when anything was left out. */\n truncation?: WorkspaceTruncation;\n}\n\n/** What spilling a list of entries produced. */\nexport interface SpilledList<T> {\n items: T[];\n truncation?: WorkspaceTruncation;\n}\n\nfunction byteLength(value: string): number {\n return Buffer.byteLength(value, \"utf8\");\n}\n\n/** The first `maxBytes` bytes of `value`, cut on a character boundary. */\nfunction headOf(value: string, maxBytes: number): string {\n const bytes = Buffer.from(value, \"utf8\");\n\n if (bytes.byteLength <= maxBytes) {\n return value;\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 bytes.subarray(0, cut).toString(\"utf8\");\n}\n\n/** The last `maxBytes` bytes of `value`, cut on a character boundary. */\nfunction tailOf(value: string, maxBytes: number): string {\n const bytes = Buffer.from(value, \"utf8\");\n\n if (bytes.byteLength <= maxBytes) {\n return value;\n }\n\n let from = bytes.byteLength - Math.max(0, maxBytes);\n\n while (from < bytes.byteLength && ((bytes[from] ?? 0) & 0xc0) === 0x80) {\n from++;\n }\n\n return bytes.subarray(from).toString(\"utf8\");\n}\n\n/** Lines of `value`; a trailing line break does not add an empty line. */\nexport function splitLines(value: string): string[] {\n if (value === \"\") {\n return [];\n }\n\n const lines = value.split(\"\\n\");\n\n if (lines[lines.length - 1] === \"\") {\n lines.pop();\n }\n\n return lines;\n}\n\n/** Pick lines from the front within `lines` / `bytes`; one oversized line is clipped. */\nfunction takeHead(lines: string[], maxLines: number, maxBytes: number): string[] {\n const out: string[] = [];\n let used = 0;\n\n for (const line of lines) {\n if (out.length >= maxLines) {\n break;\n }\n\n const cost = byteLength(line) + 1;\n\n if (used + cost > maxBytes) {\n if (out.length === 0 && maxBytes > 0) {\n out.push(headOf(line, maxBytes));\n }\n\n break;\n }\n\n out.push(line);\n used += cost;\n }\n\n return out;\n}\n\n/** Pick lines from the back, same limits; the result is in file order. */\nfunction takeTail(lines: string[], maxLines: number, maxBytes: number): string[] {\n const out: string[] = [];\n let used = 0;\n\n for (let index = lines.length - 1; index >= 0; index--) {\n const line = lines[index] ?? \"\";\n\n if (out.length >= maxLines) {\n break;\n }\n\n const cost = byteLength(line) + 1;\n\n if (used + cost > maxBytes) {\n if (out.length === 0 && maxBytes > 0) {\n out.push(tailOf(line, maxBytes));\n }\n\n break;\n }\n\n out.unshift(line);\n used += cost;\n }\n\n return out;\n}\n\n/** Choose the lines to keep for `keep`; `headCount` lines come from the front. */\nfunction select(\n lines: string[],\n keep: WorkspaceOutputKeep,\n maxLines: number,\n maxBytes: number,\n): { head: string[]; tail: string[] } {\n if (keep === \"head\") {\n return { head: takeHead(lines, maxLines, maxBytes), tail: [] };\n }\n\n if (keep === \"tail\") {\n return { head: [], tail: takeTail(lines, maxLines, maxBytes) };\n }\n\n const headLines = Math.ceil(maxLines / 2);\n const headBytes = Math.ceil(maxBytes / 2);\n const head = takeHead(lines, headLines, headBytes);\n const rest = lines.slice(head.length);\n const tail = takeTail(rest, maxLines - head.length, maxBytes - headBytes);\n\n return { head, tail };\n}\n\n/**\n * Keeps oversized command, search and read output from flooding the model:\n * with `output.spillDir` set it saves the full output to a file and returns\n * the part the policy keeps plus a pointer to it. One instance per ops layer;\n * it remembers the files it wrote so `dispose()` can remove them.\n */\nexport class OutputSpiller {\n private readonly created: string[] = [];\n private readonly dir: string | undefined;\n\n public constructor(\n private readonly backend: WorkspaceBackend,\n private readonly policy: WorkspacePolicy,\n ) {\n this.dir = resolveSpillDir(policy);\n\n if (this.dir !== undefined && this.retentionMs > 0) {\n void this.sweep();\n }\n }\n\n /** True when `output.spillDir` is set. */\n public get enabled(): boolean {\n return this.dir !== undefined;\n }\n\n /** The spill directory, when spilling is on. */\n public get spillDir(): string | undefined {\n return this.dir;\n }\n\n private get retentionMs(): number {\n return this.policy.output?.retentionMs ?? DEFAULT_RETENTION_MS;\n }\n\n private limits(cap: number | undefined): { maxLines: number; maxBytes: number } {\n const configured = this.policy.output?.maxBytes ?? DEFAULT_MAX_BYTES;\n\n return {\n maxLines: Math.max(1, Math.floor(this.policy.output?.maxLines ?? DEFAULT_MAX_LINES)),\n maxBytes: Math.max(1, Math.floor(cap === undefined ? configured : Math.min(configured, cap))),\n };\n }\n\n /** Remove spill files older than the retention (only files named like ours). */\n public async sweep(): Promise<void> {\n if (this.dir === undefined) {\n return;\n }\n\n try {\n const now = Date.now();\n\n for (const file of await this.backend.list(this.dir)) {\n const match = SPILL_NAME.exec(path.basename(file));\n\n if (match !== null && now - Number(match[1]) > this.retentionMs) {\n await this.backend.remove(file).catch(() => undefined);\n }\n }\n } catch {\n // No spill directory yet, or it is not listable: nothing to clean.\n }\n }\n\n /** Write `content` to a new spill file and return its absolute path. */\n private async write(label: string, content: string): Promise<string | undefined> {\n if (this.dir === undefined) {\n return undefined;\n }\n\n try {\n if (this.retentionMs > 0) {\n await this.sweep();\n }\n\n const safe = label.toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\") || \"output\";\n const rand = Math.random().toString(36).slice(2, 8) || \"x\";\n const file = path.join(this.dir, `spill-${Date.now()}-${rand}-${safe}.txt`);\n\n await this.backend.mkdir(this.dir);\n await this.backend.writeFile(file, content);\n this.created.push(file);\n\n return file;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Fit `value` to the policy. Returns it untouched when it fits. Over the\n * limits with spilling on, the whole text is saved and the kept lines come\n * back with a `[showing lines … of …; full output saved to …]` marker.\n * `cap` is a tighter byte limit from elsewhere (`shell.maxOutputBytes`).\n */\n public async text(\n label: string,\n value: string,\n defaultKeep: WorkspaceOutputKeep,\n cap?: number,\n ): Promise<SpilledText> {\n if (this.dir === undefined) {\n return { value };\n }\n\n const { maxLines, maxBytes } = this.limits(cap);\n const lines = splitLines(value);\n const totalBytes = byteLength(value);\n\n if (lines.length <= maxLines && totalBytes <= maxBytes) {\n return { value };\n }\n\n const keep = this.policy.output?.keep ?? defaultKeep;\n const { head, tail } = select(lines, keep, maxLines, maxBytes);\n const file = await this.write(label, value);\n const shown = head.length + tail.length;\n const total = lines.length;\n const where = file === undefined ? \"the rest was dropped\" : `full output saved to ${file}`;\n const range =\n tail.length === 0\n ? `lines 1-${head.length}`\n : head.length === 0\n ? `lines ${total - tail.length + 1}-${total}`\n : `lines 1-${head.length} and ${total - tail.length + 1}-${total}`;\n const marker = `[showing ${range} of ${total}; ${where}]`;\n const parts = [...(head.length > 0 ? [head.join(\"\\n\")] : []), marker];\n\n if (tail.length > 0) {\n parts.push(tail.join(\"\\n\"));\n\n // The marker sits between head and tail; end the text with the file too.\n if (file !== undefined) {\n parts.push(`[full output saved to ${file}]`);\n }\n }\n\n return {\n value: parts.join(\"\\n\"),\n truncation: {\n shown: { lines: shown },\n total: { lines: total, bytes: totalBytes },\n ...(file !== undefined ? { path: file } : {}),\n },\n };\n }\n\n /**\n * Fit a list of entries (one line each when rendered) to the policy; the\n * rendered full list is what gets saved.\n */\n public async list<T>(\n label: string,\n items: T[],\n render: (item: T) => string,\n defaultKeep: WorkspaceOutputKeep,\n ): Promise<SpilledList<T>> {\n if (this.dir === undefined) {\n return { items };\n }\n\n const { maxLines, maxBytes } = this.limits(undefined);\n const lines = items.map(render);\n const totalBytes = lines.reduce((sum, line) => sum + byteLength(line) + 1, 0);\n\n if (items.length <= maxLines && totalBytes <= maxBytes) {\n return { items };\n }\n\n const keep = this.policy.output?.keep ?? defaultKeep;\n // Count how many whole entries fit from each end, then slice the items.\n const { head, tail } = select(lines, keep, maxLines, maxBytes);\n const headCount = head.length;\n const tailCount = tail.length;\n const file = await this.write(label, lines.join(\"\\n\") + \"\\n\");\n\n return {\n items: [...items.slice(0, headCount), ...items.slice(items.length - tailCount)],\n truncation: {\n shown: { lines: headCount + tailCount },\n total: { lines: items.length, bytes: totalBytes },\n ...(file !== undefined ? { path: file } : {}),\n },\n };\n }\n\n /** Save the complete text of an output that was already cut elsewhere. */\n public async save(label: string, content: string): Promise<string | undefined> {\n return this.write(label, content);\n }\n\n /** Remove expired files, and this run's own when `retentionMs` is `0`. */\n public async dispose(): Promise<void> {\n if (this.dir === undefined) {\n return;\n }\n\n if (this.retentionMs === 0) {\n for (const file of this.created.splice(0)) {\n await this.backend.remove(file).catch(() => undefined);\n }\n\n return;\n }\n\n await this.sweep();\n }\n}\n"],"mappings":"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"}
|