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

306 lines • 10.7 kB
// Resource Management System // Provides consistent browser resource tracking and cleanup across all handlers /** * ResourceManager - Centralized browser resource management * * Provides consistent resource tracking and cleanup across all handlers * to prevent memory leaks and ensure proper resource disposal */ export class ResourceManager { resources = new Map(); cleanupCallbacks = []; /** * Track a resource for automatic cleanup */ trackResource(resourceType, sessionId, resource, addedAt) { const resourceId = `${resourceType}-${sessionId}-${Date.now()}`; this.resources.set(resourceId, { sessionId, resourceType, resource, addedAt: addedAt || new Date(), lastUsed: new Date() }); } /** * Track a resource with handler information */ trackHandlerResource(handlerName, sessionId, resource) { const resourceId = `handler-${handlerName}-${sessionId}-${Date.now()}`; this.resources.set(resourceId, { sessionId, resourceType: 'page', // Most handlers use pages resource, handlerName, addedAt: new Date(), lastUsed: new Date() }); } /** * Get total number of tracked resources */ getResourceCount() { return this.resources.size; } /** * Get detailed resource statistics */ getResourceStats() { const resourceTypes = {}; const handlerUsage = {}; const sessions = new Set(); for (const resource of this.resources.values()) { resourceTypes[resource.resourceType] = (resourceTypes[resource.resourceType] || 0) + 1; sessions.add(resource.sessionId); if (resource.handlerName) { handlerUsage[resource.handlerName] = (handlerUsage[resource.handlerName] || 0) + 1; } } return { totalResources: this.resources.size, resourceTypes, sessionCount: sessions.size, handlerUsage }; } /** * Detect resource leaks (resources older than threshold) */ detectResourceLeaks(ageThresholdMinutes = 10) { const leaks = []; const now = Date.now(); const threshold = ageThresholdMinutes * 60 * 1000; for (const resource of this.resources.values()) { const age = now - resource.addedAt.getTime(); if (age > threshold) { leaks.push({ sessionId: resource.sessionId, resourceType: resource.resourceType, age: Math.floor(age / 1000 / 60), // age in minutes handlerName: resource.handlerName }); } } return leaks; } /** * Detect resource inconsistencies */ detectInconsistencies() { const inconsistencies = []; for (const resource of this.resources.values()) { try { // Check if page is closed but still tracked if (resource.resourceType === 'page' && resource.resource.isClosed && resource.resource.isClosed()) { inconsistencies.push({ sessionId: resource.sessionId, resourceType: resource.resourceType, issue: 'tracked-but-closed', resource: resource.resource }); } // Check if browser is disconnected but still tracked if (resource.resourceType === 'browser' && resource.resource.isConnected && !resource.resource.isConnected()) { inconsistencies.push({ sessionId: resource.sessionId, resourceType: resource.resourceType, issue: 'tracked-but-disconnected', resource: resource.resource }); } } catch (error) { // Resource might be disposed, mark as inconsistent inconsistencies.push({ sessionId: resource.sessionId, resourceType: resource.resourceType, issue: 'resource-disposed', resource: resource.resource }); } } return inconsistencies; } /** * Fix detected inconsistencies */ async fixInconsistencies() { const inconsistencies = this.detectInconsistencies(); for (const inconsistency of inconsistencies) { // Remove inconsistent resources from tracking const toRemove = []; for (const [id, resource] of this.resources.entries()) { if (resource.sessionId === inconsistency.sessionId && resource.resourceType === inconsistency.resourceType && resource.resource === inconsistency.resource) { toRemove.push(id); } } toRemove.forEach(id => this.resources.delete(id)); } } /** * Get monitoring data for analysis */ getMonitoringData() { const stats = this.getResourceStats(); const resourceAge = {}; // Calculate average age by resource type const agesByType = {}; for (const resource of this.resources.values()) { if (!agesByType[resource.resourceType]) { agesByType[resource.resourceType] = []; } agesByType[resource.resourceType].push(Date.now() - resource.addedAt.getTime()); } for (const [type, ages] of Object.entries(agesByType)) { resourceAge[type] = ages.reduce((sum, age) => sum + age, 0) / ages.length; } return { totalResources: stats.totalResources, resourceAge, sessionCount: stats.sessionCount, averageResourcesPerSession: stats.sessionCount > 0 ? stats.totalResources / stats.sessionCount : 0, handlerUsage: stats.handlerUsage }; } /** * Get cleanup recommendations */ getCleanupRecommendations() { const stats = this.getResourceStats(); const leaks = this.detectResourceLeaks(); const inconsistencies = this.detectInconsistencies(); const recommendedActions = []; if (leaks.length > 0) { recommendedActions.push({ action: 'cleanup-leaks', priority: 'high', description: `${leaks.length} resource leaks detected - immediate cleanup recommended` }); } if (inconsistencies.length > 0) { recommendedActions.push({ action: 'fix-inconsistencies', priority: 'medium', description: `${inconsistencies.length} resource inconsistencies detected` }); } if (stats.totalResources > 50) { recommendedActions.push({ action: 'bulk-cleanup', priority: 'medium', description: 'High resource count - consider bulk cleanup' }); } const priority = leaks.length > 0 ? 'high' : inconsistencies.length > 0 ? 'medium' : 'low'; return { totalTracked: stats.totalResources, recommendedActions, priority }; } /** * Cleanup resources for a specific session */ async cleanupSession(sessionId) { const sessionResources = []; // Find all resources for this session for (const [id, resource] of this.resources.entries()) { if (resource.sessionId === sessionId) { sessionResources.push(id); } } // Cleanup each resource for (const resourceId of sessionResources) { const resource = this.resources.get(resourceId); if (resource) { await this.cleanupResource(resource); this.resources.delete(resourceId); } } // Call registered cleanup callbacks for (const callback of this.cleanupCallbacks) { try { await callback(sessionId); } catch (error) { console.warn(`Cleanup callback failed for session ${sessionId}:`, error); } } } /** * Cleanup all resources */ async cleanupAll() { const sessions = new Set(); // Get all unique session IDs for (const resource of this.resources.values()) { sessions.add(resource.sessionId); } // Cleanup each session for (const sessionId of sessions) { await this.cleanupSession(sessionId); } } /** * Cleanup individual resource */ async cleanupResource(resource) { try { if (resource.resourceType === 'page' && resource.resource.close) { await resource.resource.close(); } else if (resource.resourceType === 'browser' && resource.resource.close) { await resource.resource.close(); } else if (resource.resourceType === 'context' && resource.resource.close) { await resource.resource.close(); } } catch (error) { console.warn(`Failed to cleanup ${resource.resourceType} resource:`, error); } } /** * Register cleanup callback */ registerCleanupCallback(callback) { this.cleanupCallbacks.push(callback); } /** * Enhance existing cleanup function */ enhanceCleanup(originalCleanup) { return async (sessionId) => { // Run resource manager cleanup first await this.cleanupSession(sessionId); // Then run original cleanup await originalCleanup(sessionId); }; } /** * Integration with main server */ integrateWithServer() { // This method would integrate with the main server's cleanup system // For now, it's a placeholder for the integration point } /** * Clear all resources (for testing) */ clear() { this.resources.clear(); } /** * Generic cleanup method - maps to cleanupAll for compatibility */ async cleanup() { await this.cleanupAll(); } } /** * Global resource manager instance */ export const globalResourceManager = new ResourceManager(); //# sourceMappingURL=resource-manager.js.map