How `ego.sendCDPMessage` Works: The CDP Transport Mechanism in Ego‑Lite

ego.sendCDPMessage is the low‑level bridge that Ego‑Lite injects into the page, allowing JavaScript code to send Chrome DevTools Protocol commands to the underlying browser and receive structured responses via promises.

The ego‑lite repository provides the open‑source runtime wrapper around this closed‑source host function. Understanding this mechanism is essential for anyone building browser automation agents or extending Ego‑Lite's capabilities. The implementation lives in package/ego-browser/src/browser-runtime.ts and handles everything from request serialization to automatic session recovery.

Runtime Detection and Global Hook

Before any CDP communication begins, the runtime verifies the host environment. The isBrowserRuntime() function checks for the presence of globalThis.ego.sendCDPMessage at lines 25‑30 of browser-runtime.ts. This Boolean guard prevents the runtime from loading in non‑Ego contexts and is also used throughout driver modules like pointer.ts and keyboard.ts to conditionally expose functionality.

The Request Lifecycle: From Call to Promise

The core of the mechanism is the rawCdp() function (lines 38‑77). Here's the exact flow:

Step 1: Message Construction

Each CDP call receives a unique message ID generated via incrementing counter. The runtime builds a JSON payload with this structure:

{
  "id": 42,
  "method": "DOM.getDocument",
  "params": { "depth": 1 }
}

Step 2: Pending Registration

The runtime stores a pending entry containing:

  • The message ID
  • A Promise resolver/rejector pair
  • A timeout handle (configurable, typically 30 seconds)

Step 3: Host Delivery

The payload passes to runtime.sendCDPMessage(payload) at lines 44‑46. This is the actual injection point where your JavaScript hands control to the closed‑source ego‑lite binary, which streams the request to Chrome's DevTools endpoint.

Step 4: Response Routing

When Chrome replies, the host invokes handleMessage(message) (lines 32‑50). The runtime:

  1. Parses the JSON response
  2. Looks up the pending entry by matching id
  3. Clears the timeout
  4. Resolves the promise if result exists, or rejects if error is present

Error Handling for Local Failures

Not all failures come from Chrome. If the host cannot deliver the request—due to inactive tasks, user‑controlled state, or browser process termination—handleSendError(message, error_code) triggers at lines 24‑30. This rejects all pending requests immediately with a formatted EgoError from src/ego-errors.ts, preventing hanging promises.

Session Management and Auto‑Recovery

Most high‑level helpers like browserCdp() first call ensureSession() at lines 85‑103. This attaches a CDP session to the target tab. If the session is lost—detected via SESSION_LOST regex matching on error messages—the runtime automatically re‑attaches and retries the original command transparently.

import { browserCdp } from 'ego-browser';

// This handles session loss automatically
const doc = await browserCdp('DOM.getDocument');
console.log(doc.result.root.nodeId);

Event Handling and Buffering

CDP events (messages without an id field) are dispatched to subscribers and buffered internally. The runtime exposes these through:

  • drainBrowserEvents() – returns all buffered events
  • waitForBrowserEvent(predicate, timeout) – promise‑based filtering

This buffering at lines 70‑92 ensures no events are lost during async gaps between agent instructions.

High‑Level API Usage

While rawCdp() handles transport, most code uses the wrappers in src/cdp-eval.ts:

import { evaluate, browserCdp } from 'ego-browser';

// Execute arbitrary JavaScript in page context
const title = await evaluate(() => document.title);

// Direct CDP call with full response
const metrics = await browserCdp('Performance.getMetrics');

Both helpers ultimately resolve through the ego.sendCDPMessage flow described above.

Key Source Files

Path Responsibility
src/browser-runtime.ts Core transport, pending map, timeout handling, session recovery
src/cdp-eval.ts Public API surface (cdp, evaluate, browserCdp)
src/state.ts Global mutable state: session IDs, pending queue, event buffers
src/ego-errors.ts Error code mapping and user‑friendly message formatting
src/driver/*.ts Runtime availability checks for input simulation
src/helpers.ts Agent context injection and sandbox setup

Summary

  • ego.sendCDPMessage is the host‑injected primitive that enables CDP communication from within the page
  • The runtime wraps it with promise‑based request/response handling, automatic timeouts, and session recovery
  • All failures—whether from Chrome or local delivery—surface as structured rejections via EgoError
  • Higher‑level APIs abstract the transport while preserving full access to raw CDP methods
  • Event buffering ensures reliable delivery of CDP notifications to automation agents

Frequently Asked Questions

What happens if ego.sendCDPMessage is not available?

The runtime detects this via isBrowserRuntime() and refuses to initialize. Driver modules like pointer.ts also check this flag, returning false for feature availability checks when running outside the Ego‑Lite host.

Can I use ego.sendCDPMessage directly without the wrapper?

Technically yes, but you lose timeout protection, session auto‑recovery, structured error handling, and event buffering. The rawCdp() wrapper in browser-runtime.ts adds approximately 50 lines of safety logic that raw usage bypasses.

How does session recovery work when a tab crashes?

When a command returns an error matching SESSION_LOST, ensureSession() (lines 85‑103) catches this, re‑attaches to the target via Target.attachToTarget, updates the stored session ID in state.ts, and retries the original command with the same parameters. This is transparent to calling code.

What's the difference between browserCdp and evaluate?

**browserCdp** sends raw CDP commands and returns the full protocol response. **evaluate** is a convenience wrapper that uses Runtime.evaluate under the hood, handles script execution context automatically, and returns the evaluated result directly rather than the CDP envelope.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →