How ego-browser Embeds a Node.js Helper Runtime Inside Chromium

ego-browser embeds a Node.js-compatible helper runtime inside Chromium by exposing a global globalThis.ego object that provides a thin CDP transport layer, then building a full-featured helper API on top of it.

ego-browser is the JavaScript runtime component of the ego-lite project (citrolabs/ego-lite). It transforms a standard Chromium build into a programmable browser by embedding Node-style helper functions directly into the browser's JavaScript context. This architecture lets automation scripts run with familiar await page.goto() semantics while executing entirely within the Chromium process.

How the Host Runtime Detection Works

The embedding starts with runtime detection. When src/index.ts loads, it determines whether it's running in CLI mode or inside the ego-lite Chromium host.

// src/index.ts (excerpt)
if (isDirectCli()) {
  // CLI usage – run user script from stdin
  process.exitCode = await runMain();
} else {
  // Embedded use – expose helpers on the global object
  installEgoSdk();               // <─ attaches helpers to globalThis
}

The isBrowserRuntime() function in src/helpers.ts performs the actual check:

// Detects ego-lite host availability
globalThis.ego && typeof globalThis.ego.sendCDPMessage === 'function';

This verification ensures the Chromium binary has exposed the expected CDP bridge before proceeding with helper installation.

The CDP Bridge: globalThis.ego

The ego-lite Chromium build exposes a minimal but complete CDP transport through globalThis.ego. This object provides three core methods:

  • sendCDPMessage — sends raw CDP commands to the browser
  • onCDPMessage — registers callbacks for CDP events
  • onSendCDPMessageError — handles transport failures

In src/browser-runtime.ts, the browserCdp() function wraps this interface into a Promise-based API:

import { browserCdp } from 'ego-browser';

// Enable Network domain via raw CDP
const result = await browserCdp('Network.enable');

The browser-runtime.ts module also handles session lifecycle concerns: automatic session attachment, re-attachment on connection loss, buffered event handling during reconnection, and dialog tracking. These mechanisms ensure that transient CDP failures don't break automation scripts.

Building the Helper Context

Once the host is detected, installEgoSdk() in src/index.ts invokes helpers.helperContext() from src/helpers.ts to construct the public API surface. This includes browser automation primitives:

  • click(selector) — element interaction
  • goto(url) — navigation
  • snapshot() — page state capture
  • runSiteTool(name, params) — site-specific automation extensions

Each helper is wrapped by wrapReady() before installation. This wrapper delays actual execution until an optional ready promise resolves, preserving "fire-and-forget" semantics required by the browser environment while preventing race conditions during initialization.

Global Installation and Host Binding

The wrapped helpers are installed onto the global object via Object.defineProperty:

// Manual SDK installation for custom targets
import { installEgoSdk } from 'ego-browser';

const myWindow = {/* a custom global object */};
installEgoSdk(myWindow, { ready: someInitPromise });

By default, target is globalThis, making helpers immediately available to agent scripts injected into the page. The host's ego object receives back-references to the installed helpers through ego.helpers and exposes site-specific capabilities via ego.learnings.

Mutating host methods like createTab and useTaskSpace are also wrapped to maintain session state consistency. When these methods execute, they trigger invalidateSession() or clearPreferredTarget() as needed, ensuring that src/state.ts (the singleton runtime state manager) stays synchronized.

Running Agent Scripts

Automation scripts execute with standard Node-style syntax despite running inside Chromium:

// Agent script executed via ego-browser
await page.goto('https://example.com');
await click('button#submit');
const screenshot = await screenshot();  // returns a Buffer

The src/run.ts module handles heredoc script execution, injecting all helpers into the runtime context before user code begins.

Key Files and Their Roles

File Responsibility
src/index.ts Entry point, runtime detection, SDK installation orchestration
src/helpers.ts Public helper API implementation (click, goto, snapshot, etc.)
src/browser-runtime.ts CDP transport, session management, event buffering, reconnection logic
src/run.ts Script execution harness with helper injection
src/state.ts Singleton state for session IDs, preferred targets, and buffers

Summary

  • ego-browser detects the ego-lite host through globalThis.ego.sendCDPMessage availability
  • The host provides a minimal CDP bridge; ego-browser builds a full Node-style API on top
  • Helpers are wrapped for readiness signaling, then installed globally via Object.defineProperty
  • Session state stays consistent through wrapped mutating methods in src/state.ts
  • Agent scripts use familiar await syntax while executing entirely within Chromium

Frequently Asked Questions

What is ego-lite?

ego-lite is an open-source project (citrolabs/ego-lite) that provides a modified Chromium build with embedded automation capabilities. The "ego" name refers to the global bridge object that enables external JavaScript runtimes to control the browser via CDP.

Why use CDP instead of DevTools Protocol over WebSocket?

Embedding CDP directly through globalThis.ego eliminates network overhead and connection management. The browser-runtime.ts implementation handles session attachment and re-attachment automatically, providing more reliable automation than external WebSocket connections that can drop during page navigations.

Can ego-browser run outside ego-lite?

Yes, but with reduced functionality. The isDirectCli() path in src/index.ts supports standalone execution, though without the globalThis.ego bridge, CDP-based helpers will fail. For non-ego-lite environments, manual SDK installation with a custom target object is possible but requires implementing the sendCDPMessage interface yourself.

How does session state persistence work?

The src/state.ts module maintains a singleton runtime state including current session IDs, preferred targets, and event buffers. When wrapped methods like createTab execute, they call invalidateSession() to clear stale state. This design ensures that automation scripts always interact with the correct browser context even as pages navigate or tabs change.

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 →