# CDP Message Protocol and Session Attachment Flow in Ego-Lite: A Deep Dive

> Understand the CDP message protocol and session attachment flow in Ego-Lite. Discover how Ego-Lite manages requests, responses, timeouts, and automatic retries for seamless session management.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-08-07

---

**Ego-Lite drives the Chromium DevTools Protocol (CDP) through a robust message bridge that automatically manages request/response pairing, timeouts, and page-level session attachment with automatic retry logic.**

The **CDP message protocol** in Ego-Lite abstracts the low-level Chromium DevTools Protocol into a reliable JavaScript API. According to the citrolabs/ego-lite source code, this implementation centers on the `ego.sendCDPMessage` bridge exposed by the host runtime (`globalThis.ego`), with core coordination handled in **[`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)** and mutable state managed by the singleton in **[`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)**.

## CDP Request/Response Handling

Every interaction with the browser follows a strict request/response lifecycle. The system assigns unique message IDs, enforces timeouts, and routes errors through a centralized handler.

### Message ID Management

Each outgoing CDP request receives a monotonically increasing `id` generated by `nextMessageId`. The promise is stored in the `pending` map, keyed by this ID, allowing `handleMessage` to match incoming replies to their originating requests.

In [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts), the ID generation and pending registration occur before the bridge transfer:

```typescript
// Conceptual flow from browser-runtime.ts
const id = ++nextMessageId;
pending.set(id, { resolve, reject, timer });
const payload = JSON.stringify({ id, method, params, sessionId });

```

### Timeout and Error Handling

Requests automatically abort if the host runtime fails to respond within **15 seconds** (`RESPONSE_TIMEOUT_MS` = 15000 ms). Error routing distinguishes between synchronous bridge failures and runtime send-errors:

- **Synchronous throws**: If `runtime.sendCDPMessage` throws immediately, the promise rejects with the raw error.
- **Send-errors**: If the bridge reports a transport failure, `handleSendError` rejects **all** pending promises with a unified `EgoError`, ensuring no dangling promises remain in the `pending` map.

### Raw Transport Layer

The `rawCdp` function constructs the JSON payload and forwards it to the host. It accepts an optional `sessionId` parameter, enabling both root browser commands and targeted page sessions:

```typescript
// From browser-runtime.ts implementation
function rawCdp(method: string, params?: object, sessionId?: string) {
  const id = ++nextMessageId;
  // ... pending registration ...
  const message = JSON.stringify({ id, method, params, ...(sessionId && { sessionId }) });
  runtime.sendCDPMessage(message);
  return promise;
}

```

### High-Level API

**`browserCdp`** serves as the public entry point. It automatically injects the current session ID for all calls except top-level "Browser." or "Target." domains. If a request fails with a session-lost pattern, the function triggers `invalidateSession` and performs an automatic retry after re-attaching.

## Session Attachment Flow

Ego-Lite operates on a *page-level* CDP session rather than the root `Browser` domain. The `ensureSession` function encapsulates the attachment logic with caching, concurrency control, and automatic recovery.

### Cached Session Validation

Before creating a new session, the system checks if a valid `sessionId` exists in `state.sessionId` and whether it is younger than `SESSION_TTL_MS` (2 seconds). If cached, the existing session ID returns immediately without additional CDP traffic.

### Concurrent Session Creation

To prevent duplicate attachment requests when multiple async operations initiate simultaneously, `ensureSession` guards against concurrent execution. If another call is already creating a session, the same promise is returned to all awaiters.

### Target Discovery and Attachment

When no valid session exists, the flow executes:

1. **Tab discovery**: `browserEgo().listTabs()` retrieves Chrome targets, selecting either the "preferred" target or the active tab.
2. **Attach command**: `Target.attachToTarget` invokes with `flatten: true`, returning a `sessionId` bound to the specific `targetId`.
3. **State update**: The `sessionId` and `targetId` are stored in the mutable `state` object, and `state.sessionAt` records the creation timestamp for TTL checks.

### Page Event Initialization

Immediately after attachment, the system sends `Page.enable` to start buffering page-level events such as JavaScript dialogs and network activity. This ensures event subscribers receive historical context even if they connect after the page loads.

### Session Invalidation and Retry

If a CDP request returns a **session-lost** error (matched against the `SESSION_LOST` regex), `invalidateSession` clears `state.sessionId` and `state.targetId`. Subsequent calls to `browserCdp` trigger `ensureSession` to re-attach automatically. The host also triggers invalidation upon receiving `Target.detachedFromTarget` or `Target.targetDestroyed` events.

## Event Handling and Buffering

### Event Routing

Incoming CDP events parse through `handleMessage`. If a subscriber exists for the event type (via `subscribeBrowserEvent`), the listener receives the payload immediately. Otherwise, the event enters a bounded buffer (`MAX_BUFFERED_EVENTS` = 10,000) for later consumption by helpers like `drainBrowserEvents`.

### Dialog State Tracking

The runtime maintains `pendingDialogs` per session by listening to `Page.javascriptDialogOpening` and `Page.javascriptDialogClosed`. This enables synchronous-style queries about the current dialog state from async helper functions.

### Automatic Session Teardown

When the host reports session destruction through `Target.detachedFromTarget` or `Target.targetDestroyed` for the current target, `invalidateSession` runs automatically. This ensures the next CDP call initiates a fresh attachment sequence rather than failing on stale session IDs.

## Mutable State Management

### The State Singleton

All session-related data lives in the exported `state` object from **[`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)**. This singleton stores:

- `sessionId`: The active CDP session identifier
- `sessionAt`: Timestamp of session creation for TTL validation
- `send`: The default request function, defaulting to `defaultSend` which forwards to `browserCdp`

Test overrides can inject custom `send` implementations or session IDs through this state object without modifying the runtime logic.

## Summary

- **Message Protocol**: Ego-Lite uses monotonic IDs and a `pending` map to correlate asynchronous CDP responses, with a 15-second timeout and unified error handling via `EgoError`.
- **Session Management**: The `ensureSession` function implements a 2-second TTL cache, concurrency deduplication, and automatic re-attachment on session loss.
- **Transport Layers**: `rawCdp` handles the JSON-RPC wire format, while `browserCdp` provides automatic session injection and retry logic.
- **Event System**: Unconsumed events buffer up to 10,000 entries, with dedicated tracking for dialog states and automatic cleanup on target destruction.
- **State Architecture**: A mutable singleton in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts) centralizes session data and allows for testable dependency injection.

## Frequently Asked Questions

### What is the CDP message protocol in ego-lite?

The CDP message protocol in ego-lite is a JavaScript abstraction over the Chromium DevTools Protocol that runs inside a host runtime environment. It exposes `ego.sendCDPMessage` through `globalThis.ego` and wraps raw JSON-RPC calls with Promise-based request/response matching, automatic ID generation, and timeout handling.

### How does ego-lite handle session attachment to browser targets?

Ego-lite attaches to specific page targets rather than the root browser using `Target.attachToTarget` with `flatten: true`. The `ensureSession` function manages this flow by checking a 2-second TTL cache, preventing concurrent attachment attempts, selecting the active tab via `listTabs()`, and enabling page events automatically.

### What happens when a CDP session is lost in ego-lite?

When a session is lost, either through a session-lost error pattern or `Target.detachedFromTarget` events, `invalidateSession` clears the cached `sessionId` and `targetId`. The next call to `browserCdp` automatically triggers re-attachment via `ensureSession`, making session recovery transparent to higher-level code.

### How are CDP events buffered and consumed in ego-lite?

Events arriving without an active subscriber enter a bounded buffer with a maximum capacity of 10,000 events (`MAX_BUFFERED_EVENTS`). Consumers can retrieve these later using `drainBrowserEvents`, while active subscribers receive events immediately through `subscribeBrowserEvent` callbacks.