@warlock.js/ai-workspace 5.42.0 → 5.44.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 +18 -0
- package/README.md +14 -1
- package/cjs/index.cjs +1781 -141
- package/cjs/index.cjs.map +1 -1
- package/esm/backends/local.d.mts.map +1 -1
- package/esm/backends/local.mjs +36 -22
- package/esm/backends/local.mjs.map +1 -1
- package/esm/contracts/index.d.mts +5 -1
- package/esm/contracts/shell-approval.type.d.mts +78 -0
- package/esm/contracts/shell-approval.type.d.mts.map +1 -0
- package/esm/contracts/tool-io.type.d.mts +35 -1
- package/esm/contracts/tool-io.type.d.mts.map +1 -1
- package/esm/contracts/workspace-backend.contract.d.mts +11 -1
- package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-edit.type.d.mts +66 -0
- package/esm/contracts/workspace-edit.type.d.mts.map +1 -0
- package/esm/contracts/workspace-ops.contract.d.mts +7 -1
- package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-output.type.d.mts +57 -0
- package/esm/contracts/workspace-output.type.d.mts.map +1 -0
- package/esm/contracts/workspace-policy.type.d.mts +38 -1
- package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
- package/esm/contracts/workspace.contract.d.mts +11 -0
- package/esm/contracts/workspace.contract.d.mts.map +1 -1
- package/esm/edit/apply-replacement.mjs +58 -0
- package/esm/edit/apply-replacement.mjs.map +1 -0
- package/esm/edit/match.d.mts +16 -0
- package/esm/edit/match.d.mts.map +1 -0
- package/esm/edit/match.mjs +214 -0
- package/esm/edit/match.mjs.map +1 -0
- package/esm/errors.d.mts +12 -1
- package/esm/errors.d.mts.map +1 -1
- package/esm/errors.mjs +1 -0
- package/esm/errors.mjs.map +1 -1
- package/esm/hooks/after-edit.mjs +36 -0
- package/esm/hooks/after-edit.mjs.map +1 -0
- package/esm/index.d.mts +7 -2
- package/esm/index.mjs +3 -2
- package/esm/ops.d.mts +3 -1
- package/esm/ops.d.mts.map +1 -1
- package/esm/ops.mjs +196 -75
- package/esm/ops.mjs.map +1 -1
- package/esm/output/spill-dir.mjs +15 -0
- package/esm/output/spill-dir.mjs.map +1 -0
- package/esm/output/spill.mjs +220 -0
- package/esm/output/spill.mjs.map +1 -0
- package/esm/policy/policy.d.mts +3 -1
- package/esm/policy/policy.d.mts.map +1 -1
- package/esm/policy/policy.mjs +12 -4
- package/esm/policy/policy.mjs.map +1 -1
- package/esm/policy/powershell-aliases.mjs +275 -0
- package/esm/policy/powershell-aliases.mjs.map +1 -0
- package/esm/policy/powershell-parser.mjs +418 -0
- package/esm/policy/powershell-parser.mjs.map +1 -0
- package/esm/policy/shell-check.d.mts +32 -2
- package/esm/policy/shell-check.d.mts.map +1 -1
- package/esm/policy/shell-check.mjs +183 -31
- package/esm/policy/shell-check.mjs.map +1 -1
- package/esm/policy/shell-interpreter.d.mts +45 -0
- package/esm/policy/shell-interpreter.d.mts.map +1 -0
- package/esm/policy/shell-interpreter.mjs +109 -0
- package/esm/policy/shell-interpreter.mjs.map +1 -0
- package/esm/policy/shell-parser.d.mts +31 -0
- package/esm/policy/shell-parser.d.mts.map +1 -0
- package/esm/policy/shell-parser.mjs +3 -0
- package/esm/policy/shell-parser.mjs.map +1 -1
- package/esm/tools/glob.mjs +1 -0
- package/esm/tools/glob.mjs.map +1 -1
- package/esm/tools/run-shell.d.mts +10 -0
- package/esm/tools/run-shell.d.mts.map +1 -1
- package/esm/tools/run-shell.mjs +18 -1
- package/esm/tools/run-shell.mjs.map +1 -1
- package/esm/tools/run-tests.d.mts +7 -0
- package/esm/tools/run-tests.d.mts.map +1 -1
- package/esm/tools/run-tests.mjs +5 -4
- package/esm/tools/run-tests.mjs.map +1 -1
- package/esm/tools/write-file.mjs +3 -2
- package/esm/tools/write-file.mjs.map +1 -1
- package/esm/workspace.d.mts.map +1 -1
- package/esm/workspace.mjs +16 -5
- package/esm/workspace.mjs.map +1 -1
- package/llms-full.txt +64 -17
- package/package.json +3 -3
- package/skills/use-a-workspace/SKILL.md +64 -17
package/esm/index.d.mts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { WorkspaceEditMatchTier, WorkspaceEditMatching } from "./edit/match.mjs";
|
|
2
|
+
import { WorkspaceOutputKeep, WorkspaceOutputPolicy, WorkspaceTruncation } from "./contracts/workspace-output.type.mjs";
|
|
1
3
|
import { ApplyPatchFileResult, ApplyPatchInput, ApplyPatchResult, EditFileInput, EditFileResult, ExecOptions, GlobInput, GlobResult, GrepInput, GrepMatch, GrepResult, ListDirEntry, ListDirEntryType, ListDirInput, ListDirOptions, ListDirResult, MultiEditInput, MultiEditOperation, MultiEditResult, ReadFileInput, ReadFileResult, RunShellBackgroundResult, RunShellInput, RunShellResult, RunTestsInput, ShellJobInfo, ShellJobStatus, ShellKillInput, ShellOutputInput, ShellOutputOptions, ShellOutputResult, StartShellOptions, TodoListResult, TodoWriteInput, WorkspaceTodo, WorkspaceTodoInput, WorkspaceTodoStatus, WorkspaceToolName, WriteFileInput, WriteFileResult } from "./contracts/tool-io.type.mjs";
|
|
4
|
+
import { WorkspaceCommandDecision, WorkspaceCommandRule, WorkspaceShellAsk, WorkspaceShellAskDecision, WorkspaceShellAskReason, WorkspaceShellAskRequest, WorkspaceShellClassification, WorkspaceShellOnAsk } from "./contracts/shell-approval.type.mjs";
|
|
5
|
+
import { WorkspaceAfterEditEvent, WorkspaceEditLineEndings, WorkspaceEditPolicy, WorkspaceEditTool, WorkspaceHooks } from "./contracts/workspace-edit.type.mjs";
|
|
2
6
|
import { WorkspaceAllowCommand, WorkspaceBackendType, WorkspaceBackgroundPolicy, WorkspaceCommandCheckContext, WorkspaceCommandPattern, WorkspaceListPolicy, WorkspacePolicy, WorkspaceReadPolicy, WorkspaceShellInterpreter, WorkspaceShellPolicy } from "./contracts/workspace-policy.type.mjs";
|
|
3
7
|
import { WorkspaceBackend, WorkspaceBackendExecOptions, WorkspaceBackendExecResult, WorkspaceBackendProcess, WorkspaceBackendStartOptions, WorkspaceBackendStat, WorkspaceOutputStream } from "./contracts/workspace-backend.contract.mjs";
|
|
4
8
|
import { WorkspaceOps } from "./contracts/workspace-ops.contract.mjs";
|
|
@@ -6,7 +10,8 @@ import { Workspace, WorkspaceTools } from "./contracts/workspace.contract.mjs";
|
|
|
6
10
|
import "./contracts/index.mjs";
|
|
7
11
|
import { WorkspaceEditError, WorkspaceEditErrorOptions, WorkspaceEditFailure, WorkspaceJobError, WorkspaceJobErrorOptions, WorkspaceJobFailure, WorkspacePolicyError, WorkspacePolicyErrorOptions, WorkspacePolicyViolation, WorkspaceShellRefusalReason } from "./errors.mjs";
|
|
8
12
|
import { ResolvedPath, buildEnv, isCommandAllowed, resolveInJail } from "./policy/policy.mjs";
|
|
9
|
-
import {
|
|
13
|
+
import { ResolvedShell, ResolvedShellInterpreter, ShellHost, resolveShell } from "./policy/shell-interpreter.mjs";
|
|
14
|
+
import { WorkspaceShellCheck, WorkspaceShellPath, WorkspaceShellPathAccess, checkShellCommand, classifyShellCommand } from "./policy/shell-check.mjs";
|
|
10
15
|
import { ArgvAnalysis, analyzeArgvCommand, tokenizeCommand } from "./policy/tokenize-command.mjs";
|
|
11
16
|
import { applyPatchWithOps } from "./patch/apply-patch-generic.mjs";
|
|
12
17
|
import { FilePatch, FilePatchKind, PatchHunk, PatchLine, parsePatch } from "./patch/parse-patch.mjs";
|
|
@@ -29,5 +34,5 @@ import { TodoStore } from "./todos.mjs";
|
|
|
29
34
|
import { makeTodoReadTool, makeTodoWriteTool } from "./tools/todo.mjs";
|
|
30
35
|
import { makeWriteFileTool } from "./tools/write-file.mjs";
|
|
31
36
|
import { workspace } from "./workspace.mjs";
|
|
32
|
-
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 RunShellBackgroundResult, type RunShellInput, type RunShellResult, type RunTestsInput, type ShellJobInfo, type ShellJobStatus, type ShellKillInput, type ShellOutputInput, type ShellOutputOptions, type ShellOutputResult, type StartShellOptions, type TodoListResult, TodoStore, type TodoWriteInput, type Workspace, type WorkspaceAllowCommand, type WorkspaceBackend, type WorkspaceBackendExecOptions, type WorkspaceBackendExecResult, type WorkspaceBackendProcess, type WorkspaceBackendStartOptions, type WorkspaceBackendStat, type WorkspaceBackendType, type WorkspaceBackgroundPolicy, type WorkspaceCommandCheckContext, type WorkspaceCommandPattern, WorkspaceEditError, type WorkspaceEditErrorOptions, type WorkspaceEditFailure, WorkspaceJobError, type WorkspaceJobErrorOptions, type WorkspaceJobFailure, type WorkspaceListPolicy, type WorkspaceOps, type WorkspaceOutputStream, type WorkspacePolicy, WorkspacePolicyError, type WorkspacePolicyErrorOptions, type WorkspacePolicyViolation, type WorkspaceReadPolicy, type WorkspaceShellCheck, type WorkspaceShellInterpreter, type WorkspaceShellPath, type WorkspaceShellPathAccess, type WorkspaceShellPolicy, type WorkspaceShellRefusalReason, type WorkspaceTodo, type WorkspaceTodoInput, type WorkspaceTodoStatus, type WorkspaceToolName, type WorkspaceTools, type WriteFileInput, type WriteFileResult, analyzeArgvCommand, applyHunks, applyPatchWithOps, buildEnv, checkShellCommand, createLocalBackend, createMockBackend, createOps, isCommandAllowed, makeApplyPatchTool, makeEditFileTool, makeGlobTool, makeGrepTool, makeListDirTool, makeMultiEditTool, makeReadFileTool, makeRunShellTool, makeRunTestsTool, makeShellKillTool, makeShellOutputTool, makeTodoReadTool, makeTodoWriteTool, makeWriteFileTool, parsePatch, resolveInJail, 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 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 };
|
|
33
38
|
import "./workspace.mjs";
|
package/esm/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WorkspaceEditError, WorkspaceJobError, WorkspacePolicyError } from "./errors.mjs";
|
|
2
|
+
import { resolveShell } from "./policy/shell-interpreter.mjs";
|
|
2
3
|
import { analyzeArgvCommand, tokenizeCommand } from "./policy/tokenize-command.mjs";
|
|
3
|
-
import { checkShellCommand } from "./policy/shell-check.mjs";
|
|
4
|
+
import { checkShellCommand, classifyShellCommand } from "./policy/shell-check.mjs";
|
|
4
5
|
import { buildEnv, isCommandAllowed, resolveInJail } from "./policy/policy.mjs";
|
|
5
6
|
import { applyHunks } from "./patch/apply-hunks.mjs";
|
|
6
7
|
import { parsePatch } from "./patch/parse-patch.mjs";
|
|
@@ -24,4 +25,4 @@ import { makeTodoReadTool, makeTodoWriteTool } from "./tools/todo.mjs";
|
|
|
24
25
|
import { makeWriteFileTool } from "./tools/write-file.mjs";
|
|
25
26
|
import { workspace } from "./workspace.mjs";
|
|
26
27
|
|
|
27
|
-
export { TodoStore, WorkspaceEditError, WorkspaceJobError, WorkspacePolicyError, analyzeArgvCommand, applyHunks, applyPatchWithOps, buildEnv, checkShellCommand, createLocalBackend, createMockBackend, createOps, isCommandAllowed, makeApplyPatchTool, makeEditFileTool, makeGlobTool, makeGrepTool, makeListDirTool, makeMultiEditTool, makeReadFileTool, makeRunShellTool, makeRunTestsTool, makeShellKillTool, makeShellOutputTool, makeTodoReadTool, makeTodoWriteTool, makeWriteFileTool, parsePatch, resolveInJail, tokenizeCommand, workspace };
|
|
28
|
+
export { TodoStore, WorkspaceEditError, WorkspaceJobError, WorkspacePolicyError, 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 };
|
package/esm/ops.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkspacePolicy } from "./contracts/workspace-policy.type.mjs";
|
|
1
|
+
import { WorkspaceCommandPattern, WorkspacePolicy } from "./contracts/workspace-policy.type.mjs";
|
|
2
2
|
import { WorkspaceBackend } from "./contracts/workspace-backend.contract.mjs";
|
|
3
3
|
import { WorkspaceOps } from "./contracts/workspace-ops.contract.mjs";
|
|
4
4
|
import "./contracts/index.mjs";
|
|
@@ -18,6 +18,8 @@ import { ShellJobs } from "./shell-jobs.mjs";
|
|
|
18
18
|
export interface OpsRuntime {
|
|
19
19
|
/** The workspace's background commands. */
|
|
20
20
|
jobs: ShellJobs;
|
|
21
|
+
/** Patterns the host approved with `remember`, shared by every view of the workspace. */
|
|
22
|
+
approvals?: WorkspaceCommandPattern[];
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Create the policy-enforced operation layer over a backend.
|
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":";;;;;;;;;;;;;;;;;iBA0MiB;;EAEf,MAAM;;EAEN,YAAY;;;;;;;;;;;;;;;;;;;;;wBA23BE,UACd,SAAS,kBACT,QAAQ,iBACR,UAAU,aACT"}
|
package/esm/ops.mjs
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { WorkspaceEditError, WorkspacePolicyError } from "./errors.mjs";
|
|
2
2
|
import { globToRegExp, isInside, matchesAnyGlob } from "./policy/jail.mjs";
|
|
3
|
+
import { resolveShell } from "./policy/shell-interpreter.mjs";
|
|
3
4
|
import { checkShellCommand } from "./policy/shell-check.mjs";
|
|
4
5
|
import { buildCommandEnv, resolveInJail } from "./policy/policy.mjs";
|
|
5
6
|
import { executePatch } from "./patch/execute-patch.mjs";
|
|
7
|
+
import { applyReplacement } from "./edit/apply-replacement.mjs";
|
|
8
|
+
import { runAfterEdit } from "./hooks/after-edit.mjs";
|
|
9
|
+
import { OutputSpiller, splitLines } from "./output/spill.mjs";
|
|
6
10
|
import { ShellJobs } from "./shell-jobs.mjs";
|
|
7
11
|
import path from "node:path";
|
|
8
12
|
import { fs } from "@warlock.js/fs";
|
|
@@ -139,6 +143,26 @@ var Ops = class {
|
|
|
139
143
|
this.policy = policy;
|
|
140
144
|
this.runtime = runtime;
|
|
141
145
|
this.pathLocks = /* @__PURE__ */ new Map();
|
|
146
|
+
this.resolvedShell = resolveShell(policy.shell);
|
|
147
|
+
this.approvals = runtime.approvals ?? [];
|
|
148
|
+
this.spiller = new OutputSpiller(backend, policy);
|
|
149
|
+
}
|
|
150
|
+
/** The edit-policy settings applied to every replacement. */
|
|
151
|
+
editOptions() {
|
|
152
|
+
const edit = this.policy.edit;
|
|
153
|
+
return {
|
|
154
|
+
matching: edit?.matching ?? "tiered",
|
|
155
|
+
missHintLines: edit?.missHintLines ?? 12,
|
|
156
|
+
lineEndings: edit?.lineEndings ?? "preserve",
|
|
157
|
+
...this.policy.read?.maxBytes !== void 0 ? { hintMaxBytes: this.policy.read.maxBytes } : {}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** Run `afterEdit` for a call that wrote `paths`; `undefined` when there is no output. */
|
|
161
|
+
diagnosticsFor(tool, paths) {
|
|
162
|
+
return runAfterEdit(this.policy, {
|
|
163
|
+
paths,
|
|
164
|
+
tool
|
|
165
|
+
});
|
|
142
166
|
}
|
|
143
167
|
/** Run `task` after every earlier mutation of `absolutePath` has settled. */
|
|
144
168
|
async withPathLock(absolutePath, task) {
|
|
@@ -183,7 +207,7 @@ var Ops = class {
|
|
|
183
207
|
}
|
|
184
208
|
}
|
|
185
209
|
async readFile(inputPath, opts) {
|
|
186
|
-
const { absolutePath } = await resolveInJail(this.policy, inputPath);
|
|
210
|
+
const { absolutePath } = await resolveInJail(this.policy, inputPath, "read");
|
|
187
211
|
const raw = await this.backend.readFile(absolutePath);
|
|
188
212
|
const hash = fs.hash.string(raw);
|
|
189
213
|
const lines = raw.split("\n");
|
|
@@ -192,10 +216,29 @@ var Ops = class {
|
|
|
192
216
|
const limit = opts?.limit ?? this.policy.read?.defaultLines ?? DEFAULT_READ_LINES;
|
|
193
217
|
const maxBytes = this.policy.read?.maxBytes;
|
|
194
218
|
const startIndex = offset - 1;
|
|
195
|
-
const
|
|
219
|
+
const window = lines.slice(startIndex, startIndex + Math.max(0, limit));
|
|
220
|
+
const { content, returnedLines, bytesCapped } = numberWindow(window, offset, maxBytes === void 0 ? void 0 : Math.max(0, Math.floor(maxBytes)));
|
|
196
221
|
const endLine = offset + returnedLines - 1;
|
|
222
|
+
const spillDir = this.spiller.spillDir;
|
|
223
|
+
let spilled;
|
|
224
|
+
if (bytesCapped && spillDir !== void 0 && !isInside(absolutePath, spillDir)) {
|
|
225
|
+
const full = numberWindow(window, offset, void 0).content;
|
|
226
|
+
const file = await this.spiller.save("read_file", full);
|
|
227
|
+
if (file !== void 0) spilled = {
|
|
228
|
+
content: `${content}\n[showing lines 1-${returnedLines} of ${window.length}; full output saved to ${file}]`,
|
|
229
|
+
truncation: {
|
|
230
|
+
shown: { lines: returnedLines },
|
|
231
|
+
total: {
|
|
232
|
+
lines: window.length,
|
|
233
|
+
bytes: Buffer.byteLength(full, "utf8")
|
|
234
|
+
},
|
|
235
|
+
path: file
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
197
239
|
return {
|
|
198
|
-
content,
|
|
240
|
+
content: spilled?.content ?? content,
|
|
241
|
+
...spilled !== void 0 ? { truncation: spilled.truncation } : {},
|
|
199
242
|
startLine: offset,
|
|
200
243
|
endLine,
|
|
201
244
|
totalLines,
|
|
@@ -206,8 +249,8 @@ var Ops = class {
|
|
|
206
249
|
};
|
|
207
250
|
}
|
|
208
251
|
async writeFile(inputPath, content) {
|
|
209
|
-
const { absolutePath } = await resolveInJail(this.policy, inputPath);
|
|
210
|
-
|
|
252
|
+
const { absolutePath, relativePath } = await resolveInJail(this.policy, inputPath);
|
|
253
|
+
const written = await this.withPathLock(absolutePath, async () => {
|
|
211
254
|
const parent = path.dirname(absolutePath);
|
|
212
255
|
await this.backend.mkdir(parent);
|
|
213
256
|
await this.backend.writeFile(absolutePath, content);
|
|
@@ -216,10 +259,20 @@ var Ops = class {
|
|
|
216
259
|
bytesWritten: Buffer.byteLength(content, "utf8")
|
|
217
260
|
};
|
|
218
261
|
});
|
|
262
|
+
const diagnostics = await this.diagnosticsFor("write_file", [relativePath || inputPath]);
|
|
263
|
+
return {
|
|
264
|
+
...written,
|
|
265
|
+
...diagnostics !== void 0 ? { diagnostics } : {}
|
|
266
|
+
};
|
|
219
267
|
}
|
|
220
268
|
async editFile(input) {
|
|
221
269
|
const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);
|
|
222
|
-
|
|
270
|
+
const result = await this.withPathLock(absolutePath, () => this.applyEdit(input, absolutePath, relativePath));
|
|
271
|
+
const diagnostics = await this.diagnosticsFor("edit_file", [result.path]);
|
|
272
|
+
return diagnostics === void 0 ? result : {
|
|
273
|
+
...result,
|
|
274
|
+
diagnostics
|
|
275
|
+
};
|
|
223
276
|
}
|
|
224
277
|
/** The read-check-replace-write of {@link editFile}, run under its path lock. */
|
|
225
278
|
async applyEdit(input, absolutePath, relativePath) {
|
|
@@ -231,19 +284,20 @@ var Ops = class {
|
|
|
231
284
|
expectedHash: input.expectHash,
|
|
232
285
|
actualHash: currentHash
|
|
233
286
|
});
|
|
234
|
-
const { content: updated, replacements } = applyReplacement(current, input, input.path, relativePath || input.path);
|
|
287
|
+
const { content: updated, replacements, matchTier } = applyReplacement(current, input, input.path, relativePath || input.path, this.editOptions());
|
|
235
288
|
await this.backend.writeFile(absolutePath, updated);
|
|
236
289
|
return {
|
|
237
290
|
path: relativePath || input.path,
|
|
238
291
|
replacements,
|
|
239
|
-
hash: fs.hash.string(updated)
|
|
292
|
+
hash: fs.hash.string(updated),
|
|
293
|
+
...matchTier !== void 0 ? { matchTier } : {}
|
|
240
294
|
};
|
|
241
295
|
}
|
|
242
296
|
async multiEdit(input) {
|
|
243
297
|
const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);
|
|
244
298
|
const shownPath = relativePath || input.path;
|
|
245
299
|
if (input.edits.length === 0 || input.edits.length > 100) throw new RangeError(`A multi-edit needs between 1 and ${100} edits (got ${input.edits.length}).`);
|
|
246
|
-
|
|
300
|
+
const result = await this.withPathLock(absolutePath, async () => {
|
|
247
301
|
const current = await this.backend.readFile(absolutePath);
|
|
248
302
|
const currentHash = fs.hash.string(current);
|
|
249
303
|
if (input.expectHash !== void 0 && input.expectHash !== currentHash) throw new WorkspaceEditError(`File "${input.path}" changed since it was read; none of the edits were applied.`, {
|
|
@@ -254,21 +308,40 @@ var Ops = class {
|
|
|
254
308
|
});
|
|
255
309
|
let content = current;
|
|
256
310
|
let replacements = 0;
|
|
311
|
+
let matchTier;
|
|
312
|
+
const options = this.editOptions();
|
|
257
313
|
input.edits.forEach((edit, editIndex) => {
|
|
258
|
-
const applied = applyReplacement(content, edit, input.path, shownPath, editIndex);
|
|
314
|
+
const applied = applyReplacement(content, edit, input.path, shownPath, options, editIndex);
|
|
259
315
|
content = applied.content;
|
|
260
316
|
replacements += applied.replacements;
|
|
317
|
+
if (applied.matchTier === "whitespace" || matchTier === void 0) matchTier = applied.matchTier ?? matchTier;
|
|
261
318
|
});
|
|
262
319
|
await this.backend.writeFile(absolutePath, content);
|
|
263
320
|
return {
|
|
264
321
|
path: shownPath,
|
|
265
322
|
edits: input.edits.length,
|
|
266
323
|
replacements,
|
|
267
|
-
hash: fs.hash.string(content)
|
|
324
|
+
hash: fs.hash.string(content),
|
|
325
|
+
...matchTier !== void 0 ? { matchTier } : {}
|
|
268
326
|
};
|
|
269
327
|
});
|
|
328
|
+
const diagnostics = await this.diagnosticsFor("multi_edit", [shownPath]);
|
|
329
|
+
return diagnostics === void 0 ? result : {
|
|
330
|
+
...result,
|
|
331
|
+
diagnostics
|
|
332
|
+
};
|
|
270
333
|
}
|
|
271
334
|
async applyPatch(input) {
|
|
335
|
+
const result = await this.runPatch(input);
|
|
336
|
+
if (result.dryRun) return result;
|
|
337
|
+
const paths = result.files.filter((file) => file.action !== "deleted").map((file) => file.path);
|
|
338
|
+
const diagnostics = await this.diagnosticsFor("apply_patch", paths);
|
|
339
|
+
return diagnostics === void 0 ? result : {
|
|
340
|
+
...result,
|
|
341
|
+
diagnostics
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
runPatch(input) {
|
|
272
345
|
return executePatch(input, {
|
|
273
346
|
resolve: (inputPath) => resolveInJail(this.policy, inputPath),
|
|
274
347
|
exists: (absolutePath) => this.backend.exists(absolutePath),
|
|
@@ -288,9 +361,14 @@ var Ops = class {
|
|
|
288
361
|
* (argv or shell mode), then — shell mode — every redirection target and
|
|
289
362
|
* `cd` directory against the jail with symlinks resolved.
|
|
290
363
|
*/
|
|
291
|
-
async assertCommandAllowed(command) {
|
|
292
|
-
const verdict = checkShellCommand(this.policy, command
|
|
293
|
-
|
|
364
|
+
async assertCommandAllowed(command, signal) {
|
|
365
|
+
const verdict = checkShellCommand(this.policy, command, {
|
|
366
|
+
remembered: this.approvals,
|
|
367
|
+
resolved: this.resolvedShell
|
|
368
|
+
});
|
|
369
|
+
const onAsk = this.policy.shell?.onAsk;
|
|
370
|
+
const needsApproval = !verdict.allowed && verdict.reason === "approval-required" && verdict.asks.length > 0;
|
|
371
|
+
if (!verdict.allowed && !(needsApproval && onAsk !== void 0)) throw new WorkspacePolicyError(verdict.message ?? `Command is not permitted by the workspace shell policy: ${command}`, {
|
|
294
372
|
type: "denied-command",
|
|
295
373
|
command,
|
|
296
374
|
...verdict.reason !== void 0 ? { reason: verdict.reason } : {},
|
|
@@ -313,9 +391,29 @@ var Ops = class {
|
|
|
313
391
|
reason: "not-allowed",
|
|
314
392
|
command
|
|
315
393
|
});
|
|
394
|
+
if (needsApproval && onAsk !== void 0) await this.askForApproval(command, verdict.asks, onAsk, signal);
|
|
395
|
+
}
|
|
396
|
+
/** Put the commands that need approval to `shell.onAsk`; refuse the line unless it says yes. */
|
|
397
|
+
async askForApproval(command, asks, onAsk, signal) {
|
|
398
|
+
let decision;
|
|
399
|
+
try {
|
|
400
|
+
decision = await onAsk({
|
|
401
|
+
command,
|
|
402
|
+
asks,
|
|
403
|
+
...signal ? { signal } : {}
|
|
404
|
+
});
|
|
405
|
+
} catch {
|
|
406
|
+
decision = void 0;
|
|
407
|
+
}
|
|
408
|
+
if (decision?.allow !== true) throw new WorkspacePolicyError(`Running ${asks.map((ask) => `\`${ask.argv.join(" ")}\``).join(", ")} was not approved.`, {
|
|
409
|
+
type: "denied-command",
|
|
410
|
+
reason: "approval-denied",
|
|
411
|
+
command
|
|
412
|
+
});
|
|
413
|
+
if (decision.remember !== void 0 && !this.approvals.includes(decision.remember)) this.approvals.push(decision.remember);
|
|
316
414
|
}
|
|
317
415
|
async exec(command, opts) {
|
|
318
|
-
await this.assertCommandAllowed(command);
|
|
416
|
+
await this.assertCommandAllowed(command, opts?.signal);
|
|
319
417
|
const signal = opts?.signal;
|
|
320
418
|
if (signal?.aborted) return {
|
|
321
419
|
exitCode: CANCELLED_BEFORE_START_EXIT_CODE,
|
|
@@ -328,24 +426,27 @@ var Ops = class {
|
|
|
328
426
|
const shell = this.policy.shell;
|
|
329
427
|
const timeoutMs = opts?.timeoutMs ?? shell?.timeoutMs;
|
|
330
428
|
const maxOutputBytes = shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
331
|
-
const interpreter =
|
|
429
|
+
const interpreter = this.resolvedShell.interpreter;
|
|
332
430
|
const result = await this.backend.exec(command, {
|
|
333
431
|
cwd: this.policy.cwd,
|
|
334
432
|
timeoutMs,
|
|
335
433
|
env: buildCommandEnv(this.policy),
|
|
336
434
|
...signal ? { signal } : {},
|
|
337
|
-
...interpreter !== "none" ? { interpreter } : {}
|
|
435
|
+
...interpreter !== "none" ? { interpreter } : {},
|
|
436
|
+
...interpreter !== "none" && this.resolvedShell.file !== void 0 ? { interpreterPath: this.resolvedShell.file } : {}
|
|
338
437
|
});
|
|
339
|
-
const stdout =
|
|
340
|
-
const stderr =
|
|
438
|
+
const stdout = await this.fitStream("run_shell-stdout", result.stdout, maxOutputBytes);
|
|
439
|
+
const stderr = await this.fitStream("run_shell-stderr", result.stderr, maxOutputBytes);
|
|
341
440
|
const cancelled = result.cancelled === true;
|
|
441
|
+
const truncation = streamTruncation(stdout.truncation, stderr.truncation);
|
|
342
442
|
return {
|
|
343
443
|
exitCode: result.exitCode,
|
|
344
444
|
stdout: stdout.value,
|
|
345
445
|
stderr: stderr.value,
|
|
346
446
|
truncated: stdout.truncated || stderr.truncated,
|
|
347
447
|
timedOut: result.timedOut && !cancelled,
|
|
348
|
-
cancelled
|
|
448
|
+
cancelled,
|
|
449
|
+
...truncation !== void 0 ? { truncation } : {}
|
|
349
450
|
};
|
|
350
451
|
}
|
|
351
452
|
async startShell(command, opts) {
|
|
@@ -363,7 +464,8 @@ var Ops = class {
|
|
|
363
464
|
command
|
|
364
465
|
});
|
|
365
466
|
const shell = this.policy.shell;
|
|
366
|
-
const interpreter =
|
|
467
|
+
const interpreter = this.resolvedShell.interpreter;
|
|
468
|
+
const interpreterPath = interpreter !== "none" ? this.resolvedShell.file : void 0;
|
|
367
469
|
const timeoutMs = lowerOf(background.timeoutMs, opts?.timeoutMs);
|
|
368
470
|
const env = buildCommandEnv(this.policy);
|
|
369
471
|
return this.runtime.jobs.start({
|
|
@@ -375,12 +477,51 @@ var Ops = class {
|
|
|
375
477
|
cwd: this.policy.cwd,
|
|
376
478
|
env,
|
|
377
479
|
...interpreter !== "none" ? { interpreter } : {},
|
|
480
|
+
...interpreterPath !== void 0 ? { interpreterPath } : {},
|
|
378
481
|
onOutput
|
|
379
482
|
})
|
|
380
483
|
});
|
|
381
484
|
}
|
|
382
|
-
shellOutput(id, opts) {
|
|
383
|
-
|
|
485
|
+
async shellOutput(id, opts) {
|
|
486
|
+
const result = await this.runtime.jobs.output(id, opts);
|
|
487
|
+
if (!this.spiller.enabled) return result;
|
|
488
|
+
const stdout = await this.spiller.text(`shell_output-${id}-stdout`, result.stdout, "head+tail");
|
|
489
|
+
const stderr = await this.spiller.text(`shell_output-${id}-stderr`, result.stderr, "head+tail");
|
|
490
|
+
const truncation = streamTruncation(stdout.truncation, stderr.truncation);
|
|
491
|
+
return truncation === void 0 ? result : {
|
|
492
|
+
...result,
|
|
493
|
+
stdout: stdout.value,
|
|
494
|
+
stderr: stderr.value,
|
|
495
|
+
truncated: true,
|
|
496
|
+
truncation
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Fit one captured stream to the limits: spilled to a file when
|
|
501
|
+
* `output.spillDir` is set, otherwise clipped at `maxBytes` as always.
|
|
502
|
+
*/
|
|
503
|
+
async fitStream(label, value, maxBytes) {
|
|
504
|
+
if (this.spiller.enabled) {
|
|
505
|
+
const cap = this.policy.shell?.maxOutputBytes;
|
|
506
|
+
const spilled = await this.spiller.text(label, value, "head+tail", cap);
|
|
507
|
+
if (spilled.truncation !== void 0) return {
|
|
508
|
+
value: spilled.value,
|
|
509
|
+
truncated: true,
|
|
510
|
+
truncation: spilled.truncation
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const capped = capOutput(value, maxBytes);
|
|
514
|
+
if (!capped.truncated) return capped;
|
|
515
|
+
return {
|
|
516
|
+
...capped,
|
|
517
|
+
truncation: {
|
|
518
|
+
shown: { lines: splitLines(capped.value).length },
|
|
519
|
+
total: {
|
|
520
|
+
lines: splitLines(value).length,
|
|
521
|
+
bytes: Buffer.byteLength(value, "utf8")
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
};
|
|
384
525
|
}
|
|
385
526
|
shellKill(id) {
|
|
386
527
|
return this.runtime.jobs.kill(id);
|
|
@@ -388,8 +529,9 @@ var Ops = class {
|
|
|
388
529
|
listShells() {
|
|
389
530
|
return this.runtime.jobs.list();
|
|
390
531
|
}
|
|
391
|
-
dispose() {
|
|
392
|
-
|
|
532
|
+
async dispose() {
|
|
533
|
+
await this.runtime.jobs.dispose();
|
|
534
|
+
await this.spiller.dispose();
|
|
393
535
|
}
|
|
394
536
|
async listDir(inputPath = ".", opts) {
|
|
395
537
|
const { absolutePath: root, relativePath: rootRelative } = await resolveInJail(this.policy, inputPath);
|
|
@@ -493,16 +635,32 @@ var Ops = class {
|
|
|
493
635
|
line: index + 1,
|
|
494
636
|
text: line
|
|
495
637
|
});
|
|
496
|
-
if (matches.length >= DEFAULT_MAX_GREP_MATCHES) return
|
|
497
|
-
matches,
|
|
498
|
-
total: matches.length
|
|
499
|
-
};
|
|
638
|
+
if (matches.length >= DEFAULT_MAX_GREP_MATCHES) return this.fitMatches(matches);
|
|
500
639
|
}
|
|
501
640
|
}
|
|
502
641
|
}
|
|
503
|
-
return
|
|
642
|
+
return this.fitMatches(matches);
|
|
643
|
+
}
|
|
644
|
+
/** Apply the `output` limits to a finished match list. */
|
|
645
|
+
async fitMatches(matches) {
|
|
646
|
+
const fitted = await this.spiller.list("grep", matches, (match) => `${match.path}:${match.line}:${match.text}`, "head");
|
|
647
|
+
return fitted.truncation === void 0 ? {
|
|
504
648
|
matches,
|
|
505
649
|
total: matches.length
|
|
650
|
+
} : {
|
|
651
|
+
matches: fitted.items,
|
|
652
|
+
total: matches.length,
|
|
653
|
+
truncated: true,
|
|
654
|
+
truncation: fitted.truncation
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
async globResult(pattern) {
|
|
658
|
+
const all = await this.glob(pattern);
|
|
659
|
+
const fitted = await this.spiller.list("glob", all, (entry) => entry, "head");
|
|
660
|
+
return fitted.truncation === void 0 ? { paths: all } : {
|
|
661
|
+
paths: fitted.items,
|
|
662
|
+
truncated: true,
|
|
663
|
+
truncation: fitted.truncation
|
|
506
664
|
};
|
|
507
665
|
}
|
|
508
666
|
async glob(pattern) {
|
|
@@ -535,57 +693,20 @@ var Ops = class {
|
|
|
535
693
|
return denyPaths !== void 0 && denyPaths.length > 0 && matchesAnyGlob(relativePath, denyPaths);
|
|
536
694
|
}
|
|
537
695
|
};
|
|
696
|
+
/** Combine per-stream truncation metadata; `undefined` when neither stream was clipped. */
|
|
697
|
+
function streamTruncation(stdout, stderr) {
|
|
698
|
+
if (stdout === void 0 && stderr === void 0) return;
|
|
699
|
+
return {
|
|
700
|
+
...stdout !== void 0 ? { stdout } : {},
|
|
701
|
+
...stderr !== void 0 ? { stderr } : {}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
538
704
|
/** The lower of two optional limits (either may be absent). */
|
|
539
705
|
function lowerOf(policyLimit, requested) {
|
|
540
706
|
if (requested === void 0 || requested <= 0) return policyLimit;
|
|
541
707
|
return policyLimit === void 0 ? requested : Math.min(policyLimit, requested);
|
|
542
708
|
}
|
|
543
709
|
/**
|
|
544
|
-
* Apply one exact-string replacement to `content` under the edit rules:
|
|
545
|
-
* `oldString` must occur, and exactly once unless `replaceAll`.
|
|
546
|
-
*/
|
|
547
|
-
function applyReplacement(content, edit, inputPath, shownPath, editIndex) {
|
|
548
|
-
const occurrences = countOccurrences(content, edit.oldString);
|
|
549
|
-
const which = editIndex === void 0 ? "" : `Edit ${editIndex + 1}: `;
|
|
550
|
-
const nothingApplied = editIndex === void 0 ? "" : " None of the edits were applied.";
|
|
551
|
-
const indexOption = editIndex === void 0 ? {} : { editIndex };
|
|
552
|
-
if (occurrences === 0) throw new WorkspaceEditError(`${which}The text to replace was not found in "${inputPath}".${nothingApplied}`, {
|
|
553
|
-
type: "not-found",
|
|
554
|
-
path: shownPath,
|
|
555
|
-
matches: 0,
|
|
556
|
-
...indexOption
|
|
557
|
-
});
|
|
558
|
-
if (occurrences > 1 && !edit.replaceAll) throw new WorkspaceEditError(`${which}The text to replace is not unique in "${inputPath}" (${occurrences} matches); pass replaceAll or include more surrounding context.${nothingApplied}`, {
|
|
559
|
-
type: "not-unique",
|
|
560
|
-
path: shownPath,
|
|
561
|
-
matches: occurrences,
|
|
562
|
-
...indexOption
|
|
563
|
-
});
|
|
564
|
-
return {
|
|
565
|
-
content: edit.replaceAll ? content.split(edit.oldString).join(edit.newString) : replaceFirst(content, edit.oldString, edit.newString),
|
|
566
|
-
replacements: edit.replaceAll ? occurrences : 1
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
/** Count non-overlapping occurrences of `needle` in `haystack`. */
|
|
570
|
-
function countOccurrences(haystack, needle) {
|
|
571
|
-
if (needle === "") return 0;
|
|
572
|
-
let count = 0;
|
|
573
|
-
let from = 0;
|
|
574
|
-
while (true) {
|
|
575
|
-
const index = haystack.indexOf(needle, from);
|
|
576
|
-
if (index === -1) break;
|
|
577
|
-
count++;
|
|
578
|
-
from = index + needle.length;
|
|
579
|
-
}
|
|
580
|
-
return count;
|
|
581
|
-
}
|
|
582
|
-
/** Replace the first occurrence of `needle` with `replacement`. */
|
|
583
|
-
function replaceFirst(haystack, needle, replacement) {
|
|
584
|
-
const index = haystack.indexOf(needle);
|
|
585
|
-
if (index === -1) return haystack;
|
|
586
|
-
return haystack.slice(0, index) + replacement + haystack.slice(index + needle.length);
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
710
|
* Create the policy-enforced operation layer over a backend.
|
|
590
711
|
*
|
|
591
712
|
* The returned {@link WorkspaceOps} is the single seam both the
|