How to Manage Tabs Using the Browser Facade in Ego-Lite

Ego-Lite provides a Playwright-style browser façade that exposes tab-management methods directly to agent scripts, delegating low-level Chrome DevTools Protocol (CDP) operations to the driver layer in src/driver/nav.ts.

The browser object is automatically injected into every Ego-Lite agent script, offering a clean API for manipulating browser tabs without handling raw CDP commands. This façade is constructed in src/helpers.ts and serves as the primary interface for tab lifecycle operations within the citrolabs/ego-lite framework.

Architecture of the Browser Facade

The tab-management stack consists of three distinct layers that isolate high-level agent calls from native browser bindings.

Facade Layer

The façade is instantiated via createBrowserFacade() in src/helpers.ts (lines 813-815) and exposes seven core methods: listTabs, currentTab, switchTab, openOrReuseTab, closeTab, ensureRealTab, and iframeTarget. Each method maps directly to equivalent functions in the driver layer while providing typed parameters and inline documentation through the FACADE_HELP mapping.

Driver Layer

Located in src/driver/nav.ts, this layer handles all direct communication with the Ego CDP bridge via browserEgo(). For example, when browser.listTabs() is called, the façade forwards to nav.listTabs(), which queries the underlying browser using browserEgo().listTabs() (lines 16-17). The driver manages CDP targets, session state, and error normalization before returning plain JavaScript objects to the façade.

Runtime Helpers

Supporting utilities in src/state.ts and src/browser-runtime.ts provide session lifecycle management (ensureSession(), invalidateSession()), state tracking (state.sleep()), and error handling (assertNoEgoError() from src/ego-errors.ts). These ensure that tab operations maintain consistent harness attachment even when switching contexts.

Core Tab Management Methods

The browser façade offers granular control over tab discovery, activation, creation, and destruction.

Listing and Identifying Tabs

Use listTabs({includeChrome?: boolean}) to retrieve an array of TabInfo objects containing targetId, title, url, active, and index. By default, internal Chrome URLs are filtered out unless includeChrome is set to true (lines 20-25 in nav.ts).

const tabs = await browser.listTabs();
console.log('Available tabs:', tabs);

The currentTab() method returns the active tab from the list, falling back to the first tab when no explicit active flag is detected (lines 39-45).

const { url, targetId } = await browser.currentTab();
console.log('Active context:', targetId, 'at', url);

Switching Between Tabs

The switchTab(target) method accepts either a targetId string or a TabInfo object. It resolves the identifier via targetIdFrom, invokes cdp("Target.activateTarget") to activate the CDP target, invalidates the current session, and updates the preferred target record (lines 53-61).

// Switch using targetId
await browser.switchTab('target-12345');

// Switch using TabInfo object from listTabs
const tabs = await browser.listTabs();
const dashboard = tabs.find(t => t.url.includes('/dashboard'));
await browser.switchTab(dashboard);

Opening and Reusing Tabs

openOrReuseTab(url, options) implements intelligent tab deduplication using configurable matching strategies. The match parameter accepts four modes: exact, origin, origin+path, or includes. If a matching tab exists, the method switches to it; otherwise, it creates a new tab via newTab(url). Optional wait and settle parameters control page-load timing (lines 77-99).

const result = await browser.openOrReuseTab(
  'https://example.com/report',
  { 
    match: 'origin+path', 
    wait: true, 
    settle: 500 
  }
);
console.log('Operation result:', result.targetId, 'Reused?', result.reused);

Closing Tabs

closeTab(target?) accepts an optional target identifier, defaulting to the current tab. It issues cdp("Target.closeTarget"), cleans up the associated session, and clears the preferred target preference if the closed tab was the active preference (lines 16-30).

// Close current tab
await browser.closeTab();

// Close specific tab
await browser.closeTab('target-67890');

Ensuring Real Page Context

ensureRealTab() guarantees that the script operates on a non-internal page. When attached to chrome:// or devtools:// URLs, it automatically switches to the first usable tab and returns the new context (lines 36-52).

const realContext = await browser.ensureRealTab();
if (realContext) {
  console.log('Now operating on:', realContext.url);
}

Practical Code Examples

Below are complete patterns for common tab-management workflows inside Ego-Lite agents.

List all non-internal tabs and log their titles:

const tabs = await browser.listTabs({ includeChrome: false });
tabs.forEach((tab, idx) => {
  console.log(`${idx + 1}. ${tab.title} (${tab.url})`);
});

Find and activate a tab by URL pattern:

const tabs = await browser.listTabs();
const target = tabs.find(t => t.url.includes('/invoices'));
if (!target) throw new Error('Invoices tab not found');
await browser.switchTab(target.targetId);
console.log('Switched to:', (await browser.currentTab()).url);

Open a URL with fallback to new tab:

const { targetId, reused } = await browser.openOrReuseTab(
  'https://app.example.com/settings',
  { match: 'origin', wait: true }
);
console.log(`Using ${reused ? 'existing' : 'new'} tab:`, targetId);

Summary

  • The browser façade in citrolabs/ego-lite is constructed in src/helpers.ts and delegates to src/driver/nav.ts for CDP operations.
  • Tab discovery uses listTabs() with optional Chrome URL filtering and currentTab() for active context retrieval.
  • Context switching via switchTab() handles session invalidation and target activation automatically.
  • Smart navigation with openOrReuseTab() supports four URL matching strategies to prevent duplicate tabs.
  • Cleanup operations through closeTab() manage both CDP target closure and internal session state.
  • Safety utilities like ensureRealTab() prevent scripts from executing against internal browser pages.

Frequently Asked Questions

How does the browser façade handle session state when switching tabs?

When browser.switchTab() is called, the driver invokes invalidateSession() to clear the current CDP session context before activating the new target. This ensures that subsequent commands attach to the correct page context and prevents stale execution handles from leaking between tabs.

Can I filter internal Chrome tabs when listing available targets?

Yes. Pass { includeChrome: true } to browser.listTabs() to include chrome:// and devtools:// URLs in the results. By default, the driver filters these internal URLs (lines 20-25 in src/driver/nav.ts) to ensure agents operate only on web content.

What is the difference between the match modes in openOrReuseTab?

The match parameter accepts four strategies: exact requires identical URLs; origin matches protocol and hostname only; origin+path matches protocol, hostname, and pathname; includes performs a substring search. If no match is found, the method creates a new tab via newTab().

Where is the browser façade instantiated for agent scripts?

The façade is created by createBrowserFacade() in src/helpers.ts and injected into the agent's global scope before script execution. This initialization maps FACADE_HELP documentation and binds each method to the corresponding implementation in src/driver/nav.ts.

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 →