UNPKG

ai-debug-local-mcp

Version:

๐ŸŽฏ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

298 lines โ€ข 10.8 kB
/** * Browser Process Guardian * Monitors and manages browser processes to prevent zombies and resource leaks */ import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export class BrowserProcessGuardian { static instance; browserProcesses = new Map(); maxProcesses = 20; maxProcessAge = 30 * 60 * 1000; // 30 minutes maxMemoryPerProcess = 512; // 512 MB per process killCount = 0; monitoringInterval = null; static getInstance() { if (!BrowserProcessGuardian.instance) { BrowserProcessGuardian.instance = new BrowserProcessGuardian(); } return BrowserProcessGuardian.instance; } /** * Start monitoring browser processes */ startMonitoring() { if (this.monitoringInterval) { return; // Already monitoring } console.log('๐Ÿ–ฅ๏ธ Starting browser process monitoring...'); // Monitor every 2 minutes this.monitoringInterval = setInterval(async () => { try { await this.monitorProcesses(); await this.cleanupZombieProcesses(); await this.enforceResourceLimits(); } catch (error) { console.error('โš ๏ธ Browser process monitoring error:', error); } }, 2 * 60 * 1000); // Initial cleanup this.cleanupExistingBrowserProcesses(); } /** * Stop monitoring browser processes */ stopMonitoring() { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); this.monitoringInterval = null; console.log('๐Ÿ›‘ Stopped browser process monitoring'); } } /** * Register a new browser process */ registerBrowserProcess(pid, sessionId, processName = 'browser') { const process = { pid, sessionId, createdAt: Date.now(), lastActivity: Date.now(), processName, status: 'active' }; this.browserProcesses.set(pid, process); console.log(`๐Ÿ“ Registered browser process ${pid} for session ${sessionId}`); // Check if we're over the limit if (this.browserProcesses.size > this.maxProcesses) { console.warn(`โš ๏ธ Browser process limit exceeded (${this.browserProcesses.size}/${this.maxProcesses})`); this.cleanupOldestProcesses(5); // Kill 5 oldest processes } } /** * Update process activity timestamp */ updateProcessActivity(pid) { const process = this.browserProcesses.get(pid); if (process) { process.lastActivity = Date.now(); } } /** * Unregister a browser process (normal cleanup) */ unregisterBrowserProcess(pid) { const process = this.browserProcesses.get(pid); if (process) { process.status = 'terminated'; this.browserProcesses.delete(pid); console.log(`โœ… Unregistered browser process ${pid}`); } } /** * Monitor all tracked browser processes */ async monitorProcesses() { const currentProcesses = Array.from(this.browserProcesses.values()); for (const process of currentProcesses) { try { const isRunning = await this.isProcessRunning(process.pid); const memoryMB = await this.getProcessMemoryMB(process.pid); if (!isRunning) { process.status = 'zombie'; console.warn(`๐Ÿ’€ Detected zombie browser process ${process.pid}`); } else { process.memoryMB = memoryMB; process.status = 'active'; } } catch (error) { console.warn(`โš ๏ธ Failed to monitor process ${process.pid}:`, error); process.status = 'zombie'; } } } /** * Clean up zombie and stale browser processes */ async cleanupZombieProcesses() { const now = Date.now(); const processesToKill = []; for (const process of this.browserProcesses.values()) { const age = now - process.lastActivity; // Mark for cleanup if zombie or too old if (process.status === 'zombie' || age > this.maxProcessAge) { processesToKill.push(process); } } if (processesToKill.length > 0) { console.log(`๐Ÿงน Cleaning up ${processesToKill.length} stale browser processes`); for (const process of processesToKill) { await this.killProcess(process.pid); this.browserProcesses.delete(process.pid); } } } /** * Enforce resource limits (memory, process count) */ async enforceResourceLimits() { const processesToKill = []; // Check memory limits for (const process of this.browserProcesses.values()) { if (process.memoryMB && process.memoryMB > this.maxMemoryPerProcess) { console.warn(`๐Ÿšจ Process ${process.pid} using ${process.memoryMB}MB (limit: ${this.maxMemoryPerProcess}MB)`); processesToKill.push(process); } } // Kill processes that exceed limits for (const process of processesToKill) { await this.killProcess(process.pid); this.browserProcesses.delete(process.pid); } } /** * Clean up oldest processes when limit is exceeded */ async cleanupOldestProcesses(count) { const processes = Array.from(this.browserProcesses.values()) .sort((a, b) => a.lastActivity - b.lastActivity) .slice(0, count); console.log(`๐Ÿงน Cleaning up ${processes.length} oldest browser processes`); for (const process of processes) { await this.killProcess(process.pid); this.browserProcesses.delete(process.pid); } } /** * Check if a process is still running */ async isProcessRunning(pid) { try { process.kill(pid, 0); // Signal 0 checks if process exists return true; } catch (error) { return false; } } /** * Get process memory usage in MB */ async getProcessMemoryMB(pid) { try { if (process.platform === 'darwin') { const { stdout } = await execAsync(`ps -o rss= -p ${pid}`); const rssKB = parseInt(stdout.trim()); return Math.round(rssKB / 1024); // Convert KB to MB } else if (process.platform === 'linux') { const { stdout } = await execAsync(`ps -o rss= -p ${pid}`); const rssKB = parseInt(stdout.trim()); return Math.round(rssKB / 1024); } else { // Windows const { stdout } = await execAsync(`tasklist /FI "PID eq ${pid}" /FO CSV`); const lines = stdout.split('\\n'); if (lines.length > 1) { const memStr = lines[1].split(',')[4].replace(/"/g, '').replace(/,/g, ''); const memKB = parseInt(memStr); return Math.round(memKB / 1024); } } } catch (error) { // Process might not exist or permission denied } return 0; } /** * Kill a browser process */ async killProcess(pid) { try { console.log(`๐Ÿ’€ Killing browser process ${pid}`); if (process.platform === 'win32') { await execAsync(`taskkill /F /T /PID ${pid}`); } else { // First try SIGTERM, then SIGKILL try { process.kill(pid, 'SIGTERM'); await this.sleep(2000); // Wait 2 seconds if (await this.isProcessRunning(pid)) { process.kill(pid, 'SIGKILL'); } } catch (error) { // Process might already be dead } } this.killCount++; } catch (error) { console.warn(`โš ๏ธ Failed to kill process ${pid}:`, error); } } /** * Clean up any existing browser processes on startup */ async cleanupExistingBrowserProcesses() { try { console.log('๐Ÿงน Cleaning up existing browser processes...'); if (process.platform === 'darwin' || process.platform === 'linux') { // Kill chrome/chromium processes // SAFE: Only kill AI-Debug specific browser processes await execAsync('pkill -f "ai-debug-browser" || true'); } else if (process.platform === 'win32') { // SAFE: Only kill AI-Debug specific browser processes on Windows await execAsync('wmic process where "CommandLine like \'%ai-debug-browser%\'" delete || echo "No AI-Debug browser processes"'); } console.log('โœ… Existing browser process cleanup completed'); } catch (error) { console.warn('โš ๏ธ Browser process cleanup failed:', error); } } /** * Get process statistics */ getProcessStats() { const processes = Array.from(this.browserProcesses.values()); const activeProcesses = processes.filter(p => p.status === 'active').length; const zombieProcesses = processes.filter(p => p.status === 'zombie').length; const totalMemoryMB = processes.reduce((sum, p) => sum + (p.memoryMB || 0), 0); return { totalProcesses: processes.length, activeProcesses, zombieProcesses, totalMemoryMB, processesKilled: this.killCount }; } /** * Force cleanup of all browser processes */ async forceCleanupAll() { console.log('๐Ÿšจ Force cleanup of all browser processes...'); const processes = Array.from(this.browserProcesses.values()); for (const process of processes) { await this.killProcess(process.pid); } this.browserProcesses.clear(); await this.cleanupExistingBrowserProcesses(); console.log('โœ… Force cleanup completed'); } /** * Sleep utility */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } //# sourceMappingURL=browser-process-guardian.js.map