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