Understanding the CDP Transport Layer in ego-lite: Architecture and Implementation
The CDP transport layer in ego-lite is a promise-based abstraction around the Chrome DevTools Protocol that handles session management, automatic retries, and event buffering, allowing agents to interact with browser internals without managing low-level message serialization.
The citrolabs/ego-lite repository provides a browser automation framework built on the Chrome DevTools Protocol (CDP). At its core, the CDP transport layer translates high-level JavaScript calls into raw CDP messages, manages page session lifecycles, and handles transient failures transparently. This system resides primarily in package/ego-browser/src/browser-runtime.ts and exposes a clean browserCdp API that hides the complexity of tab attachment, timeout handling, and session recovery.
Core Architecture
The transport layer follows a layered design that separates raw message serialization from session lifecycle management. This separation allows the system to recover gracefully from transient failures while maintaining a consistent API surface for agent scripts.
The Entry Point: browserCdp
The primary interface for CDP communication is the browserCdp function defined at lines 79‑85 of browser-runtime.ts. This function serves as the gatekeeper for all CDP traffic, implementing a test-override check (state.cdpOverride) before processing any real commands.
When invoked, browserCdp determines the appropriate session context automatically. If the requested method is not a top-level Browser or Target command, it delegates to ensureSession() to guarantee a valid page session exists before transmitting the message. This automatic session resolution ensures that domain-specific commands (such as Page.getNavigationHistory or Runtime.evaluate) always execute within the correct browsing context.
Raw Message Transport: rawCdp
Beneath the session management layer, the rawCdp function (lines 38‑77 of browser-runtime.ts) handles the actual wire protocol. It constructs CDP request objects with the standard {id, method, params, sessionId?} structure and transmits them via globalThis.ego.sendCDPMessage.
The function registers response handlers using runtime.onCDPMessage and error callbacks via runtime.onSendCDPMessageError. Incoming responses route through a pending map keyed by request ID, enabling promise-based resolution of asynchronous CDP operations. A hard timeout of 15 seconds (RESPONSE_TIMEOUT_MS = 15000) ensures that unresponsive commands reject rather than hang indefinitely.
Session Lifecycle Management: ensureSession
Session state management occurs in ensureSession (lines 107‑143 of browser-runtime.ts). This function implements an intelligent caching mechanism that reuses existing sessions while they remain fresh, using a time-to-live (TTL) threshold of approximately 2 seconds.
When no valid session exists, the system:
- Lists available tabs via
ego.listTabs() - Selects the preferred or active tab
- Attaches to the target using
Target.attachToTarget - Enables page-level events for the session
- Caches the resulting
sessionIdfor subsequent calls
Session loss detection relies on a SESSION_LOST regex (defined at lines 9‑10) that matches error messages containing "Session not found" or "Target closed". When detected, the system invalidates the cached session and triggers recovery logic.
Error Recovery and Retry Logic
The transport layer implements automatic resilience against transient session failures. When browserCdp encounters a SESSION_LOST error at lines 95‑104, it checks whether the request was explicitly targeted at the browser level. For page-level commands, the system:
- Invalidates the stale session via
invalidateSession() - Retries the request with a fresh session
- Preserves the original caller's intent without throwing
This retry mechanism operates transparently, ensuring that agents experience minimal disruption when tabs close or devtools sessions detach unexpectedly.
Event Handling Infrastructure
The runtime buffers CDP events in an internal events array and exposes three utility functions for event consumption (referenced at lines 64‑95 of browser-runtime.ts):
drainBrowserEvents– Retrieves and clears the current event bufferwaitForBrowserEvent– Returns a promise that resolves when a specific event type arrivessubscribeBrowserEvent– Registers persistent listeners for domain-specific events (Network, DOM, etc.)
These helpers allow agents to monitor browser activity without manually parsing the low-level CDP message stream or managing event subscription state.
Centralized State Management
All session metadata lives in package/ego-browser/src/state.ts, exported as a singleton. The transport layer tracks:
state.sessionId– Active CDP session identifierstate.sessionAt– Timestamp of last session activitystate.sessionTargetId– Target tab identifierstate.sessionInflight– Pending operation counter
This centralization guarantees that every helper function (including those in cdp-eval.ts) operates against a consistent CDP context, preventing race conditions during rapid-fire automation sequences.
High-Level Helper APIs
Building upon the transport foundation, package/ego-browser/src/cdp-eval.ts (lines 7‑10) exposes convenient wrappers for common operations:
cdp(method, params?)– Arbitrary CDP command executionjs(expression)– JavaScript evaluation in the page context
These functions import browserCdp directly and provide type-safe interfaces for agent scripts, transforming raw protocol operations into intuitive JavaScript function calls.
Practical Usage Examples
The following examples demonstrate interaction patterns with the ego-lite CDP transport layer:
// Retrieve navigation history for the active page
const history = await cdp('Page.getNavigationHistory');
console.log('Current index:', history.currentIndex);
// Evaluate JavaScript in the page context and return the result
const title = await js('document.title');
console.log('Page title:', title);
// Enable network monitoring for the current session
await browserCdp('Network.enable');
const events = await drainBrowserEvents();
console.log('Captured network events:', events);
// Direct low-level usage with full control over session targeting
const response = await browserCdp('Runtime.evaluate', {
expression: 'window.location.href',
returnByValue: true
});
Summary
- The CDP transport layer in citrolabs/ego-lite abstracts Chrome DevTools Protocol complexity through the
browserCdpfunction inbrowser-runtime.ts. - Session management automatically handles tab attachment, 2-second TTL caching, and transparent retry logic when sessions disconnect.
- Raw transport (
rawCdp) implements 15-second timeouts and promise-based response routing viaglobalThis.ego.sendCDPMessage. - Event buffering supports asynchronous monitoring of browser events through
drainBrowserEventsandwaitForBrowserEvent. - State centralization in
state.tsensures consistent context across all automation helpers.
Frequently Asked Questions
How does ego-lite handle CDP session timeouts?
The transport layer enforces a default timeout of 15 seconds (RESPONSE_TIMEOUT_MS) in the rawCdp function. If the browser fails to respond within this window, the promise rejects with a EGO_CDP_SEND_FAILED error (defined in ego-errors.ts), allowing calling code to implement fallback logic or retry strategies.
Can I use the CDP transport without automatic session management?
Yes. While the high-level cdp() and js() helpers automatically manage sessions, you can import browserCdp directly from browser-runtime.ts and pass explicit session identifiers in the parameters. However, for Browser and Target domain commands, the system bypasses session resolution automatically, allowing direct communication with the browser endpoint.
What happens when a tab closes during automation?
When a command fails with a "Session not found" or "Target closed" message (matched by the SESSION_LOST regex), the transport layer invalidates the cached session, re-attaches to an available target via ensureSession, and retries the original request. This recovery happens transparently unless the original request explicitly targeted a specific closed browser session.
Where is the CDP transport state stored?
All session state—including sessionId, sessionAt, and sessionTargetId—resides in the singleton exported from package/ego-browser/src/state.ts. This design ensures that multiple helper modules (such as cdp-eval.ts and helpers.ts) share a consistent view of the active CDP context without passing state objects through every function call.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →