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