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

436 lines 17.8 kB
/** * Session Stability Manager * Addresses critical feedback: "Had to restart sessions multiple times, losing context and workflow momentum" * Provides robust session recovery, state persistence, and connection resilience */ import { EventEmitter } from 'events'; export class SessionStabilityManager extends EventEmitter { sessions = new Map(); recoveryAttempts = new Map(); maxRecoveryAttempts = 3; sessionTimeout = 30 * 60 * 1000; // 30 minutes heartbeatInterval = 5000; // 5 seconds heartbeatTimers = new Map(); constructor() { super(); this.startGlobalMonitoring(); } /** * Create a new session with stability tracking * Addresses: "Easy session management but session instability issues" */ async createStableSession(url, options = {}) { const sessionId = this.generateSessionId(); const now = Date.now(); const sessionState = { sessionId, url, framework: options.framework, lastActivity: now, browserConnected: false, accessibility: { enabled: false }, quantumDebug: { initialized: false }, tools: {}, context: { workflowPhase: options.workflowPhase, debuggingGoal: options.debuggingGoal, reproducedIssue: false } }; this.sessions.set(sessionId, sessionState); this.startSessionHeartbeat(sessionId); this.emit('sessionCreated', { sessionId, state: sessionState }); return { sessionId, state: sessionState }; } /** * Update session state with tool usage tracking * Addresses: "Context loss during debugging" */ updateSessionState(sessionId, updates) { const session = this.sessions.get(sessionId); if (!session) { console.warn(`Session ${sessionId} not found for state update`); return null; } // Merge updates Object.assign(session, updates, { lastActivity: Date.now() }); this.emit('sessionUpdated', { sessionId, state: session }); return session; } /** * Track tool usage with success/failure monitoring * Addresses: "Tools failing due to unmet requirements" */ trackToolUsage(sessionId, toolName, result) { const session = this.sessions.get(sessionId); if (!session) return; session.tools[toolName] = { lastUsed: Date.now(), status: result.status, errorDetails: result.errorDetails }; session.lastActivity = Date.now(); // Special handling for critical tools if (toolName === 'flutter_enable_accessibility') { session.accessibility.enabled = result.status === 'success'; session.accessibility.lastAttempt = Date.now(); if (result.status === 'failed') { session.accessibility.failureReason = result.errorDetails; } } if (toolName === 'flutter_quantum_analyze') { session.quantumDebug.initialized = result.status === 'success'; session.quantumDebug.lastAttempt = Date.now(); if (result.status === 'failed') { session.quantumDebug.failureReason = result.errorDetails; } } this.emit('toolUsed', { sessionId, toolName, result, state: session }); } /** * Detect session failures and create recovery plan * Addresses: "Failed to enable Flutter accessibility: Target page, context or browser has been closed" */ async detectSessionFailure(sessionId, error) { const session = this.sessions.get(sessionId); if (!session) { return { canRecover: false, steps: [], preservedState: {}, riskFactors: ['Session not found in stability manager'] }; } const errorMessage = error.message.toLowerCase(); const isConnectionError = errorMessage.includes('closed') || errorMessage.includes('disconnected') || errorMessage.includes('target page'); const isTimeoutError = errorMessage.includes('timeout') || errorMessage.includes('30s') || errorMessage.includes('waiting'); // Mark browser as disconnected session.browserConnected = false; const recoveryPlan = { canRecover: true, steps: [], preservedState: { url: session.url, framework: session.framework, context: session.context, tools: session.tools }, riskFactors: [] }; if (isConnectionError) { recoveryPlan.steps.push({ action: 'Restart browser connection', tool: 'inject_debugging', reason: 'Browser connection lost', estimatedTime: 5000 }, { action: 'Verify framework detection', tool: 'get_session_info', reason: 'Ensure framework still detected correctly', estimatedTime: 2000 }); // If accessibility was previously enabled, try to restore it if (session.accessibility.enabled) { recoveryPlan.steps.push({ action: 'Restore accessibility features', tool: 'flutter_enable_accessibility', reason: 'Accessibility was previously working', estimatedTime: 10000 }); } } if (isTimeoutError && errorMessage.includes('accessibility')) { recoveryPlan.steps.push({ action: 'Check Flutter app semantics configuration', tool: 'flutter_health_check', reason: 'Accessibility timeout suggests app configuration issue', estimatedTime: 3000 }); recoveryPlan.riskFactors.push('Flutter app may not have semantics enabled', 'Consider flutter.engine.semanticsEnabled = true', 'Check if app uses Semantics() widgets'); } this.emit('sessionFailureDetected', { sessionId, error, recoveryPlan }); return recoveryPlan; } /** * Execute automatic session recovery * Addresses: "Auto-restart with previous context if browser closes" */ async executeSessionRecovery(sessionId, recoveryPlan) { const attempts = this.recoveryAttempts.get(sessionId) || 0; if (attempts >= this.maxRecoveryAttempts) { this.emit('sessionRecoveryFailed', { sessionId, reason: 'Max recovery attempts exceeded' }); return { success: false, restoredFeatures: [], failedFeatures: ['Recovery abandoned after maximum attempts'] }; } this.recoveryAttempts.set(sessionId, attempts + 1); const result = { success: false, newSessionId: undefined, restoredFeatures: [], failedFeatures: [] }; try { // Create new session with preserved state const { sessionId: newSessionId } = await this.createStableSession(recoveryPlan.preservedState.url, { framework: recoveryPlan.preservedState.framework, workflowPhase: recoveryPlan.preservedState.context?.workflowPhase, debuggingGoal: recoveryPlan.preservedState.context?.debuggingGoal }); result.newSessionId = newSessionId; // Execute recovery steps for (const step of recoveryPlan.steps) { try { // This would integrate with the actual tool execution // For now, we track the attempt this.trackToolUsage(newSessionId, step.tool, { status: 'success', // Would be actual result duration: step.estimatedTime }); result.restoredFeatures.push(step.action); } catch (stepError) { result.failedFeatures.push(`${step.action}: ${stepError}`); } } // Mark old session as recovered this.sessions.delete(sessionId); this.stopSessionHeartbeat(sessionId); result.success = result.failedFeatures.length === 0; this.emit('sessionRecovered', { oldSessionId: sessionId, newSessionId, result }); } catch (error) { result.failedFeatures.push(`Recovery execution failed: ${error}`); this.emit('sessionRecoveryFailed', { sessionId, error }); } return result; } /** * Get comprehensive session diagnostics * Addresses: "Show session state clearly" */ getSessionDiagnostics(sessionId) { const session = this.sessions.get(sessionId); if (!session) { return null; } const now = Date.now(); const ageMinutes = Math.floor((now - session.lastActivity) / 60000); // Analyze tool statuses const workingTools = Object.entries(session.tools) .filter(([_, tool]) => tool.status === 'success') .map(([name]) => name); const failedTools = Object.entries(session.tools) .filter(([_, tool]) => tool.status === 'failed' || tool.status === 'timeout') .map(([name]) => name); // Determine overall status let status = 'healthy'; if (!session.browserConnected) status = 'failed'; else if (failedTools.length > 0) status = 'degraded'; const recommendations = []; if (!session.accessibility.enabled && session.framework === 'flutter') { recommendations.push('Enable Flutter accessibility: flutter.engine.semanticsEnabled = true'); } if (!session.quantumDebug.initialized && session.accessibility.enabled) { recommendations.push('Initialize quantum debugging after accessibility is enabled'); } if (failedTools.length > 0) { recommendations.push(`Retry failed tools: ${failedTools.join(', ')}`); } if (ageMinutes > 10) { recommendations.push('Session inactive for >10 minutes - consider refreshing'); } return { status, details: { browser: { status: session.browserConnected ? 'Connected ✅' : 'Disconnected ❌', lastCheck: session.lastActivity }, framework: { detected: !!session.framework, type: session.framework }, accessibility: { enabled: session.accessibility.enabled, lastAttempt: session.accessibility.lastAttempt, error: session.accessibility.failureReason }, quantumDebug: { initialized: session.quantumDebug.initialized, lastAttempt: session.quantumDebug.lastAttempt, error: session.quantumDebug.failureReason }, tools: { working: workingTools, failed: failedTools }, activity: { lastActivity: session.lastActivity, ageMinutes } }, recommendations }; } /** * Get recommended tool workflow based on session state * Addresses: "Suggest logical tool sequences" */ getRecommendedWorkflow(sessionId) { const session = this.sessions.get(sessionId); if (!session) { return { nextSteps: [{ tool: 'inject_debugging', reason: 'Session not found', prerequisites: [], estimatedTime: 5000 }], currentPhase: 'Session Creation', blockers: ['Session does not exist'] }; } const nextSteps = []; const blockers = []; let currentPhase = 'Initial Setup'; // Phase 1: Basic Connection if (!session.browserConnected) { nextSteps.push({ tool: 'inject_debugging', reason: 'Establish browser connection', prerequisites: [], estimatedTime: 5000 }); blockers.push('Browser not connected'); currentPhase = 'Connection Setup'; } // Phase 2: Framework Detection else if (!session.framework) { nextSteps.push({ tool: 'get_session_info', reason: 'Detect framework type', prerequisites: ['Browser connected'], estimatedTime: 2000 }); currentPhase = 'Framework Detection'; } // Phase 3: Framework-Specific Setup else if (session.framework === 'flutter') { currentPhase = 'Flutter Setup'; if (!session.accessibility.enabled) { // Check if we've tried accessibility recently and it failed const recentFailure = session.accessibility.lastAttempt && (Date.now() - session.accessibility.lastAttempt < 60000); if (recentFailure && session.accessibility.failureReason) { blockers.push(`Accessibility failed: ${session.accessibility.failureReason}`); nextSteps.push({ tool: 'flutter_health_check', reason: 'Diagnose accessibility issues before retrying', prerequisites: ['Browser connected'], estimatedTime: 3000 }); } else { nextSteps.push({ tool: 'flutter_enable_accessibility', reason: 'Required for Flutter UI analysis', prerequisites: ['Browser connected', 'Flutter detected'], estimatedTime: 10000 }); } } else if (!session.quantumDebug.initialized) { nextSteps.push({ tool: 'flutter_quantum_analyze', reason: 'Initialize quantum debugging for advanced analysis', prerequisites: ['Accessibility enabled'], estimatedTime: 8000 }); } else { currentPhase = 'Ready for Debugging'; nextSteps.push({ tool: 'take_screenshot', reason: 'Capture current UI state for analysis', prerequisites: ['Flutter fully initialized'], estimatedTime: 3000 }); } } // Phase 4: Active Debugging if (session.browserConnected && session.framework && nextSteps.length === 0) { currentPhase = 'Active Debugging'; nextSteps.push({ tool: 'monitor_realtime', reason: 'Start real-time monitoring for active debugging', prerequisites: ['Session fully initialized'], estimatedTime: 2000 }, { tool: 'simulate_user_action', reason: 'Begin issue reproduction', prerequisites: ['Monitoring active'], estimatedTime: 5000 }); } return { nextSteps, currentPhase, blockers }; } /** * Private helper methods */ generateSessionId() { return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now(); } startSessionHeartbeat(sessionId) { const timer = setInterval(() => { const session = this.sessions.get(sessionId); if (!session) { this.stopSessionHeartbeat(sessionId); return; } // Check if session is stale if (Date.now() - session.lastActivity > this.sessionTimeout) { this.emit('sessionStale', { sessionId, session }); this.stopSessionHeartbeat(sessionId); return; } // Emit heartbeat for monitoring this.emit('sessionHeartbeat', { sessionId, session }); }, this.heartbeatInterval); this.heartbeatTimers.set(sessionId, timer); } stopSessionHeartbeat(sessionId) { const timer = this.heartbeatTimers.get(sessionId); if (timer) { clearInterval(timer); this.heartbeatTimers.delete(sessionId); } } startGlobalMonitoring() { // Clean up stale sessions every 5 minutes setInterval(() => { const now = Date.now(); for (const [sessionId, session] of this.sessions) { if (now - session.lastActivity > this.sessionTimeout) { this.sessions.delete(sessionId); this.stopSessionHeartbeat(sessionId); this.emit('sessionExpired', { sessionId, session }); } } }, 5 * 60 * 1000); } /** * Cleanup resources */ destroy() { for (const sessionId of this.heartbeatTimers.keys()) { this.stopSessionHeartbeat(sessionId); } this.sessions.clear(); this.recoveryAttempts.clear(); this.removeAllListeners(); } } //# sourceMappingURL=session-stability-manager.js.map