@warlock.js/ai-workspace 5.31.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/cjs/index.cjs +34 -22
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/backends/local.d.mts +1 -3
  5. package/esm/backends/local.d.mts.map +1 -1
  6. package/esm/backends/local.mjs +1 -1
  7. package/esm/backends/local.mjs.map +1 -1
  8. package/esm/backends/mock.d.mts +5 -6
  9. package/esm/backends/mock.d.mts.map +1 -1
  10. package/esm/contracts/index.d.mts +2 -1
  11. package/esm/contracts/tool-io.type.d.mts +37 -38
  12. package/esm/contracts/tool-io.type.d.mts.map +1 -1
  13. package/esm/contracts/workspace-backend.contract.d.mts +7 -9
  14. package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
  15. package/esm/contracts/workspace-ops.contract.d.mts +1 -3
  16. package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
  17. package/esm/contracts/workspace-policy.type.d.mts +10 -11
  18. package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
  19. package/esm/contracts/workspace.contract.d.mts +2 -4
  20. package/esm/contracts/workspace.contract.d.mts.map +1 -1
  21. package/esm/errors.d.mts +35 -23
  22. package/esm/errors.d.mts.map +1 -1
  23. package/esm/index.d.mts +1 -0
  24. package/esm/ops.d.mts +3 -4
  25. package/esm/ops.d.mts.map +1 -1
  26. package/esm/policy/policy.d.mts +5 -5
  27. package/esm/policy/policy.d.mts.map +1 -1
  28. package/esm/policy/policy.mjs +4 -3
  29. package/esm/policy/policy.mjs.map +1 -1
  30. package/esm/policy/shell-check.d.mts +5 -6
  31. package/esm/policy/shell-check.d.mts.map +1 -1
  32. package/esm/policy/shell-check.mjs +24 -13
  33. package/esm/policy/shell-check.mjs.map +1 -1
  34. package/esm/policy/shell-parser.mjs +2 -2
  35. package/esm/policy/shell-parser.mjs.map +1 -1
  36. package/esm/policy/tokenize-command.d.mts +3 -4
  37. package/esm/policy/tokenize-command.d.mts.map +1 -1
  38. package/esm/policy/tokenize-command.mjs +2 -2
  39. package/esm/policy/tokenize-command.mjs.map +1 -1
  40. package/esm/shell-jobs.d.mts +3 -3
  41. package/esm/shell-jobs.d.mts.map +1 -1
  42. package/esm/shell-jobs.mjs +1 -1
  43. package/esm/todos.d.mts +2 -2
  44. package/esm/todos.d.mts.map +1 -1
  45. package/esm/todos.mjs +1 -1
  46. package/esm/tools/edit-file.d.mts +2 -3
  47. package/esm/tools/edit-file.d.mts.map +1 -1
  48. package/esm/tools/glob.d.mts +3 -4
  49. package/esm/tools/glob.d.mts.map +1 -1
  50. package/esm/tools/grep.d.mts +3 -4
  51. package/esm/tools/grep.d.mts.map +1 -1
  52. package/esm/tools/list-dir.d.mts +2 -3
  53. package/esm/tools/list-dir.d.mts.map +1 -1
  54. package/esm/tools/multi-edit.d.mts +2 -3
  55. package/esm/tools/multi-edit.d.mts.map +1 -1
  56. package/esm/tools/read-file.d.mts +2 -3
  57. package/esm/tools/read-file.d.mts.map +1 -1
  58. package/esm/tools/run-shell.d.mts +5 -6
  59. package/esm/tools/run-shell.d.mts.map +1 -1
  60. package/esm/tools/run-tests.d.mts +3 -4
  61. package/esm/tools/run-tests.d.mts.map +1 -1
  62. package/esm/tools/shell-kill.d.mts +2 -3
  63. package/esm/tools/shell-kill.d.mts.map +1 -1
  64. package/esm/tools/shell-output.d.mts +2 -3
  65. package/esm/tools/shell-output.d.mts.map +1 -1
  66. package/esm/tools/todo.d.mts +3 -4
  67. package/esm/tools/todo.d.mts.map +1 -1
  68. package/esm/tools/write-file.d.mts +2 -3
  69. package/esm/tools/write-file.d.mts.map +1 -1
  70. package/esm/workspace.d.mts +3 -3
  71. package/esm/workspace.d.mts.map +1 -1
  72. package/esm/workspace.mjs +1 -1
  73. package/esm/workspace.mjs.map +1 -1
  74. package/package.json +3 -3
@@ -1,5 +1,4 @@
1
1
  import { WorkspaceBackend } from "../contracts/workspace-backend.contract.mjs";
2
-
3
2
  //#region ../../packages/ai-workspace/src/backends/local.d.ts
4
3
  /**
5
4
  * Create the **local** workspace backend — the default executor that runs the
@@ -16,7 +15,6 @@ import { WorkspaceBackend } from "../contracts/workspace-backend.contract.mjs";
16
15
  * await backend.writeFile("/srv/app/src/index.ts", "export const x = 1;");
17
16
  * const { exitCode } = await backend.exec("node -v", { cwd: "/srv/app" });
18
17
  */
19
- declare function createLocalBackend(): WorkspaceBackend;
18
+ export declare function createLocalBackend(): WorkspaceBackend;
20
19
  //#endregion
21
- export { createLocalBackend };
22
20
  //# sourceMappingURL=local.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"local.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/backends/local.ts"],"mappings":";;;;;AAokBA;;;;AAAsD;;;;;;;;;iBAAtC,kBAAA,IAAsB,gBAAgB"}
1
+ {"version":3,"file":"local.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/backends/local.ts"],"mappings":";;;;;;;;;;;;;;;;;wBAokBgB,sBAAsB"}
@@ -13,7 +13,7 @@ import { StringDecoder } from "node:string_decoder";
13
13
  * policy-driven cap (and the `truncated` flag) on top. Generous enough that
14
14
  * ordinary command output is never clipped here.
15
15
  */
16
- const MAX_STREAM_BYTES = 10 * 1024 * 1024;
16
+ const MAX_STREAM_BYTES = 10485760;
17
17
  /**
18
18
  * Append a chunk to a capped list of buffers, tracking the running byte
19
19
  * total and stopping once {@link MAX_STREAM_BYTES} is reached. Trailing bytes
@@ -1 +1 @@
1
- {"version":3,"file":"local.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/backends/local.ts"],"sourcesContent":["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"],"mappings":"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"}
1
+ {"version":3,"file":"local.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/backends/local.ts"],"sourcesContent":["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"],"mappings":";;;;;;;;;;;;;;;AAuBA,MAAM,mBAAmB;;;;;;AAOzB,SAAS,WAAW,QAAkB,OAAe,OAAuB;CAC1E,IAAI,SAAS,kBACX,OAAO;CAGT,MAAM,YAAY,mBAAmB;CAErC,IAAI,MAAM,UAAU,WAAW;EAC7B,OAAO,KAAK,KAAK;EAEjB,OAAO,QAAQ,MAAM;CACvB;CAEA,OAAO,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC;CAExC,OAAO;AACT;;;;;;;;;;;;;AAcA,SAAS,SAAS,KAAyB,OAAwD;CACjG,IAAI,QAAQ,QAAW;EACrB,MAAM,KAAK,SAAS;EAEpB;CACF;CAEA,IAAI,aAAa,SAAS;EAMxB,AALe,MAAM,YAAY;GAAC;GAAQ,OAAO,GAAG;GAAG;GAAM;EAAI,GAAG,EAAE,aAAa,KAAK,CAKnF,CAAC,CAAC,GAAG,eAAe;GACvB,MAAM,KAAK,SAAS;EACtB,CAAC;EAED;CACF;CAEA,IAAI;EACF,QAAQ,KAAK,CAAC,KAAK,SAAS;CAC9B,QAAQ;EAEN,MAAM,KAAK,SAAS;CACtB;AACF;;;;;;;;AASA,MAAM,wBAAwB;;;;;;;;;;;;;AAc9B,SAAS,qBAAqB,MAAyD;CACrF,IAAI,KAAK,MAAM,YAAY,sBAAsB,KAAK,OAAO,CAAC,GAC5D,OAAO;CAGT,MAAM,cAAc,KAAK,KAAK,YAAY,IAAI,QAAQ,EAAE,CAAC,CAAC,KAAK,GAAG;CAElE,OAAO;EACL,MAAM,QAAQ,IAAI,WAAW;EAC7B,MAAM;GAAC;GAAM;GAAM;GAAM,IAAI,YAAY;EAAE;CAC7C;AACF;;AAGA,MAAM,cAAc;;;;;;;;;;;AAiBpB,SAAS,UAAU,SAAiB,aAAmD;CACrF,IAAI,gBAAgB,MAAM;EACxB,IAAI,aAAa,SACf,OAAO;GACL,MAAM,QAAQ,IAAI,WAAW;GAC7B,MAAM;IAAC;IAAM;IAAU;IAAM;IAAM,IAAI,QAAQ;GAAE;GACjD,0BAA0B;EAC5B;EAGF,OAAO;GAAE,MAAM;GAAa,MAAM,CAAC,MAAM,OAAO;GAAG,0BAA0B;EAAM;CACrF;CAEA,MAAM,OAAO,gBAAgB,OAAO;CACpC,MAAM,aAAa,OAAO;CAE1B,IAAI,SAAS,QAAQ,eAAe,QAClC,OAAO,EACL,SACE,mQAIJ;CAGF,IAAI,aAAa,SAAS;EACxB,MAAM,aAAa,qBAAqB,IAAI;EAE5C,IAAI,eAAe,MACjB,OAAO,EACL,SACE,oHAEJ;EAGF,OAAO;GAAE,GAAG;GAAY,0BAA0B;EAAK;CACzD;CAEA,OAAO;EAAE,MAAM;EAAY,MAAM,KAAK,MAAM,CAAC;EAAG,0BAA0B;CAAM;AAClF;;;;;;AAOA,MAAM,sCAAsB,IAAI,IAAgB;AAEhD,IAAI,oBAAoB;;AAGxB,SAAS,uBAAuB,MAA8B;CAC5D,oBAAoB,IAAI,IAAI;CAE5B,IAAI,CAAC,mBAAmB;EACtB,oBAAoB;EACpB,QAAQ,GAAG,cAAc;GACvB,KAAK,MAAM,eAAe,qBACxB,YAAY;EAEhB,CAAC;CACH;CAEA,aAAa;EACX,oBAAoB,OAAO,IAAI;CACjC;AACF;;;;;;;;;;;AAYA,IAAM,eAAN,MAA+C;;CAE7C,MAAa,SAAS,SAAkC;EACtD,OAAO,GAAG,MAAM,IAAI,OAAO;CAC7B;;;;;;CAOA,MAAa,UAAU,SAAiB,SAAgC;EACtE,MAAM,GAAG,MAAM,IAAI,SAAS,SAAS,EAAE,QAAQ,KAAK,CAAC;CACvD;;CAGA,MAAa,OAAO,SAAmC;EACrD,OAAO,GAAG,OAAO,OAAO;CAC1B;;CAGA,MAAa,MAAM,SAAgC;EACjD,MAAM,GAAG,KAAK,OAAO,OAAO;CAC9B;;;;;;;CAQA,MAAa,OAAO,SAAgC;EAClD,IAAI,cAAc;EAElB,IAAI;GACF,eAAe,MAAM,GAAG,MAAM,MAAM,OAAO,EAAC,CAAE,SAAS;EACzD,SAAS,OAAO;GACd,IAAK,OAAiC,SAAS,UAC7C;GAGF,MAAM;EACR;EAEA,IAAI,aAAa;GACf,MAAM,GAAG,KAAK,OAAO,OAAO;GAE5B;EACF;EAEA,MAAM,GAAG,MAAM,OAAO,OAAO;CAC/B;;CAGA,MAAa,KAAK,QAAmC;EACnD,OAAO,GAAG,KAAK,KAAK,MAAM;CAC5B;;;;;CAMA,MAAa,KAAK,SAAgD;EAChE,MAAM,QAAQ,MAAM,MAAM,OAAO;EASjC,OAAO;GAAE,MARI,MAAM,eAAe,IAC9B,YACA,MAAM,YAAY,IAChB,cACA,MAAM,OAAO,IACX,SACA;GAEO,MAAM,MAAM;EAAK;CAClC;;;;;;;CAQA,MAAa,SAAS,SAAkC;EACtD,OAAO,SAAS,OAAO;CACzB;;;;;;;;;;;;;;;;;;;;;;;;;CA0BA,AAAO,KACL,SACA,OAAoC,CAAC,GACA;EACrC,OAAO,IAAI,SAAqC,YAAY;GAC1D,MAAM,UAAU,WACd,QAAQ;IAAE,UAAU;IAAK,QAAQ;IAAI;IAAQ,UAAU;GAAM,CAAC;GAEhE,MAAM,SAAS,KAAK;GAGpB,IAAI,QAAQ,SAAS;IACnB,QAAQ;KACN,UAAU;KACV,QAAQ;KACR,QAAQ;KACR,UAAU;KACV,WAAW;IACb,CAAC;IAED;GACF;GAEA,MAAM,OAAO,UAAU,SAAS,KAAK,eAAe,MAAM;GAE1D,IAAI,aAAa,MAAM;IACrB,OAAO,KAAK,OAAO;IAEnB;GACF;GAEA,MAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,MAAM;IACxC,KAAK,KAAK;IACV,KAAK,KAAK;IACV,OAAO;IACP,aAAa;IACb,0BAA0B,KAAK;IAI/B,UAAU,aAAa;GACzB,CAAC;GAED,MAAM,eAAyB,CAAC;GAChC,MAAM,eAAyB,CAAC;GAChC,IAAI,cAAc;GAClB,IAAI,cAAc;GAClB,IAAI,WAAW;GACf,IAAI,YAAY;GAChB,IAAI,UAAU;GAGd,IAAI,SAAS;GAIb,MAAM,QACJ,KAAK,cAAc,UAAa,KAAK,YAAY,IAC7C,iBAAiB;IACf,IAAI,WACF;IAGF,WAAW;IACX,SAAS,MAAM,KAAK,KAAK;GAC3B,GAAG,KAAK,SAAS,IACjB;GAEN,MAAM,gBAAsB;IAC1B,IAAI,WAAW,UACb;IAMF,IAAI,CAAC,QACH,YAAY;IAGd,SAAS,MAAM,KAAK,KAAK;GAC3B;GAKA,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;GAEzD,MAAM,UAAU,aAAqB;IACnC,IAAI,SACF;IAGF,UAAU;IAEV,IAAI,UAAU,QACZ,aAAa,KAAK;IAGpB,QAAQ,oBAAoB,SAAS,OAAO;IAE5C,QAAQ;KACN;KACA,QAAQ,OAAO,OAAO,YAAY,CAAC,CAAC,SAAS,MAAM;KACnD,QAAQ,OAAO,OAAO,YAAY,CAAC,CAAC,SAAS,MAAM;KACnD;KACA;IACF,CAAC;GACH;GAEA,MAAM,QAAQ,GAAG,SAAS,UAAkB;IAC1C,cAAc,WAAW,cAAc,aAAa,KAAK;GAC3D,CAAC;GAED,MAAM,QAAQ,GAAG,SAAS,UAAkB;IAC1C,cAAc,WAAW,cAAc,aAAa,KAAK;GAC3D,CAAC;GAKD,MAAM,GAAG,eAAe;IACtB,OAAO,GAAG;GACZ,CAAC;GAED,MAAM,GAAG,cAAc;IACrB,SAAS;GACX,CAAC;GAED,MAAM,GAAG,UAAU,MAAM,WAAW;IAIlC,IAAI,SAAS,MAAM;KAEjB,OAAO,OADc,WAAW,YAAY,IAAI,EACvB;KAEzB;IACF;IAEA,OAAO,IAAI;GACb,CAAC;EACH,CAAC;CACH;;;;;;;;CASA,AAAO,MAAM,SAAiB,MAA6D;EACzF,MAAM,OAAO,UAAU,SAAS,KAAK,eAAe,MAAM;EAE1D,IAAI,aAAa,MAAM;GACrB,KAAK,SAAS,UAAU,KAAK,OAAO;GAEpC,OAAO;IAAE,MAAM,QAAQ,QAAQ,EAAE,UAAU,IAAI,CAAC;IAAG,YAAY,CAAC;GAAE;EACpE;EAEA,MAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,MAAM;GACxC,KAAK,KAAK;GACV,KAAK,KAAK;GACV,OAAO;GACP,aAAa;GACb,0BAA0B,KAAK;GAC/B,UAAU,aAAa;GACvB,OAAO;IAAC;IAAU;IAAQ;GAAM;EAClC,CAAC;EAED,MAAM,gBAAgB,IAAI,cAAc,MAAM;EAC9C,MAAM,gBAAgB,IAAI,cAAc,MAAM;EAC9C,IAAI,WAAW;EAEf,MAAM,aAAmB;GACvB,IAAI,CAAC,UACH,SAAS,MAAM,KAAK,KAAK;EAE7B;EAEA,MAAM,UAAU,uBAAuB,IAAI;EAE3C,MAAM,QAAQ,GAAG,SAAS,UAAkB;GAC1C,MAAM,OAAO,cAAc,MAAM,KAAK;GAEtC,IAAI,SAAS,IACX,KAAK,SAAS,UAAU,IAAI;EAEhC,CAAC;EAED,MAAM,QAAQ,GAAG,SAAS,UAAkB;GAC1C,MAAM,OAAO,cAAc,MAAM,KAAK;GAEtC,IAAI,SAAS,IACX,KAAK,SAAS,UAAU,IAAI;EAEhC,CAAC;EAED,MAAM,OAAO,IAAI,SAA+B,YAAY;GAC1D,MAAM,UAAU,aAA2B;IACzC,IAAI,UACF;IAGF,WAAW;IACX,QAAQ;IAER,MAAM,aAAa,cAAc,IAAI;IACrC,MAAM,aAAa,cAAc,IAAI;IAErC,IAAI,eAAe,IACjB,KAAK,SAAS,UAAU,UAAU;IAGpC,IAAI,eAAe,IACjB,KAAK,SAAS,UAAU,UAAU;IAGpC,QAAQ,EAAE,SAAS,CAAC;GACtB;GAEA,MAAM,GAAG,UAAU,UAAU;IAC3B,KAAK,SAAS,UAAU,iCAAiC,MAAM,SAAS;IACxE,OAAO,GAAG;GACZ,CAAC;GAED,MAAM,GAAG,UAAU,MAAM,WAAW;IAClC,OAAO,SAAS,OAAO,OAAO,WAAW,YAAY,IAAI,KAAK,IAAI;GACpE,CAAC;EACH,CAAC;EAED,OAAO;GAAE,GAAI,MAAM,QAAQ,SAAY,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;GAAI;GAAM;EAAK;CAC9E;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,qBAAuC;CACrD,OAAO,IAAI,aAAa;AAC1B"}
@@ -1,5 +1,4 @@
1
1
  import { WorkspaceBackend, WorkspaceBackendExecResult } from "../contracts/workspace-backend.contract.mjs";
2
-
3
2
  //#region ../../packages/ai-workspace/src/backends/mock.d.ts
4
3
  /**
5
4
  * A scripted outcome for the mock backend's `exec` (and background
@@ -11,8 +10,9 @@ import { WorkspaceBackend, WorkspaceBackendExecResult } from "../contracts/works
11
10
  * its scripted output) until it is killed, like a dev server; otherwise it
12
11
  * exits at once with `exitCode`.
13
12
  */
14
- type MockExecResult = Partial<WorkspaceBackendExecResult> & {
15
- /** Background starts only: stay running until killed. */running?: boolean;
13
+ export type MockExecResult = Partial<WorkspaceBackendExecResult> & {
14
+ /** Background starts only: stay running until killed. */
15
+ running?: boolean;
16
16
  };
17
17
  /**
18
18
  * The two seedable inputs of a mock backend:
@@ -22,7 +22,7 @@ type MockExecResult = Partial<WorkspaceBackendExecResult> & {
22
22
  * - `commands` — scripted `exec` outcomes keyed on the **exact** command
23
23
  * line. Any command not registered here resolves to a `0`-exit no-op.
24
24
  */
25
- interface MockBackendSeed {
25
+ export interface MockBackendSeed {
26
26
  /** Initial file contents, keyed by absolute path. */
27
27
  files?: Record<string, string>;
28
28
  /** Scripted `exec` results, keyed by exact command line. */
@@ -62,7 +62,6 @@ interface MockBackendSeed {
62
62
  * await backend.exec("npm test"); // { exitCode: 1, stderr: "1 failing", ... }
63
63
  * await backend.exec("echo hi"); // { exitCode: 0, stdout: "", ... } (no-op)
64
64
  */
65
- declare function createMockBackend(seed?: Record<string, string> | MockBackendSeed): WorkspaceBackend;
65
+ export declare function createMockBackend(seed?: Record<string, string> | MockBackendSeed): WorkspaceBackend;
66
66
  //#endregion
67
- export { MockBackendSeed, MockExecResult, createMockBackend };
68
67
  //# sourceMappingURL=mock.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/backends/mock.ts"],"mappings":";;;;;AAmBA;;;;;;;;KAAY,cAAA,GAAiB,OAAO,CAAC,0BAAA;EAapB,yDAXf,OAAA;AAAA;;;;;;;;;UAWe,eAAA;EAIW;EAF1B,KAAA,GAAQ,MAAA;EAEgC;EAAxC,QAAA,GAAW,MAAA,SAAe,cAAA;AAAA;;;;;;;;;;;;AAuRT;;;;;;;;;;;;;;;;;;;;;;;iBAFH,iBAAA,CACd,IAAA,GAAO,MAAA,mBAAyB,eAAA,GAC/B,gBAAA"}
1
+ {"version":3,"file":"mock.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/backends/mock.ts"],"mappings":";;;;;;;;;;;;YAmBY,iBAAiB,QAAQ;;EAEnC;;;;;;;;;;iBAWe;;EAEf,QAAQ;;EAER,WAAW,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAqRZ,kBACd,OAAO,yBAAyB,kBAC/B"}
@@ -2,4 +2,5 @@ import { EditFileInput, EditFileResult, ExecOptions, GlobInput, GlobResult, Grep
2
2
  import { WorkspaceAllowCommand, WorkspaceBackendType, WorkspaceBackgroundPolicy, WorkspaceCommandCheckContext, WorkspaceCommandPattern, WorkspaceListPolicy, WorkspacePolicy, WorkspaceReadPolicy, WorkspaceShellInterpreter, WorkspaceShellPolicy } from "./workspace-policy.type.mjs";
3
3
  import { WorkspaceBackend, WorkspaceBackendExecOptions, WorkspaceBackendExecResult, WorkspaceBackendProcess, WorkspaceBackendStartOptions, WorkspaceBackendStat, WorkspaceOutputStream } from "./workspace-backend.contract.mjs";
4
4
  import { WorkspaceOps } from "./workspace-ops.contract.mjs";
5
- import { Workspace, WorkspaceTools } from "./workspace.contract.mjs";
5
+ import { Workspace, WorkspaceTools } from "./workspace.contract.mjs";
6
+ export type { EditFileInput, EditFileResult, ExecOptions, GlobInput, GlobResult, GrepInput, GrepMatch, GrepResult, ListDirEntry, ListDirEntryType, ListDirInput, ListDirOptions, ListDirResult, MultiEditInput, MultiEditOperation, MultiEditResult, ReadFileInput, ReadFileResult, RunShellBackgroundResult, RunShellInput, RunShellResult, RunTestsInput, ShellJobInfo, ShellJobStatus, ShellKillInput, ShellOutputInput, ShellOutputOptions, ShellOutputResult, StartShellOptions, TodoListResult, TodoWriteInput, Workspace, WorkspaceAllowCommand, WorkspaceBackend, WorkspaceBackendExecOptions, WorkspaceBackendExecResult, WorkspaceBackendProcess, WorkspaceBackendStartOptions, WorkspaceBackendStat, WorkspaceBackendType, WorkspaceBackgroundPolicy, WorkspaceCommandCheckContext, WorkspaceCommandPattern, WorkspaceListPolicy, WorkspaceOps, WorkspaceOutputStream, WorkspacePolicy, WorkspaceReadPolicy, WorkspaceShellInterpreter, WorkspaceShellPolicy, WorkspaceTodo, WorkspaceTodoInput, WorkspaceTodoStatus, WorkspaceToolName, WorkspaceTools, WriteFileInput, WriteFileResult };
@@ -10,9 +10,9 @@
10
10
  * The discriminating set of tool names a workspace can vend, used by
11
11
  * `tools.pick(...)` to select a least-privilege subset.
12
12
  */
13
- type WorkspaceToolName = "readFile" | "editFile" | "multiEdit" | "writeFile" | "listDir" | "runShell" | "shellOutput" | "shellKill" | "runTests" | "grep" | "glob" | "todoWrite" | "todoRead";
13
+ export type WorkspaceToolName = "readFile" | "editFile" | "multiEdit" | "writeFile" | "listDir" | "runShell" | "shellOutput" | "shellKill" | "runTests" | "grep" | "glob" | "todoWrite" | "todoRead";
14
14
  /** Input to the `read_file` tool / `readFile()` direct method. */
15
- interface ReadFileInput {
15
+ export interface ReadFileInput {
16
16
  /** Workspace-relative path to read. */
17
17
  path: string;
18
18
  /** 1-based line to start from. Omit to start at the top. */
@@ -32,7 +32,7 @@ interface ReadFileInput {
32
32
  * returned line may be partial — clipped on a character boundary, never
33
33
  * mid-character — and `bytesCapped` is `true`.
34
34
  */
35
- interface ReadFileResult {
35
+ export interface ReadFileResult {
36
36
  /** The returned, line-numbered file content for the requested window. */
37
37
  content: string;
38
38
  /** 1-based first line requested. */
@@ -65,7 +65,7 @@ interface ReadFileResult {
65
65
  * Input to the `edit_file` tool / `editFile()` direct method — an
66
66
  * exact-string replacement with a read-before-edit stale guard.
67
67
  */
68
- interface EditFileInput {
68
+ export interface EditFileInput {
69
69
  /** Workspace-relative path to edit. */
70
70
  path: string;
71
71
  /** Exact substring to replace; must be unique unless `replaceAll`. */
@@ -78,7 +78,7 @@ interface EditFileInput {
78
78
  expectHash?: string;
79
79
  }
80
80
  /** Result of a successful edit. */
81
- interface EditFileResult {
81
+ export interface EditFileResult {
82
82
  /** Workspace-relative path that was edited. */
83
83
  path: string;
84
84
  /** Number of occurrences replaced. */
@@ -87,7 +87,7 @@ interface EditFileResult {
87
87
  hash: string;
88
88
  }
89
89
  /** One replacement inside a {@link MultiEditInput}. */
90
- interface MultiEditOperation {
90
+ export interface MultiEditOperation {
91
91
  /** Exact substring to replace; must be unique unless `replaceAll`. */
92
92
  oldString: string;
93
93
  /** Replacement text. */
@@ -101,7 +101,7 @@ interface MultiEditOperation {
101
101
  * the result of the previous ones) and written once. All of them apply or
102
102
  * none do.
103
103
  */
104
- interface MultiEditInput {
104
+ export interface MultiEditInput {
105
105
  /** Workspace-relative path to edit. */
106
106
  path: string;
107
107
  /** The replacements, applied in order. At least one. */
@@ -110,7 +110,7 @@ interface MultiEditInput {
110
110
  expectHash?: string;
111
111
  }
112
112
  /** Result of a successful multi-edit. */
113
- interface MultiEditResult {
113
+ export interface MultiEditResult {
114
114
  /** Workspace-relative path that was edited. */
115
115
  path: string;
116
116
  /** Number of edits applied (always `edits.length`). */
@@ -121,14 +121,14 @@ interface MultiEditResult {
121
121
  hash: string;
122
122
  }
123
123
  /** Input to the `write_file` tool / `writeFile()` direct method. */
124
- interface WriteFileInput {
124
+ export interface WriteFileInput {
125
125
  /** Workspace-relative path to write (created if absent). */
126
126
  path: string;
127
127
  /** Full file content to write atomically. */
128
128
  content: string;
129
129
  }
130
130
  /** Result of a write. */
131
- interface WriteFileResult {
131
+ export interface WriteFileResult {
132
132
  /** Workspace-relative path that was written. */
133
133
  path: string;
134
134
  /** Number of bytes written. */
@@ -137,7 +137,7 @@ interface WriteFileResult {
137
137
  hash: string;
138
138
  }
139
139
  /** Options for the `exec()` direct method. */
140
- interface ExecOptions {
140
+ export interface ExecOptions {
141
141
  /** Per-call wall-clock cap in ms; overrides the policy `shell.timeoutMs`. */
142
142
  timeoutMs?: number;
143
143
  /**
@@ -148,7 +148,7 @@ interface ExecOptions {
148
148
  signal?: AbortSignal;
149
149
  }
150
150
  /** Input to the `run_shell` tool / `exec()` direct method. */
151
- interface RunShellInput {
151
+ export interface RunShellInput {
152
152
  /** The command line to run; every command in it is policy-checked. */
153
153
  command: string;
154
154
  /** Per-call wall-clock cap (overrides the policy default if lower). */
@@ -168,9 +168,9 @@ interface RunShellInput {
168
168
  * - `"timed-out"` — killed for exceeding `shell.background.timeoutMs` (or
169
169
  * the per-call `timeoutMs`).
170
170
  */
171
- type ShellJobStatus = "running" | "exited" | "killed" | "timed-out";
171
+ export type ShellJobStatus = "running" | "exited" | "killed" | "timed-out";
172
172
  /** A background command owned by the workspace. */
173
- interface ShellJobInfo {
173
+ export interface ShellJobInfo {
174
174
  /** Workspace-unique id (`"job-1"`, `"job-2"`, …) for `shell_output` / `shell_kill`. */
175
175
  id: string;
176
176
  /** The command line it runs. */
@@ -187,12 +187,12 @@ interface ShellJobInfo {
187
187
  endedAt?: string;
188
188
  }
189
189
  /** What `run_shell` returns when called with `background: true`. */
190
- interface RunShellBackgroundResult extends ShellJobInfo {
190
+ export interface RunShellBackgroundResult extends ShellJobInfo {
191
191
  /** Always `true` — tells this result apart from a foreground {@link RunShellResult}. */
192
192
  background: true;
193
193
  }
194
194
  /** Options for `startShell()`. */
195
- interface StartShellOptions {
195
+ export interface StartShellOptions {
196
196
  /**
197
197
  * Wall-clock cap for this background command, in ms. Lowers
198
198
  * `shell.background.timeoutMs` (never raises it); absent ⇒ the policy
@@ -201,7 +201,7 @@ interface StartShellOptions {
201
201
  timeoutMs?: number;
202
202
  }
203
203
  /** Input to the `shell_output` tool. */
204
- interface ShellOutputInput {
204
+ export interface ShellOutputInput {
205
205
  /** The background command id `run_shell` returned. */
206
206
  id: string;
207
207
  /**
@@ -216,7 +216,7 @@ interface ShellOutputInput {
216
216
  waitMs?: number;
217
217
  }
218
218
  /** Options for `shellOutput()`. */
219
- interface ShellOutputOptions {
219
+ export interface ShellOutputOptions {
220
220
  /** See {@link ShellOutputInput.since}. */
221
221
  since?: number;
222
222
  /** See {@link ShellOutputInput.waitMs}. */
@@ -225,7 +225,7 @@ interface ShellOutputOptions {
225
225
  signal?: AbortSignal;
226
226
  }
227
227
  /** New output of a background command plus its current state. */
228
- interface ShellOutputResult extends ShellJobInfo {
228
+ export interface ShellOutputResult extends ShellJobInfo {
229
229
  /** Standard output produced after `since`. */
230
230
  stdout: string;
231
231
  /** Standard error produced after `since`. */
@@ -244,12 +244,12 @@ interface ShellOutputResult extends ShellJobInfo {
244
244
  truncated: boolean;
245
245
  }
246
246
  /** Input to the `shell_kill` tool. */
247
- interface ShellKillInput {
247
+ export interface ShellKillInput {
248
248
  /** The background command id to stop. */
249
249
  id: string;
250
250
  }
251
251
  /** Result of a shell command — captured streams plus how it terminated. */
252
- interface RunShellResult {
252
+ export interface RunShellResult {
253
253
  /** Process exit code (non-zero ⇒ the command failed, not the tool). */
254
254
  exitCode: number;
255
255
  /** Captured standard output (byte-capped per policy). */
@@ -272,12 +272,12 @@ interface RunShellResult {
272
272
  * the workspace's configured test command, optionally narrowed to a
273
273
  * pattern (a path/suite filter passed through to the runner).
274
274
  */
275
- interface RunTestsInput {
275
+ export interface RunTestsInput {
276
276
  /** Optional path/name filter forwarded to the test runner. */
277
277
  pattern?: string;
278
278
  }
279
279
  /** Input to the `grep` tool / `grep()` direct method. */
280
- interface GrepInput {
280
+ export interface GrepInput {
281
281
  /** Regular-expression pattern to search file contents for. */
282
282
  pattern: string;
283
283
  /** Glob narrowing which files are scanned (e.g. `"src/api/*.ts"`). */
@@ -286,7 +286,7 @@ interface GrepInput {
286
286
  ignoreCase?: boolean;
287
287
  }
288
288
  /** A single matched line within a file. */
289
- interface GrepMatch {
289
+ export interface GrepMatch {
290
290
  /** Workspace-relative path of the file containing the match. */
291
291
  path: string;
292
292
  /** 1-based line number of the match. */
@@ -295,14 +295,14 @@ interface GrepMatch {
295
295
  text: string;
296
296
  }
297
297
  /** Result of a content search across the jailed file set. */
298
- interface GrepResult {
298
+ export interface GrepResult {
299
299
  /** Every matching line found, in file/line order. */
300
300
  matches: GrepMatch[];
301
301
  /** Total number of matches (== `matches.length` unless capped). */
302
302
  total: number;
303
303
  }
304
304
  /** Input to the `list_dir` tool. */
305
- interface ListDirInput {
305
+ export interface ListDirInput {
306
306
  /** Workspace-relative directory to list. Default `"."` (the root). */
307
307
  path?: string;
308
308
  /** How many levels deep to list (1 = direct children). Default `1`. */
@@ -311,16 +311,16 @@ interface ListDirInput {
311
311
  ignore?: string[];
312
312
  }
313
313
  /** Options for `listDir()`. */
314
- interface ListDirOptions {
314
+ export interface ListDirOptions {
315
315
  /** See {@link ListDirInput.depth}. */
316
316
  depth?: number;
317
317
  /** See {@link ListDirInput.ignore}. */
318
318
  ignore?: string[];
319
319
  }
320
320
  /** Kind of a listed entry. A symlink is reported, never followed. */
321
- type ListDirEntryType = "file" | "directory" | "symlink" | "other";
321
+ export type ListDirEntryType = "file" | "directory" | "symlink" | "other";
322
322
  /** One entry of a directory listing. */
323
- interface ListDirEntry {
323
+ export interface ListDirEntry {
324
324
  /** Workspace-relative path (absolute for a directory outside `cwd` in `allowPaths`). */
325
325
  path: string;
326
326
  /** Entry kind. */
@@ -331,7 +331,7 @@ interface ListDirEntry {
331
331
  depth: number;
332
332
  }
333
333
  /** Result of a directory listing. */
334
- interface ListDirResult {
334
+ export interface ListDirResult {
335
335
  /** The listed directory, workspace-relative (`"."` for the root). */
336
336
  path: string;
337
337
  /** Entries, breadth-first, each level sorted by name. */
@@ -340,19 +340,19 @@ interface ListDirResult {
340
340
  truncated: boolean;
341
341
  }
342
342
  /** Input to the `glob` tool / `glob()` direct method. */
343
- interface GlobInput {
343
+ export interface GlobInput {
344
344
  /** Glob pattern resolved relative to `cwd` (e.g. `"src/api/*.ts"`). */
345
345
  pattern: string;
346
346
  }
347
347
  /** Result of a glob — the matched workspace-relative paths. */
348
- interface GlobResult {
348
+ export interface GlobResult {
349
349
  /** Workspace-relative paths matching the pattern, sorted. */
350
350
  paths: string[];
351
351
  }
352
352
  /** State of one todo item. */
353
- type WorkspaceTodoStatus = "pending" | "in_progress" | "completed";
353
+ export type WorkspaceTodoStatus = "pending" | "in_progress" | "completed";
354
354
  /** One item of the workspace's todo list. */
355
- interface WorkspaceTodo {
355
+ export interface WorkspaceTodo {
356
356
  /** Stable id (assigned `"1"`, `"2"`, … when the writer omits it). */
357
357
  id: string;
358
358
  /** What the step is. */
@@ -361,7 +361,7 @@ interface WorkspaceTodo {
361
361
  status: WorkspaceTodoStatus;
362
362
  }
363
363
  /** One item as written by `todo_write` / `writeTodos()` — `id` is optional. */
364
- interface WorkspaceTodoInput {
364
+ export interface WorkspaceTodoInput {
365
365
  /** Stable id; omit to have one assigned from the item's position. */
366
366
  id?: string;
367
367
  /** What the step is (non-empty, at most 1 000 characters). */
@@ -370,17 +370,16 @@ interface WorkspaceTodoInput {
370
370
  status: WorkspaceTodoStatus;
371
371
  }
372
372
  /** Input to the `todo_write` tool — the whole new list, replacing the old one. */
373
- interface TodoWriteInput {
373
+ export interface TodoWriteInput {
374
374
  /** The complete list (at most 100 items). An empty list clears it. */
375
375
  todos: WorkspaceTodoInput[];
376
376
  }
377
377
  /** The todo list plus a count per status. */
378
- interface TodoListResult {
378
+ export interface TodoListResult {
379
379
  /** Every item, in order. */
380
380
  todos: WorkspaceTodo[];
381
381
  /** How many items are in each status. */
382
382
  counts: Record<WorkspaceTodoStatus, number>;
383
383
  }
384
384
  //#endregion
385
- export { EditFileInput, EditFileResult, ExecOptions, GlobInput, GlobResult, GrepInput, GrepMatch, GrepResult, ListDirEntry, ListDirEntryType, ListDirInput, ListDirOptions, ListDirResult, MultiEditInput, MultiEditOperation, MultiEditResult, ReadFileInput, ReadFileResult, RunShellBackgroundResult, RunShellInput, RunShellResult, RunTestsInput, ShellJobInfo, ShellJobStatus, ShellKillInput, ShellOutputInput, ShellOutputOptions, ShellOutputResult, StartShellOptions, TodoListResult, TodoWriteInput, WorkspaceTodo, WorkspaceTodoInput, WorkspaceTodoStatus, WorkspaceToolName, WriteFileInput, WriteFileResult };
386
385
  //# sourceMappingURL=tool-io.type.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-io.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/tool-io.type.ts"],"mappings":"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"}
1
+ {"version":3,"file":"tool-io.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/tool-io.type.ts"],"mappings":";;;;;;;;;;;;YAYY;;iBAgBK;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;;iBAee;;EAEf;;EAEA;;;;;EAKA;;EAEA;;;;;;EAMA;;EAEA;;EAEA;;;;;;EAMA;;;;;;iBAOe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;;;;;;;iBASe;;EAEf;;EAEA,OAAO;;EAEP;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf;;;;;;EAMA,SAAS;;;iBAIM;;EAEf;;EAEA;;;;;EAKA;;;;;;;;;;;YAYU;;iBAGK;;EAEf;;EAEA;;EAEA,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;;;iBAIe,iCAAiC;;EAEhD;;;iBAIe;;;;;;EAMf;;;iBAIe;;EAEf;;;;;EAKA;;;;;EAKA;;;iBAIe;;EAEf;;EAEA;;EAEA,SAAS;;;iBAIM,0BAA0B;;EAEzC;;EAEA;;;;;EAKA;;;;;EAKA;;EAEA;;;iBAIe;;EAEf;;;iBAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;EAMA;;;;;;;iBAQe;;EAEf;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf,SAAS;;EAET;;;iBAIe;;EAEf;;EAEA;;EAEA;;;iBAIe;;EAEf;;EAEA;;;YAIU;;iBAGK;;EAEf;;EAEA,MAAM;;EAEN;;EAEA;;;iBAIe;;EAEf;;EAEA,SAAS;;EAET;;;iBAIe;;EAEf;;;iBAIe;;EAEf;;;YAIU;;iBAGK;;EAEf;;EAEA;;EAEA,QAAQ;;;iBAIO;;EAEf;;EAEA;;EAEA,QAAQ;;;iBAIO;;EAEf,OAAO;;;iBAIQ;;EAEf,OAAO;;EAEP,QAAQ,OAAO"}
@@ -1,5 +1,4 @@
1
1
  import { WorkspaceShellInterpreter } from "./workspace-policy.type.mjs";
2
-
3
2
  //#region ../../packages/ai-workspace/src/contracts/workspace-backend.contract.d.ts
4
3
  /**
5
4
  * Low-level result of a backend command execution. Mirrors the shape the
@@ -7,7 +6,7 @@ import { WorkspaceShellInterpreter } from "./workspace-policy.type.mjs";
7
6
  * byte-cap `truncated` flag (capping is an ops-layer concern, not the
8
7
  * raw executor's).
9
8
  */
10
- interface WorkspaceBackendExecResult {
9
+ export interface WorkspaceBackendExecResult {
11
10
  /** Process exit code. */
12
11
  exitCode: number;
13
12
  /** Captured standard output. */
@@ -29,7 +28,7 @@ interface WorkspaceBackendExecResult {
29
28
  * the effective environment and timeout from policy before handing them
30
29
  * down — the backend just runs what it is told.
31
30
  */
32
- interface WorkspaceBackendExecOptions {
31
+ export interface WorkspaceBackendExecOptions {
33
32
  /** Working directory for the spawned process (already jail-resolved). */
34
33
  cwd?: string;
35
34
  /** Wall-clock cap in ms; on expiry the process is SIGKILLed. */
@@ -56,9 +55,9 @@ interface WorkspaceBackendExecOptions {
56
55
  interpreter?: WorkspaceShellInterpreter;
57
56
  }
58
57
  /** Which stream a background command's output chunk came from. */
59
- type WorkspaceOutputStream = "stdout" | "stderr";
58
+ export type WorkspaceOutputStream = "stdout" | "stderr";
60
59
  /** Options for {@link WorkspaceBackend.start}. */
61
- interface WorkspaceBackendStartOptions {
60
+ export interface WorkspaceBackendStartOptions {
62
61
  /** Working directory for the spawned process (already jail-resolved). */
63
62
  cwd?: string;
64
63
  /** Exact environment for the spawned process (NOT merged with process.env). */
@@ -72,7 +71,7 @@ interface WorkspaceBackendStartOptions {
72
71
  onOutput(stream: WorkspaceOutputStream, chunk: string): void;
73
72
  }
74
73
  /** A running background command, as a backend hands it to the ops layer. */
75
- interface WorkspaceBackendProcess {
74
+ export interface WorkspaceBackendProcess {
76
75
  /** OS process id of the direct child, when there is one. */
77
76
  readonly pid?: number;
78
77
  /**
@@ -86,7 +85,7 @@ interface WorkspaceBackendProcess {
86
85
  kill(): void;
87
86
  }
88
87
  /** What {@link WorkspaceBackend.stat} reports about a path (symlinks are not followed). */
89
- interface WorkspaceBackendStat {
88
+ export interface WorkspaceBackendStat {
90
89
  /** Entry kind. */
91
90
  type: "file" | "directory" | "symlink" | "other";
92
91
  /** Size in bytes. */
@@ -110,7 +109,7 @@ interface WorkspaceBackendStat {
110
109
  * const out = await backend.readFile("/srv/acme-api/src/index.ts");
111
110
  * const { exitCode } = await backend.exec("npm test", { cwd: "/srv/acme-api" });
112
111
  */
113
- interface WorkspaceBackend {
112
+ export interface WorkspaceBackend {
114
113
  /** Read a file's full UTF-8 content at an absolute path. */
115
114
  readFile(absPath: string): Promise<string>;
116
115
  /** Write full content to an absolute path (atomically where supported). */
@@ -141,5 +140,4 @@ interface WorkspaceBackend {
141
140
  start?(command: string, opts: WorkspaceBackendStartOptions): WorkspaceBackendProcess;
142
141
  }
143
142
  //#endregion
144
- export { WorkspaceBackend, WorkspaceBackendExecOptions, WorkspaceBackendExecResult, WorkspaceBackendProcess, WorkspaceBackendStartOptions, WorkspaceBackendStat, WorkspaceOutputStream };
145
143
  //# sourceMappingURL=workspace-backend.contract.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-backend.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/workspace-backend.contract.ts"],"mappings":";;;;;AAQA;;;;UAAiB,0BAAA;EAIf;EAFA,QAAA;EAMA;EAJA,MAAA;EAWS;EATT,MAAA;EAiBe;EAff,QAAA;;;;;;;EAOA,SAAA;AAAA;;;;;;UAQe,2BAAA;EAwBwB;EAtBvC,GAAA;EA0BU;EAxBV,SAAA;;EAEA,GAAA,GAAM,MAAA;EAsByB;AAGjC;;;;;;;EAhBE,MAAA,GAAS,WAAA;EAkBT;;;;;;;;EATA,WAAA,GAAc,yBAAA;AAAA;AAkBuC;AAAA,KAd3C,qBAAA;;UAGK,4BAAA;EAsBO;EApBtB,GAAA;EAoBS;EAlBT,GAAA,GAAM,MAAA;EAkBmB;EAhBzB,WAAA,GAAc,yBAAA;EAkBV;AAAA;AAIN;;EAjBE,QAAA,CAAS,MAAA,EAAQ,qBAAA,EAAuB,KAAA;AAAA;AAqBpC;AAAA,UAjBW,uBAAA;EAsCgB;EAAA,SApCtB,GAAA;EAsCkB;;;;EAAA,SAjClB,IAAA,EAAM,OAAO;IAAG,QAAA;EAAA;EA+CI;EA7C7B,IAAA;AAAA;;UAIe,oBAAA;EAqDe;EAnD9B,IAAA;EAmDoF;EAjDpF,IAAI;AAAA;;;;;;;;;;;;;;;;;;;UAqBW,gBAAA;EAcN;EAZT,QAAA,CAAS,OAAA,WAAkB,OAAA;EAc3B;EAZA,SAAA,CAAU,OAAA,UAAiB,OAAA,WAAkB,OAAA;EAYhB;EAV7B,MAAA,CAAO,OAAA,WAAkB,OAAA;EAUkC;EAR3D,KAAA,CAAM,OAAA,WAAkB,OAAA;EAcxB;EAZA,MAAA,CAAO,OAAA,WAAkB,OAAA;EAYD;EAVxB,IAAA,CAAK,MAAA,WAAiB,OAAA;EAgBtB;EAdA,QAAA,CAAS,OAAA,WAAkB,OAAA;EAcG;EAZ9B,IAAA,CAAK,OAAA,UAAiB,IAAA,GAAO,2BAAA,GAA8B,OAAA,CAAQ,0BAAA;EAYN;;AAAuB;;;EANpF,IAAA,EAAM,OAAA,WAAkB,OAAA,CAAQ,oBAAA;;;;;;EAMhC,KAAA,EAAO,OAAA,UAAiB,IAAA,EAAM,4BAAA,GAA+B,uBAAA;AAAA"}
1
+ {"version":3,"file":"workspace-backend.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/workspace-backend.contract.ts"],"mappings":";;;;;;;;iBAQiB;;EAEf;;EAEA;;EAEA;;EAEA;;;;;;;EAOA;;;;;;;iBAQe;;EAEf;;EAEA;;EAEA,MAAM;;;;;;;;;EASN,SAAS;;;;;;;;;EAST,cAAc;;;YAIJ;;iBAGK;;EAEf;;EAEA,MAAM;;EAEN,cAAc;;;;;EAKd,SAAS,QAAQ,uBAAuB;;;iBAIzB;;WAEN;;;;;WAKA,MAAM;IAAU;;;EAEzB;;;iBAIe;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;;iBAqBe;;EAEf,SAAS,kBAAkB;;EAE3B,UAAU,iBAAiB,kBAAkB;;EAE7C,OAAO,kBAAkB;;EAEzB,MAAM,kBAAkB;;EAExB,OAAO,kBAAkB;;EAEzB,KAAK,iBAAiB;;EAEtB,SAAS,kBAAkB;;EAE3B,KAAK,iBAAiB,OAAO,8BAA8B,QAAQ;;;;;;EAMnE,MAAM,kBAAkB,QAAQ;;;;;;EAMhC,OAAO,iBAAiB,MAAM,+BAA+B"}
@@ -1,5 +1,4 @@
1
1
  import { EditFileInput, EditFileResult, ExecOptions, GrepResult, ListDirOptions, ListDirResult, MultiEditInput, MultiEditResult, ReadFileResult, RunShellResult, ShellJobInfo, ShellOutputOptions, ShellOutputResult, StartShellOptions } from "./tool-io.type.mjs";
2
-
3
2
  //#region ../../packages/ai-workspace/src/contracts/workspace-ops.contract.d.ts
4
3
  /**
5
4
  * The **policy-enforced** operation layer that sits between the dumb
@@ -20,7 +19,7 @@ import { EditFileInput, EditFileResult, ExecOptions, GrepResult, ListDirOptions,
20
19
  * const { content, hash } = await ops.readFile("src/index.ts");
21
20
  * await ops.editFile({ path: "src/index.ts", oldString: "x", newString: "y", expectHash: hash });
22
21
  */
23
- interface WorkspaceOps {
22
+ export interface WorkspaceOps {
24
23
  /**
25
24
  * Read a jailed file, returning a numbered line window, the full file's
26
25
  * hash and the window metadata. `offset`/`limit` select a 1-based line
@@ -93,5 +92,4 @@ interface WorkspaceOps {
93
92
  dispose(): Promise<void>;
94
93
  }
95
94
  //#endregion
96
- export { WorkspaceOps };
97
95
  //# sourceMappingURL=workspace-ops.contract.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-ops.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/workspace-ops.contract.ts"],"mappings":";;;;;AAoCA;;;;;;;;;;;;;;;;;UAAiB,YAAA;EA0CgB;;;;;;EAnC/B,QAAA,CAAS,IAAA,UAAc,IAAA;IAAS,MAAA;IAAiB,KAAA;EAAA,IAAmB,OAAA,CAAQ,cAAA;EAkDL;EA/CvE,SAAA,CAAU,IAAA,UAAc,OAAA,WAAkB,OAAA;IAAU,IAAA;IAAc,YAAA;EAAA;EAkEvD;;;;;EA3DX,QAAA,CAAS,KAAA,EAAO,aAAA,GAAgB,OAAA,CAAQ,cAAA;EAVS;;;;;;EAkBjD,SAAA,CAAU,KAAA,EAAO,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAfA;;;;;;EAuB1C,IAAA,CAAK,OAAA,UAAiB,IAAA,GAAO,WAAA,GAAc,OAAA,CAAQ,cAAA;EAhBX;;;;EAsBxC,UAAA,CAAW,OAAA,UAAiB,IAAA,GAAO,iBAAA,GAAoB,OAAA,CAAQ,YAAA;EAdrB;EAiB1C,WAAA,CAAY,EAAA,UAAY,IAAA,GAAO,kBAAA,GAAqB,OAAA,CAAQ,iBAAA;EATvD;EAYL,SAAA,CAAU,EAAA,WAAa,OAAA,CAAQ,YAAA;EAZT;EAetB,UAAA,IAAc,YAAA;EAfqC;;;;EAqBnD,OAAA,CAAQ,IAAA,WAAe,IAAA,GAAO,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAfA;EAkBvD,IAAA,CAAK,OAAA,UAAiB,IAAA;IAAS,IAAA;IAAe,UAAA;EAAA,IAAyB,OAAA,CAAQ,UAAA;EAfvD;EAkBxB,IAAA,CAAK,OAAA,WAAkB,OAAA;EAlBqC;EAqB5D,MAAA,CAAO,IAAA,WAAe,OAAA;EAlBZ;EAqBV,KAAA,CAAM,IAAA,WAAe,OAAA;EArBU;EAwB/B,MAAA,CAAO,IAAA,WAAe,OAAA;EArBR;;;;;EA4Bd,OAAA,IAAW,OAAA;AAAA"}
1
+ {"version":3,"file":"workspace-ops.contract.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/contracts/workspace-ops.contract.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAoCiB;;;;;;;EAOf,SAAS,cAAc;IAAS;IAAiB;MAAmB,QAAQ;;EAG5E,UAAU,cAAc,kBAAkB;IAAU;IAAc;;;;;;;EAOlE,SAAS,OAAO,gBAAgB,QAAQ;;;;;;;EAQxC,UAAU,OAAO,iBAAiB,QAAQ;;;;;;;EAQ1C,KAAK,iBAAiB,OAAO,cAAc,QAAQ;;;;;EAMnD,WAAW,iBAAiB,OAAO,oBAAoB,QAAQ;;EAG/D,YAAY,YAAY,OAAO,qBAAqB,QAAQ;;EAG5D,UAAU,aAAa,QAAQ;;EAG/B,cAAc;;;;;EAMd,QAAQ,eAAe,OAAO,iBAAiB,QAAQ;;EAGvD,KAAK,iBAAiB;IAAS;IAAe;MAAyB,QAAQ;;EAG/E,KAAK,kBAAkB;;EAGvB,OAAO,eAAe;;EAGtB,MAAM,eAAe;;EAGrB,OAAO,eAAe;;;;;;EAOtB,WAAW"}