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