/** * CDP Connection Manager * Handles connection to Chrome DevTools Protocol (Chrome or Node.js) */ import type { BreakpointInfo, CallFrame, RuntimeType, ConsoleMessageCallback, DOMBreakpointInfo, DOMBreakpointType, EventListenerBreakpointInfo, XHRBreakpointInfo } from './types.js'; import type { SourceMapHandler } from './sourcemap-handler.js'; /** * Thrown by evaluateExpression() when the evaluated code itself threw * (CDP reports this via `exceptionDetails` on an otherwise-successful * Runtime.evaluate/Debugger.evaluateOnCallFrame response, NOT as a rejected * promise) - e.g. `Math.max(...bigArray)` throwing a stack-exhaustion * RangeError. This is an ordinary outcome of evaluating arbitrary user code * and must be surfaced as a normal tool error rather than silently ignored. */ export declare class EvaluateExpressionExceptionError extends Error { readonly expression: string; readonly exceptionType: string; readonly exceptionMessage: string; readonly exceptionStack?: string | undefined; constructor(expression: string, exceptionType: string, exceptionMessage: string, exceptionStack?: string | undefined); } /** * Thrown by evaluateExpression() when the underlying CDP call does not * return within the bounded client-side timeout. This is the backstop for * cases where the renderer/execution context stops responding entirely (no * result, no exception, no CDP-level timeout) - see the RangeError * stack-exhaustion hang this was written to fix. */ export declare class EvaluateExpressionTimeoutError extends Error { readonly expression: string; readonly timeoutMs: number; constructor(expression: string, timeoutMs: number); } /** * Thrown by evaluateExpression() when the expression returned a Promise that * is still pending while the debugger is paused. The paused event loop can * never run the microtask queue, so the promise can never settle - awaiting * it would only burn the full client-side timeout. Failing fast with an * explanation is strictly better. (Promises that already settled - e.g. * `Promise.resolve(x)` or an async IIFE with no real await - ARE recovered * via their [[PromiseState]]/[[PromiseResult]] internal properties and never * reach this error.) */ export declare class EvaluateExpressionPendingPromiseError extends Error { readonly expression: string; constructor(expression: string); } /** Options accepted by evaluateExpression()/evaluateExpressionDetailed(). */ export interface EvaluateExpressionOptions { /** * Await a returned Promise and use its settled value (default: true). * Pass false to inspect the Promise object itself. */ awaitPromise?: boolean; /** * Also attempt an exact, machine-readable capture of the result (via CDP * returnByValue on the result object - no re-execution, no display * round-trip). Only meaningful through evaluateExpressionDetailed(). */ captureRaw?: boolean; } /** Result of evaluateExpressionDetailed(). */ export interface EvaluateExpressionDetailedResult { /** Human-facing formatted value (same shape evaluateExpression() returns). */ formatted: any; /** Exact value, only meaningful when rawCaptured is true. */ rawValue?: unknown; /** * True when rawValue is an exact by-value capture of the result. False when * the value is not serializable by value (DOM node, Map, Date, window, ...) * - callers should fall back to reconstructing from `formatted`. */ rawCaptured: boolean; } export declare class CDPManager { private client; private state; private scriptIdToUrl; private urlToScriptId; private pauseResolvers; private scriptWaitResolvers; private sourceMapHandler; private logpointLimitExceeded; private consoleMessageCallback; private pauseCallback; private resumeCallback; private domBreakpoints; private eventBreakpoints; private xhrBreakpoints; private advancedBpCounter; constructor(sourceMapHandler?: SourceMapHandler); /** * Set a callback to receive console messages from Runtime.consoleAPICalled * This is used for Node.js debugging where Puppeteer's page.on('console') isn't available */ setConsoleMessageCallback(callback: ConsoleMessageCallback | null): void; /** * Set a callback to be called when debugger pauses at a breakpoint */ setPauseCallback(callback: (() => void) | null): void; /** * Set a callback to be called when debugger resumes from a breakpoint */ setResumeCallback(callback: (() => void) | null): void; /** * Find script IDs by URL, with fallback matching strategies: * 1. Exact match * 2. Base URL match (strips query params) * 3. Filename/path suffix match (e.g., "click.js" matches "/controls/click.js") * * This allows breakpoints to work with partial URLs and across page reloads * Returns the most recently loaded script (highest scriptId) when multiple matches exist */ private findScriptIds; /** * Check if a script with the given URL (or matching suffix) is loaded * This is useful to detect Vite-style serving where .ts files are loaded directly */ isScriptLoaded(url: string): boolean; /** * Get the URL for a script by its ID */ getScriptUrl(scriptId: string): string | undefined; /** * Connect to a Chrome or Node.js debugger instance * @param host - The debugger host (default: localhost) * @param port - The debugger port (default: 9222) * @param targetId - Optional target ID to connect to a specific page/tab */ connect(host?: string, port?: number, targetId?: string): Promise; /** * Disconnect from the debugger */ disconnect(): Promise; /** * Check if connected */ isConnected(): boolean; /** * Check if currently paused at a breakpoint */ isPaused(): boolean; /** * Get the runtime type (chrome, node, or unknown) */ getRuntimeType(): RuntimeType; /** * Detect whether we're connected to Chrome/browser or Node.js * This detection happens by checking for browser-specific global objects */ private detectRuntimeType; /** * Set a breakpoint at a specific file and line */ setBreakpoint(url: string, lineNumber: number, columnNumber?: number, condition?: string): Promise; /** * Remove a breakpoint */ removeBreakpoint(breakpointId: string): Promise; /** * Get all active breakpoints */ getBreakpoints(): BreakpointInfo[]; /** * Get breakpoint counts (total, regular, logpoints) */ getBreakpointCounts(): { total: number; breakpoints: number; logpoints: number; }; /** * Wait for a script matching the given URL pattern to load * @param urlPattern - String (substring match) or RegExp to match against script URLs * @param timeout - Maximum time to wait in milliseconds (default: 10000) * @returns The URL of the matched script * @throws Error if timeout expires before script loads */ waitForScript(urlPattern: string | RegExp, timeout?: number): Promise; /** * Get list of all loaded scripts * @returns Array of script URLs */ getLoadedScripts(): string[]; /** * Check if a script matching the pattern is loaded * @param urlPattern - String (substring match) or RegExp * @returns The matched URL or null */ findLoadedScript(urlPattern: string | RegExp): string | null; /** * Synchronize breakpoint state with CDP's actual breakpoints * Use this to recover from state desynchronization */ syncBreakpoints(): Promise<{ synced: number; removed: number; }>; /** * Diagnose why a breakpoint failed to set (empty locations array) * Performs lazy validation to determine exact cause */ diagnoseBreakpointFailure(url: string, lineNumber: number): Promise<{ cause: 'script_not_found' | 'line_out_of_bounds' | 'line_not_executable'; message: string; scriptUrl: string; requestedLine: number; totalLines?: number; suggestion: string; }>; /** * Resume execution */ resume(): Promise; /** * Pause execution */ pause(): Promise; /** * Handle logpoint execution limit exceeded * This should be called by the LogpointExecutionTracker when a logpoint hits its limit */ handleLogpointLimitExceeded(metadata: { breakpointId: string; url: string; lineNumber: number; logMessage: string; executionCount: number; maxExecutions: number; logs: any[]; }): Promise; /** * Get information about the logpoint that exceeded its limit (if any) */ getLogpointLimitExceeded(): typeof this.logpointLimitExceeded; /** * Clear the logpoint limit exceeded state */ clearLogpointLimitExceeded(): void; /** * Step over (next line) */ stepOver(): Promise; /** * Step into function */ stepInto(): Promise; /** * Step out of function */ stepOut(): Promise; /** * Get current call stack */ getCallStack(): CallFrame[] | undefined; /** * Get variables for a specific call frame */ /** * Estimate token count for a value (rough approximation: ~4 chars per token) */ private estimateTokens; /** * Calculate effective token budget accounting for response overhead. * The final MCP response includes: * - Message template text (~100 tokens) * - JSON code block markers and wrapper (~50 tokens) * - JSON.stringify indentation adds ~30% to data size */ private calculateEffectiveBudget; getVariables(callFrameId: string, includeGlobal?: boolean, filter?: string, expandObjects?: boolean, maxDepth?: number, maxTokens?: number): Promise<{ data: any; totalCount: number; usedDepth: number; requestedDepth: number; responseType: 'full' | 'depth_reduced' | 'names_only' | 'counts_only'; filterInsufficient: boolean; }>; /** * Bounded upper limit on how long evaluateExpression() will wait for CDP to * respond, in milliseconds. Exported as an instance property (rather than a * module constant) so tests can override it on a per-manager basis without * needing real wall-clock waits. Kept comfortably below the 180s default * Puppeteer protocolTimeout that would otherwise leak through as a raw, * untyped "Runtime.callFunctionOn timed out" error on this path. */ evaluateExpressionTimeoutMs: number; /** * Evaluate an expression in the current context. * * Two distinct failure modes are handled explicitly (this method used to * handle neither, which is how a stack-exhaustion RangeError thrown by * evaluated code could hang a caller forever with no result, no error, and * no timeout): * * 1. The evaluated code throws. CDP reports this via `exceptionDetails` on * an otherwise-successful response - it is NOT a rejected promise - so * it must be checked explicitly and surfaced as * EvaluateExpressionExceptionError instead of being formatted as if it * were a normal return value. * 2. The CDP call itself never resolves (a wedged renderer/execution * context). A bounded client-side timeout (racing the CDP call against * a timer) guarantees this method always settles, throwing * EvaluateExpressionTimeoutError instead of hanging indefinitely. We * also pass CDP's own `timeout` param so well-behaved renderers abort * the runaway script themselves and return exceptionDetails cleanly * (case 1) rather than relying solely on our client-side timer. */ evaluateExpression(expression: string, callFrameId?: string, expandObjects?: boolean, maxDepth?: number, options?: EvaluateExpressionOptions): Promise; /** * Like evaluateExpression(), but also attempts an exact by-value capture of * the result when options.captureRaw is set (used for `saveAs` and the * structured `_meta.inspect.value`, so captured data does not round-trip * through display formatting). * * Promise handling (bug-015), all empirically verified against Chrome 150: * - Running page, no callFrameId: `awaitPromise: true` is passed to * Runtime.evaluate, so async results resolve to their settled value and * rejections surface as exceptionDetails (-> ExceptionError). A promise * that never settles is bounded by the client-side timeout. * - Paused (callFrameId given, OR a global evaluation while the debugger is * paused - Runtime.evaluate with awaitPromise HANGS in that state, and * Debugger.evaluateOnCallFrame silently ignores awaitPromise): we never * pass awaitPromise. Instead, a Promise result is resolved through its * [[PromiseState]]/[[PromiseResult]] internal properties, which works * without running the event loop: already-settled promises yield their * exact value (or ExceptionError if rejected); a pending promise fails * fast with EvaluateExpressionPendingPromiseError instead of burning the * timeout, because it can never settle while the event loop is stopped. */ evaluateExpressionDetailed(expression: string, callFrameId?: string, expandObjects?: boolean, maxDepth?: number, options?: EvaluateExpressionOptions): Promise; /** Map CDP exceptionDetails to a typed error (thrown code or awaited promise rejection). */ private toEvaluateExpressionExceptionError; /** * Resolve a Promise RemoteObject without running the event loop, via its * [[PromiseState]]/[[PromiseResult]] internal properties (readable even * while the debugger is paused). Returns the settled value's RemoteObject; * throws for rejected/pending promises. */ private resolveSettledPromiseWhilePaused; /** * Attempt an exact by-value capture of an evaluation result, WITHOUT * re-executing the expression: primitives come straight off the * RemoteObject; plain objects/arrays are serialized in place via * Runtime.callFunctionOn(returnByValue). Values whose by-value form is * lossy or impossible (DOM nodes, Date, Map/Set, functions, window - CDP * serializes these to {} or errors) report rawCaptured: false so callers * can fall back to the display-derived reconstruction. */ private captureRawValue; /** * Get available variables at a specific source location * Useful for validating logpoint expressions */ getScopeVariablesAtLocation(url: string, lineNumber: number): Promise<{ variables: string[]; scopes: Array<{ type: string; variables: string[]; }>; } | null>; /** * Validate logpoint expressions at a specific location * Sets temp breakpoint, waits for execution, tests expressions * * @param url File URL (e.g., http://localhost:3000/app.js) * @param lineNumber Line number (1-based, will be converted to 0-based for CDP) * @param columnNumber Optional column number (1-based, will be converted to 0-based for CDP) * @param expressions Array of expressions to validate (e.g., ["user.name", "user.id"]) * @param timeout Max wait time for execution in milliseconds * @returns Validation results with pass/fail for each expression, plus available variables */ validateLogpointAtActualLocation(url: string, lineNumber: number, columnNumber: number | undefined, expressions: string[], timeout?: number): Promise<{ executed: boolean; allValid: boolean; results: Array<{ expression: string; valid: boolean; value?: any; error?: string; }>; availableVariables?: string[]; actualLocation?: { line: number; column: number; }; }>; /** * Search for the best location to set a logpoint by trying nearby lines/columns * Returns suggestions ranked by how many expressions are valid * * @param url File URL * @param lineNumber Desired line number (1-based) * @param columnNumber Desired column number (1-based, optional) * @param expressions Array of expressions to validate * @param searchRadius Number of lines to search in each direction (default: 2) * @param timeout Timeout per location test in milliseconds (default: 1000ms) * @returns Array of suggestions sorted by score (best first), max 3 results */ findBestLogpointLocation(url: string, lineNumber: number, columnNumber: number | undefined, expressions: string[], searchRadius?: number, timeout?: number): Promise>; /** * Get source code from a file at a specific line range */ getSourceCode(url: string, startLine?: number, endLine?: number): Promise<{ code: string; totalLines: number; hasSourceMap: boolean; }>; /** * Inject a clickable console link in the browser */ injectConsoleLink(url: string, lineNumber: number, message: string): Promise; /** * Get detailed information about current pause state */ getPausedInfo(): { paused: boolean; location?: any; callStack?: CallFrame[]; }; /** * Wait for debugger to pause (for race detection) * Returns a promise that resolves when debugger pauses, or rejects on timeout */ waitForPause(timeoutMs?: number): Promise; /** * Format a CDP RemoteObject value for display * @param value The CDP RemoteObject to format * @param expandObjects Whether to expand object/array contents (default: false for backward compatibility) * @param maxDepth Maximum depth for object expansion (default: 2) * @param currentDepth Current recursion depth (internal use) */ private formatValue; /** * Expand a CDP RemoteObject by its objectId * This is used to get full object details for console messages * @param objectId - The CDP object ID to expand * @param maxDepth - Maximum depth for nested object expansion (default: 2) */ expandObjectById(objectId: string, maxDepth?: number): Promise; /** * Get all loaded scripts */ getAllScripts(): Array<{ scriptId: string; url: string; }>; /** * Search within a specific script using regex */ searchInScript(scriptId: string, pattern: string, caseSensitive?: boolean, isRegex?: boolean): Promise>; /** * Get a specific line from a script by scriptId and line number * Returns the full line content (useful for getting full webpack eval lines) */ getScriptLine(scriptId: string, lineNumber: number): Promise; /** * Generate a unique ID for advanced breakpoints */ private generateAdvancedBpId; /** * Resolve a CSS selector to a CDP nodeId * @param selector - CSS selector to resolve * @returns nodeId for the element */ resolveSelector(selector: string): Promise; /** * Set a DOM breakpoint on a node * Pauses execution when the specified DOM mutation occurs * @param nodeId - The node ID from resolveSelector * @param type - Type of DOM change to break on * @param selector - Original CSS selector (for reference/display) */ setDOMBreakpoint(nodeId: number, type: DOMBreakpointType, selector: string): Promise; /** * Remove a DOM breakpoint by its ID */ removeDOMBreakpoint(breakpointId: string): Promise; /** * Get all DOM breakpoints */ getDOMBreakpoints(): DOMBreakpointInfo[]; /** * Set an event listener breakpoint * Pauses execution when the specified event fires * @param eventName - DOM event name (e.g., 'click', 'submit', 'input') * @param targetName - Optional EventTarget interface filter (e.g., 'HTMLInputElement') */ setEventListenerBreakpoint(eventName: string, targetName?: string): Promise; /** * Remove an event listener breakpoint by its ID */ removeEventListenerBreakpoint(breakpointId: string): Promise; /** * Get all event listener breakpoints */ getEventListenerBreakpoints(): EventListenerBreakpointInfo[]; /** * Set an XHR/Fetch breakpoint * Pauses execution when an XHR or Fetch request URL contains the pattern * @param urlPattern - URL substring to match */ setXHRBreakpoint(urlPattern: string): Promise; /** * Remove an XHR/Fetch breakpoint by its ID */ removeXHRBreakpoint(breakpointId: string): Promise; /** * Get all XHR/Fetch breakpoints */ getXHRBreakpoints(): XHRBreakpointInfo[]; } //# sourceMappingURL=cdp-manager.d.ts.map