@wrongstack/tools 0.296.3 → 0.297.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/audit.js +15 -0
  2. package/dist/audit.js.map +2 -2
  3. package/dist/bash.js +15 -0
  4. package/dist/bash.js.map +2 -2
  5. package/dist/builtin.js +1291 -1216
  6. package/dist/builtin.js.map +4 -4
  7. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  8. package/dist/codebase-index/index.d.ts +1 -0
  9. package/dist/codebase-index/index.d.ts.map +1 -1
  10. package/dist/codebase-index/index.js +1259 -1196
  11. package/dist/codebase-index/index.js.map +4 -4
  12. package/dist/codebase-index/project-server-client.d.ts +13 -1
  13. package/dist/codebase-index/project-server-client.d.ts.map +1 -1
  14. package/dist/codebase-index/project-server-endpoint.d.ts +29 -1
  15. package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -1
  16. package/dist/codebase-index/project-server.js +20 -5
  17. package/dist/codebase-index/project-server.js.map +2 -2
  18. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  19. package/dist/exec.js +15 -0
  20. package/dist/exec.js.map +2 -2
  21. package/dist/format.js +15 -0
  22. package/dist/format.js.map +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1260 -1182
  26. package/dist/index.js.map +4 -4
  27. package/dist/install.js +15 -0
  28. package/dist/install.js.map +2 -2
  29. package/dist/languages/index.js +15 -0
  30. package/dist/languages/index.js.map +2 -2
  31. package/dist/lint.js +15 -0
  32. package/dist/lint.js.map +2 -2
  33. package/dist/next-steps.d.ts +23 -0
  34. package/dist/next-steps.d.ts.map +1 -1
  35. package/dist/next-steps.js +4 -0
  36. package/dist/next-steps.js.map +2 -2
  37. package/dist/outdated.js +15 -0
  38. package/dist/outdated.js.map +2 -2
  39. package/dist/pack.js +1291 -1216
  40. package/dist/pack.js.map +4 -4
  41. package/dist/process-registry.d.ts +9 -0
  42. package/dist/process-registry.d.ts.map +1 -1
  43. package/dist/process-registry.js +15 -0
  44. package/dist/process-registry.js.map +2 -2
  45. package/dist/ps-slash.js +15 -0
  46. package/dist/ps-slash.js.map +2 -2
  47. package/dist/read.js +67 -9
  48. package/dist/read.js.map +2 -2
  49. package/dist/test.js +15 -0
  50. package/dist/test.js.map +2 -2
  51. package/dist/tool-tier.js +1291 -1216
  52. package/dist/tool-tier.js.map +4 -4
  53. package/dist/typecheck.js +15 -0
  54. package/dist/typecheck.js.map +2 -2
  55. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/ps-slash.ts", "../src/process-registry-persistent.ts", "../src/process-registry.ts", "../src/circuit-breaker.ts"],
4
- "sourcesContent": ["/**\n * Global Process Registry - Cross-Instance Process Tracking\n * \n * Provides functionality to list all WrongStack instances running on the system,\n * track their processes, and display detailed status information.\n */\n\nimport * as os from 'node:os';\nimport { getPersistentProcessRegistry, type PersistentProcessEntry } from './process-registry-persistent.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Represents a WrongStack instance's aggregated information.\n */\nexport interface InstanceInfo {\n instanceId: string;\n hostname: string;\n mainPid: number;\n startedAt: number;\n lastActivity: number;\n status: 'active' | 'idle' | 'stale';\n processCount: number;\n processes: PersistentProcessEntry[];\n sessionIds: Set<string>;\n}\n\n/**\n * Counts of instances by status.\n */\nexport interface InstanceCounts {\n total: number;\n active: number;\n idle: number;\n stale: number;\n byHostname: Map<string, number>;\n}\n\n/**\n * Global process status encompassing all instances.\n */\nexport interface GlobalProcessStatus {\n localInstance: {\n instanceId: string;\n mainPid: number;\n protectedCount: number;\n platform: string;\n hostname: string;\n uptime: number;\n };\n allInstances: Array<{\n instanceId: string;\n hostname: string;\n mainPid: number;\n processes: PersistentProcessEntry[];\n startedAt: number;\n lastActivity: number;\n }>;\n summary: {\n totalProcesses: number;\n protectedCount: number;\n staleCount: number;\n instanceCount: number;\n activeInstanceCount: number;\n };\n timestamp: number;\n}\n\n/**\n * Options for filtering instance listings.\n */\nexport interface InstanceListOptions {\n /** Include stale instances in the list */\n includeStale?: boolean;\n /** Filter by hostname pattern (supports glob patterns) */\n hostname?: string;\n /** Filter by instance status */\n status?: 'active' | 'idle' | 'stale' | 'all';\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** If no heartbeat for this long, instance is considered idle */\nconst IDLE_THRESHOLD_MS = 2 * 60_000; // 2 minutes\n\n/** If no heartbeat for this long, instance is considered stale */\nconst STALE_THRESHOLD_MS = 5 * 60_000; // 5 minutes\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/** Get current timestamp */\nfunction now(): number {\n return Date.now();\n}\n\n/** Format a duration in milliseconds to a human-readable string */\nfunction formatAge(ms: number): string {\n if (ms < 1000) return '0s';\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h`;\n const days = Math.floor(hours / 24);\n return `${days}d`;\n}\n\n/** Format uptime in milliseconds to a compact string */\nfunction formatUptime(ms: number): string {\n return formatAge(ms);\n}\n\n/** Simple glob pattern matching for hostname filtering */\nfunction matchGlob(pattern: string, value: string): boolean {\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n\n try {\n const regex = new RegExp(`^${regexPattern}$`, 'i');\n return regex.test(value);\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Instance Listing Functions\n// ============================================================================\n\n/**\n * Get list of all known instances (from persistent registry).\n */\nexport async function listInstances(options: InstanceListOptions = {}): Promise<InstanceInfo[]> {\n const { includeStale = false, hostname, status } = options;\n const timestamp = now();\n\n const registry = getPersistentProcessRegistry();\n const globalStatus = await registry.getGlobalStatus();\n \n const instances: InstanceInfo[] = [];\n const instanceMap = globalStatus.instances;\n\n for (const [instanceId, processes] of instanceMap) {\n if (processes.length === 0) continue;\n\n // Find the main process (protected: true, spawnMode: 'main')\n const mainProc = processes.find(p => p.spawnMode === 'main');\n const firstProc = processes.at(0);\n const mainPid = mainProc?.pid ?? firstProc?.pid ?? 0;\n const hostname_ = firstProc?.hostname ?? os.hostname();\n const startedAt = Math.min(...processes.map(p => p.startedAt));\n \n // Calculate last activity (most recent heartbeat)\n const lastActivity = Math.max(...processes.map(p => p.lastHeartbeat));\n \n // Determine status based on last activity\n const age = timestamp - lastActivity;\n let instanceStatus: 'active' | 'idle' | 'stale' = 'stale';\n if (age < IDLE_THRESHOLD_MS) instanceStatus = 'active';\n else if (age < STALE_THRESHOLD_MS) instanceStatus = 'idle';\n\n // Collect unique session IDs\n const sessionIds = new Set<string>();\n for (const proc of processes) {\n if (proc.sessionId) {\n sessionIds.add(proc.sessionId);\n }\n }\n\n // Apply filters\n if (!includeStale && instanceStatus === 'stale') continue;\n if (hostname && !matchGlob(hostname, hostname_)) continue;\n if (status && status !== 'all' && instanceStatus !== status) continue;\n\n instances.push({\n instanceId,\n hostname: hostname_,\n mainPid,\n startedAt,\n lastActivity,\n status: instanceStatus,\n processCount: processes.length,\n processes,\n sessionIds,\n });\n }\n\n // Sort by last activity (most recent first)\n instances.sort((a, b) => b.lastActivity - a.lastActivity);\n\n return instances;\n}\n\n/**\n * Get counts of instances by status.\n */\nexport async function getInstanceCount(): Promise<InstanceCounts> {\n const instances = await listInstances({ includeStale: true });\n const byHostname = new Map<string, number>();\n\n let active = 0;\n let idle = 0;\n let stale = 0;\n\n for (const inst of instances) {\n const current = byHostname.get(inst.hostname) ?? 0;\n byHostname.set(inst.hostname, current + 1);\n\n switch (inst.status) {\n case 'active': active++; break;\n case 'idle': idle++; break;\n case 'stale': stale++; break;\n }\n }\n\n return {\n total: instances.length,\n active,\n idle,\n stale,\n byHostname,\n };\n}\n\n/**\n * Get global process status across all instances.\n */\nexport async function getGlobalProcessStatus(): Promise<GlobalProcessStatus> {\n const timestamp = now();\n const registry = getPersistentProcessRegistry();\n const globalStatus = await registry.getGlobalStatus();\n const instances = await listInstances({ includeStale: true });\n\n // Local instance\n const localInstanceId = registry.getInstanceId();\n const localInstance = instances.find(i => i.instanceId === localInstanceId);\n \n let localProtectedCount = 0;\n if (localInstance) {\n localProtectedCount = localInstance.processes.filter(p => p.protected).length;\n }\n\n let activeInstanceCount = 0;\n for (const inst of instances) {\n if (inst.status === 'active') activeInstanceCount++;\n }\n\n return {\n localInstance: localInstance ? {\n instanceId: localInstance.instanceId,\n mainPid: localInstance.mainPid,\n protectedCount: localProtectedCount,\n platform: process.platform,\n hostname: localInstance.hostname,\n uptime: timestamp - localInstance.startedAt,\n } : {\n instanceId: localInstanceId,\n mainPid: process.pid,\n protectedCount: 0,\n platform: process.platform,\n hostname: os.hostname(),\n uptime: 0,\n },\n allInstances: instances.map(inst => ({\n instanceId: inst.instanceId,\n hostname: inst.hostname,\n mainPid: inst.mainPid,\n processes: inst.processes,\n startedAt: inst.startedAt,\n lastActivity: inst.lastActivity,\n })),\n summary: {\n totalProcesses: globalStatus.totalProcesses,\n protectedCount: globalStatus.protectedCount,\n staleCount: globalStatus.staleCount,\n instanceCount: instances.length,\n activeInstanceCount,\n },\n timestamp,\n };\n}\n\n// ============================================================================\n// Formatting Functions\n// ============================================================================\n\n/**\n * Format the global status as a human-readable string for display.\n */\nexport async function formatGlobalStatus(): Promise<string> {\n const status = await getGlobalProcessStatus();\n const lines: string[] = [];\n\n lines.push('=== WrongStack Global Process Status ===');\n lines.push(`Updated: ${new Date(status.timestamp).toISOString()}`);\n lines.push('');\n\n // Summary\n lines.push('Summary:');\n lines.push(` Total processes: ${status.summary.totalProcesses}`);\n lines.push(` Protected: ${status.summary.protectedCount}`);\n lines.push(` Stale entries: ${status.summary.staleCount}`);\n lines.push(` Instances: ${status.summary.instanceCount} (${status.summary.activeInstanceCount} active)`);\n lines.push('');\n\n // Local instance\n lines.push(`This instance (${status.localInstance.instanceId}):`);\n lines.push(` Main PID: ${status.localInstance.mainPid}`);\n lines.push(` Protected processes: ${status.localInstance.protectedCount}`);\n lines.push(` Platform: ${status.localInstance.platform} (${status.localInstance.hostname})`);\n lines.push(` Uptime: ${formatUptime(status.localInstance.uptime)}`);\n lines.push('');\n\n // Other instances\n for (const instance of status.allInstances) {\n if (instance.instanceId === status.localInstance.instanceId) continue;\n\n const age = Math.round((status.timestamp - instance.lastActivity) / 1000);\n lines.push(`Instance ${instance.instanceId} (${instance.hostname}):`);\n\n for (const proc of instance.processes) {\n const procAge = formatAge(status.timestamp - proc.startedAt);\n const heartbeatAge = formatAge(status.timestamp - proc.lastHeartbeat);\n const protected_ = proc.protected ? '[P]' : ' ';\n\n lines.push(\n ` ${protected_} ${String(proc.pid).padStart(6)} ${proc.name.padEnd(20)} ` +\n `started ${procAge.padStart(8)} heartbeat ${heartbeatAge.padStart(6)} ${proc.spawnMode}`\n );\n }\n lines.push(` Last activity: ${age}s ago`);\n lines.push('');\n }\n\n // Legend\n lines.push('Legend:');\n lines.push(' [P] = Protected (cannot be killed via bash)');\n lines.push(' main = Main WrongStack process');\n lines.push(' spawn = Spawned child process');\n lines.push(' fork = Forked process (e.g., worker threads)');\n\n return lines.join('\\n');\n}\n\n/**\n * Format a clean instance list suitable for display.\n */\nexport async function formatInstanceList(options: InstanceListOptions = {}): Promise<string> {\n const instances = await listInstances(options);\n const count = await getInstanceCount();\n const lines: string[] = [];\n\n lines.push('=== WrongStack Instances ===');\n lines.push(`Total: ${count.total} instances (${count.active} active, ${count.idle} idle, ${count.stale} stale)`);\n lines.push('');\n\n if (count.byHostname.size > 1) {\n lines.push('By hostname:');\n for (const [host, num] of count.byHostname) {\n lines.push(` ${host}: ${num} instance${num !== 1 ? 's' : ''}`);\n }\n lines.push('');\n }\n\n if (instances.length === 0) {\n lines.push('No instances found matching the filter.');\n return lines.join('\\n');\n }\n\n // Table header\n lines.push('INSTANCES:');\n lines.push(' ' + [\n 'STATUS'.padEnd(7),\n 'HOSTNAME'.padEnd(16),\n 'MAIN PID'.padEnd(9),\n 'PROCS'.padEnd(6),\n 'SESSIONS'.padEnd(8),\n 'UPTIME'.padEnd(8),\n 'LAST ACTIVITY',\n ].join(' '));\n lines.push(' ' + '-'.repeat(80));\n\n // Table rows\n for (const inst of instances) {\n const uptime = formatAge(Date.now() - inst.startedAt);\n const lastAct = formatAge(Date.now() - inst.lastActivity);\n const statusIcon = inst.status === 'active' ? '[*]' : inst.status === 'idle' ? '[-]' : '[ ]';\n\n lines.push(\n ' ' + [\n `${statusIcon} ${inst.status}`.padEnd(7),\n inst.hostname.padEnd(16),\n String(inst.mainPid).padEnd(9),\n String(inst.processCount).padEnd(6),\n String(inst.sessionIds.size).padEnd(8),\n uptime.padEnd(8),\n `${lastAct} ago`,\n ].join(' ')\n );\n }\n\n lines.push('');\n lines.push('Use /ps full for detailed process listing per instance.');\n\n return lines.join('\\n');\n}\n\n/**\n * Format instance details as a compact summary string.\n */\nexport async function formatInstanceSummary(): Promise<string> {\n const count = await getInstanceCount();\n const instances = await listInstances({ includeStale: false });\n\n if (instances.length === 0) {\n return 'No active WrongStack instances.';\n }\n\n const lines: string[] = [];\n lines.push(`${count.total} instance${count.total !== 1 ? 's' : ''}`);\n\n // Group by status\n const byStatus = new Map<string, number>();\n for (const inst of instances) {\n byStatus.set(inst.status, (byStatus.get(inst.status) ?? 0) + 1);\n }\n\n const parts: string[] = [];\n if (byStatus.get('active')) parts.push(`${byStatus.get('active')} active`);\n if (byStatus.get('idle')) parts.push(`${byStatus.get('idle')} idle`);\n if (byStatus.get('stale')) parts.push(`${byStatus.get('stale')} stale`);\n\n lines.push(`(${parts.join(', ')})`);\n\n // Total processes\n const totalProcs = instances.reduce((sum, inst) => sum + inst.processCount, 0);\n lines.push(`${totalProcs} total processes`);\n\n return lines.join(' ');\n}\n\n// ============================================================================\n// Slash Command\n// ============================================================================\n\n/**\n * Create the global /ps slash command.\n */\nexport function createGlobalPsSlashCommand() {\n return {\n name: 'ps' as const,\n description: 'List all WrongStack instances and their processes',\n\n async handler(input: string): Promise<{ message: string }> {\n try {\n const trimmed = input.trim();\n const parts = trimmed.split(/\\s+/);\n const sub = parts[0]?.toLowerCase() ?? '';\n\n // /ps list - show instance list\n if (sub === 'list' || sub === 'ls' || sub === '') {\n const output = await formatInstanceList();\n return { message: output };\n }\n\n // /ps summary - compact one-liner\n if (sub === 'summary' || sub === 'sum') {\n const output = await formatInstanceSummary();\n return { message: output };\n }\n\n // /ps full - detailed process listing\n if (sub === 'full' || sub === 'detail') {\n const output = await formatGlobalStatus();\n return { message: output };\n }\n\n // /ps count - just the count\n if (sub === 'count' || sub === 'num') {\n const count = await getInstanceCount();\n return {\n message: `${count.total} instance${count.total !== 1 ? 's' : ''} (${count.active} active, ${count.idle} idle, ${count.stale} stale)`,\n };\n }\n\n // /ps hostname <pattern> - filter by hostname\n if (sub === 'hostname' || sub === 'host') {\n const pattern = parts.slice(1).join(' ');\n if (!pattern) {\n return { message: 'Usage: /ps hostname <pattern> (e.g., /ps hostname workstation*)' };\n }\n const output = await formatInstanceList({ hostname: pattern });\n return { message: output };\n }\n\n // /ps status <active|idle|stale|all> - filter by status\n if (sub === 'status' || sub === 'state') {\n const filterStatus = parts[1]?.toLowerCase();\n if (!['active', 'idle', 'stale', 'all'].includes(filterStatus ?? '')) {\n return { message: 'Usage: /ps status <active|idle|stale|all>' };\n }\n const output = await formatInstanceList({ status: filterStatus as 'active' | 'idle' | 'stale' | 'all' });\n return { message: output };\n }\n\n return { message: 'Usage: /ps [list|summary|count|full|hostname <pattern>|status <state>]' };\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return { message: `Error getting process status: ${message}` };\n }\n },\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 { wstackGlobalRoot } from '@wrongstack/core/utils';\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 = '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;\n// A registry lock is only ever held for a brief read-modify-write; one older\n// than this means the holder crashed. This is the ONLY stale signal on\n// Windows, where process.kill(pid, 0) liveness checks are unreliable.\nconst LOCK_STALE_MS = 30_000;\nconst LOCKFILE = '.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 - decide whether it is stale and stealable.\n try {\n const content = await fs.readFile(lockfilePath, 'utf-8');\n const parts = content.split(':');\n const lockPid = parseInt(parts[0] ?? '0', 10);\n // Content is `pid:host:timestamp`; the last field is the\n // acquisition time (host is never empty, so index is safe).\n const lockTs = Number(parts[parts.length - 1]);\n const staleByAge = Number.isFinite(lockTs) && Date.now() - lockTs > LOCK_STALE_MS;\n\n let holderDead = false;\n if (process.platform !== 'win32' && Number.isFinite(lockPid) && lockPid > 0) {\n try {\n process.kill(lockPid, 0); // Signal 0 just checks if process exists\n } catch {\n holderDead = true;\n }\n }\n\n // Steal when the holder is provably dead (Unix) OR the lock is older\n // than LOCK_STALE_MS. The age check is the only stale signal on\n // Windows \u2014 without it a crashed holder's lock wedges the registry,\n // and with it every kill-guard, permanently.\n if (holderDead || staleByAge) {\n await fs.unlink(lockfilePath).catch(() => {});\n continue;\n }\n } catch {\n // Can't read lock file - assume stale, try to steal\n await fs.unlink(lockfilePath).catch(() => {});\n continue;\n }\n\n // Holder still alive and lock fresh \u2014 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 the file is missing, corrupted, or structurally wrong.\n */\nfunction freshRegistryData(): PersistentRegistryData {\n return {\n version: 1,\n instances: new Map(),\n protectedPatterns: ['wrongstack', 'node'],\n lastCleanup: Date.now(),\n };\n}\n\nasync function readRegistryFile(filePath: string): Promise<PersistentRegistryData> {\n let content: string;\n try {\n content = await fs.readFile(filePath, 'utf-8');\n } catch (err) {\n if (isNodeError(err) && err.code === 'ENOENT') return freshRegistryData();\n throw err; // genuine IO error (EACCES, EMFILE\u2026) \u2014 surface it\n }\n\n // A torn write (crash mid-persist) or hand-corruption must NOT throw here:\n // that would propagate through every kill-guard and wedge them forever with\n // no self-heal. Parse defensively and fall back to empty; the next write\n // overwrites the bad file.\n try {\n const parsed = JSON.parse(content) as Partial<PersistentRegistryData> & {\n instances?: unknown;\n };\n if (!parsed || typeof parsed !== 'object') return freshRegistryData();\n const base = freshRegistryData();\n return {\n version: 1,\n instances: Array.isArray(parsed.instances)\n ? new Map(parsed.instances as [string, PersistentProcessEntry][])\n : base.instances,\n protectedPatterns: Array.isArray(parsed.protectedPatterns)\n ? parsed.protectedPatterns\n : base.protectedPatterns,\n lastCleanup:\n typeof parsed.lastCleanup === 'number' ? parsed.lastCleanup : base.lastCleanup,\n };\n } catch {\n return freshRegistryData();\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 cleanupInterval: ReturnType<typeof setInterval> | null = null;\n private heartbeatRunning = false;\n private cleanupRunning = false;\n private isShuttingDown = false;\n private readonly onProcessExit = (): void => {\n this.runInBackground(\n this.syncToPersistent(),\n 'process_registry.exit_sync_failed',\n 'PersistentProcessRegistry: exit sync failed',\n );\n };\n\n constructor(baseRegistry?: ProcessRegistryImpl) {\n this.instanceId = generateInstanceId();\n const globalRoot = wstackGlobalRoot();\n this.registryPath = path.join(globalRoot, REGISTRY_FILE);\n this.lockPath = path.join(globalRoot, 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 private runInBackground(operation: Promise<void>, event: string, message: string): void {\n void operation.catch((err) => {\n emitStructuredLog('warn', event, message, err);\n });\n }\n\n /**\n * Start the heartbeat and periodic cleanup tasks.\n */\n start(): void {\n if (this.heartbeatInterval) return;\n this.isShuttingDown = false;\n\n // Register this instance's processes with the persistent registry\n this.heartbeat();\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 this.cleanupInterval = setInterval(() => {\n this.cleanup();\n }, STALE_THRESHOLD_MS);\n this.cleanupInterval.unref?.();\n\n // Register main process on startup\n this.registerMainProcess();\n\n // Sync on significant events\n process.on('exit', this.onProcessExit);\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 if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n process.off('exit', this.onProcessExit);\n this.runInBackground(\n this.syncToPersistent(),\n 'process_registry.stop_sync_failed',\n 'PersistentProcessRegistry: stop sync failed',\n );\n }\n\n /**\n * Register the main WrongStack process as protected.\n */\n registerMainProcess(): void {\n const mainPid = process.pid;\n\n this.runInBackground(\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 'process_registry.main_register_failed',\n 'PersistentProcessRegistry: failed to register main process',\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.runInBackground(\n this.updatePersistentEntry(entry),\n 'process_registry.child_register_failed',\n 'PersistentProcessRegistry: failed to register child process',\n );\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 || this.heartbeatRunning) return;\n\n this.heartbeatRunning = true;\n void this.syncToPersistent()\n .catch((err) => {\n emitStructuredLog(\n 'warn',\n 'process_registry.heartbeat_failed',\n 'PersistentProcessRegistry: heartbeat failed',\n err,\n );\n })\n .finally(() => {\n this.heartbeatRunning = false;\n });\n }\n\n private cleanup(): void {\n if (this.isShuttingDown || this.cleanupRunning) return;\n\n this.cleanupRunning = true;\n void this.cleanupStaleEntries()\n .catch((err) => {\n emitStructuredLog(\n 'warn',\n 'process_registry.periodic_cleanup_failed',\n 'PersistentProcessRegistry: periodic cleanup failed',\n err,\n );\n })\n .finally(() => {\n this.cleanupRunning = false;\n });\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 * 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 /** True for an explicitly detached/background tool launch. */\n background: 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 /** Leave explicitly backgrounded jobs alive. Default false. */\n preserveBackground?: boolean | 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 backgroundCount: 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(\n info: Omit<TrackedProcess, 'killed' | 'protected' | 'background'> & {\n protected?: boolean | undefined;\n background?: boolean | undefined;\n },\n ): void {\n this.processes.set(info.pid, {\n ...info,\n killed: false,\n protected: info.protected ?? false,\n background: info.background ?? false,\n });\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 /** Count of active jobs explicitly launched in background mode. */\n get activeBackgroundCount(): number {\n let n = 0;\n for (const p of this.processes.values()) {\n if (p.background && !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 backgroundCount: this.activeBackgroundCount,\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, preserveBackground: true });\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 if (opts.preserveBackground && p.background) return false;\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}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Global Process Registry - Cross-Instance Process Tracking\n * \n * Provides functionality to list all WrongStack instances running on the system,\n * track their processes, and display detailed status information.\n */\n\nimport * as os from 'node:os';\nimport { getPersistentProcessRegistry, type PersistentProcessEntry } from './process-registry-persistent.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Represents a WrongStack instance's aggregated information.\n */\nexport interface InstanceInfo {\n instanceId: string;\n hostname: string;\n mainPid: number;\n startedAt: number;\n lastActivity: number;\n status: 'active' | 'idle' | 'stale';\n processCount: number;\n processes: PersistentProcessEntry[];\n sessionIds: Set<string>;\n}\n\n/**\n * Counts of instances by status.\n */\nexport interface InstanceCounts {\n total: number;\n active: number;\n idle: number;\n stale: number;\n byHostname: Map<string, number>;\n}\n\n/**\n * Global process status encompassing all instances.\n */\nexport interface GlobalProcessStatus {\n localInstance: {\n instanceId: string;\n mainPid: number;\n protectedCount: number;\n platform: string;\n hostname: string;\n uptime: number;\n };\n allInstances: Array<{\n instanceId: string;\n hostname: string;\n mainPid: number;\n processes: PersistentProcessEntry[];\n startedAt: number;\n lastActivity: number;\n }>;\n summary: {\n totalProcesses: number;\n protectedCount: number;\n staleCount: number;\n instanceCount: number;\n activeInstanceCount: number;\n };\n timestamp: number;\n}\n\n/**\n * Options for filtering instance listings.\n */\nexport interface InstanceListOptions {\n /** Include stale instances in the list */\n includeStale?: boolean;\n /** Filter by hostname pattern (supports glob patterns) */\n hostname?: string;\n /** Filter by instance status */\n status?: 'active' | 'idle' | 'stale' | 'all';\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** If no heartbeat for this long, instance is considered idle */\nconst IDLE_THRESHOLD_MS = 2 * 60_000; // 2 minutes\n\n/** If no heartbeat for this long, instance is considered stale */\nconst STALE_THRESHOLD_MS = 5 * 60_000; // 5 minutes\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/** Get current timestamp */\nfunction now(): number {\n return Date.now();\n}\n\n/** Format a duration in milliseconds to a human-readable string */\nfunction formatAge(ms: number): string {\n if (ms < 1000) return '0s';\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h`;\n const days = Math.floor(hours / 24);\n return `${days}d`;\n}\n\n/** Format uptime in milliseconds to a compact string */\nfunction formatUptime(ms: number): string {\n return formatAge(ms);\n}\n\n/** Simple glob pattern matching for hostname filtering */\nfunction matchGlob(pattern: string, value: string): boolean {\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n\n try {\n const regex = new RegExp(`^${regexPattern}$`, 'i');\n return regex.test(value);\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Instance Listing Functions\n// ============================================================================\n\n/**\n * Get list of all known instances (from persistent registry).\n */\nexport async function listInstances(options: InstanceListOptions = {}): Promise<InstanceInfo[]> {\n const { includeStale = false, hostname, status } = options;\n const timestamp = now();\n\n const registry = getPersistentProcessRegistry();\n const globalStatus = await registry.getGlobalStatus();\n \n const instances: InstanceInfo[] = [];\n const instanceMap = globalStatus.instances;\n\n for (const [instanceId, processes] of instanceMap) {\n if (processes.length === 0) continue;\n\n // Find the main process (protected: true, spawnMode: 'main')\n const mainProc = processes.find(p => p.spawnMode === 'main');\n const firstProc = processes.at(0);\n const mainPid = mainProc?.pid ?? firstProc?.pid ?? 0;\n const hostname_ = firstProc?.hostname ?? os.hostname();\n const startedAt = Math.min(...processes.map(p => p.startedAt));\n \n // Calculate last activity (most recent heartbeat)\n const lastActivity = Math.max(...processes.map(p => p.lastHeartbeat));\n \n // Determine status based on last activity\n const age = timestamp - lastActivity;\n let instanceStatus: 'active' | 'idle' | 'stale' = 'stale';\n if (age < IDLE_THRESHOLD_MS) instanceStatus = 'active';\n else if (age < STALE_THRESHOLD_MS) instanceStatus = 'idle';\n\n // Collect unique session IDs\n const sessionIds = new Set<string>();\n for (const proc of processes) {\n if (proc.sessionId) {\n sessionIds.add(proc.sessionId);\n }\n }\n\n // Apply filters\n if (!includeStale && instanceStatus === 'stale') continue;\n if (hostname && !matchGlob(hostname, hostname_)) continue;\n if (status && status !== 'all' && instanceStatus !== status) continue;\n\n instances.push({\n instanceId,\n hostname: hostname_,\n mainPid,\n startedAt,\n lastActivity,\n status: instanceStatus,\n processCount: processes.length,\n processes,\n sessionIds,\n });\n }\n\n // Sort by last activity (most recent first)\n instances.sort((a, b) => b.lastActivity - a.lastActivity);\n\n return instances;\n}\n\n/**\n * Get counts of instances by status.\n */\nexport async function getInstanceCount(): Promise<InstanceCounts> {\n const instances = await listInstances({ includeStale: true });\n const byHostname = new Map<string, number>();\n\n let active = 0;\n let idle = 0;\n let stale = 0;\n\n for (const inst of instances) {\n const current = byHostname.get(inst.hostname) ?? 0;\n byHostname.set(inst.hostname, current + 1);\n\n switch (inst.status) {\n case 'active': active++; break;\n case 'idle': idle++; break;\n case 'stale': stale++; break;\n }\n }\n\n return {\n total: instances.length,\n active,\n idle,\n stale,\n byHostname,\n };\n}\n\n/**\n * Get global process status across all instances.\n */\nexport async function getGlobalProcessStatus(): Promise<GlobalProcessStatus> {\n const timestamp = now();\n const registry = getPersistentProcessRegistry();\n const globalStatus = await registry.getGlobalStatus();\n const instances = await listInstances({ includeStale: true });\n\n // Local instance\n const localInstanceId = registry.getInstanceId();\n const localInstance = instances.find(i => i.instanceId === localInstanceId);\n \n let localProtectedCount = 0;\n if (localInstance) {\n localProtectedCount = localInstance.processes.filter(p => p.protected).length;\n }\n\n let activeInstanceCount = 0;\n for (const inst of instances) {\n if (inst.status === 'active') activeInstanceCount++;\n }\n\n return {\n localInstance: localInstance ? {\n instanceId: localInstance.instanceId,\n mainPid: localInstance.mainPid,\n protectedCount: localProtectedCount,\n platform: process.platform,\n hostname: localInstance.hostname,\n uptime: timestamp - localInstance.startedAt,\n } : {\n instanceId: localInstanceId,\n mainPid: process.pid,\n protectedCount: 0,\n platform: process.platform,\n hostname: os.hostname(),\n uptime: 0,\n },\n allInstances: instances.map(inst => ({\n instanceId: inst.instanceId,\n hostname: inst.hostname,\n mainPid: inst.mainPid,\n processes: inst.processes,\n startedAt: inst.startedAt,\n lastActivity: inst.lastActivity,\n })),\n summary: {\n totalProcesses: globalStatus.totalProcesses,\n protectedCount: globalStatus.protectedCount,\n staleCount: globalStatus.staleCount,\n instanceCount: instances.length,\n activeInstanceCount,\n },\n timestamp,\n };\n}\n\n// ============================================================================\n// Formatting Functions\n// ============================================================================\n\n/**\n * Format the global status as a human-readable string for display.\n */\nexport async function formatGlobalStatus(): Promise<string> {\n const status = await getGlobalProcessStatus();\n const lines: string[] = [];\n\n lines.push('=== WrongStack Global Process Status ===');\n lines.push(`Updated: ${new Date(status.timestamp).toISOString()}`);\n lines.push('');\n\n // Summary\n lines.push('Summary:');\n lines.push(` Total processes: ${status.summary.totalProcesses}`);\n lines.push(` Protected: ${status.summary.protectedCount}`);\n lines.push(` Stale entries: ${status.summary.staleCount}`);\n lines.push(` Instances: ${status.summary.instanceCount} (${status.summary.activeInstanceCount} active)`);\n lines.push('');\n\n // Local instance\n lines.push(`This instance (${status.localInstance.instanceId}):`);\n lines.push(` Main PID: ${status.localInstance.mainPid}`);\n lines.push(` Protected processes: ${status.localInstance.protectedCount}`);\n lines.push(` Platform: ${status.localInstance.platform} (${status.localInstance.hostname})`);\n lines.push(` Uptime: ${formatUptime(status.localInstance.uptime)}`);\n lines.push('');\n\n // Other instances\n for (const instance of status.allInstances) {\n if (instance.instanceId === status.localInstance.instanceId) continue;\n\n const age = Math.round((status.timestamp - instance.lastActivity) / 1000);\n lines.push(`Instance ${instance.instanceId} (${instance.hostname}):`);\n\n for (const proc of instance.processes) {\n const procAge = formatAge(status.timestamp - proc.startedAt);\n const heartbeatAge = formatAge(status.timestamp - proc.lastHeartbeat);\n const protected_ = proc.protected ? '[P]' : ' ';\n\n lines.push(\n ` ${protected_} ${String(proc.pid).padStart(6)} ${proc.name.padEnd(20)} ` +\n `started ${procAge.padStart(8)} heartbeat ${heartbeatAge.padStart(6)} ${proc.spawnMode}`\n );\n }\n lines.push(` Last activity: ${age}s ago`);\n lines.push('');\n }\n\n // Legend\n lines.push('Legend:');\n lines.push(' [P] = Protected (cannot be killed via bash)');\n lines.push(' main = Main WrongStack process');\n lines.push(' spawn = Spawned child process');\n lines.push(' fork = Forked process (e.g., worker threads)');\n\n return lines.join('\\n');\n}\n\n/**\n * Format a clean instance list suitable for display.\n */\nexport async function formatInstanceList(options: InstanceListOptions = {}): Promise<string> {\n const instances = await listInstances(options);\n const count = await getInstanceCount();\n const lines: string[] = [];\n\n lines.push('=== WrongStack Instances ===');\n lines.push(`Total: ${count.total} instances (${count.active} active, ${count.idle} idle, ${count.stale} stale)`);\n lines.push('');\n\n if (count.byHostname.size > 1) {\n lines.push('By hostname:');\n for (const [host, num] of count.byHostname) {\n lines.push(` ${host}: ${num} instance${num !== 1 ? 's' : ''}`);\n }\n lines.push('');\n }\n\n if (instances.length === 0) {\n lines.push('No instances found matching the filter.');\n return lines.join('\\n');\n }\n\n // Table header\n lines.push('INSTANCES:');\n lines.push(' ' + [\n 'STATUS'.padEnd(7),\n 'HOSTNAME'.padEnd(16),\n 'MAIN PID'.padEnd(9),\n 'PROCS'.padEnd(6),\n 'SESSIONS'.padEnd(8),\n 'UPTIME'.padEnd(8),\n 'LAST ACTIVITY',\n ].join(' '));\n lines.push(' ' + '-'.repeat(80));\n\n // Table rows\n for (const inst of instances) {\n const uptime = formatAge(Date.now() - inst.startedAt);\n const lastAct = formatAge(Date.now() - inst.lastActivity);\n const statusIcon = inst.status === 'active' ? '[*]' : inst.status === 'idle' ? '[-]' : '[ ]';\n\n lines.push(\n ' ' + [\n `${statusIcon} ${inst.status}`.padEnd(7),\n inst.hostname.padEnd(16),\n String(inst.mainPid).padEnd(9),\n String(inst.processCount).padEnd(6),\n String(inst.sessionIds.size).padEnd(8),\n uptime.padEnd(8),\n `${lastAct} ago`,\n ].join(' ')\n );\n }\n\n lines.push('');\n lines.push('Use /ps full for detailed process listing per instance.');\n\n return lines.join('\\n');\n}\n\n/**\n * Format instance details as a compact summary string.\n */\nexport async function formatInstanceSummary(): Promise<string> {\n const count = await getInstanceCount();\n const instances = await listInstances({ includeStale: false });\n\n if (instances.length === 0) {\n return 'No active WrongStack instances.';\n }\n\n const lines: string[] = [];\n lines.push(`${count.total} instance${count.total !== 1 ? 's' : ''}`);\n\n // Group by status\n const byStatus = new Map<string, number>();\n for (const inst of instances) {\n byStatus.set(inst.status, (byStatus.get(inst.status) ?? 0) + 1);\n }\n\n const parts: string[] = [];\n if (byStatus.get('active')) parts.push(`${byStatus.get('active')} active`);\n if (byStatus.get('idle')) parts.push(`${byStatus.get('idle')} idle`);\n if (byStatus.get('stale')) parts.push(`${byStatus.get('stale')} stale`);\n\n lines.push(`(${parts.join(', ')})`);\n\n // Total processes\n const totalProcs = instances.reduce((sum, inst) => sum + inst.processCount, 0);\n lines.push(`${totalProcs} total processes`);\n\n return lines.join(' ');\n}\n\n// ============================================================================\n// Slash Command\n// ============================================================================\n\n/**\n * Create the global /ps slash command.\n */\nexport function createGlobalPsSlashCommand() {\n return {\n name: 'ps' as const,\n description: 'List all WrongStack instances and their processes',\n\n async handler(input: string): Promise<{ message: string }> {\n try {\n const trimmed = input.trim();\n const parts = trimmed.split(/\\s+/);\n const sub = parts[0]?.toLowerCase() ?? '';\n\n // /ps list - show instance list\n if (sub === 'list' || sub === 'ls' || sub === '') {\n const output = await formatInstanceList();\n return { message: output };\n }\n\n // /ps summary - compact one-liner\n if (sub === 'summary' || sub === 'sum') {\n const output = await formatInstanceSummary();\n return { message: output };\n }\n\n // /ps full - detailed process listing\n if (sub === 'full' || sub === 'detail') {\n const output = await formatGlobalStatus();\n return { message: output };\n }\n\n // /ps count - just the count\n if (sub === 'count' || sub === 'num') {\n const count = await getInstanceCount();\n return {\n message: `${count.total} instance${count.total !== 1 ? 's' : ''} (${count.active} active, ${count.idle} idle, ${count.stale} stale)`,\n };\n }\n\n // /ps hostname <pattern> - filter by hostname\n if (sub === 'hostname' || sub === 'host') {\n const pattern = parts.slice(1).join(' ');\n if (!pattern) {\n return { message: 'Usage: /ps hostname <pattern> (e.g., /ps hostname workstation*)' };\n }\n const output = await formatInstanceList({ hostname: pattern });\n return { message: output };\n }\n\n // /ps status <active|idle|stale|all> - filter by status\n if (sub === 'status' || sub === 'state') {\n const filterStatus = parts[1]?.toLowerCase();\n if (!['active', 'idle', 'stale', 'all'].includes(filterStatus ?? '')) {\n return { message: 'Usage: /ps status <active|idle|stale|all>' };\n }\n const output = await formatInstanceList({ status: filterStatus as 'active' | 'idle' | 'stale' | 'all' });\n return { message: output };\n }\n\n return { message: 'Usage: /ps [list|summary|count|full|hostname <pattern>|status <state>]' };\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return { message: `Error getting process status: ${message}` };\n }\n },\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 { wstackGlobalRoot } from '@wrongstack/core/utils';\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 = '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;\n// A registry lock is only ever held for a brief read-modify-write; one older\n// than this means the holder crashed. This is the ONLY stale signal on\n// Windows, where process.kill(pid, 0) liveness checks are unreliable.\nconst LOCK_STALE_MS = 30_000;\nconst LOCKFILE = '.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 - decide whether it is stale and stealable.\n try {\n const content = await fs.readFile(lockfilePath, 'utf-8');\n const parts = content.split(':');\n const lockPid = parseInt(parts[0] ?? '0', 10);\n // Content is `pid:host:timestamp`; the last field is the\n // acquisition time (host is never empty, so index is safe).\n const lockTs = Number(parts[parts.length - 1]);\n const staleByAge = Number.isFinite(lockTs) && Date.now() - lockTs > LOCK_STALE_MS;\n\n let holderDead = false;\n if (process.platform !== 'win32' && Number.isFinite(lockPid) && lockPid > 0) {\n try {\n process.kill(lockPid, 0); // Signal 0 just checks if process exists\n } catch {\n holderDead = true;\n }\n }\n\n // Steal when the holder is provably dead (Unix) OR the lock is older\n // than LOCK_STALE_MS. The age check is the only stale signal on\n // Windows \u2014 without it a crashed holder's lock wedges the registry,\n // and with it every kill-guard, permanently.\n if (holderDead || staleByAge) {\n await fs.unlink(lockfilePath).catch(() => {});\n continue;\n }\n } catch {\n // Can't read lock file - assume stale, try to steal\n await fs.unlink(lockfilePath).catch(() => {});\n continue;\n }\n\n // Holder still alive and lock fresh \u2014 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 the file is missing, corrupted, or structurally wrong.\n */\nfunction freshRegistryData(): PersistentRegistryData {\n return {\n version: 1,\n instances: new Map(),\n protectedPatterns: ['wrongstack', 'node'],\n lastCleanup: Date.now(),\n };\n}\n\nasync function readRegistryFile(filePath: string): Promise<PersistentRegistryData> {\n let content: string;\n try {\n content = await fs.readFile(filePath, 'utf-8');\n } catch (err) {\n if (isNodeError(err) && err.code === 'ENOENT') return freshRegistryData();\n throw err; // genuine IO error (EACCES, EMFILE\u2026) \u2014 surface it\n }\n\n // A torn write (crash mid-persist) or hand-corruption must NOT throw here:\n // that would propagate through every kill-guard and wedge them forever with\n // no self-heal. Parse defensively and fall back to empty; the next write\n // overwrites the bad file.\n try {\n const parsed = JSON.parse(content) as Partial<PersistentRegistryData> & {\n instances?: unknown;\n };\n if (!parsed || typeof parsed !== 'object') return freshRegistryData();\n const base = freshRegistryData();\n return {\n version: 1,\n instances: Array.isArray(parsed.instances)\n ? new Map(parsed.instances as [string, PersistentProcessEntry][])\n : base.instances,\n protectedPatterns: Array.isArray(parsed.protectedPatterns)\n ? parsed.protectedPatterns\n : base.protectedPatterns,\n lastCleanup:\n typeof parsed.lastCleanup === 'number' ? parsed.lastCleanup : base.lastCleanup,\n };\n } catch {\n return freshRegistryData();\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 cleanupInterval: ReturnType<typeof setInterval> | null = null;\n private heartbeatRunning = false;\n private cleanupRunning = false;\n private isShuttingDown = false;\n private readonly onProcessExit = (): void => {\n this.runInBackground(\n this.syncToPersistent(),\n 'process_registry.exit_sync_failed',\n 'PersistentProcessRegistry: exit sync failed',\n );\n };\n\n constructor(baseRegistry?: ProcessRegistryImpl) {\n this.instanceId = generateInstanceId();\n const globalRoot = wstackGlobalRoot();\n this.registryPath = path.join(globalRoot, REGISTRY_FILE);\n this.lockPath = path.join(globalRoot, 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 private runInBackground(operation: Promise<void>, event: string, message: string): void {\n void operation.catch((err) => {\n emitStructuredLog('warn', event, message, err);\n });\n }\n\n /**\n * Start the heartbeat and periodic cleanup tasks.\n */\n start(): void {\n if (this.heartbeatInterval) return;\n this.isShuttingDown = false;\n\n // Register this instance's processes with the persistent registry\n this.heartbeat();\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 this.cleanupInterval = setInterval(() => {\n this.cleanup();\n }, STALE_THRESHOLD_MS);\n this.cleanupInterval.unref?.();\n\n // Register main process on startup\n this.registerMainProcess();\n\n // Sync on significant events\n process.on('exit', this.onProcessExit);\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 if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n process.off('exit', this.onProcessExit);\n this.runInBackground(\n this.syncToPersistent(),\n 'process_registry.stop_sync_failed',\n 'PersistentProcessRegistry: stop sync failed',\n );\n }\n\n /**\n * Register the main WrongStack process as protected.\n */\n registerMainProcess(): void {\n const mainPid = process.pid;\n\n this.runInBackground(\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 'process_registry.main_register_failed',\n 'PersistentProcessRegistry: failed to register main process',\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.runInBackground(\n this.updatePersistentEntry(entry),\n 'process_registry.child_register_failed',\n 'PersistentProcessRegistry: failed to register child process',\n );\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 || this.heartbeatRunning) return;\n\n this.heartbeatRunning = true;\n void this.syncToPersistent()\n .catch((err) => {\n emitStructuredLog(\n 'warn',\n 'process_registry.heartbeat_failed',\n 'PersistentProcessRegistry: heartbeat failed',\n err,\n );\n })\n .finally(() => {\n this.heartbeatRunning = false;\n });\n }\n\n private cleanup(): void {\n if (this.isShuttingDown || this.cleanupRunning) return;\n\n this.cleanupRunning = true;\n void this.cleanupStaleEntries()\n .catch((err) => {\n emitStructuredLog(\n 'warn',\n 'process_registry.periodic_cleanup_failed',\n 'PersistentProcessRegistry: periodic cleanup failed',\n err,\n );\n })\n .finally(() => {\n this.cleanupRunning = false;\n });\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 * 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 /** True for an explicitly detached/background tool launch. */\n background: 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 /** Leave explicitly backgrounded jobs alive. Default false. */\n preserveBackground?: boolean | 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 backgroundCount: 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(\n info: Omit<TrackedProcess, 'killed' | 'protected' | 'background'> & {\n protected?: boolean | undefined;\n background?: boolean | undefined;\n },\n ): void {\n this.processes.set(info.pid, {\n ...info,\n killed: false,\n protected: info.protected ?? false,\n background: info.background ?? false,\n });\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 this._pruneAllStale();\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 /** Count of active jobs explicitly launched in background mode. */\n get activeBackgroundCount(): number {\n let n = 0;\n for (const p of this.processes.values()) {\n if (p.background && !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 this._pruneAllStale();\n return {\n activeCount: this.activeCount,\n backgroundCount: this.activeBackgroundCount,\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, preserveBackground: true });\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 if (opts.preserveBackground && p.background) return false;\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 * Remove every stale entry, not just one PID. `list()`/`stats()` \u2014 the\n * surfaces the TUI status bar and `/ps` poll \u2014 must prune too: a child\n * whose 'close' event never fires (e.g. Windows grandchildren holding stdio\n * open) would otherwise linger in the registry until someone looks up its\n * exact PID, and PID reuse meanwhile makes `get()`/`kill()` target the\n * wrong process. RAM-leak audit 2026-07-31, MEDIUM.\n */\n private _pruneAllStale(): void {\n for (const [pid, entry] of this.processes) {\n if (this._isStaleEntry(entry)) 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}"],
5
+ "mappings": "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",
6
6
  "names": ["os", "os", "now", "hostname", "now", "STALE_THRESHOLD_MS", "hostname"]
7
7
  }