# How Helpers Are Injected into the ego-browser Runtime via helperContext

> Learn how helpers are injected into the ego-browser runtime via helperContext. Discover the two-step pipeline for assembling driver primitives and projecting façades onto globalThis.

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

---

**Helpers are injected into the ego-browser runtime through a two-step pipeline: `helperContext()` assembles driver-level primitives into high-level façades, and `installEgoSdk()` projects those façades onto `globalThis` and the native `ego.helpers` bridge.**

The **ego-browser** package exposes a specialized runtime environment where agent scripts run with direct access to browser automation primitives. Understanding how `helperContext` populates this environment is essential for debugging helper availability, creating custom contexts, or extending the SDK's capabilities. This article explains the complete injection mechanism based on the source code in `citrolabs/ego-lite`.

## How helperContext Builds the Helper Façade

The `helperContext` function in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) serves as the factory for all runtime helpers. It does not expose raw driver modules directly; instead, it composes higher-level **façade objects** that wrap lower-level functionality.

### Individual Drivers → Façade Objects

`helperContext` gathers driver objects and transforms them through dedicated factory functions:

| Factory Function | Wrapped Drivers | Exposed As |
|-----------------|-----------------|------------|
| `createPageFacade()` | `pointer`, `keyboard`, `locator`, `nav`, `observe`, `waits` | `page.goto()`, `page.click()`, `page.locator()` |
| `createBrowserFacade()` | Browser-level CDP operations | `browser.close()`, `browser.version()` |
| `createTaskSpacesFacade()` | Task space management | `taskSpaces.create()`, `taskSpaces.switch()` |
| `createSiteFacade()` | Site-scoped operations | `site.runTool()`, `site.getState()` |

Additional standalone utilities include `fetch` (with `server` and `browser` variants) and direct `cdp` access for Chrome DevTools Protocol commands.

### The helperContext Implementation

The composite object construction happens at lines 222–250 of [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts):

```ts
export function helperContext(extra: any = {}) {
  const all = {
    page: createPageFacade(),
    browser: createBrowserFacade(),
    taskSpaces: createTaskSpacesFacade(),
    site: createSiteFacade(),
    fetch: { server: serverFetch, browser: browserFetch },
    cdp,
    ...extra,
  };
  return {
    ...all,
    help: (...names: string[]) => {
      // Delegates to helpRuntime / formatHelp for introspection
    },
  };
}

```

The `help` method enables runtime introspection—agents can call `help('page')` to receive formatted documentation for specific helpers.

## How installEgoSdk Injects Helpers into the Runtime

With the façade assembled, `installEgoSdk` in [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts) handles the actual projection into the JavaScript environment. This function runs automatically when the `ego-browser` package loads or when the CLI invokes a script.

### Target Object and Property Definition

By default, `installEgoSdk` uses `globalThis` as its target. It defines each helper as a **non-enumerable property** to avoid polluting `Object.keys(globalThis)` output while remaining accessible to agent code.

```ts
export function installEgoSdk(
  target: InstallTarget = globalThis,
  options: InstallEgoSdkOptions = {},
) {
  const context = options.context || helpers.helperContext();
  
  // Remove legacy helper names to prevent collisions
  for (const legacy of LEGACY_GLOBAL_HELPERS) {
    if (legacy in target) {
      delete (target as any)[legacy];
    }
  }
  
  for (const [name, value] of Object.entries(context)) {
    const exposed = SYNC_HELPERS.has(name)
      ? value
      : wrapReady(value, readySignal, () => readyError, [name]);
    Object.defineProperty(target, name, {
      value: exposed,
      writable: true,
      configurable: true,
      enumerable: false,  // Hidden from enumeration
    });
    installed[name] = exposed;
  }
  
  // Expose to native bridge via ego.helpers
  if (target.ego && typeof target.ego === 'object') {
    target.ego.helpers = installed;
  }
}

```

### Synchronous vs. Asynchronous Helper Handling

Not all helpers wait for the same lifecycle events:

- **Synchronous helpers** (`help` and a few others listed in `SYNC_HELPERS`) execute immediately. These are typically introspection utilities that don't require browser state.
- **Asynchronous helpers** are wrapped by `wrapReady`, which intercepts calls and queues them until the SDK emits a ready signal. This ensures browser session initialization completes before any navigation or element interaction occurs.

### Factory Helper Special Wrapping

Factory helpers like `page.locator` receive **recursive wrapping**. The factory function itself executes synchronously and returns a locator object, but that object's methods (`click()`, `fill()`, `textContent()`) are individually wrapped to respect the ready state. This design allows:

```ts
// Valid: synchronous factory call
const link = page.locator('a.external');

// Deferred: method executes only after ready signal
await link.click();

```

## Practical Code Examples

### Standard Agent Script Usage

In typical ego-browser agent scripts, no imports are required—helpers exist as globals:

```ts
// Legacy global helper (backwards compatible)
click('button#submit');

// Façade-based navigation
await page.goto('https://example.com');

// Factory pattern with deferred execution
await page.locator('a.link').click();
await site.runTool('extractTable', { selector: 'table.data' });

```

### Custom Helper Context

For testing or specialized environments, create a modified context:

```ts
import { helperContext } from 'ego-browser/helpers.js';

const custom = helperContext({ 
  myVar: 42,
  customLogger: console.debug 
});

// Manually project to global scope for legacy script compatibility
globalThis.myVar = custom.myVar;

```

### SDK Installation Internals

The bootstrap sequence that prepares any ego-browser execution environment:

```ts
import * as helpers from './helpers.js';
import { installEgoSdk } from './index.js';

// Default installation: globalThis + ego.helpers
installEgoSdk();

// Custom target for isolated environments
const isolatedScope = {};
installEgoSdk(isolatedScope, {
  context: helpers.helperContext({ sandboxed: true })
});

```

## Key Source Files Reference

| File Path | Responsibility |
|-----------|--------------|
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | `helperContext()` implementation; façade factories; driver aggregation |
| [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts) | `installEgoSdk()`; property injection; legacy cleanup; `ego.helpers` attachment |
| [`src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/run.ts) | CLI entry point that invokes `installEgoSdk` before executing user scripts |
| [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) | CDP session management; ready signal source that `wrapReady` awaits |
| `src/driver/*.ts` | Low-level modules: [`pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/pointer.ts), [`keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/keyboard.ts), [`locator.ts`](https://github.com/citrolabs/ego-lite/blob/main/locator.ts), [`nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/nav.ts), [`observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/observe.ts), [`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts), [`files.ts`](https://github.com/citrolabs/ego-lite/blob/main/files.ts), [`screencast.ts`](https://github.com/citrolabs/ego-lite/blob/main/screencast.ts), [`http.ts`](https://github.com/citrolabs/ego-lite/blob/main/http.ts) |

## Summary

- `helperContext()` in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) assembles raw drivers into high-level façades (`page`, `browser`, `site`, etc.) and returns a composite object with introspection support.
- `installEgoSdk()` in [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts) projects this composite onto `globalThis` as non-enumerable properties and attaches it to `ego.helpers` for native bridge access.
- The `wrapReady` wrapper ensures asynchronous helpers defer execution until browser session initialization completes.
- Factory helpers receive recursive wrapping so their returned objects also respect the ready state.
- Legacy helper names are explicitly removed before injection to prevent naming collisions.

---

## Frequently Asked Questions

### What is the difference between helperContext and installEgoSdk?

`helperContext` is a **pure factory function** that creates the helper object structure without any side effects. `installEgoSdk` is the **mutating installer** that takes that structure and attaches it to `globalThis` and `ego.helpers`. You can call `helperContext` multiple times to create independent context instances, but `installEgoSdk` should typically run once per environment.

### Why are some helpers synchronous while others wait for a ready signal?

The `SYNC_HELPERS` set contains only utilities that don't depend on browser state—primarily `help` for documentation introspection. All navigation, element interaction, and CDP-dependent helpers are wrapped with `wrapReady` to prevent errors from calling `page.goto()` before the Chrome DevTools Protocol session is established.

### How can I extend helpers with custom functionality?

Pass additional properties to `helperContext()` via its `extra` parameter, then either use that custom context directly or pass it through `installEgoSdk`'s `options.context`. Custom helpers follow the same wrapping rules: unless added to `SYNC_HELPERS`, they will wait for the ready signal.

### Where can access the helpers if globalThis is polluted or unavailable?

The SDK mirrors all installed helpers onto `globalThis.ego.helpers` (or your custom target's `.ego.helpers`). This provides a reliable secondary access point and enables the native bridge to expose helpers to external agents or debugging tools.