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

272 lines • 11.9 kB
import { BaseToolHandler } from './base-handler.js'; import { SessionDebugFixes } from './session-debug-fixes.js'; import { SessionStabilityManager } from './session-stability-manager.js'; import { MemorySafeActions } from './memory-safe-actions.js'; import { ProjectSessionManager } from '../utils/project-session-manager.js'; import { lazyBrowser } from '../utils/lazy-dependencies.js'; export class InteractionHandler extends BaseToolHandler { localEngine; tools = [ { name: 'simulate_user_action', description: 'Simulate user interactions in the local browser. FREE: Basic actions. PREMIUM: AI-optimized interaction patterns.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The debug session ID' }, action: { type: 'string', description: 'Type of user action', enum: ['click', 'type', 'submit', 'scroll', 'hover'] }, selector: { type: 'string', description: 'CSS selector for target element' }, value: { type: 'string', description: 'Value for type actions' }, coordinates: { type: 'object', description: 'Coordinates for click actions (e.g., for Flutter canvas)', properties: { x: { type: 'number' }, y: { type: 'number' } } } }, required: ['sessionId', 'action', 'selector'] } }, { name: 'take_screenshot', description: 'Capture screenshots of your local application for visual debugging.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'The debug session ID' }, fullPage: { type: 'boolean', description: 'Capture full page (default: true)', default: true } }, required: ['sessionId'] } } ]; constructor(localEngine) { super(); this.localEngine = localEngine; // Initialize memory-safe actions to prevent EventTarget leaks MemorySafeActions.initialize(); } getTools() { return this.tools; } /** * Get session with persistent storage support */ async getSessionWithPersistence(sessionId, sessions) { // First check in-memory sessions let session = sessions.get(sessionId); if (session) { return session; } // Try to load from 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 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 handle(toolName, args, sessions) { try { // Enhanced session validation with detailed debugging await SessionDebugFixes.validateSessionStorage(args.sessionId, sessions); const session = await this.getSessionWithPersistence(args.sessionId, sessions); // P0 FIX: Enhanced session health validation with auto-recovery const healthResult = await SessionStabilityManager.validateSessionWithRecovery(session, sessions, { timeoutMs: 5000, maxRetries: 2 }); if (!healthResult.isHealthy) { return SessionStabilityManager.createRecoveryErrorResponse(args.sessionId, new Error('Session validation failed after recovery attempts'), true); } // Use recovered session if recovery occurred const validatedSession = healthResult.session; switch (toolName) { case 'simulate_user_action': return await this.simulateUserAction(args, validatedSession); case 'take_screenshot': return await this.takeScreenshot(args, validatedSession); default: return this.createErrorResponse(new Error(`Unknown tool: ${toolName}`)); } } catch (error) { return SessionDebugFixes.createDetailedErrorResponse(error, 'User Interaction', args.sessionId); } } async simulateUserAction(args, session) { try { const { action, selector, value, coordinates } = args; console.error(`🎯 Memory-safe action: ${action} on ${selector}`); // For Flutter/canvas clicks with coordinates, use memory-safe evaluation if (coordinates && selector) { await MemorySafeActions.safePageAction(session.page, async () => { return session.page.evaluate((sel, coords) => { const element = document.querySelector(sel); if (element) { const event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, clientX: coords.x, clientY: coords.y }); element.dispatchEvent(event); } }, selector, coordinates); }); } else { // Use memory-safe actions instead of delegating to local engine await this.performMemorySafeAction(session.page, action, selector, value); } // Capture post-action events const postActionEvents = session.events.slice(-5); // Format response message let message = `✅ Successfully performed ${action} action on "${selector}"`; if (value) { message += ` with value "${value}"`; } if (coordinates) { message += ` at coordinates (${coordinates.x}, ${coordinates.y})`; } message += '\n\n'; // Add recent events analysis if (postActionEvents.length > 0) { const domChanges = postActionEvents.filter(e => e.type === 'dom_mutation').length; const networkRequests = postActionEvents.filter(e => e.type === 'network_request' || e.type === 'network_response').length; const consoleMessages = postActionEvents.filter(e => e.type === 'console_log' || e.type === 'console_error').length; message += '**Post-action analysis:**\n'; if (domChanges > 0) message += `- ${domChanges} DOM changes detected\n`; if (networkRequests > 0) message += `- ${networkRequests} network requests triggered\n`; if (consoleMessages > 0) message += `- ${consoleMessages} console messages logged\n`; message += '\n_Use `monitor_realtime` to see detailed event logs_'; } return { content: [{ type: 'text', text: message }] }; } catch (error) { throw new Error(`Failed to simulate user action: ${error instanceof Error ? error.message : error}`); } } async takeScreenshot(args, session) { try { const fullPage = args.fullPage !== false; // Default to true // Take screenshot directly using page const screenshotBuffer = await session.page.screenshot({ fullPage, type: 'png' }); // Convert buffer to base64 const base64Data = screenshotBuffer.toString('base64'); const timestamp = Date.now(); const message = `📸 Screenshot captured successfully!\n\n` + `**Type:** ${fullPage ? 'Full page' : 'Viewport only'}\n` + `**Timestamp:** ${new Date(timestamp).toISOString()}\n\n` + `_Screenshot displayed below:_`; return { content: [ { type: 'text', text: message }, { type: 'image', data: base64Data, mimeType: 'image/png' } ] }; } catch (error) { throw new Error(`Failed to take screenshot: ${error instanceof Error ? error.message : error}`); } } /** * Perform memory-safe action using the MemorySafeActions class */ async performMemorySafeAction(page, action, selector, value) { if (!selector) { throw new Error(`Selector required for action: ${action}`); } switch (action.toLowerCase()) { case 'click': await MemorySafeActions.safeClick(page, selector); break; case 'type': if (!value) throw new Error('Value required for type action'); await MemorySafeActions.safeType(page, selector, value); break; case 'submit': await MemorySafeActions.safeSubmit(page, selector); break; case 'scroll': await MemorySafeActions.safeScroll(page, selector); break; case 'hover': await MemorySafeActions.safeHover(page, selector); break; default: throw new Error(`Unsupported action: ${action}`); } } } //# sourceMappingURL=interaction-handler.js.map