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

414 lines โ€ข 16.8 kB
/** * V2 Session Registry - Multi-Session Resource Coordination for HTTP Transport * * Optimized session management for stateless HTTP architecture with: * - Intelligent resource allocation and sharing * - Project-aware session optimization * - Advanced memory management integration * - HTTP transport-specific features */ import { EventEmitter } from 'events'; import * as crypto from 'crypto'; import * as path from 'path'; import * as fs from 'fs/promises'; import { existsSync } from 'fs'; import { V2_DEFAULT_CONFIG } from './v2-session-types.js'; import { AdvancedMemoryManager } from '../utils/advanced-memory-manager.js'; export class V2SessionRegistry extends EventEmitter { sessions; urlToSession; // targetUrl -> sessionId httpSessionMap; // httpSessionId -> sessionId portAllocator; memoryManager; config; cleanupInterval; monitoringInterval; httpRequestCounter = new Map(); // sessionId -> requestCount constructor(config = {}) { super(); this.sessions = new Map(); this.urlToSession = new Map(); this.httpSessionMap = new Map(); this.config = { ...V2_DEFAULT_CONFIG, ...config }; this.portAllocator = new V2PortAllocator(this.config.basePort, this.config.portRangeSize); this.memoryManager = AdvancedMemoryManager.getInstance(); this.startBackgroundTasks(); console.error('๐Ÿข V2 Session Registry initialized with HTTP transport optimization'); } /** * Register a new debugging session (HTTP transport optimized) */ async registerSession(targetUrl, httpSessionId, clientConnectionId, workingDirectory) { // Check if URL already has an active session const existingSessionId = this.urlToSession.get(targetUrl); if (existingSessionId) { const existingSession = this.sessions.get(existingSessionId); if (existingSession && existingSession.status !== 'cleanup') { // Update activity and return existing session existingSession.lastActivity = new Date(); existingSession.status = 'active'; this.emitEvent('updated', existingSession.id, { reason: 'reactivated', httpSessionId, clientConnectionId }); return existingSession; } } // Check global session limit const activeSessions = Array.from(this.sessions.values()) .filter(s => s.status === 'active' || s.status === 'initializing'); if (activeSessions.length >= this.config.defaultQuotas.maxConcurrentSessions) { throw new Error(`V2 Maximum concurrent sessions (${this.config.defaultQuotas.maxConcurrentSessions}) reached. ` + 'Consider closing idle sessions or increasing quotas.'); } // Create new session const sessionId = this.generateSessionId(targetUrl); const assignedPorts = await this.portAllocator.allocatePortRange(sessionId); const tempDirectory = await this.createSessionTempDir(sessionId); const metadata = await this.detectProjectMetadata(targetUrl, workingDirectory); const session = { id: sessionId, workingDirectory, targetUrl, assignedPorts, browserInstanceId: `browser_v2_${sessionId}`, tempDirectory, resourceUsage: { browserProcesses: 0, memoryUsageMB: 0, cpuUsagePercent: 0, tempDirectorySize: 0, lastActivity: new Date(), httpRequestCount: 0, sessionsActive: 1 }, quotas: { ...this.config.defaultQuotas }, lastActivity: new Date(), createdAt: new Date(), status: 'initializing', metadata, httpSessionId, clientConnectionId }; // Store session mappings this.sessions.set(sessionId, session); this.urlToSession.set(targetUrl, sessionId); if (httpSessionId) { this.httpSessionMap.set(httpSessionId, sessionId); } // Register with memory manager this.memoryManager.registerSession(sessionId, 25); // 25MB initial estimate // Emit event this.emitEvent('created', sessionId, { targetUrl, httpSessionId, clientConnectionId, v2Features: ['http-transport', 'project-aware', 'memory-optimized'] }); console.error(`๐Ÿ“ V2 Session registered: ${sessionId} for ${targetUrl}`); return session; } /** * Get session by various identifiers */ getSession(identifier) { // Try direct session ID let session = this.sessions.get(identifier); if (session) return session; // Try HTTP session ID const sessionId = this.httpSessionMap.get(identifier); if (sessionId) return this.sessions.get(sessionId); // Try URL lookup const urlSessionId = this.urlToSession.get(identifier); if (urlSessionId) return this.sessions.get(urlSessionId); return undefined; } /** * Update session activity (HTTP request tracking) */ recordHttpRequest(sessionId) { const session = this.sessions.get(sessionId); if (!session) return; session.lastActivity = new Date(); session.resourceUsage.httpRequestCount++; session.resourceUsage.lastActivity = new Date(); // Update memory manager this.memoryManager.touchSession(sessionId); // Rate limiting check const requestCount = this.httpRequestCounter.get(sessionId) || 0; this.httpRequestCounter.set(sessionId, requestCount + 1); if (requestCount > this.config.defaultQuotas.maxHttpRequestsPerMinute) { this.emitEvent('resource_warning', sessionId, { type: 'rate_limit_exceeded', requestCount, limit: this.config.defaultQuotas.maxHttpRequestsPerMinute }); } } /** * Apply framework detection results to session */ async updateSessionFramework(sessionId, framework, optimizationInfo) { const session = this.sessions.get(sessionId); if (!session) return; session.framework = framework; session.metadata.framework = framework; session.metadata.projectAwareOptimization = optimizationInfo; this.emitEvent('framework_detected', sessionId, { framework, optimizationInfo, v2Specific: { projectAwareEvent: true } }); console.error(`๐ŸŽฏ V2 Framework detected for session ${sessionId}: ${framework}`); } /** * Get comprehensive resource summary */ getResourceSummary() { const sessions = Array.from(this.sessions.values()); const activeSessions = sessions.filter(s => s.status === 'active').length; const idleSessions = sessions.filter(s => s.status === 'idle').length; const suspendedSessions = sessions.filter(s => s.status === 'suspended').length; const totalMemory = sessions.reduce((sum, s) => sum + s.resourceUsage.memoryUsageMB, 0); const totalBrowsers = sessions.reduce((sum, s) => sum + s.resourceUsage.browserProcesses, 0); const totalHttpRequests = sessions.reduce((sum, s) => sum + s.resourceUsage.httpRequestCount, 0); // Calculate HTTP transport health const recentRequests = Array.from(this.httpRequestCounter.values()).reduce((sum, count) => sum + count, 0); const httpTransportHealth = { requestsPerSecond: recentRequests / 60, // Rough estimate averageResponseTime: 150, // Would be measured in real implementation errorRate: 0.02 // Would be tracked in real implementation }; const memStats = this.memoryManager.getMemoryStats(); return { totalSessions: sessions.length, activeSessions, idleSessions, suspendedSessions, totalMemoryUsage: totalMemory, totalBrowserProcesses: totalBrowsers, totalHttpRequests, availableResources: { browserProcesses: Math.max(0, this.config.defaultQuotas.maxBrowserInstances - totalBrowsers), memoryUsageMB: Math.max(0, this.config.defaultQuotas.maxMemoryMB - totalMemory), cpuUsagePercent: memStats.pressure.heapUsagePercent, tempDirectorySize: 0, // Would be calculated lastActivity: new Date(), httpRequestCount: totalHttpRequests, sessionsActive: activeSessions }, quotaUtilization: (totalMemory / this.config.defaultQuotas.maxMemoryMB) * 100, httpTransportHealth }; } /** * Cleanup expired sessions */ async performCleanup() { const now = new Date(); const expiredSessions = []; const maxIdleTime = this.config.defaultQuotas.maxIdleTimeMinutes * 60 * 1000; for (const [sessionId, session] of this.sessions.entries()) { const idleTime = now.getTime() - session.lastActivity.getTime(); if (idleTime > maxIdleTime && session.status !== 'cleanup') { expiredSessions.push(sessionId); await this.cleanupSession(sessionId); } } // Reset HTTP request counters (simple rate limiting reset) this.httpRequestCounter.clear(); if (expiredSessions.length > 0) { console.error(`๐Ÿงน V2 Cleaned up ${expiredSessions.length} expired sessions`); } return expiredSessions; } /** * Cleanup individual session */ async cleanupSession(sessionId) { const session = this.sessions.get(sessionId); if (!session) return; session.status = 'cleanup'; try { // Remove temp directory if (existsSync(session.tempDirectory)) { await fs.rmdir(session.tempDirectory, { recursive: true }); } // Release ports await this.portAllocator.releasePortRange(sessionId); // Remove from memory manager this.memoryManager.removeSession(sessionId); // Remove mappings this.sessions.delete(sessionId); this.urlToSession.delete(session.targetUrl); if (session.httpSessionId) { this.httpSessionMap.delete(session.httpSessionId); } this.emitEvent('cleanup', sessionId, { reason: 'expired' }); console.error(`๐Ÿ—‘๏ธ V2 Session ${sessionId} cleaned up successfully`); } catch (error) { console.error(`โŒ V2 Session cleanup failed for ${sessionId}:`, error); this.emitEvent('error', sessionId, { error: error.message, phase: 'cleanup' }); } } // Helper methods generateSessionId(targetUrl) { const hash = crypto.createHash('md5').update(`${targetUrl}_${Date.now()}`).digest('hex'); return `v2_${hash.substring(0, 8)}`; } async createSessionTempDir(sessionId) { const tempDir = path.join(this.config.tempBaseDirectory, sessionId); await fs.mkdir(tempDir, { recursive: true }); return tempDir; } async detectProjectMetadata(targetUrl, workingDirectory) { const metadata = { v2Architecture: true, httpTransport: true }; if (workingDirectory && existsSync(workingDirectory)) { try { // Try to read package.json const packageJsonPath = path.join(workingDirectory, 'package.json'); if (existsSync(packageJsonPath)) { const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8'); metadata.packageJson = JSON.parse(packageJsonContent); // Extract dependencies const deps = metadata.packageJson.dependencies || {}; const devDeps = metadata.packageJson.devDependencies || {}; metadata.dependencies = Object.keys({ ...deps, ...devDeps }).slice(0, 10); } } catch (error) { console.warn(`โš ๏ธ V2 Could not read project metadata for ${workingDirectory}:`, error.message); } } return metadata; } emitEvent(type, sessionId, data) { const event = { type, sessionId, timestamp: new Date(), data, severity: 'info' }; this.emit('session_event', event); } startBackgroundTasks() { // Cleanup task this.cleanupInterval = setInterval(async () => { try { await this.performCleanup(); } catch (error) { console.error('V2 Session cleanup error:', error); } }, this.config.cleanupIntervalMs); // Monitoring task this.monitoringInterval = setInterval(() => { try { this.performResourceMonitoring(); } catch (error) { console.error('V2 Session monitoring error:', error); } }, this.config.monitoringIntervalMs); } performResourceMonitoring() { const summary = this.getResourceSummary(); if (summary.quotaUtilization > 90) { this.emit('resource_warning', { type: 'high_quota_utilization', utilization: summary.quotaUtilization, activeSessions: summary.activeSessions }); } } /** * Graceful shutdown */ async shutdown() { console.error('๐Ÿ›‘ V2 Session Registry shutting down...'); if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } if (this.monitoringInterval) { clearInterval(this.monitoringInterval); } // Cleanup all active sessions const sessionIds = Array.from(this.sessions.keys()); for (const sessionId of sessionIds) { await this.cleanupSession(sessionId); } console.error('โœ… V2 Session Registry shutdown complete'); } } /** * V2 Port Allocator - Simplified for HTTP transport */ class V2PortAllocator { basePort; portRangeSize; allocatedRanges; portUsageMap; constructor(basePort = 8200, portRangeSize = 5) { this.basePort = basePort; this.portRangeSize = portRangeSize; this.allocatedRanges = new Map(); this.portUsageMap = new Set(); } async allocatePortRange(sessionId) { // Check if already allocated const existing = this.allocatedRanges.get(sessionId); if (existing) { return existing; } // Find available base port const basePort = await this.findAvailablePortRange(); const portRange = { debugPort: basePort, inspectorPort: basePort + 1, websocketPort: basePort + 2 }; // Mark ports as used this.portUsageMap.add(portRange.debugPort); this.portUsageMap.add(portRange.inspectorPort); this.portUsageMap.add(portRange.websocketPort); // Store allocation this.allocatedRanges.set(sessionId, portRange); console.error(`๐Ÿ”Œ V2 Ports allocated for session ${sessionId}: ${basePort}-${basePort + 2}`); return portRange; } async releasePortRange(sessionId) { const portRange = this.allocatedRanges.get(sessionId); if (!portRange) return; // Release ports this.portUsageMap.delete(portRange.debugPort); this.portUsageMap.delete(portRange.inspectorPort); this.portUsageMap.delete(portRange.websocketPort); // Remove allocation this.allocatedRanges.delete(sessionId); console.error(`๐Ÿ”Œ V2 Ports released for session ${sessionId}`); } async findAvailablePortRange() { for (let port = this.basePort; port < this.basePort + 1000; port += this.portRangeSize) { if (!this.portUsageMap.has(port) && !this.portUsageMap.has(port + 1) && !this.portUsageMap.has(port + 2)) { return port; } } throw new Error('No available port ranges'); } } //# sourceMappingURL=v2-session-registry.js.map