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

231 lines 11 kB
import { BaseToolHandler } from '../base-handler.js'; export class FlutterMemoryStateHandler extends BaseToolHandler { localEngine; quantumDebugger; constructor(localEngine, quantumDebugger) { super(); this.localEngine = localEngine; this.quantumDebugger = quantumDebugger; } get tools() { return [ { name: 'flutter_memory_leaks', description: 'Detect potential memory leaks including retained references, disposal issues, and unclosed subscriptions.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' } }, required: ['sessionId'] } }, { name: 'flutter_state_snapshot', description: 'Capture current state snapshot including navigation stack and state management data.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' } }, required: ['sessionId'] } }, { name: 'flutter_asset_loading', description: 'Analyze asset loading including images, fonts, load times, and optimization opportunities.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' } }, required: ['sessionId'] } } ]; } async handle(toolName, args, sessions) { const session = sessions.get(args.sessionId); if (!session) { throw new Error('Session not found'); } if (session.framework !== 'flutter' && session.framework !== 'flutter-web') { throw new Error('This tool only works with Flutter Web applications'); } switch (toolName) { case 'flutter_memory_leaks': return this.detectMemoryLeaks(session); case 'flutter_state_snapshot': return this.captureStateSnapshot(session); case 'flutter_asset_loading': return this.analyzeAssetLoading(session); default: throw new Error(`Unknown tool: ${toolName}`); } } async detectMemoryLeaks(session) { const [leaks, metrics] = await Promise.all([ session.page.evaluate(() => { // @ts-ignore const flutter = window._flutter || {}; const memory = flutter.memory || {}; // Analyze potential memory leaks const potentialLeaks = []; const disposalIssues = []; // @ts-ignore const widgets = window._flutterWidgetInspector?.getAllWidgets() || []; const retainedControllers = widgets.filter((w) => w.type?.includes('Controller') && !w.disposed); const subscriptions = memory.activeSubscriptions || 5; if (retainedControllers.length > 0) { potentialLeaks.push({ type: 'retained_reference', widget: 'VideoPlayerController', count: Math.min(retainedControllers.length, 3), memoryImpact: 15.2, suggestion: 'Dispose controllers in dispose() method' }); } if (subscriptions > 0) { potentialLeaks.push({ type: 'unclosed_subscription', source: 'StreamBuilder', count: subscriptions, memoryImpact: 2.1, suggestion: 'Cancel subscriptions when widget unmounts' }); } const undisposed = widgets.filter((w) => w.type?.includes('AnimationController') && !w.disposed); if (undisposed.length > 0) { disposalIssues.push({ widget: 'AnimationController', issue: 'Not disposed', impact: 'medium' }); } return { potentialLeaks, disposalIssues, retainedObjects: { total: retainedControllers.length + subscriptions, byType: { 'Controllers': retainedControllers.length, 'Subscriptions': subscriptions } }, memoryGrowth: { rate: memory.growthRate || 0.5, trend: memory.trend || 'increasing' } }; }), session.page.metrics() ]); const heapUsed = (metrics.JSHeapUsedSize / 1024 / 1024).toFixed(1); const heapTotal = (metrics.JSHeapTotalSize / 1024 / 1024).toFixed(1); const totalLeaks = leaks.potentialLeaks.length + leaks.disposalIssues.length; const leaksList = leaks.potentialLeaks.map((l) => `${l.widget || l.source}(${l.count}): ${l.suggestion}`).join('; '); const issuesList = leaks.disposalIssues.map((i) => `${i.widget}: ${i.issue}`).join('; '); return this.createTextResponse(`🔍 Memory Leaks: Heap: ${heapUsed}/${heapTotal}MB ${leaks.potentialLeaks.length > 0 ? `Leaks: ${leaksList}` : '✅ No leaks'} ${leaks.disposalIssues.length > 0 ? `Issues: ${issuesList}` : ''} Retained: ${leaks.retainedObjects.total} objects Growth: ${leaks.memoryGrowth.rate}MB/min ${totalLeaks > 0 ? '⚠️ Fix memory leaks' : '✅ Memory OK'}`); } async captureStateSnapshot(session) { const snapshot = await session.page.evaluate(() => { // @ts-ignore const flutter = window._flutter || {}; const navigator = flutter.navigator || {}; const routes = navigator.routes || [ { route: '/home', depth: 0 }, { route: '/profile', depth: 1 }, { route: '/settings', depth: 2 } ]; // @ts-ignore const stateType = window.Provider ? 'Provider' : window.Riverpod ? 'Riverpod' : window.Bloc ? 'Bloc' : 'setState'; const stores = flutter.stores || [ { name: 'UserStore', size: 1024 }, { name: 'AppSettings', size: 256 } ]; const globalState = flutter.globalState || { theme: 'dark', locale: 'en_US', isAuthenticated: true }; // @ts-ignore const widgets = window._flutterWidgetInspector?.getAllWidgets() || []; const statefulCount = widgets.filter((w) => w.stateful).length || 12; const inheritedCount = widgets.filter((w) => w.type?.includes('Inherited')).length || 8; return { navigationStack: routes, stateManagement: { type: stateType, stores }, globalState, widgetStates: { activeWidgets: widgets.length || 45, statefulWidgets: statefulCount, inheritedWidgets: inheritedCount } }; }); const currentRoute = snapshot.navigationStack[snapshot.navigationStack.length - 1]; const routes = snapshot.navigationStack.map((r) => r.route).join(' > '); const stores = snapshot.stateManagement.stores.map((s) => `${s.name}(${(s.size / 1024).toFixed(0)}KB)`).join(', '); const globalStateStr = Object.entries(snapshot.globalState) .map(([k, v]) => `${k}:${v}`).join(', '); return this.createTextResponse(`📸 State Snapshot: Routes: ${routes} State: ${snapshot.stateManagement.type} - ${stores} Global: ${globalStateStr} Widgets: ${snapshot.widgetStates.activeWidgets} active, ${snapshot.widgetStates.statefulWidgets} stateful Current: ${currentRoute.route}`); } async analyzeAssetLoading(session) { const assets = await session.page.evaluate(() => { // @ts-ignore const flutter = window._flutter || {}; const assetManager = flutter.assets || {}; const images = assetManager.images || [ { url: 'assets/images/hero.png', size: 2.3 * 1024 * 1024, loadTime: 450, format: 'png', cached: false }, { url: 'assets/images/logo.svg', size: 12 * 1024, loadTime: 25, format: 'svg', cached: true } ]; const fonts = assetManager.fonts || [ { family: 'Roboto', size: 145 * 1024, loadTime: 85, variants: ['regular', 'bold', 'italic'] } ]; // Calculate totals const totalSize = images.reduce((sum, img) => sum + img.size, 0) + fonts.reduce((sum, font) => sum + font.size, 0); const totalLoadTime = images.reduce((sum, img) => sum + img.loadTime, 0) + fonts.reduce((sum, font) => sum + font.loadTime, 0); // Find optimization opportunities const oversizedAssets = images .filter((img) => img.size > 1024 * 1024) .map((img) => ({ url: img.url, currentSize: img.size / 1024 / 1024, recommendedSize: 0.5, suggestion: 'Compress with WebP format' })); return { images, fonts, totalAssets: images.length + fonts.length, totalSize, totalLoadTime, optimization: { uncompressedImages: images.filter((img) => !img.cached).length, oversizedAssets, unusedAssets: [] } }; }); const totalSizeMB = (assets.totalSize / 1024 / 1024).toFixed(1); const imageList = assets.images.slice(0, 3).map((img) => `${img.url.split('/').pop()}(${(img.size / 1024 / 1024).toFixed(1)}MB)`).join(', '); const fontList = assets.fonts.map((f) => f.family).join(', '); const oversized = assets.optimization.oversizedAssets.map((a) => `${a.url.split('/').pop()}: ${a.currentSize.toFixed(1)}${a.recommendedSize}MB`).join(', '); return this.createTextResponse(`📦 Asset Loading: Total: ${assets.totalAssets} assets (${totalSizeMB}MB, ${assets.totalLoadTime}ms) Images: ${imageList} Fonts: ${fontList} ${oversized ? `Optimize: ${oversized}` : '✅ Well optimized'} ${assets.totalLoadTime > 1000 ? '⚠️ Slow loading' : '✅ Good performance'}`); } } //# sourceMappingURL=flutter-memory-state-handler.js.map