@wrongstack/tools 0.5.6 → 0.5.7
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/bash.d.ts +2 -1
- package/dist/bash.js +361 -3
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +389 -5
- package/dist/builtin.js.map +1 -1
- package/dist/circuit-breaker.d.ts +111 -0
- package/dist/circuit-breaker.js +150 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/exec.js +346 -2
- package/dist/exec.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +393 -6
- package/dist/index.js.map +1 -1
- package/dist/pack.js +389 -5
- package/dist/pack.js.map +1 -1
- package/dist/process-registry.d.ts +112 -0
- package/dist/process-registry.js +327 -0
- package/dist/process-registry.js.map +1 -0
- package/package.json +10 -2
package/dist/exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/exec.ts"],"names":["resolve"],"mappings":"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","file":"exec.js","sourcesContent":["import { spawn } from 'node:child_process';\r\nimport * as path from 'node:path';\r\nimport type { Tool } from '@wrongstack/core';\r\nimport { buildChildEnv } from './_env.js';\r\n\r\nconst ALLOWED_COMMANDS: Record<string, string[]> = {\r\n node: ['--version', '-r', '--input-type=module'],\r\n npm: ['--version', 'list', 'pkg', 'doctor', 'view', 'outdated', 'audit'],\r\n pnpm: ['--version', 'remove', 'list', 'view', 'outdated', 'audit'],\r\n npx: ['--version'],\r\n git: [\r\n '--version',\r\n 'status',\r\n 'log',\r\n 'diff',\r\n 'branch',\r\n 'checkout',\r\n 'stash',\r\n 'add',\r\n 'commit',\r\n 'push',\r\n 'pull',\r\n ],\r\n ls: ['-la', '-l', '-a'],\r\n cat: [],\r\n head: ['-n'],\r\n tail: ['-n'],\r\n wc: ['-l', '-w', '-c'],\r\n grep: [],\r\n find: [],\r\n echo: [],\r\n mkdir: ['-p'],\r\n cp: ['-r'],\r\n mv: [],\r\n rm: ['-rf'],\r\n touch: [],\r\n bun: ['--version'],\r\n tsc: ['--version', '--noEmit', '--project'],\r\n vitest: ['--version', 'run', '--coverage'],\r\n biome: ['--version', 'lint', 'format', 'check'],\r\n cargo: ['--version', 'build', 'test', 'check'],\r\n rustc: ['--version'],\r\n go: ['version', 'run', 'build', 'test'],\r\n python: ['--version'],\r\n pip: ['--version', 'list'],\r\n docker: ['--version', 'ps', 'images'],\r\n kubectl: ['version', 'get', 'describe', 'logs'],\r\n};\r\n\r\nconst MAX_ARGS = 20;\r\nconst MAX_OUTPUT = 200_000;\r\nconst TIMEOUT_MS = 30_000;\r\n\r\n// Per-command argument validation. Each entry is a list of regex patterns\r\n// that, if matched against any argument, will reject the invocation.\r\n// This blocks common injection vectors through allowlisted commands.\r\nconst BLOCKED_ARG_PATTERNS: Record<string, RegExp[]> = {\r\n // python -c/--command executes arbitrary code; python -m runs modules\r\n python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],\r\n // git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack;\r\n // -C <dir> changes working directory, bypassing cwd sandbox\r\n git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/, /^-C$/],\r\n // node -r/--require preloads arbitrary modules; --eval executes code\r\n node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],\r\n // go run could execute arbitrary .go files; -ldflags could inject build-time code\r\n go: [/^-ldflags$/],\r\n // bun --preload is similar to node --require\r\n bun: [/^--preload$/],\r\n // docker build/run can create containers with host access;\r\n // only allow read-only commands (ps, images, version)\r\n docker: [/^build$/, /^run$/, /^exec$/, /^push$/, /^pull$/],\r\n // find -exec/-ok/-execdir execute arbitrary commands\r\n find: [/^-exec$/, /^-exec;$/, /^-ok$/, /^-ok;$/, /^-execdir$/, /^-execdir;$/, /^-exec=/, /^-ok=/, /^-execdir=/],\r\n // rm -rf / is catastrophic — block root and home targets\r\n rm: [/^\\/$/, /^\\/\\*$/, /^~$/],\r\n};\r\n\r\nfunction validateArgs(cmd: string, args: string[]): string | null {\r\n const blocked = BLOCKED_ARG_PATTERNS[cmd];\r\n if (!blocked) return null;\r\n\r\n for (const arg of args) {\r\n for (const pattern of blocked) {\r\n if (pattern.test(arg)) {\r\n return `Blocked argument \"${arg}\" for command \"${cmd}\" (matches security pattern ${pattern})`;\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\ninterface ExecInput {\r\n command: string;\r\n args?: string[];\r\n cwd?: string;\r\n timeout?: number;\r\n}\r\n\r\ninterface ExecOutput {\r\n command: string;\r\n args: string[];\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number;\r\n truncated: boolean;\r\n allowed: boolean;\r\n}\r\n\r\nexport const execTool: Tool<ExecInput, ExecOutput> = {\r\n name: 'exec',\r\n category: 'Shell',\r\n description:\r\n 'Restricted shell that only runs pre-approved commands with constrained arguments. Safer alternative to `bash`.',\r\n usageHint:\r\n 'Set `command` (must be in allowlist). `args` passed through. For arbitrary shell access use the `bash` tool instead.',\r\n permission: 'confirm',\r\n mutating: true,\r\n timeoutMs: TIMEOUT_MS,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n command: { type: 'string', description: 'Command to run (must be in allowlist)' },\r\n args: { type: 'array', items: { type: 'string' }, description: 'Arguments' },\r\n cwd: { type: 'string', description: 'Working directory (must resolve inside project root)' },\r\n timeout: { type: 'integer', description: 'Timeout in ms (default: 30000)' },\r\n },\r\n required: ['command'],\r\n },\r\n async execute(input, ctx, opts) {\r\n const cmd = input.command.trim();\r\n if (!cmd)\r\n return {\r\n command: cmd,\r\n args: [],\r\n stdout: '',\r\n stderr: 'Empty command',\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n\r\n if (!(cmd in ALLOWED_COMMANDS)) {\r\n return {\r\n command: cmd,\r\n args: input.args ?? [],\r\n stdout: '',\r\n stderr: `Command \"${cmd}\" not in allowlist. Use the bash tool for arbitrary commands.`,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n }\r\n\r\n const args = (input.args ?? []).slice(0, MAX_ARGS);\r\n const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));\r\n\r\n // Validate args against per-command security patterns\r\n const argError = validateArgs(cmd, args);\r\n if (argError) {\r\n return {\r\n command: cmd,\r\n args,\r\n stdout: '',\r\n stderr: argError,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n }\r\n\r\n // Resolve cwd inside the project root. Model-supplied paths like '/etc'\r\n // would otherwise let allowlisted commands operate anywhere on disk.\r\n const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;\r\n const rel = path.relative(ctx.projectRoot, requestedCwd);\r\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\r\n return {\r\n command: cmd,\r\n args,\r\n stdout: '',\r\n stderr: `cwd \"${input.cwd}\" resolves outside project root`,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n }\r\n const cwd = requestedCwd;\r\n const signal = opts.signal;\r\n\r\n return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id);\r\n },\r\n};\r\n\r\nfunction runCommand(\r\n cmd: string,\r\n args: string[],\r\n cwd: string,\r\n timeout: number,\r\n signal: AbortSignal,\r\n sessionId: string | undefined,\r\n): Promise<ExecOutput> {\r\n return new Promise((resolve) => {\r\n let stdout = '';\r\n let stderr = '';\r\n let killed = false;\r\n\r\n const child = spawn(cmd, args, {\r\n cwd,\r\n signal,\r\n env: buildChildEnv(sessionId),\r\n stdio: ['ignore', 'pipe', 'pipe'],\r\n });\r\n const timer = setTimeout(() => {\r\n killed = true;\r\n child.kill('SIGTERM');\r\n }, timeout);\r\n\r\n child.stdout?.on('data', (chunk: Buffer) => {\r\n if (stdout.length < MAX_OUTPUT) stdout += chunk.toString();\r\n });\r\n\r\n child.stderr?.on('data', (chunk: Buffer) => {\r\n if (stderr.length < MAX_OUTPUT) stderr += chunk.toString();\r\n });\r\n\r\n child.on('close', (code) => {\r\n clearTimeout(timer);\r\n resolve({\r\n command: cmd,\r\n args,\r\n stdout: stdout.slice(0, MAX_OUTPUT),\r\n stderr: stderr.slice(0, MAX_OUTPUT),\r\n exitCode: killed ? 124 : (code ?? 1),\r\n truncated: stdout.length >= MAX_OUTPUT || stderr.length >= MAX_OUTPUT,\r\n allowed: true,\r\n });\r\n });\r\n\r\n child.on('error', (err) => {\r\n clearTimeout(timer);\r\n resolve({\r\n command: cmd,\r\n args,\r\n stdout: stdout.slice(0, MAX_OUTPUT),\r\n stderr: err.message,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: true,\r\n });\r\n });\r\n });\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/circuit-breaker.ts","../src/process-registry.ts","../src/exec.ts"],"names":["resolve"],"mappings":"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","file":"exec.js","sourcesContent":["/**\n * CircuitBreaker — 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 — 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;\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;\n /**\n * Max slow calls before trip (within the sliding window). Default: 3.\n */\n maxSlowCalls?: number;\n /**\n * Sliding window for rate-limit and slow-call counting, in ms.\n * Default: 60_000 (1 minute).\n */\n windowMs?: number;\n /**\n * Max calls within the sliding window. Default: 30.\n * Burst exceeding this trips the breaker immediately.\n */\n maxCallsPerWindow?: number;\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;\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 = 60_000;\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 /** Timestamp when the last call ran (for half-open gate). */\n private lastCallAt: number | null = null;\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 /**\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 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 beforeCall(): boolean {\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 afterCall(durationMs: number, failed: boolean): void {\n const now = Date.now();\n this.lastCallAt = now;\n\n if (this.state === 'half-open') {\n // First call through after cooldown — if it failed, go back to open.\n if (failed) {\n this._trip();\n return;\n }\n // Success in half-open → 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 — 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 }\n\n private _reset(): void {\n this.state = 'closed';\n this.consecutiveFailures = 0;\n this.window = [];\n this.openedAt = null;\n }\n\n /** Transition from open → 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}","/**\n * ProcessRegistry — 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 type { ChildProcess } from 'node:child_process';\nimport * as os from 'node:os';\nimport { CircuitBreaker, type CircuitBreakerSnapshot, type CircuitBreakerConfig } from './circuit-breaker.js';\n\nexport { type CircuitBreakerSnapshot, type CircuitBreakerConfig } from './circuit-breaker.js';\n\nexport interface TrackedProcess {\n pid: number;\n name: string;\n command: string;\n startedAt: number;\n sessionId?: string;\n /** The raw ChildProcess handle. Never call .kill() directly on this —\n * use `kill()` below which handles process groups correctly on POSIX\n * and degrades gracefully on Windows. */\n child: ChildProcess;\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}\n\ninterface KillOpts {\n /** SIGKILL instead of SIGTERM. Default: false (SIGTERM first). */\n force?: boolean;\n /** MS to wait between SIGTERM and SIGKILL on POSIX. Default: 2000. */\n graceMs?: number;\n}\n\nexport interface RegistryStats {\n activeCount: number;\n totalCount: number;\n breaker: CircuitBreakerSnapshot;\n}\n\nconst DEFAULT_GRACE_MS = 2000;\n\nclass ProcessRegistryImpl {\n private readonly processes = new Map<number, TrackedProcess>();\n private readonly breaker: CircuitBreaker;\n\n constructor(breakerConfig?: CircuitBreakerConfig) {\n this.breaker = new CircuitBreaker(breakerConfig);\n }\n\n register(info: Omit<TrackedProcess, 'killed'>): void {\n this.processes.set(info.pid, { ...info, killed: false });\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 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 — 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 beforeCall(): boolean {\n return this.breaker.beforeCall();\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 afterCall(durationMs: number, failed: boolean): void {\n this.breaker.afterCall(durationMs, failed);\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 /** 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 — process groups\n * are not meaningfully supported. A second `force=true` call sends\n * SIGKILL (which maps to TerminateProcess again — 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 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\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; just kill the process.\n try {\n p.child.kill(force ? 'SIGKILL' : 'SIGTERM');\n } catch {\n // Process may have already exited.\n }\n p.killed = true;\n return true;\n }\n\n // POSIX: kill the process group so grandchildren are cleaned up too.\n try {\n if (force) {\n try {\n process.kill(-pid, 'SIGKILL');\n } catch {\n p.child.kill('SIGKILL');\n }\n } else {\n try {\n process.kill(-pid, 'SIGTERM');\n } catch {\n p.child.kill('SIGTERM');\n }\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 try {\n process.kill(-pid, 'SIGKILL');\n } catch {\n try {\n p.child.kill('SIGKILL');\n } catch {\n /* already gone */\n }\n }\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 if (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/** 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// ── Convenience re-exports ────────────────────────────────────────────────────\n\nexport type { KillOpts };","import { spawn } from 'node:child_process';\nimport * as path from 'node:path';\nimport type { Tool } from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\nimport { getProcessRegistry } from './process-registry.js';\n\nconst ALLOWED_COMMANDS: Record<string, string[]> = {\n node: ['--version', '-r', '--input-type=module'],\n npm: ['--version', 'list', 'pkg', 'doctor', 'view', 'outdated', 'audit'],\n pnpm: ['--version', 'remove', 'list', 'view', 'outdated', 'audit'],\n npx: ['--version'],\n git: [\n '--version',\n 'status',\n 'log',\n 'diff',\n 'branch',\n 'checkout',\n 'stash',\n 'add',\n 'commit',\n 'push',\n 'pull',\n ],\n ls: ['-la', '-l', '-a'],\n cat: [],\n head: ['-n'],\n tail: ['-n'],\n wc: ['-l', '-w', '-c'],\n grep: [],\n find: [],\n echo: [],\n mkdir: ['-p'],\n cp: ['-r'],\n mv: [],\n rm: ['-rf'],\n touch: [],\n bun: ['--version'],\n tsc: ['--version', '--noEmit', '--project'],\n vitest: ['--version', 'run', '--coverage'],\n biome: ['--version', 'lint', 'format', 'check'],\n cargo: ['--version', 'build', 'test', 'check'],\n rustc: ['--version'],\n go: ['version', 'run', 'build', 'test'],\n python: ['--version'],\n pip: ['--version', 'list'],\n docker: ['--version', 'ps', 'images'],\n kubectl: ['version', 'get', 'describe', 'logs'],\n};\n\nconst MAX_ARGS = 20;\nconst MAX_OUTPUT = 200_000;\nconst TIMEOUT_MS = 30_000;\n\n// Per-command argument validation. Each entry is a list of regex patterns\n// that, if matched against any argument, will reject the invocation.\n// This blocks common injection vectors through allowlisted commands.\nconst BLOCKED_ARG_PATTERNS: Record<string, RegExp[]> = {\n // python -c/--command executes arbitrary code; python -m runs modules\n python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],\n // git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack;\n // -C <dir> changes working directory, bypassing cwd sandbox\n git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/, /^-C$/],\n // node -r/--require preloads arbitrary modules; --eval executes code\n node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],\n // go run could execute arbitrary .go files; -ldflags could inject build-time code\n go: [/^-ldflags$/],\n // bun --preload is similar to node --require\n bun: [/^--preload$/],\n // docker build/run can create containers with host access;\n // only allow read-only commands (ps, images, version)\n docker: [/^build$/, /^run$/, /^exec$/, /^push$/, /^pull$/],\n // find -exec/-ok/-execdir execute arbitrary commands\n find: [/^-exec$/, /^-exec;$/, /^-ok$/, /^-ok;$/, /^-execdir$/, /^-execdir;$/, /^-exec=/, /^-ok=/, /^-execdir=/],\n // rm -rf / is catastrophic — block root and home targets\n rm: [/^\\/$/, /^\\/\\*$/, /^~$/],\n};\n\nfunction validateArgs(cmd: string, args: string[]): string | null {\n const blocked = BLOCKED_ARG_PATTERNS[cmd];\n if (!blocked) return null;\n\n for (const arg of args) {\n for (const pattern of blocked) {\n if (pattern.test(arg)) {\n return `Blocked argument \"${arg}\" for command \"${cmd}\" (matches security pattern ${pattern})`;\n }\n }\n }\n return null;\n}\n\ninterface ExecInput {\n command: string;\n args?: string[];\n cwd?: string;\n timeout?: number;\n}\n\ninterface ExecOutput {\n command: string;\n args: string[];\n stdout: string;\n stderr: string;\n exitCode: number;\n truncated: boolean;\n allowed: boolean;\n}\n\nexport const execTool: Tool<ExecInput, ExecOutput> = {\n name: 'exec',\n category: 'Shell',\n description:\n 'Restricted shell that only runs pre-approved commands with constrained arguments. Safer alternative to `bash`.',\n usageHint:\n 'Set `command` (must be in allowlist). `args` passed through. For arbitrary shell access use the `bash` tool instead.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: TIMEOUT_MS,\n inputSchema: {\n type: 'object',\n properties: {\n command: { type: 'string', description: 'Command to run (must be in allowlist)' },\n args: { type: 'array', items: { type: 'string' }, description: 'Arguments' },\n cwd: { type: 'string', description: 'Working directory (must resolve inside project root)' },\n timeout: { type: 'integer', description: 'Timeout in ms (default: 30000)' },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n const registry = getProcessRegistry();\n if (!registry.canProceed) {\n return {\n command: input.command,\n args: input.args ?? [],\n stdout: '',\n stderr: 'Circuit breaker is open — too many consecutive failures. Use /kill reset to recover.',\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n }\n\n const cmd = input.command.trim();\n if (!cmd)\n return {\n command: cmd,\n args: [],\n stdout: '',\n stderr: 'Empty command',\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n\n if (!(cmd in ALLOWED_COMMANDS)) {\n return {\n command: cmd,\n args: input.args ?? [],\n stdout: '',\n stderr: `Command \"${cmd}\" not in allowlist. Use the bash tool for arbitrary commands.`,\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n }\n\n const args = (input.args ?? []).slice(0, MAX_ARGS);\n const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));\n\n // Validate args against per-command security patterns\n const argError = validateArgs(cmd, args);\n if (argError) {\n return {\n command: cmd,\n args,\n stdout: '',\n stderr: argError,\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n }\n\n // Resolve cwd inside the project root. Model-supplied paths like '/etc'\n // would otherwise let allowlisted commands operate anywhere on disk.\n const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;\n const rel = path.relative(ctx.projectRoot, requestedCwd);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n return {\n command: cmd,\n args,\n stdout: '',\n stderr: `cwd \"${input.cwd}\" resolves outside project root`,\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n }\n const cwd = requestedCwd;\n const signal = opts.signal;\n\n return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id);\n },\n};\n\nfunction runCommand(\n cmd: string,\n args: string[],\n cwd: string,\n timeout: number,\n signal: AbortSignal,\n sessionId: string | undefined,\n): Promise<ExecOutput> {\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n let killed = false;\n const startedAt = Date.now();\n\n const child = spawn(cmd, args, {\n cwd,\n signal,\n env: buildChildEnv(sessionId),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n const registry = getProcessRegistry();\n const pid = child.pid;\n if (typeof pid === 'number') {\n const fullCommand = `${cmd} ${args.join(' ')}`;\n registry.register({ pid, name: 'exec', command: fullCommand, startedAt: Date.now(), sessionId, child });\n }\n\n const timer = setTimeout(() => {\n killed = true;\n if (typeof pid === 'number') registry.kill(pid);\n else child.kill('SIGTERM');\n }, timeout);\n\n child.stdout?.on('data', (chunk: Buffer) => {\n if (stdout.length < MAX_OUTPUT) stdout += chunk.toString();\n });\n\n child.stderr?.on('data', (chunk: Buffer) => {\n if (stderr.length < MAX_OUTPUT) stderr += chunk.toString();\n });\n\n child.on('close', (code) => {\n clearTimeout(timer);\n if (typeof pid === 'number') registry.unregister(pid);\n const durationMs = Date.now() - startedAt;\n const exitCode = killed ? 124 : (code ?? 1);\n registry.afterCall(durationMs, exitCode !== 0);\n resolve({\n command: cmd,\n args,\n stdout: stdout.slice(0, MAX_OUTPUT),\n stderr: stderr.slice(0, MAX_OUTPUT),\n exitCode,\n truncated: stdout.length >= MAX_OUTPUT || stderr.length >= MAX_OUTPUT,\n allowed: true,\n });\n });\n\n child.on('error', (err) => {\n clearTimeout(timer);\n if (typeof pid === 'number') registry.unregister(pid);\n registry.afterCall(Date.now() - startedAt, true);\n resolve({\n command: cmd,\n args,\n stdout: stdout.slice(0, MAX_OUTPUT),\n stderr: err.message,\n exitCode: 1,\n truncated: false,\n allowed: true,\n });\n });\n });\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -31,8 +31,11 @@ export { batchToolUseTool } from './batch-tool-use.js';
|
|
|
31
31
|
export { toolHelpTool } from './tool-help.js';
|
|
32
32
|
export { forgetTool, rememberTool } from './memory.js';
|
|
33
33
|
export { createModeTool } from './mode.js';
|
|
34
|
+
export { KillOpts, RegistryStats, TrackedProcess, _resetProcessRegistry, getProcessRegistry } from './process-registry.js';
|
|
35
|
+
export { CircuitBreaker, CircuitBreakerConfig, CircuitBreakerSnapshot } from './circuit-breaker.js';
|
|
34
36
|
export { builtinTools } from './builtin.js';
|
|
35
37
|
export { builtinToolsPack } from './pack.js';
|
|
38
|
+
import 'node:child_process';
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* `planTool` — the LLM-callable counterpart to the `/plan` slash command.
|
package/dist/index.js
CHANGED
|
@@ -854,6 +854,326 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
854
854
|
};
|
|
855
855
|
}
|
|
856
856
|
|
|
857
|
+
// src/circuit-breaker.ts
|
|
858
|
+
var DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;
|
|
859
|
+
var DEFAULT_SLOW_CALL_THRESHOLD_MS = 6e4;
|
|
860
|
+
var DEFAULT_MAX_SLOW_CALLS = 3;
|
|
861
|
+
var DEFAULT_WINDOW_MS = 6e4;
|
|
862
|
+
var DEFAULT_MAX_CALLS_PER_WINDOW = 30;
|
|
863
|
+
var DEFAULT_COOLDOWN_MS = 3e4;
|
|
864
|
+
var CircuitBreaker = class {
|
|
865
|
+
maxConsecutiveFailures;
|
|
866
|
+
slowCallThresholdMs;
|
|
867
|
+
maxSlowCalls;
|
|
868
|
+
windowMs;
|
|
869
|
+
maxCallsPerWindow;
|
|
870
|
+
cooldownMs;
|
|
871
|
+
state = "closed";
|
|
872
|
+
consecutiveFailures = 0;
|
|
873
|
+
window = [];
|
|
874
|
+
lastFailureAt = null;
|
|
875
|
+
lastSlowAt = null;
|
|
876
|
+
/** Timestamp when the breaker was opened (for cooldown calculation). */
|
|
877
|
+
openedAt = null;
|
|
878
|
+
/** Timestamp when the last call ran (for half-open gate). */
|
|
879
|
+
lastCallAt = null;
|
|
880
|
+
constructor(config = {}) {
|
|
881
|
+
this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
|
|
882
|
+
this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;
|
|
883
|
+
this.maxSlowCalls = config.maxSlowCalls ?? DEFAULT_MAX_SLOW_CALLS;
|
|
884
|
+
this.windowMs = config.windowMs ?? DEFAULT_WINDOW_MS;
|
|
885
|
+
this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;
|
|
886
|
+
this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Returns true if the circuit allows a new call to proceed.
|
|
890
|
+
* When false, callers should abort the tool call and return a
|
|
891
|
+
* circuit-breaker error instead of spawning a process.
|
|
892
|
+
*/
|
|
893
|
+
get canProceed() {
|
|
894
|
+
this._checkStateTransition();
|
|
895
|
+
return this.state !== "open";
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Snapshot of the current breaker state for observability (`/kill`).
|
|
899
|
+
*/
|
|
900
|
+
snapshot() {
|
|
901
|
+
this._checkStateTransition();
|
|
902
|
+
const now = Date.now();
|
|
903
|
+
let cooldownRemaining = null;
|
|
904
|
+
if (this.openedAt !== null && this.state === "open") {
|
|
905
|
+
const elapsed = now - this.openedAt;
|
|
906
|
+
cooldownRemaining = Math.max(0, this.cooldownMs - elapsed);
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
state: this.state,
|
|
910
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
911
|
+
slowCallsInWindow: this.window.filter((c) => c.slow).length,
|
|
912
|
+
callsInWindow: this.window.length,
|
|
913
|
+
windowMs: this.windowMs,
|
|
914
|
+
cooldownRemainingMs: cooldownRemaining,
|
|
915
|
+
lastFailureAt: this.lastFailureAt,
|
|
916
|
+
lastSlowAt: this.lastSlowAt
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Call this BEFORE spawning a bash/exec process.
|
|
921
|
+
* Returns true if the call is allowed; false if the breaker is open.
|
|
922
|
+
* When false, callers MUST NOT spawn a process.
|
|
923
|
+
*/
|
|
924
|
+
beforeCall() {
|
|
925
|
+
this._checkStateTransition();
|
|
926
|
+
if (this.state === "open") return false;
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Call this AFTER a bash/exec process finishes (success or failure).
|
|
931
|
+
* `durationMs` is the wall-clock time the process ran.
|
|
932
|
+
* `failed` is true when the process returned a non-zero exit code or
|
|
933
|
+
* threw an exception before spawning.
|
|
934
|
+
*/
|
|
935
|
+
afterCall(durationMs, failed) {
|
|
936
|
+
const now = Date.now();
|
|
937
|
+
this.lastCallAt = now;
|
|
938
|
+
if (this.state === "half-open") {
|
|
939
|
+
if (failed) {
|
|
940
|
+
this._trip();
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
this._reset();
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
this._pruneWindow(now);
|
|
947
|
+
const slow = durationMs >= this.slowCallThresholdMs;
|
|
948
|
+
this.window.push({ at: now, failed, slow });
|
|
949
|
+
if (failed) {
|
|
950
|
+
this.consecutiveFailures++;
|
|
951
|
+
this.lastFailureAt = now;
|
|
952
|
+
if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
|
|
953
|
+
this._trip();
|
|
954
|
+
}
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
this.consecutiveFailures = 0;
|
|
958
|
+
if (slow) {
|
|
959
|
+
this.lastSlowAt = now;
|
|
960
|
+
const slowCount = this.window.filter((c) => c.slow).length;
|
|
961
|
+
if (slowCount >= this.maxSlowCalls) {
|
|
962
|
+
this._trip();
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
const callCount = this.window.length;
|
|
966
|
+
if (callCount >= this.maxCallsPerWindow) {
|
|
967
|
+
this._trip();
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/** Force the breaker open. Used by /kill force and Ctrl+C. */
|
|
971
|
+
forceOpen() {
|
|
972
|
+
this._trip();
|
|
973
|
+
}
|
|
974
|
+
/** Force a reset to closed. Used by tests and /kill reset. */
|
|
975
|
+
forceReset() {
|
|
976
|
+
this._reset();
|
|
977
|
+
}
|
|
978
|
+
_trip() {
|
|
979
|
+
if (this.state === "open") return;
|
|
980
|
+
this.state = "open";
|
|
981
|
+
this.openedAt = Date.now();
|
|
982
|
+
}
|
|
983
|
+
_reset() {
|
|
984
|
+
this.state = "closed";
|
|
985
|
+
this.consecutiveFailures = 0;
|
|
986
|
+
this.window = [];
|
|
987
|
+
this.openedAt = null;
|
|
988
|
+
}
|
|
989
|
+
/** Transition from open → half-open when cooldown elapses. */
|
|
990
|
+
_checkStateTransition() {
|
|
991
|
+
if (this.state !== "open" || this.openedAt === null) return;
|
|
992
|
+
const elapsed = Date.now() - this.openedAt;
|
|
993
|
+
if (elapsed >= this.cooldownMs) {
|
|
994
|
+
this.state = "half-open";
|
|
995
|
+
this.openedAt = null;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
_pruneWindow(now) {
|
|
999
|
+
const cutoff = now - this.windowMs;
|
|
1000
|
+
this.window = this.window.filter((c) => c.at >= cutoff);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
// src/process-registry.ts
|
|
1005
|
+
var DEFAULT_GRACE_MS = 2e3;
|
|
1006
|
+
var ProcessRegistryImpl = class {
|
|
1007
|
+
processes = /* @__PURE__ */ new Map();
|
|
1008
|
+
breaker;
|
|
1009
|
+
constructor(breakerConfig) {
|
|
1010
|
+
this.breaker = new CircuitBreaker(breakerConfig);
|
|
1011
|
+
}
|
|
1012
|
+
register(info) {
|
|
1013
|
+
this.processes.set(info.pid, { ...info, killed: false });
|
|
1014
|
+
}
|
|
1015
|
+
/** Unregister a process by PID. Called on 'close' / 'exit' events. */
|
|
1016
|
+
unregister(pid) {
|
|
1017
|
+
this.processes.delete(pid);
|
|
1018
|
+
}
|
|
1019
|
+
/** Get a single process by PID. */
|
|
1020
|
+
get(pid) {
|
|
1021
|
+
return this.processes.get(pid);
|
|
1022
|
+
}
|
|
1023
|
+
/** Get all tracked processes. */
|
|
1024
|
+
list() {
|
|
1025
|
+
return Array.from(this.processes.values());
|
|
1026
|
+
}
|
|
1027
|
+
/** Get processes filtered by name (e.g. 'bash', 'exec'). */
|
|
1028
|
+
byName(name) {
|
|
1029
|
+
return this.list().filter((p) => p.name === name);
|
|
1030
|
+
}
|
|
1031
|
+
/** Get processes filtered by session. */
|
|
1032
|
+
bySession(sessionId) {
|
|
1033
|
+
return this.list().filter((p) => p.sessionId === sessionId);
|
|
1034
|
+
}
|
|
1035
|
+
/** Count of active (non-killed) processes. */
|
|
1036
|
+
get activeCount() {
|
|
1037
|
+
let n = 0;
|
|
1038
|
+
for (const p of this.processes.values()) {
|
|
1039
|
+
if (!p.killed) n++;
|
|
1040
|
+
}
|
|
1041
|
+
return n;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Combined stats for observability — used by /ps and the TUI status bar.
|
|
1045
|
+
*/
|
|
1046
|
+
stats() {
|
|
1047
|
+
return {
|
|
1048
|
+
activeCount: this.activeCount,
|
|
1049
|
+
totalCount: this.processes.size,
|
|
1050
|
+
breaker: this.breaker.snapshot()
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Returns true if the circuit allows a new bash/exec call to proceed.
|
|
1055
|
+
* When false, callers MUST NOT spawn a process.
|
|
1056
|
+
*/
|
|
1057
|
+
get canProceed() {
|
|
1058
|
+
return this.breaker.canProceed;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Called before spawning a process. Returns true if allowed; false if
|
|
1062
|
+
* the circuit breaker is open.
|
|
1063
|
+
*/
|
|
1064
|
+
beforeCall() {
|
|
1065
|
+
return this.breaker.beforeCall();
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Called after a process finishes. `durationMs` is wall-clock time;
|
|
1069
|
+
* `failed` is true for non-zero exit codes.
|
|
1070
|
+
*/
|
|
1071
|
+
afterCall(durationMs, failed) {
|
|
1072
|
+
this.breaker.afterCall(durationMs, failed);
|
|
1073
|
+
}
|
|
1074
|
+
/** Force-open the circuit breaker (Ctrl+C, /kill force). */
|
|
1075
|
+
forceBreakerOpen() {
|
|
1076
|
+
this.breaker.forceOpen();
|
|
1077
|
+
}
|
|
1078
|
+
/** Force-reset the circuit breaker to closed (/kill reset). */
|
|
1079
|
+
forceBreakerReset() {
|
|
1080
|
+
this.breaker.forceReset();
|
|
1081
|
+
}
|
|
1082
|
+
/** Kill a single process by PID.
|
|
1083
|
+
*
|
|
1084
|
+
* On POSIX: sends SIGTERM to the *process group* (-pid) so that
|
|
1085
|
+
* runaway grandchild processes (`sleep 9999 & disown`) are also killed.
|
|
1086
|
+
* After `graceMs` a SIGKILL is sent if the process hasn't exited.
|
|
1087
|
+
*
|
|
1088
|
+
* On Windows: `child.kill()` maps to TerminateProcess — process groups
|
|
1089
|
+
* are not meaningfully supported. A second `force=true` call sends
|
|
1090
|
+
* SIGKILL (which maps to TerminateProcess again — the distinction is
|
|
1091
|
+
* in the exit code, not the signal).
|
|
1092
|
+
*
|
|
1093
|
+
* Returns true if the process was found and kill was attempted.
|
|
1094
|
+
*/
|
|
1095
|
+
kill(pid, opts = {}) {
|
|
1096
|
+
const p = this.processes.get(pid);
|
|
1097
|
+
if (!p) return false;
|
|
1098
|
+
if (p.killed) return true;
|
|
1099
|
+
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
1100
|
+
const isWin = os.platform() === "win32";
|
|
1101
|
+
if (isWin) {
|
|
1102
|
+
try {
|
|
1103
|
+
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
1104
|
+
} catch {
|
|
1105
|
+
}
|
|
1106
|
+
p.killed = true;
|
|
1107
|
+
return true;
|
|
1108
|
+
}
|
|
1109
|
+
try {
|
|
1110
|
+
if (force) {
|
|
1111
|
+
try {
|
|
1112
|
+
process.kill(-pid, "SIGKILL");
|
|
1113
|
+
} catch {
|
|
1114
|
+
p.child.kill("SIGKILL");
|
|
1115
|
+
}
|
|
1116
|
+
} else {
|
|
1117
|
+
try {
|
|
1118
|
+
process.kill(-pid, "SIGTERM");
|
|
1119
|
+
} catch {
|
|
1120
|
+
p.child.kill("SIGTERM");
|
|
1121
|
+
}
|
|
1122
|
+
const timer = setTimeout(() => {
|
|
1123
|
+
if (this.processes.has(pid) && !p.child.killed) {
|
|
1124
|
+
try {
|
|
1125
|
+
process.kill(-pid, "SIGKILL");
|
|
1126
|
+
} catch {
|
|
1127
|
+
try {
|
|
1128
|
+
p.child.kill("SIGKILL");
|
|
1129
|
+
} catch {
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}, graceMs);
|
|
1134
|
+
timer.unref?.();
|
|
1135
|
+
}
|
|
1136
|
+
} catch {
|
|
1137
|
+
}
|
|
1138
|
+
p.killed = true;
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Kill all tracked processes.
|
|
1143
|
+
* Returns the PIDs that were kill()ed.
|
|
1144
|
+
*/
|
|
1145
|
+
killAll(opts = {}) {
|
|
1146
|
+
const pids = Array.from(this.processes.keys());
|
|
1147
|
+
const killed = [];
|
|
1148
|
+
for (const pid of pids) {
|
|
1149
|
+
if (this.kill(pid, opts)) killed.push(pid);
|
|
1150
|
+
}
|
|
1151
|
+
return killed;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Kill all processes for a specific session.
|
|
1155
|
+
* Returns the PIDs that were kill()ed.
|
|
1156
|
+
*/
|
|
1157
|
+
killSession(sessionId, opts = {}) {
|
|
1158
|
+
const pids = this.bySession(sessionId).map((p) => p.pid);
|
|
1159
|
+
const killed = [];
|
|
1160
|
+
for (const pid of pids) {
|
|
1161
|
+
if (this.kill(pid, opts)) killed.push(pid);
|
|
1162
|
+
}
|
|
1163
|
+
return killed;
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
var _registry;
|
|
1167
|
+
function getProcessRegistry() {
|
|
1168
|
+
if (!_registry) {
|
|
1169
|
+
_registry = new ProcessRegistryImpl();
|
|
1170
|
+
}
|
|
1171
|
+
return _registry;
|
|
1172
|
+
}
|
|
1173
|
+
function _resetProcessRegistry() {
|
|
1174
|
+
_registry = void 0;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
857
1177
|
// src/bash.ts
|
|
858
1178
|
var MAX_OUTPUT = 32768;
|
|
859
1179
|
var DEFAULT_TIMEOUT = 3e4;
|
|
@@ -892,12 +1212,27 @@ var bashTool = {
|
|
|
892
1212
|
},
|
|
893
1213
|
async *executeStream(input, ctx, opts) {
|
|
894
1214
|
if (!input?.command) throw new Error("bash: command is required");
|
|
1215
|
+
const registry = getProcessRegistry();
|
|
1216
|
+
if (!registry.beforeCall()) {
|
|
1217
|
+
yield {
|
|
1218
|
+
type: "final",
|
|
1219
|
+
output: {
|
|
1220
|
+
output: "",
|
|
1221
|
+
exit_code: 1,
|
|
1222
|
+
timed_out: false,
|
|
1223
|
+
pid: null,
|
|
1224
|
+
error: "bash: circuit breaker open \u2014 too many consecutive failures or slow calls. Use /kill to inspect or /kill reset to recover."
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
895
1229
|
const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT, 6e5));
|
|
896
1230
|
const isWin = os.platform() === "win32";
|
|
897
1231
|
const shell = isWin ? process.env["COMSPEC"] ?? "cmd.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
898
1232
|
const args = isWin ? ["/c", input.command] : ["-c", input.command];
|
|
899
1233
|
const env = buildChildEnv(ctx.session?.id);
|
|
900
1234
|
const detached = isWin ? !!input.background : true;
|
|
1235
|
+
const startedAt = Date.now();
|
|
901
1236
|
if (input.background) {
|
|
902
1237
|
let buf2 = "";
|
|
903
1238
|
let truncated = false;
|
|
@@ -908,7 +1243,18 @@ var bashTool = {
|
|
|
908
1243
|
detached: true,
|
|
909
1244
|
signal: opts.signal
|
|
910
1245
|
});
|
|
911
|
-
const
|
|
1246
|
+
const pid2 = child2.pid;
|
|
1247
|
+
if (typeof pid2 === "number") {
|
|
1248
|
+
registry.register({
|
|
1249
|
+
pid: pid2,
|
|
1250
|
+
name: "bash",
|
|
1251
|
+
command: input.command,
|
|
1252
|
+
startedAt: Date.now(),
|
|
1253
|
+
sessionId: ctx.session?.id,
|
|
1254
|
+
child: child2
|
|
1255
|
+
});
|
|
1256
|
+
child2.on("close", () => registry.unregister(pid2));
|
|
1257
|
+
}
|
|
912
1258
|
child2.stdout?.on("data", (chunk) => {
|
|
913
1259
|
if (!truncated) {
|
|
914
1260
|
const remain = MAX_OUTPUT - buf2.length;
|
|
@@ -928,15 +1274,16 @@ var bashTool = {
|
|
|
928
1274
|
}
|
|
929
1275
|
});
|
|
930
1276
|
child2.on("close", () => {
|
|
1277
|
+
registry.afterCall(Date.now() - startedAt, false);
|
|
931
1278
|
});
|
|
932
|
-
if (typeof
|
|
1279
|
+
if (typeof pid2 === "number") child2.unref();
|
|
933
1280
|
yield {
|
|
934
1281
|
type: "final",
|
|
935
1282
|
output: {
|
|
936
1283
|
output: truncated ? buf2.slice(0, MAX_OUTPUT) + "\u2026[truncated]" : buf2,
|
|
937
1284
|
exit_code: null,
|
|
938
1285
|
timed_out: false,
|
|
939
|
-
pid
|
|
1286
|
+
pid: pid2
|
|
940
1287
|
}
|
|
941
1288
|
};
|
|
942
1289
|
return;
|
|
@@ -948,6 +1295,17 @@ var bashTool = {
|
|
|
948
1295
|
detached,
|
|
949
1296
|
signal: opts.signal
|
|
950
1297
|
});
|
|
1298
|
+
const pid = child.pid;
|
|
1299
|
+
if (typeof pid === "number") {
|
|
1300
|
+
registry.register({
|
|
1301
|
+
pid,
|
|
1302
|
+
name: "bash",
|
|
1303
|
+
command: input.command,
|
|
1304
|
+
startedAt: Date.now(),
|
|
1305
|
+
sessionId: ctx.session?.id,
|
|
1306
|
+
child
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
951
1309
|
let buf = "";
|
|
952
1310
|
let pending = "";
|
|
953
1311
|
let timedOut = false;
|
|
@@ -1030,10 +1388,13 @@ var bashTool = {
|
|
|
1030
1388
|
});
|
|
1031
1389
|
child.on("error", (err) => {
|
|
1032
1390
|
for (const t of timers) clearTimeout(t);
|
|
1391
|
+
registry.afterCall(Date.now() - startedAt, true);
|
|
1033
1392
|
push({ kind: "error", err });
|
|
1034
1393
|
});
|
|
1035
1394
|
child.on("close", (code) => {
|
|
1036
1395
|
for (const t of timers) clearTimeout(t);
|
|
1396
|
+
if (typeof pid === "number") registry.unregister(pid);
|
|
1397
|
+
registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);
|
|
1037
1398
|
push({ kind: "end", code });
|
|
1038
1399
|
});
|
|
1039
1400
|
try {
|
|
@@ -1164,6 +1525,18 @@ var execTool = {
|
|
|
1164
1525
|
required: ["command"]
|
|
1165
1526
|
},
|
|
1166
1527
|
async execute(input, ctx, opts) {
|
|
1528
|
+
const registry = getProcessRegistry();
|
|
1529
|
+
if (!registry.canProceed) {
|
|
1530
|
+
return {
|
|
1531
|
+
command: input.command,
|
|
1532
|
+
args: input.args ?? [],
|
|
1533
|
+
stdout: "",
|
|
1534
|
+
stderr: "Circuit breaker is open \u2014 too many consecutive failures. Use /kill reset to recover.",
|
|
1535
|
+
exitCode: 1,
|
|
1536
|
+
truncated: false,
|
|
1537
|
+
allowed: false
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1167
1540
|
const cmd = input.command.trim();
|
|
1168
1541
|
if (!cmd)
|
|
1169
1542
|
return {
|
|
@@ -1223,15 +1596,23 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1223
1596
|
let stdout = "";
|
|
1224
1597
|
let stderr = "";
|
|
1225
1598
|
let killed = false;
|
|
1599
|
+
const startedAt = Date.now();
|
|
1226
1600
|
const child = spawn(cmd, args, {
|
|
1227
1601
|
cwd,
|
|
1228
1602
|
signal,
|
|
1229
1603
|
env: buildChildEnv(sessionId),
|
|
1230
1604
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1231
1605
|
});
|
|
1606
|
+
const registry = getProcessRegistry();
|
|
1607
|
+
const pid = child.pid;
|
|
1608
|
+
if (typeof pid === "number") {
|
|
1609
|
+
const fullCommand = `${cmd} ${args.join(" ")}`;
|
|
1610
|
+
registry.register({ pid, name: "exec", command: fullCommand, startedAt: Date.now(), sessionId, child });
|
|
1611
|
+
}
|
|
1232
1612
|
const timer = setTimeout(() => {
|
|
1233
1613
|
killed = true;
|
|
1234
|
-
|
|
1614
|
+
if (typeof pid === "number") registry.kill(pid);
|
|
1615
|
+
else child.kill("SIGTERM");
|
|
1235
1616
|
}, timeout);
|
|
1236
1617
|
child.stdout?.on("data", (chunk) => {
|
|
1237
1618
|
if (stdout.length < MAX_OUTPUT2) stdout += chunk.toString();
|
|
@@ -1241,18 +1622,24 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1241
1622
|
});
|
|
1242
1623
|
child.on("close", (code) => {
|
|
1243
1624
|
clearTimeout(timer);
|
|
1625
|
+
if (typeof pid === "number") registry.unregister(pid);
|
|
1626
|
+
const durationMs = Date.now() - startedAt;
|
|
1627
|
+
const exitCode = killed ? 124 : code ?? 1;
|
|
1628
|
+
registry.afterCall(durationMs, exitCode !== 0);
|
|
1244
1629
|
resolve5({
|
|
1245
1630
|
command: cmd,
|
|
1246
1631
|
args,
|
|
1247
1632
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
1248
1633
|
stderr: stderr.slice(0, MAX_OUTPUT2),
|
|
1249
|
-
exitCode
|
|
1634
|
+
exitCode,
|
|
1250
1635
|
truncated: stdout.length >= MAX_OUTPUT2 || stderr.length >= MAX_OUTPUT2,
|
|
1251
1636
|
allowed: true
|
|
1252
1637
|
});
|
|
1253
1638
|
});
|
|
1254
1639
|
child.on("error", (err) => {
|
|
1255
1640
|
clearTimeout(timer);
|
|
1641
|
+
if (typeof pid === "number") registry.unregister(pid);
|
|
1642
|
+
registry.afterCall(Date.now() - startedAt, true);
|
|
1256
1643
|
resolve5({
|
|
1257
1644
|
command: cmd,
|
|
1258
1645
|
args,
|
|
@@ -4525,6 +4912,6 @@ var builtinToolsPack = {
|
|
|
4525
4912
|
tools: builtinTools
|
|
4526
4913
|
};
|
|
4527
4914
|
|
|
4528
|
-
export { auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, createModeTool, diffTool, documentTool, editTool, execTool, fetchTool, forgetTool, formatTool, gitTool, globTool, grepTool, installTool, jsonTool, lintTool, logsTool, outdatedTool, patchTool, planTool, readTool, rememberTool, replaceTool, scaffoldTool, searchTool, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
|
|
4915
|
+
export { CircuitBreaker, _resetProcessRegistry, auditTool, bashTool, batchToolUseTool, builtinTools, builtinToolsPack, createModeTool, diffTool, documentTool, editTool, execTool, fetchTool, forgetTool, formatTool, getProcessRegistry, gitTool, globTool, grepTool, installTool, jsonTool, lintTool, logsTool, outdatedTool, patchTool, planTool, readTool, rememberTool, replaceTool, scaffoldTool, searchTool, testTool, todoTool, toolHelpTool, toolSearchTool, toolUseTool, treeTool, typecheckTool, writeTool };
|
|
4529
4916
|
//# sourceMappingURL=index.js.map
|
|
4530
4917
|
//# sourceMappingURL=index.js.map
|