What Is the State Singleton in Ego-Lite? Centralized Runtime Management Explained

The state singleton is a centralized mutable store exported from src/state.ts that acts as the single source of truth for CDP communication, session tracking, and runtime configuration across the Ego-Lite browser automation framework.

The citrolabs/ego-lite repository implements a lightweight browser automation runtime that relies on a single shared object to coordinate process-wide state. This state singleton consolidates Chrome DevTools Protocol (CDP) request handling, session bookkeeping, and utility functions into one mutable instance that every module imports and mutates.

Core Responsibilities of the Runtime State

The singleton defined in src/state.ts consolidates diverse concerns into a single mutable object. Because every module receives the same instance, changes propagate instantly across the entire runtime.

CDP Request Handling and Defaults

At its foundation, the singleton provides send, which points to defaultSend (lines 10‑21). This function forwards requests to the browser via the internal browserCdp transport. The exported send(req) wrapper (lines 42‑44) simply forwards to state.send(req), allowing the rest of the codebase to issue CDP commands without knowing the underlying transport implementation.

Session Bookkeeping

The singleton maintains critical session state fields that track the lifecycle of the browser connection:

  • sessionId – Active CDP session identifier
  • sessionTargetId – Target ID for the current session
  • sessionAt – Timestamp of the last session update
  • sessionInflight – Pending operation counter
  • preferredTargetId – Preferred target override

These fields enable src/ref-state.ts to resolve element references consistently while src/browser-runtime.ts manages target lifecycle.

Time Utilities and Platform Information

The singleton exposes now (current timestamp) and sleep (promise-based delay) for time-sensitive operations (lines 27‑28). It also provides platform (exposing process.platform) and agentWorkspace (resolving the agent’s workspace directory) to abstract environment details away from helper modules (lines 29‑30).

File System Access

Rather than requiring every helper to import Node’s fs module, the singleton directly re-exports writeFile (line 31), letting modules persist data while maintaining a centralized dependency graph.

Configuration and Capability Detection

Runtime configuration is centralized through defaultTimeout, which supplies fallback values for helper calls, and networkDomainEnabled, which mirrors the last observed Network domain state (lines 37‑40). The cdpAvailable() function (lines 46‑48) tells callers whether a custom CDP implementation is present, enabling conditional logic for capability detection.

Pluggable Overrides and Testability

A key design feature is the setOverrides helper (lines 50‑55), which temporarily replaces any property on the singleton—such as send, now, or session fields—and returns a restore function that safely resets the state to its previous snapshot.

This mechanism powers the extensive test suite in src/helpers.test.mjs. Tests can inject mocked CDP responses or frozen timestamps without mutating global state permanently, ensuring isolation between test cases.

Source File Integration

The state singleton is consumed across the codebase:

  • src/browser-runtime.ts calls state.send to communicate with the underlying CDP transport.
  • src/helpers.ts retrieves timestamps, sleeps, and other utilities from the singleton.
  • src/ref-state.ts accesses state.sessionId and related fields to resolve element references against the current session.
  • src/helpers.test.mjs demonstrates setOverrides usage for mocking dependencies.

Practical Usage Examples

The following examples demonstrate how to interact with the state singleton in production code and tests.

Issuing CDP Commands

Import the send wrapper to issue commands to the browser:

import { send } from "ego-browser/src/state.js";

await send({ method: "Page.navigate", params: { url: "https://example.com" } });

Mocking Dependencies in Tests

Use setOverrides to inject test doubles and restore the original implementation afterward:

import { setOverrides, send } from "ego-browser/src/state.js";

const restore = setOverrides({
  send: async (req) => ({ result: { mocked: true } }),
});

const resp = await send({ method: "Runtime.evaluate", params: { expression: "1+1" } });
console.log(resp.result.mocked); // true

restore(); // Returns state to the real implementation

Accessing Session State

Directly manipulate session bookkeeping fields when managing complex navigation flows:

import { state } from "ego-browser/src/state.js";

state.sessionId = "abcd1234";
console.log(state.sessionAt); // Timestamp of the last session update

Summary

  • The state singleton in src/state.ts serves as the centralized mutable store for the entire Ego-Lite runtime.
  • It consolidates CDP request handling, session bookkeeping (sessionId, sessionTargetId, etc.), time utilities (now, sleep), and platform information.
  • setOverrides enables safe, temporary mutation for testing without permanent side effects.
  • Every module that imports the singleton receives the same instance, ensuring consistent session data across browser-runtime.ts, helpers.ts, and ref-state.ts.

Frequently Asked Questions

How does the state singleton differ from a standard global variable?

Unlike a plain global variable, the state singleton is a structured object exported from src/state.ts that implements specific methods like setOverrides for safe mutation and restoration. This design allows the runtime to maintain type safety while providing a centralized, mockable dependency injection point for the entire citrolabs/ego-lite codebase.

Can I use the state singleton in a multi-session environment?

Yes, the singleton tracks multiple session-related fields (sessionId, sessionTargetId, sessionAt, sessionInflight) that can be updated dynamically. However, because it is a singleton, only one set of session values exists at a time; concurrent sessions require careful management of these fields or separate process isolation.

What happens if I call setOverrides but forget to run the restore function?

Without calling the returned restore function, the mock implementation remains active for the remainder of the process lifetime. In production code this could break CDP communication, but in test suites this is often acceptable if the process exits after the test completes. The src/helpers.test.mjs file demonstrates patterns for ensuring restoration via afterEach hooks or try/finally blocks.

Is the send function on the singleton the same as the exported send wrapper?

They are effectively aliases. The exported send function (lines 42‑44) is a convenience wrapper that delegates to state.send, which in turn points to defaultSend unless overridden. This indirection allows setOverrides to replace the implementation globally while keeping import statements simple for consuming modules.

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 →