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

> Learn to manage browser tabs in Ego-Lite with the browser facade. This guide explains how to use Playwright-style methods for direct tab control, simplifying CDP operations.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-27

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) and [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) provide session lifecycle management (`ensureSession()`, `invalidateSession()`), state tracking (`state.sleep()`), and error handling (`assertNoEgoError()` from [`src/ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/nav.ts)).

```javascript
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).

```javascript
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).

```javascript
// 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).

```javascript
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).

```javascript
// 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).

```javascript
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:**

```javascript
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:**

```javascript
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:**

```javascript
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`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) and delegates to [`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts).