# Core Components of the ego-lite Browser Runtime: CDP Transport, Session Management, and Element Resolution

> Discover the core components of the ego-lite browser runtime including CDP transport, session management, and element resolution. Learn how these modules enable powerful browser automation.

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

---

**The ego-lite browser runtime is built around nine cohesive TypeScript modules—centered on [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts), [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts), and [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts)—that provide Chrome DevTools Protocol (CDP) transport, mutable session state, stable DOM reference mapping, and high-level automation drivers.**

The [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) repository delivers a compact browser automation layer designed to interact with CDP-compatible browsers. The **ego-lite browser runtime** abstracts the complexity of Chrome DevTools Protocol messaging into a deterministic API that handles session lifecycle, event buffering, and element resolution. Understanding these core components is essential for extending the runtime or debugging automation scripts.

## Runtime State and Session Management

The foundation of the **ego-lite browser runtime** rests on two critical modules that manage mutable state and CDP session lifecycles.

**[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)** exports a mutable singleton that tracks the current CDP session, configuration overrides, timing helpers, and workspace information. This global state object serves as the source of truth for the entire runtime, ensuring that drivers and resolvers share consistent configuration across async boundaries.

**[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)** implements the core session management logic through the `ensureSession()` function. This utility lazily attaches to the active browser tab, caches the session ID for `SESSION_TTL_MS` (2000 milliseconds), and automatically re-attaches when the session is lost. The runtime detects session loss via the `SESSION_LOST` regex pattern and triggers transparent reconnection without throwing errors to the caller. This mechanism ensures that long-running automation scripts survive tab crashes or navigation events that would otherwise terminate the CDP connection.

## CDP Transport and Message Routing

All communication with the browser flows through the transport layer in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts), which wraps `globalThis.ego.sendCDPMessage` and implements request-response correlation.

**`rawCdp()`** sends raw CDP commands and stores pending promises keyed by message ID. Each request times out after `RESPONSE_TIMEOUT_MS` (15000 milliseconds) if no response arrives. Incoming CDP messages route through **`handleMessage()`**, which resolves pending promises, buffers unrelated events for later consumption, and forwards notifications to subscribed listeners.

**`browserCdp()`** provides the primary interface for higher-level modules. It automatically injects the active session ID (obtained via `ensureSession()`) into every request, eliminating the need for drivers to manage session state manually. When the session expires or disconnects, `browserCdp()` triggers `ensureSession()` to re-establish the connection before retrying the command.

## Event Handling and Dialog Tracking

The runtime implements a pub-sub event system atop the CDP transport to handle asynchronous browser events.

**`subscribeBrowserEvent()`** registers listeners for specific CDP methods such as `Network.responseReceived` or `Page.javascriptDialogOpening`. The system buffers the most recent 100 events in a circular buffer, allowing late subscribers to drain historical events via **`drainBrowserEvents()`**.

**`waitForBrowserEvent()`** offers a promise-based interface that awaits a specific event matching a predicate function with a configurable timeout. This is particularly useful for waiting on network responses or DOM mutations.

JavaScript dialogs are tracked separately in a `pendingDialogs` Map. When `Page.javascriptDialogOpening` fires, the runtime stores the dialog metadata; it clears the entry on `Page.javascriptDialogClosed`. Helpers can query **`pendingDialog()`** to check for active modal dialogs before attempting interactions that would otherwise hang.

## Element Resolution and Reference Mapping

Stable element identification is handled by the reference mapping and resolution modules, which decouple selectors from volatile DOM nodes.

**[`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts)** and **[`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts)** maintain the mapping between snapshot references (denoted as `@N` in selectors) and their underlying Accessibility (AX) backend node IDs. When a snapshot is captured, `browserSnapshotRefsToRefMap()` converts backend node IDs into a `RefMap` instance. This enables the runtime to reference specific elements using stable `@N` identifiers that survive DOM re-renders and attribute changes.

**[`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)** provides the high-level resolution API through **`resolveElementCenter()`** and **`resolveElementObjectId()`**. These functions accept multiple locator types:

- **Role-based**: `role:button[name="Submit"]`
- **CSS selectors**: `#id`, `.class`
- **XPath**: `//button[@id='submit']`
- **Text-based**: `text=Click me`
- **Href**: `href=/path/to/page`
- **Snapshot refs**: `@0`, `@1`

The resolver classifies errors as *transient* (retryable network or timing issues) or *permanent* (invalid selectors), allowing drivers to implement appropriate retry logic.

## Driver Layer and Public API

The concrete automation primitives live in **`src/driver/`**, with thin wrappers implementing navigation ([`nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/nav.ts)), pointer actions ([`pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/pointer.ts)), keyboard input ([`keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/keyboard.ts)), and observation ([`observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/observe.ts)). Each driver calls `browserCdp()` or the low-level `rawCdp()` to execute CDP commands such as `Input.dispatchMouseEvent`, `Page.navigate`, or `DOM.getBoxModel`.

**[`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts)** provides the **`cdp()`** and **`js()`** helpers for evaluating JavaScript within the page context. These functions automatically wrap top-level `return` statements to ensure expression results are captured correctly.

**[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** curates the public API surface exposed to automation scripts. The `helperContext()` function aggregates all runtime services—`browserRuntime`, `state`, `cdp-eval`, and drivers—into a single context object. **[`src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/run.ts)** serves as the CLI entry point, reading scripts from stdin and executing them inside an async wrapper with the helper context pre-loaded.

## Practical Usage Examples

### Sending Raw CDP Commands

Use `browserCdp()` to send manual CDP commands for metrics or debugging:

```typescript
import { browserCdp } from "./browser-runtime.js";

async function getViewportDimensions() {
  const result = await browserCdp("Page.getLayoutMetrics");
  console.log("Viewport:", result.result?.clientWidth, result.result?.clientHeight);
}

```

### Ensuring Session Before Driver Calls

Explicitly ensure a session exists before invoking driver actions:

```typescript
import { ensureSession } from "./browser-runtime.js";
import { click } from "./driver/pointer.js";

async function clickButton(selector: string) {
  const sessionId = await ensureSession();
  await click(sessionId, selector);
}

```

### Resolving Elements by Role Locator

Combine element resolution with pointer actions for precise interactions:

```typescript
import { resolveElementCenter } from "./element-resolver.js";
import { clickAt } from "./driver/pointer.js";

async function clickRoleButton(role: string, name?: string) {
  const { x, y, sessionId } = await resolveElementCenter(
    globalThis.ego,
    undefined,
    new Map(),
    `role:${role}[name=${name}]`
  );
  await clickAt(sessionId, x, y);
}

```

### Waiting for Network Events

Listen for specific CDP events to synchronize with page loads or API calls:

```typescript
import { waitForBrowserEvent } from "./browser-runtime.js";

async function waitForJson(url: string) {
  const event = await waitForBrowserEvent(
    (e) => e.method === "Network.responseReceived" && e.params?.response?.url === url,
    10_000
  );
  console.log("Got response:", event.params.response);
}

```

## Summary

The **ego-lite browser runtime** architecture consists of nine interconnected modules that provide deterministic browser automation:

- **[`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)** manages global mutable state and workspace configuration.
- **[`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)** handles CDP transport, session lifecycle (`ensureSession`), and event buffering with automatic reconnection on `SESSION_LOST`.
- **[`ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-map.ts)** and **[`ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-state.ts)** translate DOM snapshots into stable `@N` references using AX backend node IDs.
- **[`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts)** converts role-based, CSS, XPath, and text locators into concrete screen coordinates or object IDs.
- **`driver/`** contains thin wrappers that translate high-level actions (click, type, navigate) into CDP commands.
- **[`cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/cdp-eval.ts)** facilitates JavaScript execution in the page context.
- **[`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)** and **[`run.ts`](https://github.com/citrolabs/ego-lite/blob/main/run.ts)** expose the curated public API and CLI entry point for script execution.

Together, these components create a resilient automation layer that abstracts CDP complexity while maintaining full access to browser internals.

## Frequently Asked Questions

### How does the ego-lite browser runtime handle lost CDP sessions?

The runtime detects session loss via the `SESSION_LOST` regex pattern in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts). When a command fails with a session error, `ensureSession()` automatically re-attaches to the active tab, caches the new session ID for `SESSION_TTL_MS` (2 seconds), and retries the original command transparently without throwing to the caller.

### What selector strategies does the element resolver support?

According to [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts), the resolver supports role-based locators (`role:button[name=Submit]`), CSS selectors (`#id`, `.class`), XPath expressions (`//button`), text-based matching (`text=Click me`), href matching (`href=/path`), and stable snapshot references (`@0`, `@1`). The `parseLocator` function determines the strategy based on the selector prefix.

### How does the runtime maintain stable references to DOM elements?

The runtime uses `RefMap` instances created from AX backend node IDs in [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts). When a snapshot is taken via `browserSnapshotRefsToRefMap()`, each DOM node receives a numeric reference (e.g., `@0`) mapped to its backend ID. Because AX node IDs persist across DOM mutations better than JavaScript object references, these `@N` refs provide stable identifiers for element resolution even when attributes or classes change.

### What is the difference between `browserCdp()` and `rawCdp()`?

`rawCdp()` in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) sends low-level CDP messages via `globalThis.ego.sendCDPMessage` and manages the promise resolution map directly. `browserCdp()` is the higher-level wrapper that automatically calls `ensureSession()` to inject the current session ID into requests, making it the preferred interface for drivers that need session resilience.