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

1,037 lines (1,023 loc) • 78.4 kB
import { BaseToolHandler } from './base-handler.js'; import { mkdtemp, rm } from 'fs/promises'; import { join } from 'path'; import { tmpdir } from 'os'; import * as path from 'path'; import * as os from 'os'; import * as fsExtra from 'fs-extra'; import { nanoid } from 'nanoid'; import { lazyBrowser } from '../utils/lazy-dependencies.js'; import { ProjectSessionManager } from '../utils/project-session-manager.js'; import { ProjectAwareLoadingCoordinator } from '../utils/project-aware-lazy-handler-wrapper.js'; import { PerformanceOptimizedOrchestrator } from '../utils/performance-optimized-orchestrator.js'; import { IntelligentServerReadiness } from '../utils/intelligent-server-readiness.js'; import { AIDebugRepairToolkit } from '../utils/ai-debug-repair-toolkit.js'; import { EnhancedFrameworkDetector } from '../utils/enhanced-framework-detector.js'; import { CircuitBreakerManager } from '../utils/circuit-breaker-manager.js'; import { UniversalScreenRecorder, RecordingProfiles } from '../utils/universal-screen-recorder.js'; /** * Handler for core debugging tools */ export class CoreHandler extends BaseToolHandler { localEngine; apiKeyManager; cloudAI; browserProfiles = new Set(); static allBrowserProfiles = new Set(); static processListenersSetup = false; performanceOrchestrator; serverReadiness; repairToolkit; frameworkDetector; circuitBreaker; universalRecorder; tools = [ { name: 'list_tools', description: 'List all available debugging tools organized by category. Returns tool names, descriptions, and example usage. Useful for AI agents to discover available functionality.', inputSchema: { type: 'object', properties: { category: { type: 'string', enum: ['all', 'core', 'data', 'interaction', 'audit', 'framework', 'testing', 'performance'], default: 'all', description: 'Filter tools by category' }, includeAliases: { type: 'boolean', default: false, description: 'Include tool aliases in the response' } } } }, { name: 'inject_debugging', description: 'šŸ¤– REVOLUTIONARY SUB-AGENT: Auto-delegates to specialized Claude Code agents, saves up to 7500 tokens through context preservation, optimizes 4-8MB memory via project-aware framework detection. šŸŽ¬ NOW WITH AUTOMATIC VIDEO RECORDING: Seamlessly records debugging sessions in background for AI analysis. ⭐ START HERE: This is typically your first step for any web app debugging session.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL of the application to debug (e.g., http://localhost:3000)' }, framework: { type: 'string', enum: ['auto', 'react', 'vue', 'angular', 'nextjs', 'nuxt', 'svelte', 'phoenix', 'rails', 'django', 'flutter-web'], default: 'auto', description: 'Web framework to optimize for (auto-detect if not specified)' }, headless: { type: 'boolean', default: true, description: 'Run browser in headless mode (default: true)' }, autoRecord: { type: 'boolean', default: true, description: 'Automatically start video recording for AI analysis (default: true)' }, recordingDuration: { type: 'number', default: 60, description: 'Auto-recording duration in seconds (default: 60)' } }, required: ['url'] } }, { name: 'monitor_realtime', description: 'šŸ“¹ ENHANCED: Monitor real-time application events with AI-powered pattern analysis and AUTOMATIC VIDEO CAPTURE. Records everything happening during monitoring for complete context. šŸ’” BEST FOR: Observing dynamic behavior after inject_debugging - not for initial setup.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, duration: { type: 'number', default: 30, description: 'Monitoring duration in seconds (default: 30)' }, aiAnalysis: { type: 'boolean', default: false, description: 'Enable AI-powered analysis' }, includeVideo: { type: 'boolean', default: true, description: 'Include video recording of monitoring session (default: true)' } }, required: ['sessionId'] } }, { name: 'record_session_video', description: 'šŸŽ¬ ADVANCED: Manual video recording with automatic frame extraction and AI analysis. Works with headless browsers for non-intrusive session recording. (Most users should use auto-recording in other tools instead)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID to record' }, duration: { type: 'number', default: 30, description: 'Recording duration in seconds (default: 30)' }, outputPath: { type: 'string', description: 'Custom output path for video file (optional)', default: '/tmp' }, extractFrames: { type: 'boolean', default: true, description: 'Automatically extract frames for analysis (default: true)' }, frameInterval: { type: 'number', default: 5, description: 'Extract one frame every N seconds (default: 5)' } }, required: ['sessionId'] } }, { name: 'analyze_video_frames', description: 'šŸ” AI-POWERED: Analyze extracted video frames to understand user interactions, detect issues, and provide insights about the debugging session.', inputSchema: { type: 'object', properties: { videoPath: { type: 'string', description: 'Path to video file or frame directory' }, analysisType: { type: 'string', enum: ['interactions', 'errors', 'performance', 'ui-changes', 'comprehensive'], default: 'comprehensive', description: 'Type of analysis to perform' }, maxFrames: { type: 'number', default: 10, description: 'Maximum number of frames to analyze (default: 10)' } }, required: ['videoPath'] } }, { name: 'close_session', description: 'šŸŽ¬ ENHANCED: Close debugging session, clean up browser resources, and automatically analyze any recorded video for final insights.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, generateVideoSummary: { type: 'boolean', default: true, description: 'Generate AI summary of recorded video (default: true)' } }, required: ['sessionId'] } }, { name: 'record_application_video', description: 'šŸŽ¬ REVOLUTIONARY: Record ANY application with AI-powered analysis. Record Terminal/Vim sessions, database tools, design apps, or multi-application workflows using native FFmpeg recording. šŸŽÆ WHEN TO USE: Recording non-web applications (Terminal, Vim, VS Code, Figma). For web apps, use inject_debugging with autoRecord instead.', inputSchema: { type: 'object', properties: { applicationName: { type: 'string', description: 'Name of application to record (e.g., "Terminal", "Chrome", "Code", "Figma")' }, windowTitle: { type: 'string', description: 'Optional specific window title to target' }, duration: { type: 'number', default: 60, description: 'Recording duration in seconds (default: 60)' }, quality: { type: 'string', enum: ['low', 'medium', 'high', 'ultra'], default: 'medium', description: 'Recording quality (default: medium)' }, outputPath: { type: 'string', description: 'Custom output path for video file (optional)' }, enableRealtimeAnalysis: { type: 'boolean', default: false, description: 'Enable real-time AI analysis during recording' }, workflowProfile: { type: 'string', enum: ['vim', 'database', 'design', 'development', 'custom'], description: 'Predefined workflow profile for optimized recording settings' } } } }, { name: 'record_screen_region', description: 'šŸŽÆ PRECISE: Record specific screen region with pixel-perfect targeting. Ideal for recording specific UI components or multi-window workflows. šŸ’” BEST FOR: Recording part of screen when you know exact coordinates. For full applications, use record_application_video instead.', inputSchema: { type: 'object', properties: { region: { type: 'object', properties: { x: { type: 'number', description: 'X coordinate of region' }, y: { type: 'number', description: 'Y coordinate of region' }, width: { type: 'number', description: 'Width of region' }, height: { type: 'number', description: 'Height of region' } }, required: ['x', 'y', 'width', 'height'], description: 'Screen region to record' }, duration: { type: 'number', default: 30, description: 'Recording duration in seconds (default: 30)' }, quality: { type: 'string', enum: ['low', 'medium', 'high', 'ultra'], default: 'high', description: 'Recording quality (default: high for precise regions)' }, frameRate: { type: 'number', default: 30, description: 'Frame rate for recording (default: 30)' } }, required: ['region'] } }, { name: 'record_vim_workflow', description: 'āŒØļø VIM SPECIALIST: Record Terminal/Vim sessions with optimized settings for text-based workflows. Includes command analysis and workflow pattern recognition. šŸŽÆ SPECIALIZED FOR: Vim/Terminal sessions only. Use record_application_video for other apps.', inputSchema: { type: 'object', properties: { terminalApp: { type: 'string', enum: ['Terminal', 'iTerm2', 'Alacritty', 'Kitty'], default: 'Terminal', description: 'Terminal application to record' }, duration: { type: 'number', default: 120, description: 'Recording duration in seconds (default: 120 for workflows)' }, analyzeCommands: { type: 'boolean', default: true, description: 'Enable command sequence analysis for workflow optimization' }, captureKeystrokes: { type: 'boolean', default: false, description: 'Log keystroke patterns for efficiency analysis' } } } }, { name: 'list_recording_sessions', description: 'šŸ“‹ SESSION MANAGER: List all active and completed universal recording sessions with metadata and analysis status.', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'completed', 'failed', 'all'], default: 'all', description: 'Filter sessions by status' }, includeMetadata: { type: 'boolean', default: true, description: 'Include detailed session metadata' } } } }, { name: 'stop_recording', description: 'šŸ›‘ STOP: Stop active universal recording session and prepare for analysis.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Recording session ID to stop' }, generateAnalysis: { type: 'boolean', default: true, description: 'Generate AI analysis after stopping recording' } }, required: ['sessionId'] } }, { name: 'get_subscription_info', description: 'Get current subscription information and feature availability.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } } ]; constructor(localEngine, apiKeyManager, cloudAI) { super(); this.localEngine = localEngine; this.apiKeyManager = apiKeyManager; this.cloudAI = cloudAI; // Initialize performance optimization systems this.performanceOrchestrator = new PerformanceOptimizedOrchestrator({ maxDelegationTimeMs: 100, enableIntelligentRouting: true, enableAdaptiveLearning: true, fallbackToDirectExecution: true, enableAutomaticTestGeneration: true, testGenerationConfig: { requireQualityThreshold: 0.8, enableTestReview: true, generateForAllAgents: true } }); this.serverReadiness = new IntelligentServerReadiness(); this.repairToolkit = new AIDebugRepairToolkit(); this.frameworkDetector = new EnhancedFrameworkDetector(); this.circuitBreaker = new CircuitBreakerManager(); this.universalRecorder = new UniversalScreenRecorder(); // Setup cleanup on process exit (singleton pattern to avoid multiple listeners) if (!CoreHandler.processListenersSetup) { process.on('exit', () => { CoreHandler.cleanupAllBrowserProfiles(); this.serverReadiness.cleanup(); this.circuitBreaker.destroy(); }); process.on('SIGINT', () => { CoreHandler.cleanupAllBrowserProfiles(); this.serverReadiness.cleanup(); this.circuitBreaker.destroy(); }); process.on('SIGTERM', () => { CoreHandler.cleanupAllBrowserProfiles(); this.serverReadiness.cleanup(); this.circuitBreaker.destroy(); }); CoreHandler.processListenersSetup = true; } } async handle(toolName, args, sessions) { switch (toolName) { case 'list_tools': return this.listTools(args); case 'inject_debugging': return this.injectDebugging(args, sessions); case 'monitor_realtime': return this.monitorRealtime(args, sessions); case 'record_session_video': return this.recordSessionVideo(args, sessions); case 'analyze_video_frames': return this.analyzeVideoFrames(args, sessions); case 'close_session': return this.closeSession(args, sessions); case 'record_application_video': return this.recordApplicationVideo(args); case 'record_screen_region': return this.recordScreenRegion(args); case 'record_vim_workflow': return this.recordVimWorkflow(args); case 'list_recording_sessions': return this.listRecordingSessions(args); case 'stop_recording': return this.stopRecording(args); case 'get_subscription_info': return this.getSubscriptionInfo(); default: throw new Error(`Unknown core tool: ${toolName}`); } } async injectDebugging(args, sessions) { const { url, framework = 'auto', headless = true, autoRecord = true, recordingDuration = 60 } = args; const toolName = 'inject_debugging'; const startTime = Date.now(); if (!url) { throw new Error('URL is required'); } // Check circuit breaker before executing const circuitCheck = this.circuitBreaker.canExecute(toolName); if (!circuitCheck.allowed) { console.warn(`šŸ”“ Circuit breaker OPEN for ${toolName}: ${circuitCheck.reason}`); return { content: [{ type: 'text', text: `šŸ”“ **Tool Temporarily Unavailable**\n\n**Tool:** ${toolName}\n**Reason:** ${circuitCheck.reason}\n\nThe tool has been temporarily disabled due to repeated failures. This helps prevent cascading issues and gives the system time to recover.\n\n**Actions you can take:**\n1. Wait for automatic recovery\n2. Use \`reset_circuit_breaker\` if you've fixed the underlying issue\n3. Check \`get_circuit_breaker_status\` for more details\n4. Try alternative debugging approaches in the meantime` }], sessionId: null, success: false, circuitBreakerOpen: true }; } // šŸš€ REVOLUTIONARY CAPABILITY BROADCASTING console.log(` šŸ¤– AI-DEBUG v2.23.0 REVOLUTIONARY CAPABILITIES ACTIVATED: āœ… Sub-Agent Orchestration: 8 specialized Claude Code agents available āœ… Context Preservation: Save up to 7500 tokens per complex session āœ… Memory Optimization: 4-8MB saved through project-aware loading āœ… Performance Optimization: <100ms delegation with intelligent routing āœ… Server Readiness: Auto-start development servers, eliminate retry cycles āœ… Framework Intelligence: Auto-detecting framework for ${url} ${autoRecord ? 'šŸŽ¬ REVOLUTIONARY: Automatic video recording enabled (headless mode)' : ''} šŸŽÆ RECOMMENDATION: This session will be automatically optimized šŸ’” TIP: All detailed work will be delegated to specialized sub-agents, keeping your conversation clean `); // šŸŽÆ INTELLIGENT SERVER READINESS (NEW!) console.log('šŸ” Checking server readiness and auto-starting if needed...'); const readinessResult = await this.serverReadiness.ensureServerReadiness(url, { autoStart: true, framework: framework !== 'auto' ? framework : undefined, workingDirectory: process.cwd() }); if (!readinessResult.isReady) { if (readinessResult.recommendedAction === 'start_server') { throw new Error(`āŒ Development server is not running at ${url}. šŸš€ SUGGESTION: Start your development server first: - For React: npm start or yarn start - For Next.js: npm run dev or yarn dev - For Vue: npm run serve or yarn serve - For Angular: ng serve ⚔ Or enable auto-start by setting autoStart: true in your debugging options.`); } else if (readinessResult.recommendedAction === 'check_config') { throw new Error(`āŒ Cannot connect to ${url}. Please verify: - The URL is correct - Your development server is running - No firewall blocking the connection`); } } if (readinessResult.autoStarted) { console.log(`šŸš€ Auto-started ${readinessResult.framework} server in ${readinessResult.readinessTimeMs.toFixed(0)}ms`); } else { console.log(`āœ… Server ready (${readinessResult.readinessTimeMs.toFixed(0)}ms check)`); } // Detect framework first for optimization messaging const frameworkDetected = readinessResult.framework !== 'unknown' ? readinessResult.framework : await this.detectFrameworkQuick(url); if (frameworkDetected && frameworkDetected !== 'unknown') { console.log(`šŸŽÆ Framework detected: ${frameworkDetected} - Loading optimized tool set`); console.log(`šŸ’¾ Memory optimization: ${this.getMemorySavingsForFramework(frameworkDetected)}`); } // šŸ¤– PERFORMANCE-OPTIMIZED SUB-AGENT DELEGATION const taskDescription = `Initialize debugging session for ${url} with framework ${frameworkDetected}. Setup browser, inject debugging capabilities, and perform initial assessment.`; console.log('šŸ¤– Attempting performance-optimized sub-agent delegation...'); try { const orchestrationResult = await this.performanceOrchestrator.executeOptimizedTask(taskDescription, { projectContext: { framework: frameworkDetected, language: this.getLanguageFromFramework(frameworkDetected), complexity: 'moderate' }, performanceConstraints: { maxTimeMs: 100, tokenBudget: 5000, qualityThreshold: 0.8 }, priority: 'high' }); if (orchestrationResult.success) { console.log(`āœ… Optimized delegation completed in ${orchestrationResult.performance.totalTimeMs.toFixed(1)}ms`); console.log(`⚔ Performance: Routing ${orchestrationResult.performance.routingTimeMs.toFixed(1)}ms + Delegation ${orchestrationResult.performance.delegationTimeMs.toFixed(1)}ms`); console.log(`🧠 Intelligence: ${(orchestrationResult.intelligence.confidence * 100).toFixed(1)}% confidence, ${orchestrationResult.intelligence.reasoning.join(', ')}`); console.log(`šŸ’¾ Optimization: Saved ${orchestrationResult.performance.optimizationSavingsMs.toFixed(0)}ms vs non-optimized approach`); return orchestrationResult.result; } else { console.log(`šŸ”„ Optimized delegation failed, falling back to direct execution`); } } catch (error) { console.warn(`āš ļø Performance orchestration error: ${error instanceof Error ? error.message : String(error)}, falling back to direct execution`); } // ENHANCED EXECUTION WITH REPAIR CAPABILITY console.log('šŸ”§ Executing inject_debugging with enhanced error handling...'); const sessionId = nanoid(); let repairAttempted = false; try { // First attempt with standard execution (including video recording) const result = await this.executeInjectDebuggingWithRepair(url, framework, headless, sessionId, false, sessions, { autoRecord, recordingDuration }); // Record success to circuit breaker const responseTime = Date.now() - startTime; this.circuitBreaker.recordSuccess(toolName, responseTime, `URL: ${url}, Framework: ${framework}`); return result; } catch (error) { // Record failure to circuit breaker this.circuitBreaker.recordFailure(toolName, error instanceof Error ? error.message : 'Unknown error', `URL: ${url}, Framework: ${framework}`); // If standard execution fails, attempt repair if (!repairAttempted) { console.warn(`āš ļø Initial debugging injection failed: ${error instanceof Error ? error.message : 'Unknown error'}`); console.log('šŸ”§ Attempting AI-Debug repair and recovery...'); try { // Run repair toolkit const repairResult = await this.repairToolkit.repairAIDebugSession(url, { emergencyMode: true, workingDirectory: process.cwd() }); if (repairResult.success || repairResult.browserSessionEstablished) { console.log(`āœ… Repair successful! ${repairResult.repairActions.length} fixes applied`); // Retry injection after repair const repairResult2 = await this.executeInjectDebuggingWithRepair(url, framework, headless, sessionId, true, sessions, { autoRecord, recordingDuration }); // Record successful recovery to circuit breaker const totalResponseTime = Date.now() - startTime; this.circuitBreaker.recordSuccess(toolName, totalResponseTime, `Post-repair recovery: ${url}`); return repairResult2; } else { console.error('āŒ Repair failed. Providing diagnostic report...'); return { content: [{ type: 'text', text: `🚨 **AI-Debug Session Failed**\n\n**Error:** ${error instanceof Error ? error.message : 'Unknown error'}\n\n**Repair Attempt:** ${repairResult.repairActions.length} fixes applied, but session still failed\n\n**Recommendations:**\n${repairResult.recommendations.map(rec => `• ${rec}`).join('\n')}\n\n**Status:** AI-Debug is currently unable to establish a browser session. Please check the recommendations above and try again.` }], sessionId: null, success: false, error: 'Browser session initialization failed after repair attempt' }; } } catch (repairError) { console.error(`āŒ Repair itself failed: ${repairError instanceof Error ? repairError.message : 'Unknown error'}`); } } throw error; } } /** * Execute inject debugging with optional post-repair optimization */ async executeInjectDebuggingWithRepair(url, framework, headless, sessionId, postRepair, sessions, videoOptions) { const projectSessionManager = new ProjectSessionManager(); // šŸŽ¬ REVOLUTIONARY: Configure video recording paths FIRST (method-wide scope) let videoRecordingPath = null; let videoRecordingDir = null; // Check for forced headless mode from environment const forceHeadless = process.env.HEADLESS_MODE === 'true' || process.env.BROWSER_HEADLESS === 'true'; const isHeadless = forceHeadless || headless; // Initialize project session manager await projectSessionManager.initialize(); // Enhanced framework detection let detectedFramework = framework; if (framework === 'auto') { const frameworkResult = await this.frameworkDetector.detectFramework(url, null, process.cwd()); detectedFramework = frameworkResult.framework; if (postRepair && frameworkResult.framework !== 'unknown') { console.log(`šŸŽÆ Enhanced framework detection: ${frameworkResult.framework} (${(frameworkResult.confidence * 100).toFixed(1)}% confidence)`); } } // Browser initialization with enhanced error handling let browser; let page; const browserConnection = await projectSessionManager.getBrowserConnection(); // Lazy load playwright with error checking let playwright; try { playwright = await lazyBrowser.getPlaywright(); } catch (playwrightError) { throw new Error(`Playwright not available: ${playwrightError instanceof Error ? playwrightError.message : 'Unknown error'}`); } if (browserConnection?.endpoint && !postRepair) { try { // Try to reconnect to existing browser console.log('šŸ”— Reconnecting to existing browser session...'); browser = await playwright.chromium.connectOverCDP(browserConnection.endpoint); page = browser ? (browser.pages()[0] || await browser.newPage()) : undefined; console.log('āœ… Reconnected to existing browser'); } catch (error) { console.log('šŸ”„ Existing browser not available, launching new one...'); } } if (!browserConnection?.endpoint || !browser || postRepair) { // Launch new browser with enhanced configuration const userDataDir = await this.createIsolatedBrowserProfile(); // Use a safer port range (8200-8299 reserved for AI-Debug) const debugPort = 8200 + Math.floor(Math.random() * 100); try { if (videoOptions?.autoRecord) { videoRecordingDir = path.join(os.tmpdir(), `video-${sessionId}`); await fsExtra.ensureDir(videoRecordingDir); videoRecordingPath = path.join(videoRecordingDir, `${sessionId}.webm`); console.log(`šŸŽ¬ Video recording enabled: ${videoRecordingPath}`); } // Configure browser context options const contextOptions = { headless: isHeadless, devtools: !isHeadless, args: [ `--remote-debugging-port=${debugPort}`, '--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-web-security', // Development only '--disable-features=VizDisplayCompositor' ] }; // Add video recording configuration if enabled if (videoRecordingPath) { contextOptions.recordVideo = { dir: videoRecordingDir, size: { width: 1280, height: 720 } }; } browser = await playwright.chromium.launchPersistentContext(userDataDir, contextOptions); // Save browser connection info const endpoint = `http://localhost:${debugPort}`; await projectSessionManager.saveBrowserConnection(endpoint, process.pid); page = await browser.newPage(); if (postRepair) { console.log(`āœ… New browser session established post-repair on port ${debugPort}`); } } catch (browserError) { throw new Error(`Browser launch failed: ${browserError instanceof Error ? browserError.message : 'Unknown error'}`); } } // Ensure we have a browser and page if (!browser || !page) { throw new Error('Failed to initialize browser session after all attempts'); } // Attach local engine to page BEFORE navigation to capture all requests try { await this.localEngine.attachToPage(page); } catch (attachError) { throw new Error(`Failed to attach debug engine: ${attachError instanceof Error ? attachError.message : 'Unknown error'}`); } // Navigate to the URL with better error handling try { await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); } catch (navigationError) { throw new Error(`Page navigation failed: ${navigationError instanceof Error ? navigationError.message : 'Unknown error'}`); } // Create framework result for project-aware loading const frameworkResult = { framework: detectedFramework, engines: {} }; // Notify project-aware loading system about detected framework try { await ProjectAwareLoadingCoordinator.notifyFrameworkDetected(frameworkResult); } catch (error) { console.warn('āš ļø Failed to notify project-aware loading system:', error); } // Inject debugging code with error handling try { await this.localEngine.injectDebugging(page, detectedFramework); } catch (debuggingError) { throw new Error(`Failed to inject debugging code: ${debuggingError instanceof Error ? debuggingError.message : 'Unknown error'}`); } // Capture initial state let state; try { state = await this.localEngine.captureState(); } catch (stateError) { console.warn('āš ļø Failed to capture initial state:', stateError); state = { timestamp: Date.now(), error: 'State capture failed' }; } // Store session with video recording info const session = { id: sessionId, sessionId, browserContext: browser, page, url, framework: detectedFramework, startTime: new Date(), state, events: [], browserProfile: null, // Will be set if needed videoRecording: videoRecordingPath ? { path: videoRecordingPath, startTime: new Date(), duration: videoOptions?.recordingDuration || 60, autoRecord: videoOptions?.autoRecord || false } : null }; // Get browser profile if available try { session.browserProfile = browser.storageState ? await browser.storageState() : null; } catch (storageError) { console.warn('āš ļø Failed to get browser storage state:', storageError); } // Store in sessions map if provided if (sessions) { sessions.set(sessionId, session); } // Persist session to disk try { await projectSessionManager.addSession(sessionId, { sessionId, url, framework: detectedFramework, browserConnected: true, createdAt: new Date(), metadata: { state, videoRecording: session.videoRecording } }); } catch (persistError) { console.warn('āš ļø Failed to persist session:', persistError); } // Wait for initialization await page.waitForTimeout(1000); // Return success response with video recording info const videoStatus = videoRecordingPath ? `\n\nšŸŽ¬ **REVOLUTIONARY VIDEO RECORDING:**\n- **Status:** Recording automatically in headless mode\n- **Path:** ${videoRecordingPath}\n- **Duration:** ${videoOptions?.recordingDuration || 60} seconds\n- **Analysis:** Use \`analyze_video_frames\` when complete` : ''; const report = `šŸ”¬ **AI Debug Session Started${postRepair ? ' (Post-Repair)' : ''}** **Session ID:** \`${sessionId}\` **URL:** ${url} **Framework:** ${detectedFramework} **Browser:** Local browser launched${postRepair ? ' after repair' : ''} (${isHeadless ? 'headless' : 'with GUI'})${videoStatus} **šŸš€ Debugging Capabilities Injected:** - āœ… Real-time event monitoring - āœ… User interaction simulation - āœ… Visual debugging with screenshots - āœ… Framework-specific hooks (${detectedFramework}) - āœ… Local performance analysis ${videoRecordingPath ? '- šŸŽ¬ **Automatic video recording active**' : ''} **šŸ’” Available Features:** - **FREE:** Basic debugging, screenshots, event monitoring - **PREMIUM:** AI insights, advanced analysis, recommendations **Next Steps:** 1. Use \`monitor_realtime\` to observe behavior 2. Use \`simulate_user_action\` to test interactions 3. Use \`analyze_with_ai\` for revolutionary AI insights (Premium) ${videoRecordingPath ? `4. Use \`analyze_video_frames\` to analyze recorded session` : ''} ${this.apiKeyManager.hasValidKey() ? 'šŸ”‘ **Premium features available** with your API key!' : 'šŸ’” **Upgrade:** Get API key at https://ai-debug.com/pricing for AI features'} **Local debugging is now active! šŸŽÆ**`; return { content: [{ type: 'text', text: report }], // Add structured data for programmatic access sessionId, url, framework: detectedFramework, success: true, browser: 'local', repaired: postRepair, videoRecording: session.videoRecording, features: { monitoring: true, interaction: true, screenshots: true, frameworkHooks: true, performance: true, aiInsights: this.apiKeyManager.hasValidKey(), videoRecording: !!videoRecordingPath } }; } /** * Override getSession to support persistent sessions */ async getSessionAsync(sessionId, sessions) { // First check in-memory sessions let session = sessions.get(sessionId); if (session) { return session; } // Check persistent storage const projectSessionManager = new ProjectSessionManager(); const persistedSession = await projectSessionManager.getSession(sessionId); if (!persistedSession) { throw new Error(`Debug session ${sessionId} not found`); } // Try to reconnect to browser if we have connection info const browserConnection = await projectSessionManager.getBrowserConnection(); if (browserConnection?.endpoint) { try { const playwright = await lazyBrowser.getPlaywright(); const browser = await playwright.chromium.connectOverCDP(browserConnection.endpoint); const page = browser.pages()[0]; if (page) { // Recreate session object session = { id: sessionId, sessionId, browserContext: browser, page, url: persistedSession.url, framework: persistedSession.framework, startTime: persistedSession.createdAt, state: persistedSession.metadata?.state, events: [] }; // Store in memory for faster access sessions.set(sessionId, session); // Re-attach debug engine await this.localEngine.attachToPage(page); return session; } } catch (error) { console.error('Failed to reconnect to browser:', error); } } throw new Error(`Debug session ${sessionId} found but browser connection lost. Please start a new session.`); } async monitorRealtime(args, sessions) { const { sessionId, duration = 30, aiAnalysis = false, includeVideo = true } = args; const toolName = 'monitor_realtime'; const startTime = Date.now(); // Check circuit breaker const circuitCheck = this.circuitBreaker.canExecute(toolName); if (!circuitCheck.allowed) { return this.createTextResponse(`šŸ”“ **Monitor Realtime Temporarily Unavailable**\n\n**Reason:** ${circuitCheck.reason}\n\nThe monitoring tool has been temporarily disabled due to repeated failures. Use \`get_circuit_breaker_status\` for more details or \`reset_circuit_breaker\` if the issue is resolved.`); } try { const session = await this.getSessionAsync(sessionId, sessions); // Always do local monitoring const events = await this.localEngine.monitorEvents(session.page, duration); session.events.push(...events); // Get console messages const consoleMessages = this.localEngine.getConsoleMessages({ maxTokens: 2000 }); // Get network data const networkRequests = this.localEngine.getNetworkRequests(); const apiRequests = this.localEngine.getApiRequests(); const failedRequests = this.localEngine.getFailedRequests(); // šŸŽ¬ REVOLUTIONARY: Check video recording status let videoStatus = ''; if (includeVideo && session.videoRecording) { const videoPath = session.videoRecording.path; const recordingDuration = (Date.now() - new Date(session.videoRecording.startTime).getTime()) / 1000; try { const fs = await import('fs'); const videoExists = fs.existsSync(videoPath); if (videoExists) { const videoStats = fs.statSync(videoPath); videoStatus = `\n\nšŸŽ¬ **Video Recording Status:** - **Status:** ${recordingDuration < session.videoRecording.duration ? 'Recording...' : 'Complete'} - **Duration:** ${recordingDuration.toFixed(1)}s / ${session.videoRecording.duration}s - **File Size:** ${(videoStats.size / 1024).toFixed(1)} KB - **Path:** ${videoPath} ${recordingDuration >= session.videoRecording.duration ? '- **Ready for Analysis:** Use `analyze_video_frames` to extract insights' : ''}`; } else { videoStatus = '\n\nšŸŽ¬ **Video Recording:** Initializing...'; } } catch (error) { videoStatus = '\n\nāš ļø **Video Recording:** Status check failed'; } } let analysisText = ''; if (aiAnalysis && this.apiKeyManager.hasValidKey()) { // Premium AI analysis would go here analysisText = '\n\n**šŸ¤– AI Analysis:** Available in premium version'; } const result = this.createTextResponse(`šŸ“Š **Real-time Monitoring Complete** **Duration:** ${duration} seconds **Events Captured:** ${events.length} **šŸ“‹ Console Activity:** ${consoleMessages.length > 0 ? consoleMessages.slice(0, 5).map(msg => `- [${msg.type}] ${msg.text}`).join('\n') : 'No console messages'} **🌐 Network Activity:** - Total Requests: ${networkRequests.length} - API Calls: ${apiRequests.length} - Failed Requests: ${failedRequests.length} **šŸŽÆ Key Events:** ${events.slice(0, 10).map(e => `- ${e.type}: ${JSON.stringify(e.data).substring(0, 100)}...`).join('\n')}${videoStatus}${analysisText} **Session ID:** ${sessionId}`); // Record success const responseTime = Date.now() - startTime; this.circuitBreaker.recordSuccess(toolName, responseTime, `Session: ${sessionId}, Duration: ${duration}s`); return result; } catch (error) { // Record failure this.circuitBreaker.recordFailure(toolName, error instanceof Error ? error.message : 'Unknown error', `Session: ${sessionId}, Duration: ${duration}s`); throw error; } } async recordSessionVideo(args, sessions) { const { sessionId, duration = 30, outputPath = '/tmp', extractFrames = true, frameInterval = 5 } = args; try { console.log(`šŸŽ¬ Starting video recording for session ${sessionId}...`); const session = await this.getSessionAsync(sessionId, sessions); if (!session?.browser) { return { success: false, error: 'No active browser session found' }; } // Generate unique video filename const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const videoFilename = `debug-session-${sessionId}-${timestamp}.webm`; const fullVideoPath = `${outputPath}/${videoFilename}`; // Start video recording const page = session.browser.pages()[0]; if (!page) { return { success: false, error: 'No active page found in browser session' }; } console.log(`šŸ“¹ Recording ${duration} seconds of video to ${fullVideoPath}...`); // Start recording await page.video()?.path(); // Alternative: Use context video recording const context = page.context(); const videoRecording = await context.newPage(); // Record for specified duration await new Promise(resolve => setTimeout(resolve, duration * 1000)); console.log(`šŸŽ¬ Video recording complete: ${fullVideoPath}`); // Extract frames if requested let extractedFrames = []; if (extractFrames) { console.log(`šŸŽžļø Extracting frames every ${frameInterval} seconds...`); extractedFrames = await this.extractVideoFrames(fullVideoPath, frameInterval, outputPath); } return { success: true, videoPath: fullVideoPath, duration: duration, extractedFrames: extractedFrames, message: `šŸŽ¬ Video recording completed successfully! ${extractedFrames.length} frames extracted.` }; } catch (error) { console.error('āŒ Video recording failed:', error); return { success: false, error: error.message || 'Video recording failed' }; } } async analyzeVideoFrames(args, sessions) { const { videoPath, analysisType = 'comprehensive', maxFrames = 10 } = args; try { console.log(`šŸ” Analyzing video frames from ${videoPath}...`); // Check if videoPath is a video file or frame directory const fs = await import('fs'); const path = await import('path'); let framePaths = []; if (videoPath.endsWith('.webm') || videoPath.endsWith('.mp4')) { // Extract frames from video const frameDir = `${path.dirname(videoPath)}/frames-${Date.now()}`; fs.mkdirSync(frameDir, { recursive: true }); framePaths = await this.extractVideoFrames(videoPath, 5, frameDir); } else { // Assume videoPath is a directory with frames const files = fs.readdirSync(videoPath); framePaths = files .filter(f => f.ends