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

389 lines (386 loc) โ€ข 12.6 kB
/** * Advanced UI Cache with Invalidation Strategies * Intelligent caching for UI elements with state tracking */ import { EventEmitter } from 'events'; export class AdvancedUICache extends EventEmitter { cache = new Map(); stats = { hits: 0, misses: 0, evictions: 0, invalidations: 0, hitRate: 0, averageAge: 0, totalElements: 0 }; invalidationRules = [ // Time-based invalidation { type: 'time', condition: { maxAge: 30000 }, // 30 seconds action: 'invalidate' }, // Low confidence invalidation { type: 'confidence', condition: { threshold: 0.5 }, action: 'refresh' }, // Frequency-based promotion { type: 'frequency', condition: { minAccess: 3 }, action: 'downgrade' // Actually upgrades TTL }, // State change invalidation { type: 'state', condition: { changeDetected: true }, action: 'invalidate' } ]; MAX_CACHE_SIZE = 1000; DEFAULT_TTL = 30000; // 30 seconds EXTENDED_TTL = 60000; // 1 minute for frequently accessed stateMonitor = null; applicationStates = new Map(); constructor() { super(); this.startStateMonitoring(); } /** * Get cached element with intelligent invalidation */ get(elementId) { const element = this.cache.get(elementId); if (!element) { this.stats.misses++; this.updateHitRate(); return null; } // Check invalidation rules if (this.shouldInvalidate(element)) { this.invalidate(elementId); this.stats.misses++; this.updateHitRate(); return null; } // Update access metadata element.lastAccessed = Date.now(); element.accessCount++; // Promote frequently accessed elements if (element.accessCount > 5) { element.confidence = Math.min(1, element.confidence + 0.1); } this.stats.hits++; this.updateHitRate(); return element; } /** * Set element in cache with intelligent TTL */ set(elementId, element) { const now = Date.now(); // Check cache size limit if (this.cache.size >= this.MAX_CACHE_SIZE) { this.evictLRU(); } const existing = this.cache.get(elementId); const uiElement = { id: elementId, x: element.x || 0, y: element.y || 0, width: element.width, height: element.height, type: element.type, text: element.text, state: element.state || 'visible', lastAccessed: now, lastModified: now, accessCount: existing ? existing.accessCount + 1 : 1, confidence: element.confidence || this.calculateInitialConfidence(element) }; this.cache.set(elementId, uiElement); this.stats.totalElements = this.cache.size; // Emit cache update event this.emit('cache-update', { action: 'set', elementId, element: uiElement }); } /** * Batch set multiple elements */ setBatch(elements) { const startTime = performance.now(); for (const { id, element } of elements) { this.set(id, element); } const duration = performance.now() - startTime; console.log(`โšก Batch cached ${elements.length} elements in ${duration.toFixed(2)}ms`); } /** * Invalidate element */ invalidate(elementId) { const element = this.cache.get(elementId); if (element) { this.cache.delete(elementId); this.stats.invalidations++; this.stats.totalElements = this.cache.size; // Emit invalidation event this.emit('cache-invalidate', { elementId, reason: 'manual' }); } } /** * Invalidate elements by pattern */ invalidatePattern(pattern) { let invalidated = 0; for (const [id, element] of this.cache.entries()) { if (pattern.test(id)) { this.cache.delete(id); invalidated++; } } this.stats.invalidations += invalidated; this.stats.totalElements = this.cache.size; return invalidated; } /** * Refresh element with new confidence */ refresh(elementId, updates) { const element = this.cache.get(elementId); if (element) { Object.assign(element, updates); element.lastModified = Date.now(); element.confidence = Math.min(1, element.confidence + 0.2); // Emit refresh event this.emit('cache-refresh', { elementId, updates }); } } /** * Check if element should be invalidated */ shouldInvalidate(element) { const now = Date.now(); for (const rule of this.invalidationRules) { switch (rule.type) { case 'time': const age = now - element.lastModified; const maxAge = element.accessCount > 3 ? this.EXTENDED_TTL : this.DEFAULT_TTL; if (age > maxAge) { return true; } break; case 'confidence': if (element.confidence < rule.condition.threshold) { return true; } break; case 'state': // Check if application state has changed if (this.hasStateChanged(element)) { return true; } break; } } return false; } /** * Evict least recently used element */ evictLRU() { let oldestElement = null; let oldestId = null; for (const [id, element] of this.cache.entries()) { if (!oldestElement || element.lastAccessed < oldestElement.lastAccessed) { oldestElement = element; oldestId = id; } } if (oldestId) { this.cache.delete(oldestId); this.stats.evictions++; this.stats.totalElements = this.cache.size; // Emit eviction event this.emit('cache-evict', { elementId: oldestId, reason: 'lru' }); } } /** * Calculate initial confidence score */ calculateInitialConfidence(element) { let confidence = 0.5; // Base confidence // Higher confidence for elements with more properties if (element.width && element.height) confidence += 0.1; if (element.type) confidence += 0.1; if (element.text) confidence += 0.1; if (element.state === 'visible') confidence += 0.1; return Math.min(1, confidence); } /** * Check if application state has changed */ hasStateChanged(element) { // In a real implementation, this would check actual application state // For now, use a simple heuristic based on time const timeSinceModified = Date.now() - element.lastModified; return timeSinceModified > 60000; // Consider stale after 1 minute } /** * Start monitoring application state for intelligent invalidation */ startStateMonitoring() { this.stateMonitor = setInterval(() => { this.checkApplicationStates(); }, 5000); // Check every 5 seconds } /** * Check application states and invalidate affected elements */ checkApplicationStates() { // Monitor for window focus changes, application switches, etc. // This would integrate with system APIs in production // For now, implement age-based cleanup const now = Date.now(); const toInvalidate = []; for (const [id, element] of this.cache.entries()) { const age = now - element.lastModified; if (age > this.EXTENDED_TTL * 2) { toInvalidate.push(id); } } for (const id of toInvalidate) { this.invalidate(id); } if (toInvalidate.length > 0) { console.log(`๐Ÿงน Cleaned ${toInvalidate.length} stale cache entries`); } } /** * Update hit rate statistics */ updateHitRate() { const total = this.stats.hits + this.stats.misses; this.stats.hitRate = total > 0 ? (this.stats.hits / total) * 100 : 0; } /** * Get cache statistics */ getStats() { // Calculate average age if (this.cache.size > 0) { const now = Date.now(); let totalAge = 0; for (const element of this.cache.values()) { totalAge += (now - element.lastModified); } this.stats.averageAge = totalAge / this.cache.size; } return { ...this.stats }; } /** * Clear entire cache */ clear() { this.cache.clear(); this.stats.totalElements = 0; this.stats.invalidations += this.cache.size; // Emit clear event this.emit('cache-clear', { reason: 'manual' }); } /** * Get cache summary */ getSummary() { const stats = this.getStats(); return `๐Ÿ“Š **UI Cache Status** **Performance:** - Hit Rate: ${stats.hitRate.toFixed(1)}% - Total Elements: ${stats.totalElements} - Average Age: ${(stats.averageAge / 1000).toFixed(1)}s **Operations:** - Hits: ${stats.hits} - Misses: ${stats.misses} - Evictions: ${stats.evictions} - Invalidations: ${stats.invalidations} **Top Cached Elements:** ${Array.from(this.cache.values()) .sort((a, b) => b.accessCount - a.accessCount) .slice(0, 5) .map(e => `- ${e.id}: ${e.accessCount} accesses, confidence: ${(e.confidence * 100).toFixed(0)}%`) .join('\n')}`; } /** * Export cache for persistence */ export() { const exportData = { timestamp: Date.now(), stats: this.stats, elements: Array.from(this.cache.entries()).map(([id, element]) => ({ ...element // element already contains id property })) }; return JSON.stringify(exportData, null, 2); } /** * Import cache from persistence */ import(data) { try { const importData = JSON.parse(data); // Clear existing cache this.cache.clear(); // Import elements with age adjustment const now = Date.now(); const ageAdjustment = now - importData.timestamp; for (const element of importData.elements) { if (element.id) { // Create properly typed UIElement const uiElement = { id: element.id, x: element.x, y: element.y, width: element.width, height: element.height, type: element.type, text: element.text, state: element.state, lastAccessed: element.lastAccessed + ageAdjustment, lastModified: element.lastModified + ageAdjustment, accessCount: element.accessCount, confidence: element.confidence * 0.8 }; this.cache.set(element.id, uiElement); } } // Import stats Object.assign(this.stats, importData.stats); this.stats.totalElements = this.cache.size; console.log(`โœ… Imported ${this.cache.size} cached elements`); } catch (error) { console.error('โŒ Failed to import cache:', error); } } /** * Cleanup resources */ destroy() { if (this.stateMonitor) { clearInterval(this.stateMonitor); this.stateMonitor = null; } this.cache.clear(); this.removeAllListeners(); } } // Export singleton instance export const uiCache = new AdvancedUICache(); //# sourceMappingURL=advanced-ui-cache.js.map