@warlock.js/ai-workspace 5.43.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 +31 -0
- package/README.md +14 -1
- package/cjs/index.cjs +1847 -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 +88 -0
- package/esm/contracts/shell-approval.type.d.mts.map +1 -0
- package/esm/contracts/tool-io.type.d.mts +45 -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 +23 -1
- 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 +213 -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 +223 -0
- package/esm/output/spill.mjs.map +1 -0
- package/esm/policy/approval-helpers.mjs +22 -0
- package/esm/policy/approval-helpers.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 +420 -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 +63 -0
- package/esm/policy/shell-interpreter.d.mts.map +1 -0
- package/esm/policy/shell-interpreter.mjs +123 -0
- package/esm/policy/shell-interpreter.mjs.map +1 -0
- package/esm/policy/shell-parser.d.mts +33 -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 +19 -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 +27 -5
- package/esm/workspace.mjs.map +1 -1
- package/llms-full.txt +70 -17
- package/package.json +3 -3
- package/skills/use-a-workspace/SKILL.md +70 -17
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region ../../packages/ai-workspace/src/hooks/after-edit.ts
|
|
2
|
+
/** How long a hook may take when the policy sets no `afterEditTimeoutMs`. */
|
|
3
|
+
const DEFAULT_HOOK_TIMEOUT_MS = 1e4;
|
|
4
|
+
/** Longest diagnostics text kept when the policy sets no `afterEditMaxChars`. */
|
|
5
|
+
const DEFAULT_MAX_CHARS = 4e3;
|
|
6
|
+
function wrap(text) {
|
|
7
|
+
return `[diagnostics]\n${text}\n[/diagnostics]`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run the policy's `afterEdit` hook for one call and return the text to put
|
|
11
|
+
* on the result's `diagnostics`, or `undefined` when there is none. A throw
|
|
12
|
+
* or a timeout yields "diagnostics unavailable"; it never rejects.
|
|
13
|
+
*/
|
|
14
|
+
async function runAfterEdit(policy, event) {
|
|
15
|
+
const hook = policy.hooks?.afterEdit;
|
|
16
|
+
if (hook === void 0 || event.paths.length === 0) return;
|
|
17
|
+
const timeoutMs = policy.hooks?.afterEditTimeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
|
|
18
|
+
const maxChars = policy.hooks?.afterEditMaxChars ?? DEFAULT_MAX_CHARS;
|
|
19
|
+
let timer;
|
|
20
|
+
try {
|
|
21
|
+
const answer = await Promise.race([Promise.resolve().then(() => hook(event)), new Promise((_resolve, reject) => {
|
|
22
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("afterEdit hook timed out")), timeoutMs);
|
|
23
|
+
})]);
|
|
24
|
+
if (typeof answer !== "string" || answer.trim() === "") return;
|
|
25
|
+
const text = answer.trim();
|
|
26
|
+
return wrap(text.length > maxChars ? `${text.slice(0, maxChars)}\n[diagnostics truncated: showing ${maxChars} of ${text.length} characters]` : text);
|
|
27
|
+
} catch {
|
|
28
|
+
return wrap("diagnostics unavailable");
|
|
29
|
+
} finally {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { runAfterEdit };
|
|
36
|
+
//# sourceMappingURL=after-edit.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"after-edit.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/hooks/after-edit.ts"],"sourcesContent":["import type { WorkspaceAfterEditEvent, WorkspacePolicy } from \"../contracts\";\n\n/** How long a hook may take when the policy sets no `afterEditTimeoutMs`. */\nconst DEFAULT_HOOK_TIMEOUT_MS = 10_000;\n/** Longest diagnostics text kept when the policy sets no `afterEditMaxChars`. */\nconst DEFAULT_MAX_CHARS = 4_000;\n\nfunction wrap(text: string): string {\n return `[diagnostics]\\n${text}\\n[/diagnostics]`;\n}\n\n/**\n * Run the policy's `afterEdit` hook for one call and return the text to put\n * on the result's `diagnostics`, or `undefined` when there is none. A throw\n * or a timeout yields \"diagnostics unavailable\"; it never rejects.\n */\nexport async function runAfterEdit(\n policy: WorkspacePolicy,\n event: WorkspaceAfterEditEvent,\n): Promise<string | undefined> {\n const hook = policy.hooks?.afterEdit;\n\n if (hook === undefined || event.paths.length === 0) {\n return undefined;\n }\n\n const timeoutMs = policy.hooks?.afterEditTimeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;\n const maxChars = policy.hooks?.afterEditMaxChars ?? DEFAULT_MAX_CHARS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n const answer = await Promise.race([\n Promise.resolve().then(() => hook(event)),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new Error(\"afterEdit hook timed out\")), timeoutMs);\n }),\n ]);\n\n if (typeof answer !== \"string\" || answer.trim() === \"\") {\n return undefined;\n }\n\n const text = answer.trim();\n\n return wrap(\n text.length > maxChars\n ? `${text.slice(0, maxChars)}\\n[diagnostics truncated: showing ${maxChars} of ${text.length} characters]`\n : text,\n );\n } catch {\n return wrap(\"diagnostics unavailable\");\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":";;AAGA,MAAM,0BAA0B;;AAEhC,MAAM,oBAAoB;AAE1B,SAAS,KAAK,MAAsB;CAClC,OAAO,kBAAkB,KAAK;AAChC;;;;;;AAOA,eAAsB,aACpB,QACA,OAC6B;CAC7B,MAAM,OAAO,OAAO,OAAO;CAE3B,IAAI,SAAS,UAAa,MAAM,MAAM,WAAW,GAC/C;CAGF,MAAM,YAAY,OAAO,OAAO,sBAAsB;CACtD,MAAM,WAAW,OAAO,OAAO,qBAAqB;CACpD,IAAI;CAEJ,IAAI;EACF,MAAM,SAAS,MAAM,QAAQ,KAAK,CAChC,QAAQ,QAAQ,CAAC,CAAC,WAAW,KAAK,KAAK,CAAC,GACxC,IAAI,SAAgB,UAAU,WAAW;GACvC,QAAQ,iBAAiB,uBAAO,IAAI,MAAM,0BAA0B,CAAC,GAAG,SAAS;EACnF,CAAC,CACH,CAAC;EAED,IAAI,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM,IAClD;EAGF,MAAM,OAAO,OAAO,KAAK;EAEzB,OAAO,KACL,KAAK,SAAS,WACV,GAAG,KAAK,MAAM,GAAG,QAAQ,EAAE,oCAAoC,SAAS,MAAM,KAAK,OAAO,gBAC1F,IACN;CACF,QAAQ;EACN,OAAO,KAAK,yBAAyB;CACvC,UAAU;EACR,aAAa,KAAK;CACpB;AACF"}
|
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 { PowerShellEdition, 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 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 };
|
|
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":";;;;;;;;;;;;;;;;;iBA2MiB;;EAEf,MAAM;;EAEN,YAAY;;;;;;;;;;;;;;;;;;;;;wBA65BE,UACd,SAAS,kBACT,QAAQ,iBACR,UAAU,aACT"}
|
package/esm/ops.mjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
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";
|
|
10
|
+
import { refusalReason, samePattern } from "./policy/approval-helpers.mjs";
|
|
6
11
|
import { ShellJobs } from "./shell-jobs.mjs";
|
|
7
12
|
import path from "node:path";
|
|
8
13
|
import { fs } from "@warlock.js/fs";
|
|
@@ -139,6 +144,26 @@ var Ops = class {
|
|
|
139
144
|
this.policy = policy;
|
|
140
145
|
this.runtime = runtime;
|
|
141
146
|
this.pathLocks = /* @__PURE__ */ new Map();
|
|
147
|
+
this.resolvedShell = resolveShell(policy.shell);
|
|
148
|
+
this.approvals = runtime.approvals ?? [];
|
|
149
|
+
this.spiller = new OutputSpiller(backend, policy);
|
|
150
|
+
}
|
|
151
|
+
/** The edit-policy settings applied to every replacement. */
|
|
152
|
+
editOptions() {
|
|
153
|
+
const edit = this.policy.edit;
|
|
154
|
+
return {
|
|
155
|
+
matching: edit?.matching ?? "tiered",
|
|
156
|
+
missHintLines: edit?.missHintLines ?? 12,
|
|
157
|
+
lineEndings: edit?.lineEndings ?? "preserve",
|
|
158
|
+
...this.policy.read?.maxBytes !== void 0 ? { hintMaxBytes: this.policy.read.maxBytes } : {}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Run `afterEdit` for a call that wrote `paths`; `undefined` when there is no output. */
|
|
162
|
+
diagnosticsFor(tool, paths) {
|
|
163
|
+
return runAfterEdit(this.policy, {
|
|
164
|
+
paths,
|
|
165
|
+
tool
|
|
166
|
+
});
|
|
142
167
|
}
|
|
143
168
|
/** Run `task` after every earlier mutation of `absolutePath` has settled. */
|
|
144
169
|
async withPathLock(absolutePath, task) {
|
|
@@ -183,7 +208,7 @@ var Ops = class {
|
|
|
183
208
|
}
|
|
184
209
|
}
|
|
185
210
|
async readFile(inputPath, opts) {
|
|
186
|
-
const { absolutePath } = await resolveInJail(this.policy, inputPath);
|
|
211
|
+
const { absolutePath } = await resolveInJail(this.policy, inputPath, "read");
|
|
187
212
|
const raw = await this.backend.readFile(absolutePath);
|
|
188
213
|
const hash = fs.hash.string(raw);
|
|
189
214
|
const lines = raw.split("\n");
|
|
@@ -192,10 +217,30 @@ var Ops = class {
|
|
|
192
217
|
const limit = opts?.limit ?? this.policy.read?.defaultLines ?? DEFAULT_READ_LINES;
|
|
193
218
|
const maxBytes = this.policy.read?.maxBytes;
|
|
194
219
|
const startIndex = offset - 1;
|
|
195
|
-
const
|
|
220
|
+
const window = lines.slice(startIndex, startIndex + Math.max(0, limit));
|
|
221
|
+
const { content, returnedLines, bytesCapped } = numberWindow(window, offset, maxBytes === void 0 ? void 0 : Math.max(0, Math.floor(maxBytes)));
|
|
196
222
|
const endLine = offset + returnedLines - 1;
|
|
223
|
+
const spillDir = this.spiller.spillDir;
|
|
224
|
+
let spilled;
|
|
225
|
+
if (bytesCapped && spillDir !== void 0 && !isInside(absolutePath, spillDir)) {
|
|
226
|
+
const full = numberWindow(window, offset, void 0).content;
|
|
227
|
+
const file = await this.spiller.save("read_file", full);
|
|
228
|
+
if (file !== void 0) spilled = {
|
|
229
|
+
content: `${content}\n[showing lines 1-${returnedLines} of ${window.length}; full output saved to ${file}]`,
|
|
230
|
+
truncation: {
|
|
231
|
+
shown: { lines: returnedLines },
|
|
232
|
+
total: {
|
|
233
|
+
lines: window.length,
|
|
234
|
+
bytes: Buffer.byteLength(full, "utf8")
|
|
235
|
+
},
|
|
236
|
+
path: file
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
197
240
|
return {
|
|
198
|
-
content,
|
|
241
|
+
content: spilled?.content ?? content,
|
|
242
|
+
...spilled !== void 0 ? { truncation: spilled.truncation } : {},
|
|
243
|
+
...spilled?.truncation.path !== void 0 ? { spilledTo: [spilled.truncation.path] } : {},
|
|
199
244
|
startLine: offset,
|
|
200
245
|
endLine,
|
|
201
246
|
totalLines,
|
|
@@ -206,8 +251,8 @@ var Ops = class {
|
|
|
206
251
|
};
|
|
207
252
|
}
|
|
208
253
|
async writeFile(inputPath, content) {
|
|
209
|
-
const { absolutePath } = await resolveInJail(this.policy, inputPath);
|
|
210
|
-
|
|
254
|
+
const { absolutePath, relativePath } = await resolveInJail(this.policy, inputPath);
|
|
255
|
+
const written = await this.withPathLock(absolutePath, async () => {
|
|
211
256
|
const parent = path.dirname(absolutePath);
|
|
212
257
|
await this.backend.mkdir(parent);
|
|
213
258
|
await this.backend.writeFile(absolutePath, content);
|
|
@@ -216,10 +261,20 @@ var Ops = class {
|
|
|
216
261
|
bytesWritten: Buffer.byteLength(content, "utf8")
|
|
217
262
|
};
|
|
218
263
|
});
|
|
264
|
+
const diagnostics = await this.diagnosticsFor("write_file", [relativePath || inputPath]);
|
|
265
|
+
return {
|
|
266
|
+
...written,
|
|
267
|
+
...diagnostics !== void 0 ? { diagnostics } : {}
|
|
268
|
+
};
|
|
219
269
|
}
|
|
220
270
|
async editFile(input) {
|
|
221
271
|
const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);
|
|
222
|
-
|
|
272
|
+
const result = await this.withPathLock(absolutePath, () => this.applyEdit(input, absolutePath, relativePath));
|
|
273
|
+
const diagnostics = await this.diagnosticsFor("edit_file", [result.path]);
|
|
274
|
+
return diagnostics === void 0 ? result : {
|
|
275
|
+
...result,
|
|
276
|
+
diagnostics
|
|
277
|
+
};
|
|
223
278
|
}
|
|
224
279
|
/** The read-check-replace-write of {@link editFile}, run under its path lock. */
|
|
225
280
|
async applyEdit(input, absolutePath, relativePath) {
|
|
@@ -231,19 +286,20 @@ var Ops = class {
|
|
|
231
286
|
expectedHash: input.expectHash,
|
|
232
287
|
actualHash: currentHash
|
|
233
288
|
});
|
|
234
|
-
const { content: updated, replacements } = applyReplacement(current, input, input.path, relativePath || input.path);
|
|
289
|
+
const { content: updated, replacements, matchTier } = applyReplacement(current, input, input.path, relativePath || input.path, this.editOptions());
|
|
235
290
|
await this.backend.writeFile(absolutePath, updated);
|
|
236
291
|
return {
|
|
237
292
|
path: relativePath || input.path,
|
|
238
293
|
replacements,
|
|
239
|
-
hash: fs.hash.string(updated)
|
|
294
|
+
hash: fs.hash.string(updated),
|
|
295
|
+
...matchTier !== void 0 ? { matchTier } : {}
|
|
240
296
|
};
|
|
241
297
|
}
|
|
242
298
|
async multiEdit(input) {
|
|
243
299
|
const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);
|
|
244
300
|
const shownPath = relativePath || input.path;
|
|
245
301
|
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
|
-
|
|
302
|
+
const result = await this.withPathLock(absolutePath, async () => {
|
|
247
303
|
const current = await this.backend.readFile(absolutePath);
|
|
248
304
|
const currentHash = fs.hash.string(current);
|
|
249
305
|
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 +310,40 @@ var Ops = class {
|
|
|
254
310
|
});
|
|
255
311
|
let content = current;
|
|
256
312
|
let replacements = 0;
|
|
313
|
+
let matchTier;
|
|
314
|
+
const options = this.editOptions();
|
|
257
315
|
input.edits.forEach((edit, editIndex) => {
|
|
258
|
-
const applied = applyReplacement(content, edit, input.path, shownPath, editIndex);
|
|
316
|
+
const applied = applyReplacement(content, edit, input.path, shownPath, options, editIndex);
|
|
259
317
|
content = applied.content;
|
|
260
318
|
replacements += applied.replacements;
|
|
319
|
+
if (applied.matchTier === "whitespace" || matchTier === void 0) matchTier = applied.matchTier ?? matchTier;
|
|
261
320
|
});
|
|
262
321
|
await this.backend.writeFile(absolutePath, content);
|
|
263
322
|
return {
|
|
264
323
|
path: shownPath,
|
|
265
324
|
edits: input.edits.length,
|
|
266
325
|
replacements,
|
|
267
|
-
hash: fs.hash.string(content)
|
|
326
|
+
hash: fs.hash.string(content),
|
|
327
|
+
...matchTier !== void 0 ? { matchTier } : {}
|
|
268
328
|
};
|
|
269
329
|
});
|
|
330
|
+
const diagnostics = await this.diagnosticsFor("multi_edit", [shownPath]);
|
|
331
|
+
return diagnostics === void 0 ? result : {
|
|
332
|
+
...result,
|
|
333
|
+
diagnostics
|
|
334
|
+
};
|
|
270
335
|
}
|
|
271
336
|
async applyPatch(input) {
|
|
337
|
+
const result = await this.runPatch(input);
|
|
338
|
+
if (result.dryRun) return result;
|
|
339
|
+
const paths = result.files.filter((file) => file.action !== "deleted").map((file) => file.path);
|
|
340
|
+
const diagnostics = await this.diagnosticsFor("apply_patch", paths);
|
|
341
|
+
return diagnostics === void 0 ? result : {
|
|
342
|
+
...result,
|
|
343
|
+
diagnostics
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
runPatch(input) {
|
|
272
347
|
return executePatch(input, {
|
|
273
348
|
resolve: (inputPath) => resolveInJail(this.policy, inputPath),
|
|
274
349
|
exists: (absolutePath) => this.backend.exists(absolutePath),
|
|
@@ -288,9 +363,14 @@ var Ops = class {
|
|
|
288
363
|
* (argv or shell mode), then — shell mode — every redirection target and
|
|
289
364
|
* `cd` directory against the jail with symlinks resolved.
|
|
290
365
|
*/
|
|
291
|
-
async assertCommandAllowed(command) {
|
|
292
|
-
const verdict = checkShellCommand(this.policy, command
|
|
293
|
-
|
|
366
|
+
async assertCommandAllowed(command, signal) {
|
|
367
|
+
const verdict = checkShellCommand(this.policy, command, {
|
|
368
|
+
remembered: this.approvals,
|
|
369
|
+
resolved: this.resolvedShell
|
|
370
|
+
});
|
|
371
|
+
const onAsk = this.policy.shell?.onAsk;
|
|
372
|
+
const needsApproval = !verdict.allowed && verdict.reason === "approval-required" && verdict.asks.length > 0;
|
|
373
|
+
if (!verdict.allowed && !(needsApproval && onAsk !== void 0)) throw new WorkspacePolicyError(verdict.message ?? `Command is not permitted by the workspace shell policy: ${command}`, {
|
|
294
374
|
type: "denied-command",
|
|
295
375
|
command,
|
|
296
376
|
...verdict.reason !== void 0 ? { reason: verdict.reason } : {},
|
|
@@ -313,9 +393,35 @@ var Ops = class {
|
|
|
313
393
|
reason: "not-allowed",
|
|
314
394
|
command
|
|
315
395
|
});
|
|
396
|
+
if (needsApproval && onAsk !== void 0) await this.askForApproval(command, verdict.asks, onAsk, signal);
|
|
397
|
+
}
|
|
398
|
+
/** Put the commands that need approval to `shell.onAsk`; refuse the line unless it says yes. */
|
|
399
|
+
async askForApproval(command, asks, onAsk, signal) {
|
|
400
|
+
let decision;
|
|
401
|
+
try {
|
|
402
|
+
decision = await onAsk({
|
|
403
|
+
command,
|
|
404
|
+
asks,
|
|
405
|
+
...signal ? { signal } : {}
|
|
406
|
+
});
|
|
407
|
+
} catch {
|
|
408
|
+
decision = void 0;
|
|
409
|
+
}
|
|
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
|
+
}
|
|
316
422
|
}
|
|
317
423
|
async exec(command, opts) {
|
|
318
|
-
await this.assertCommandAllowed(command);
|
|
424
|
+
await this.assertCommandAllowed(command, opts?.signal);
|
|
319
425
|
const signal = opts?.signal;
|
|
320
426
|
if (signal?.aborted) return {
|
|
321
427
|
exitCode: CANCELLED_BEFORE_START_EXIT_CODE,
|
|
@@ -328,24 +434,28 @@ var Ops = class {
|
|
|
328
434
|
const shell = this.policy.shell;
|
|
329
435
|
const timeoutMs = opts?.timeoutMs ?? shell?.timeoutMs;
|
|
330
436
|
const maxOutputBytes = shell?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
331
|
-
const interpreter =
|
|
437
|
+
const interpreter = this.resolvedShell.interpreter;
|
|
332
438
|
const result = await this.backend.exec(command, {
|
|
333
439
|
cwd: this.policy.cwd,
|
|
334
440
|
timeoutMs,
|
|
335
441
|
env: buildCommandEnv(this.policy),
|
|
336
442
|
...signal ? { signal } : {},
|
|
337
|
-
...interpreter !== "none" ? { interpreter } : {}
|
|
443
|
+
...interpreter !== "none" ? { interpreter } : {},
|
|
444
|
+
...interpreter !== "none" && this.resolvedShell.file !== void 0 ? { interpreterPath: this.resolvedShell.file } : {}
|
|
338
445
|
});
|
|
339
|
-
const stdout =
|
|
340
|
-
const stderr =
|
|
446
|
+
const stdout = await this.fitStream("run_shell-stdout", result.stdout, maxOutputBytes);
|
|
447
|
+
const stderr = await this.fitStream("run_shell-stderr", result.stderr, maxOutputBytes);
|
|
341
448
|
const cancelled = result.cancelled === true;
|
|
449
|
+
const truncation = streamTruncation(stdout.truncation, stderr.truncation);
|
|
342
450
|
return {
|
|
343
451
|
exitCode: result.exitCode,
|
|
344
452
|
stdout: stdout.value,
|
|
345
453
|
stderr: stderr.value,
|
|
346
454
|
truncated: stdout.truncated || stderr.truncated,
|
|
347
455
|
timedOut: result.timedOut && !cancelled,
|
|
348
|
-
cancelled
|
|
456
|
+
cancelled,
|
|
457
|
+
...truncation !== void 0 ? { truncation } : {},
|
|
458
|
+
...spilledField(truncation)
|
|
349
459
|
};
|
|
350
460
|
}
|
|
351
461
|
async startShell(command, opts) {
|
|
@@ -363,7 +473,8 @@ var Ops = class {
|
|
|
363
473
|
command
|
|
364
474
|
});
|
|
365
475
|
const shell = this.policy.shell;
|
|
366
|
-
const interpreter =
|
|
476
|
+
const interpreter = this.resolvedShell.interpreter;
|
|
477
|
+
const interpreterPath = interpreter !== "none" ? this.resolvedShell.file : void 0;
|
|
367
478
|
const timeoutMs = lowerOf(background.timeoutMs, opts?.timeoutMs);
|
|
368
479
|
const env = buildCommandEnv(this.policy);
|
|
369
480
|
return this.runtime.jobs.start({
|
|
@@ -375,12 +486,52 @@ var Ops = class {
|
|
|
375
486
|
cwd: this.policy.cwd,
|
|
376
487
|
env,
|
|
377
488
|
...interpreter !== "none" ? { interpreter } : {},
|
|
489
|
+
...interpreterPath !== void 0 ? { interpreterPath } : {},
|
|
378
490
|
onOutput
|
|
379
491
|
})
|
|
380
492
|
});
|
|
381
493
|
}
|
|
382
|
-
shellOutput(id, opts) {
|
|
383
|
-
|
|
494
|
+
async shellOutput(id, opts) {
|
|
495
|
+
const result = await this.runtime.jobs.output(id, opts);
|
|
496
|
+
if (!this.spiller.enabled) return result;
|
|
497
|
+
const stdout = await this.spiller.text(`shell_output-${id}-stdout`, result.stdout, "head+tail");
|
|
498
|
+
const stderr = await this.spiller.text(`shell_output-${id}-stderr`, result.stderr, "head+tail");
|
|
499
|
+
const truncation = streamTruncation(stdout.truncation, stderr.truncation);
|
|
500
|
+
return truncation === void 0 ? result : {
|
|
501
|
+
...result,
|
|
502
|
+
stdout: stdout.value,
|
|
503
|
+
stderr: stderr.value,
|
|
504
|
+
truncated: true,
|
|
505
|
+
truncation,
|
|
506
|
+
...spilledField(truncation)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Fit one captured stream to the limits: spilled to a file when
|
|
511
|
+
* `output.spillDir` is set, otherwise clipped at `maxBytes` as always.
|
|
512
|
+
*/
|
|
513
|
+
async fitStream(label, value, maxBytes) {
|
|
514
|
+
if (this.spiller.enabled) {
|
|
515
|
+
const cap = this.policy.shell?.maxOutputBytes;
|
|
516
|
+
const spilled = await this.spiller.text(label, value, "head+tail", cap);
|
|
517
|
+
if (spilled.truncation !== void 0) return {
|
|
518
|
+
value: spilled.value,
|
|
519
|
+
truncated: true,
|
|
520
|
+
truncation: spilled.truncation
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
const capped = capOutput(value, maxBytes);
|
|
524
|
+
if (!capped.truncated) return capped;
|
|
525
|
+
return {
|
|
526
|
+
...capped,
|
|
527
|
+
truncation: {
|
|
528
|
+
shown: { lines: splitLines(capped.value).length },
|
|
529
|
+
total: {
|
|
530
|
+
lines: splitLines(value).length,
|
|
531
|
+
bytes: Buffer.byteLength(value, "utf8")
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
384
535
|
}
|
|
385
536
|
shellKill(id) {
|
|
386
537
|
return this.runtime.jobs.kill(id);
|
|
@@ -388,8 +539,9 @@ var Ops = class {
|
|
|
388
539
|
listShells() {
|
|
389
540
|
return this.runtime.jobs.list();
|
|
390
541
|
}
|
|
391
|
-
dispose() {
|
|
392
|
-
|
|
542
|
+
async dispose() {
|
|
543
|
+
await this.runtime.jobs.dispose();
|
|
544
|
+
await this.spiller.dispose();
|
|
393
545
|
}
|
|
394
546
|
async listDir(inputPath = ".", opts) {
|
|
395
547
|
const { absolutePath: root, relativePath: rootRelative } = await resolveInJail(this.policy, inputPath);
|
|
@@ -493,16 +645,34 @@ var Ops = class {
|
|
|
493
645
|
line: index + 1,
|
|
494
646
|
text: line
|
|
495
647
|
});
|
|
496
|
-
if (matches.length >= DEFAULT_MAX_GREP_MATCHES) return
|
|
497
|
-
matches,
|
|
498
|
-
total: matches.length
|
|
499
|
-
};
|
|
648
|
+
if (matches.length >= DEFAULT_MAX_GREP_MATCHES) return this.fitMatches(matches);
|
|
500
649
|
}
|
|
501
650
|
}
|
|
502
651
|
}
|
|
503
|
-
return
|
|
652
|
+
return this.fitMatches(matches);
|
|
653
|
+
}
|
|
654
|
+
/** Apply the `output` limits to a finished match list. */
|
|
655
|
+
async fitMatches(matches) {
|
|
656
|
+
const fitted = await this.spiller.list("grep", matches, (match) => `${match.path}:${match.line}:${match.text}`, "head");
|
|
657
|
+
return fitted.truncation === void 0 ? {
|
|
504
658
|
matches,
|
|
505
659
|
total: matches.length
|
|
660
|
+
} : {
|
|
661
|
+
matches: fitted.items,
|
|
662
|
+
total: matches.length,
|
|
663
|
+
truncated: true,
|
|
664
|
+
truncation: fitted.truncation,
|
|
665
|
+
...spilledField({ stdout: fitted.truncation })
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
async globResult(pattern) {
|
|
669
|
+
const all = await this.glob(pattern);
|
|
670
|
+
const fitted = await this.spiller.list("glob", all, (entry) => entry, "head");
|
|
671
|
+
return fitted.truncation === void 0 ? { paths: all } : {
|
|
672
|
+
paths: fitted.items,
|
|
673
|
+
truncated: true,
|
|
674
|
+
truncation: fitted.truncation,
|
|
675
|
+
...spilledField({ stdout: fitted.truncation })
|
|
506
676
|
};
|
|
507
677
|
}
|
|
508
678
|
async glob(pattern) {
|
|
@@ -535,55 +705,23 @@ var Ops = class {
|
|
|
535
705
|
return denyPaths !== void 0 && denyPaths.length > 0 && matchesAnyGlob(relativePath, denyPaths);
|
|
536
706
|
}
|
|
537
707
|
};
|
|
538
|
-
/**
|
|
539
|
-
function
|
|
540
|
-
|
|
541
|
-
return
|
|
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 } : {};
|
|
542
712
|
}
|
|
543
|
-
/**
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
});
|
|
713
|
+
/** Combine per-stream truncation metadata; `undefined` when neither stream was clipped. */
|
|
714
|
+
function streamTruncation(stdout, stderr) {
|
|
715
|
+
if (stdout === void 0 && stderr === void 0) return;
|
|
564
716
|
return {
|
|
565
|
-
|
|
566
|
-
|
|
717
|
+
...stdout !== void 0 ? { stdout } : {},
|
|
718
|
+
...stderr !== void 0 ? { stderr } : {}
|
|
567
719
|
};
|
|
568
720
|
}
|
|
569
|
-
/**
|
|
570
|
-
function
|
|
571
|
-
if (
|
|
572
|
-
|
|
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);
|
|
721
|
+
/** The lower of two optional limits (either may be absent). */
|
|
722
|
+
function lowerOf(policyLimit, requested) {
|
|
723
|
+
if (requested === void 0 || requested <= 0) return policyLimit;
|
|
724
|
+
return policyLimit === void 0 ? requested : Math.min(policyLimit, requested);
|
|
587
725
|
}
|
|
588
726
|
/**
|
|
589
727
|
* Create the policy-enforced operation layer over a backend.
|