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
JavaScript
/**
* 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