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