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