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

430 lines • 17.7 kB
export class AsyncTrackingEngine { page = null; isTracking = false; asyncTrace = { operations: new Map(), timeline: [], eventLoopSnapshots: [], promiseChains: new Map() }; operationCounter = 0; snapshotInterval = null; attach(page) { this.page = page; } async enableTracking() { if (!this.page) { throw new Error('No page attached'); } this.isTracking = true; this.asyncTrace = { operations: new Map(), timeline: [], eventLoopSnapshots: [], promiseChains: new Map() }; // Inject async tracking code await this.injectAsyncTracking(); // Start periodic event loop snapshots this.snapshotInterval = setInterval(() => { this.captureEventLoopSnapshot(); }, 100); // Every 100ms } async disableTracking() { this.isTracking = false; if (this.snapshotInterval) { clearInterval(this.snapshotInterval); this.snapshotInterval = null; } } async injectAsyncTracking() { if (!this.page) return; await this.page.addInitScript(() => { window.__asyncTrace = { operations: new Map(), operationCounter: 0, promiseChains: new Map(), createOperation: function (type, metadata) { const id = `async_${window.__asyncTrace.operationCounter++}`; const stack = new Error().stack?.split('\n').slice(2) || []; const operation = { id, type, status: 'pending', createdAt: performance.now(), stack, metadata }; window.__asyncTrace.operations.set(id, operation); // Send timeline event window.postMessage({ type: '__asyncTrace', event: 'created', operationId: id, timestamp: performance.now() }, '*'); return id; }, resolveOperation: function (id, value) { const op = window.__asyncTrace.operations.get(id); if (op) { op.status = 'resolved'; op.resolvedAt = performance.now(); op.duration = op.resolvedAt - op.createdAt; op.value = value; window.postMessage({ type: '__asyncTrace', event: 'resolved', operationId: id, timestamp: performance.now() }, '*'); } }, rejectOperation: function (id, error) { const op = window.__asyncTrace.operations.get(id); if (op) { op.status = 'rejected'; op.resolvedAt = performance.now(); op.duration = op.resolvedAt - op.createdAt; op.error = error; window.postMessage({ type: '__asyncTrace', event: 'rejected', operationId: id, timestamp: performance.now() }, '*'); } }, linkPromises: function (parentId, childId) { const children = window.__asyncTrace.promiseChains.get(parentId) || []; children.push(childId); window.__asyncTrace.promiseChains.set(parentId, children); const childOp = window.__asyncTrace.operations.get(childId); if (childOp) { childOp.parentId = parentId; } } }; // Track Promises const OriginalPromise = window.Promise; const asyncTrace = window.__asyncTrace; // Create a properly typed Promise constructor const TrackedPromise = function (executor) { const asyncId = asyncTrace.createOperation('promise'); let isResolved = false; const promise = new OriginalPromise((resolve, reject) => { const trackedResolve = (value) => { if (!isResolved) { isResolved = true; asyncTrace.resolveOperation(asyncId, value); } return resolve(value); }; const trackedReject = (reason) => { if (!isResolved) { isResolved = true; asyncTrace.rejectOperation(asyncId, reason); } return reject(reason); }; try { executor(trackedResolve, trackedReject); } catch (error) { trackedReject(error); } }); promise._asyncId = asyncId; return promise; }; // Copy static methods TrackedPromise.resolve = OriginalPromise.resolve; TrackedPromise.reject = OriginalPromise.reject; TrackedPromise.all = OriginalPromise.all; TrackedPromise.race = OriginalPromise.race; // Set prototype TrackedPromise.prototype = OriginalPromise.prototype; window.Promise = TrackedPromise; // Track setTimeout const originalSetTimeout = window.setTimeout; window.setTimeout = function (handler, delay, ...args) { const asyncId = asyncTrace.createOperation('timeout', { delay }); const wrappedHandler = function () { asyncTrace.resolveOperation(asyncId); if (typeof handler === 'function') { return handler.apply(this, args); } }; return originalSetTimeout(wrappedHandler, delay); }; // Track setInterval const originalSetInterval = window.setInterval; const intervalMap = new Map(); window.setInterval = function (handler, delay, ...args) { const asyncId = asyncTrace.createOperation('interval', { delay }); let count = 0; const wrappedHandler = function () { // Create a new operation for each interval tick const tickId = asyncTrace.createOperation('interval', { delay, parentInterval: asyncId, tick: ++count }); asyncTrace.resolveOperation(tickId); if (typeof handler === 'function') { return handler.apply(this, args); } }; const intervalId = originalSetInterval(wrappedHandler, delay); intervalMap.set(intervalId, asyncId); return intervalId; }; // Track clearInterval const originalClearInterval = window.clearInterval; window.clearInterval = function (intervalId) { const asyncId = intervalMap.get(intervalId); if (asyncId) { const op = asyncTrace.operations.get(asyncId); if (op) { op.status = 'cancelled'; op.resolvedAt = performance.now(); op.duration = op.resolvedAt - op.createdAt; } intervalMap.delete(intervalId); } return originalClearInterval(intervalId); }; // Track requestAnimationFrame const originalRAF = window.requestAnimationFrame; window.requestAnimationFrame = function (callback) { const asyncId = asyncTrace.createOperation('animationFrame'); const wrappedCallback = function (timestamp) { asyncTrace.resolveOperation(asyncId, timestamp); return callback(timestamp); }; return originalRAF(wrappedCallback); }; // Track fetch const originalFetch = window.fetch; window.fetch = function (input, init) { const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url; const asyncId = asyncTrace.createOperation('fetch', { url }); const promise = originalFetch(input, init); promise._asyncId = asyncId; promise.then(response => { asyncTrace.resolveOperation(asyncId, { status: response.status, statusText: response.statusText, headers: (() => { const hdrs = {}; response.headers.forEach((value, key) => { hdrs[key] = value; }); return hdrs; })() }); return response; }, error => { asyncTrace.rejectOperation(asyncId, error); throw error; }); return promise; }; // Track event listeners const originalAddEventListener = EventTarget.prototype.addEventListener; const eventListenerMap = new Map(); EventTarget.prototype.addEventListener = function (type, listener, options) { const target = this.constructor.name; const asyncId = asyncTrace.createOperation('event', { eventType: type, target }); const key = `${target}_${type}`; const listeners = eventListenerMap.get(key) || new Set(); listeners.add(asyncId); eventListenerMap.set(key, listeners); const wrappedListener = function (event) { // Create a new operation for each event dispatch const dispatchId = asyncTrace.createOperation('event', { eventType: type, target, parentListener: asyncId }); try { const result = listener.call(this, event); asyncTrace.resolveOperation(dispatchId, result); return result; } catch (error) { asyncTrace.rejectOperation(dispatchId, error); throw error; } }; return originalAddEventListener.call(this, type, wrappedListener, options); }; }); // Listen for async trace events await this.page.evaluate(() => { window.addEventListener('message', (event) => { if (event.data && event.data.type === '__asyncTrace') { // Store events for later retrieval window.__asyncTraceEvents = window.__asyncTraceEvents || []; window.__asyncTraceEvents.push({ event: event.data.event, operationId: event.data.operationId, timestamp: event.data.timestamp }); } }); }); } async captureEventLoopSnapshot() { if (!this.page || !this.isTracking) return; try { const snapshot = await this.page.evaluate(() => { const trace = window.__asyncTrace; if (!trace) return null; const now = performance.now(); const snapshot = { timestamp: now, macrotaskQueue: [], microtaskQueue: [], pendingPromises: [], activeTimers: [], animationFrames: [] }; // Categorize operations for (const [id, op] of trace.operations) { if (op.status === 'pending') { switch (op.type) { case 'promise': snapshot.pendingPromises.push(op); break; case 'timeout': case 'interval': snapshot.activeTimers.push(op); break; case 'animationFrame': snapshot.animationFrames.push(op); break; case 'fetch': snapshot.macrotaskQueue.push(op); break; } } } return snapshot; }); if (snapshot) { this.asyncTrace.eventLoopSnapshots.push(snapshot); } } catch (error) { // Page might be navigating or closed } } async getAsyncTrace() { if (!this.page) { throw new Error('No page attached'); } // Get trace data from page const traceData = await this.page.evaluate(() => { const trace = window.__asyncTrace; const events = window.__asyncTraceEvents || []; if (!trace) return null; return { operations: Array.from(trace.operations.entries()), promiseChains: Array.from(trace.promiseChains.entries()), events }; }); if (traceData) { // Convert to our format this.asyncTrace.operations.clear(); traceData.operations.forEach((entry) => { const [id, op] = entry; this.asyncTrace.operations.set(id, op); }); this.asyncTrace.promiseChains.clear(); traceData.promiseChains.forEach((entry) => { const [parentId, childIds] = entry; this.asyncTrace.promiseChains.set(parentId, childIds); }); this.asyncTrace.timeline = traceData.events.map((e) => ({ timestamp: e.timestamp, type: e.event, operationId: e.operationId })); } return this.asyncTrace; } async getPromiseChain(promiseId) { const trace = await this.getAsyncTrace(); const chain = []; const visited = new Set(); const buildChain = (id) => { if (visited.has(id)) return; visited.add(id); const op = trace.operations.get(id); if (op) { chain.push(op); const children = trace.promiseChains.get(id) || []; children.forEach(childId => buildChain(childId)); } }; buildChain(promiseId); return chain; } async getAsyncStackTrace(operationId) { const trace = await this.getAsyncTrace(); const operation = trace.operations.get(operationId); if (!operation) { return []; } const fullStack = [...operation.stack]; // Add parent async contexts let currentOp = operation; while (currentOp.parentId) { const parent = trace.operations.get(currentOp.parentId); if (parent) { fullStack.push(`--- async ---`); fullStack.push(...parent.stack); currentOp = parent; } else { break; } } return fullStack; } async detectAsyncLeaks() { const trace = await this.getAsyncTrace(); const now = performance.now(); const leaks = new Map(); // Find long-running pending operations for (const [_, op] of trace.operations) { if (op.status === 'pending') { const age = now - op.createdAt; if (age > 5000) { // 5 seconds threshold const key = op.type; const list = leaks.get(key) || []; list.push(op); leaks.set(key, list); } } } return Array.from(leaks.entries()).map(([type, operations]) => ({ type, count: operations.length, oldestAge: Math.max(...operations.map(op => now - op.createdAt)), operations: operations.sort((a, b) => a.createdAt - b.createdAt).slice(0, 10) })); } } //# sourceMappingURL=async-tracking-engine.js.map