# browser

> A browser on the iii bus - shared Chromium with persistent, restorable tabs and incognito tabs. Navigate, act, read the page console, pick elements. Also parses HTML natively without a browser (browser::* - css/xpath/regex, element search, markdown).

| field | value |
|-------|-------|
| version | 0.2.31 |
| type | binary |
| license | Apache-2.0 |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii trigger compose::add worker=browser@0.2.31
```

## readme

# browser

A browser on the [iii engine](https://github.com/iii-hq/iii) bus: one shared
Chromium with tabs, a persistent profile (cookies, logins, storage) under
`data/browser`, tabs that survive restarts, and incognito tabs that save
nothing. Agents open a tab (a *session*), read the page as an
accessibility-tree outline, click and type against element refs, and read the
page's own console and network history back as data. The single most
important thing it gives you: "why is my dev server page blank?" becomes
answerable, because the page's console errors are one
`browser::console::read` away. The [console](https://github.com/iii-hq/workers/tree/main/ade)
worker adds the human window: a Chrome-style tab strip over a streaming
viewport (Chromium-pushed screencast frames), an address bar, developer tools
behind the menu, and click-to-pick elements into chat.

It also carries a native Rust scraping surface, `browser::*`: HTTP
and browser fetching, screenshots, persistent sessions and BFS crawling, plus
CSS/XPath/regex queries, element search and HTML→Markdown that run over any
HTML string with no browser at all. See
[Scraping and HTML parsing](#scraping-and-html-parsing-browser) below.

## In the console

An agent reads a page as an accessibility outline (`browser::snapshot`) while
you watch the live viewport and console feed:

<a href="https://raw.githubusercontent.com/iii-hq/workers/main/browser/assets/snapshot.png">
  <img src="https://raw.githubusercontent.com/iii-hq/workers/main/browser/assets/snapshot.png" alt="browser::snapshot rendered as an accessibility outline beside the live viewport" width="100%" />
</a>

`browser::screenshot` renders the captured image inline in the chat card:

<a href="https://raw.githubusercontent.com/iii-hq/workers/main/browser/assets/screenshot.png">
  <img src="https://raw.githubusercontent.com/iii-hq/workers/main/browser/assets/screenshot.png" alt="browser::screenshot rendered as an inline image in the chat card" width="100%" />
</a>

Pick mode highlights the element under the cursor and drops it into the chat
composer as an actionable ref:

<a href="https://raw.githubusercontent.com/iii-hq/workers/main/browser/assets/pick.png">
  <img src="https://raw.githubusercontent.com/iii-hq/workers/main/browser/assets/pick.png" alt="pick mode highlighting an element and inserting it into the chat composer" width="100%" />
</a>

## Tabs, sleep, and incognito

A session is a tab in the worker's browser. Tabs share one Chromium process
and one profile, so a login in one tab is a login in all of them, exactly as
in a browser. Regular tabs are saved to `<data_dir>/tabs.json` (url, title,
visited pages, back/forward stack) and come back after a worker restart.

- **Lifetime.** A tab stays open until `browser::sessions::stop`, or until
  the optional `ttl_ms` it was opened with elapses. The console opens tabs
  with no lifetime.
- **Sleep.** A tab nobody watches (no console viewer, no recording) or calls
  for `inactive_after_ms` (30 minutes by default) goes to sleep: its page is
  closed, the tab is kept and still listed (`active: false`). Any call on it,
  or selecting it in the console, opens the page again at the url it
  remembered; back/forward keep working from the tab's own stack. When the
  last live tab sleeps, Chromium quits and its profile is flushed to disk;
  the next tab launches it again. `max_sessions` caps *live* tabs: opening
  one more puts the least recently used unwatched tab to sleep first.
- **Incognito.** `browser::sessions::start` with `incognito: true` opens a
  PRIVATE tab in its own throwaway browser context: no shared cookies or
  logins, nothing written under `data_dir`, no history kept, not restored
  after a restart, and inactivity closes it for good instead of putting it
  to sleep. The console shows it in Chrome's dark private-window palette.
- **Clearing data.** `browser::clear-data` clears the *site* a tab is on
  (its cookies, its storage, the shared cache) — the ⋮ menu's "Clear cookies
  and site data". `browser::clear-browser-data` (Settings → Clear browser
  data) closes every page, quits Chromium, and deletes the whole profile and
  downloads; tabs stay and reopen signed out.
- **Loading like a browser.** A page that fails to load (a network error, an
  empty HTTP error response such as x.com's 400 to unknown clients) leaves
  Chromium's error page in the tab and is *reported* in `navigate`'s
  `ok`/`error`, not thrown. An `https://` url on localhost, `*.localhost`, or
  a loopback/private address whose TLS handshake fails (a plain dev server)
  is retried over `http://`, like an address bar does; public hosts never
  downgrade. Pages see a plain Chrome user agent, never `HeadlessChrome`.
- **Live view.** Every tab renders in its own headless window (a window
  shows only its active tab, so tabs sharing one would freeze), and the
  console gets frames at up to 30 fps, latest frame first, whatever the bus
  latency.

## Install

```bash
iii trigger compose::add worker=browser
```

`iii trigger compose::add` resolves the worker and its dependencies, writes
exact declarations to `worker-compose.yaml`, and reconciles the Compose
project. By default the worker drives a Chromium/Chrome already installed on
the machine; point `executable` at a specific binary if auto-detection picks
the wrong one.

### No Chrome on the machine?

The worker can download its own Chromium. In the ADE, the setup wizard shows
a **Browser** step with a **Download Chromium** button whenever the browser
worker is installed and no Chromium is found (Set up the harness → Browser).
From anywhere else:

```bash
iii trigger browser::chromium::status        # what is found, and where it looked
iii trigger browser::chromium::install       # returns { job_id, status } at once
```

`browser::chromium::install` downloads the Stable
[Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/)
build for this platform (linux64, linux-arm64, mac-arm64, mac-x64, win64; about 200 MB)
and keeps it in `${XDG_CACHE_HOME:-~/.cache}/iii/browser/chrome/<version>/`
(`III_BROWSER_CACHE_DIR` replaces `~/.cache/iii/browser`), with
`current.json` naming the version in use. The job runs in the background:
it fetches the version manifest, streams the zip to a `.partial` file,
unpacks it into a staging folder, runs the binary with `--version`, then
moves it into place in one rename, so a failed or interrupted install never
leaves a half-installed browser behind. One job runs per worker (a second
call returns the running job) and a lock file in the cache keeps two workers
sharing it apart. Progress arrives on the `browser::chromium-install-progress`
trigger and in `browser::chromium::status` (`job`). On Linux, a machine
missing Chromium's shared libraries fails at the `--version` step with a
`hint` listing the Debian packages to install. When a Chromium is already
found it does nothing unless `force: true`. Agents need approval to call it;
`browser::chromium::status` is read-only and auto-allowed.

Which binary launches is decided by one resolver, shared by sessions,
`browser::doctor`, `browser::chromium::status` and the scraping browser
tiers. First hit wins:

1. the config `executable` (when set, nothing else is tried);
2. the `CHROME` environment variable;
3. a system install — `chrome`, `google-chrome(-stable)`, `chromium(-browser)`,
   `msedge`/`microsoft-edge` on `PATH`, then the usual install locations;
4. the downloaded copy (`current.json`);
5. the newest Playwright (`ms-playwright/chromium-*`), then Puppeteer
   (`~/.cache/puppeteer/chrome/*`), Chromium already on the machine.

A Chrome you install yourself later therefore wins over the download without
anything being removed.

On a Linux that blocks unprivileged user namespaces for programs without an
AppArmor profile (Ubuntu 23.10 and later), a Chromium from a user cache — the
download, Playwright, Puppeteer — cannot start its sandbox ("No usable
sandbox!"), so the worker launches those with `--no-sandbox`, as Playwright
does; `browser::chromium::status` reports `sandboxed: false` and
`browser::doctor` lists it as an issue. A distribution Chromium or Google
Chrome ships the profile and keeps its sandbox. When nothing is found, session calls fail with an
error that starts with `chromium_missing:` and says how to fix it; the ADE
recognises the marker and offers the install.

### Engines

Interactive sessions run on one of two engines, both driven over the Chrome
DevTools Protocol, so the `browser::*` functions are the same code path:

| `engine` | What it needs | What you get |
|---|---|---|
| `chromium` (default) | Chrome, Chromium, or Edge installed | Everything: live view, real screenshots, pick mode, styles, downloads, `headless: false` |
| `lightpanda` | The [Lightpanda](https://lightpanda.io) binary (`lightpanda` on PATH, or `executable`) | DOM + JavaScript without rendering: navigate, snapshot, act, evaluate/execute, console and network capture, cookies, history, dom::read |

Lightpanda is a headless browser written in Zig (V8 for JavaScript, its own
DOM, libcurl for HTTP) that drops the rendering engine on purpose: a single
binary, sub-100 ms start, around a tenth of Chrome's memory. The worker
spawns `lightpanda serve` on a loopback port the first time a tab needs a
page and ends it when the last live tab sleeps, the same lifecycle as the
Chromium process; cookies persist through `data_dir/lightpanda/cookies.json`
(written when the process exits). Element geometry is synthetic but
consistent, so clicking by `ref` works, and the accessibility tree names
controls from their contents.

What it cannot do, because nothing is laid out or painted: there is no
screencast, so the console's live viewport and the corner preview show a
`browser::screenshot` instead (Lightpanda renders that as a text-only PNG),
re-taken when the tab navigates or updates rather than on a timer; pick mode (`Overlay`), `browser::styles::read` (`CSS`), `clear-data`'s
per-origin storage wipe, and recording are unavailable; `file://` pages are
refused ("UnsupportedProtocol"); `headful: true` and
`browser::sessions::attach` are Chromium only. `browser::doctor` reports the
configured engine and the binary it resolved.

```bash
brew install lightpanda-io/browser/lightpanda
# or the nightly binary: https://github.com/lightpanda-io/browser/releases/tag/nightly
```

Then set `engine: lightpanda` in the browser configuration (Settings →
browser → Launch).

## Quickstart

Start a session, read the page, act on it, then read the console:

```rust
use iii_sdk::protocol::TriggerRequest;
use iii_sdk::{register_worker, InitOptions};
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    let started = iii.trigger(TriggerRequest {
        function_id: "browser::sessions::start".into(),
        payload: json!({ "url": "http://localhost:3000" }),
        action: None,
        timeout_ms: Some(30_000),
    }).await?;
    let session_id = started["session_id"].as_str().unwrap();

    // The page as text: an a11y outline with [ref=eN] handles.
    let snapshot = iii.trigger(TriggerRequest {
        function_id: "browser::snapshot".into(),
        payload: json!({ "session_id": session_id }),
        action: None,
        timeout_ms: Some(15_000),
    }).await?;
    println!("{}", snapshot["tree"].as_str().unwrap());

    // What did the page log? Errors only, no dump.
    let console = iii.trigger(TriggerRequest {
        function_id: "browser::console::read".into(),
        payload: json!({ "session_id": session_id, "level": "error" }),
        action: None,
        timeout_ms: Some(10_000),
    }).await?;
    println!("{console:#}");
    Ok(())
}
```

The rest of the surface: `browser::elements` (visible controls as an
indexed table with `n` refs, plus the visible text, from one read),
`browser::run` (reach a goal in one call: the optional judge worker picks
each click/type/select/scroll from the table, text comes from the caller's
`inputs`; without a judge it returns `judge_unavailable` and the table),
`browser::act` (click/hover/type/select/press/scroll by ref or coordinates,
left/right/middle and double-click; ref targets are scrolled into view and
refused when disabled, hidden or covered; typing into a field by ref replaces
its value), `browser::evaluate`
(JS expression), `browser::screenshot` (viewable JPEG), `browser::history`
(back/forward/reload, surviving sleep and restarts), `browser::history::list`
(visited pages for a history panel), `browser::find-in-page` (find bar:
highlight matches, step next/previous), `browser::zoom` (page zoom
50-200 %), `browser::pdf` (print the page to a PDF), `browser::downloads::list`
/ `browser::download` / `browser::download::remove` (files the tab
downloaded), `browser::clear-data` (this site's cookies, storage, and the
cache), `browser::clear-browser-data` (the whole profile), `browser::resize`
(live viewport size / device presets), `browser::cookies::list` / `set` /
`clear` (import a cookie file; clear is per site), `browser::network::read`
(requests + failures), `browser::dom::read` (DOM tree with refs),
`browser::styles::read` / `browser::styles::write` (computed styles + live
inline edits, the design panel backing), and `browser::sessions::list` /
`browser::sessions::stop`. Function ids and schemas live in the code and
`iii worker info browser`.

File transfer functions:

| Function | Purpose |
|---|---|
| `browser::downloads::list` | List files the session downloaded |
| `browser::download` | Read one recorded download as base64 |
| `browser::download::remove` | Delete and forget one recorded download |
| `browser::upload` | Attach up to eight base64 files to exactly one `input[type=file]` selected by CSS |

Beyond single actions: `browser::execute` runs a multi-step async script in
the page — top-level await, `log(...)`, `sleep(ms)`, `waitFor(selector)`, and
a `state` object that persists across execute calls for the session — so one
call replaces a chain of act/evaluate round-trips. `browser::snapshot`
accepts `diff: true` to return only what changed since the previous
snapshot, and reports the document `generation` its refs belong to (ref
names are unique per snapshot and fail closed when stale, never resolving to
a different element). `browser::sessions::start` accepts `read_only: true`
for inspection-only sessions where act/evaluate/execute/styles::write are
rejected. `browser::doctor` reports the environment — detected Chromium,
where it came from (`chromium_source`), version, capacity — with an
`enable_how` string for anything degraded. `browser::chromium::status` and
`browser::chromium::install` find and fetch Chromium (see
[No Chrome on the machine?](#no-chrome-on-the-machine)).

`browser::sessions::attach` binds a session to an already-running browser
over CDP (start Chrome with `--remote-debugging-port`) instead of launching
one, so it reaches the real profile with its logins and extensions. It opens
a fresh tab the session owns, or adopts an existing tab by URL substring and
releases it untouched on stop; `browser::tabs::list` enumerates a running
browser's tabs. Attach reaches logged-in state, so it is off unless
`allow_attach` is set in config, and adoption is exclusive per tab.

`browser::handoff` pauses a session for a step only a human can do (CAPTCHA,
2FA, payment): it mounts an in-page continue banner and blocks the call until
the human clicks it, a `browser::handoff::confirm` call resolves it, or the
timeout elapses, emitting `browser::handoff-requested` for the console to
surface. Human acknowledgment is not proof, so the caller verifies the
expected page state after it returns.

`browser::recording::start` / `browser::recording::stop` capture a session's
live viewport to a webm or mp4 file by piping the screencast through ffmpeg
(turning screencast on if needed); stop returns the path, duration, and
frame count. While screencast is active a human watching the viewport also
sees a ghost cursor following the agent's clicks and a session-status badge;
both are fixed-position in-page overlays that never touch page content.
`browser::doctor` reports whether ffmpeg (recording) and attach mode are
available.

## Scraping and HTML parsing (`browser::*`)

The worker also ships a native Rust port of the [Scrapling](https://github.com/D4Vinci/Scrapling)
surface: 19 functions covering HTTP and browser fetching, screenshots,
persistent sessions, crawling, and — the part that needs no browser at all —
parsing HTML you already have.

Start with the parse functions: they work on any HTML string with no browser
or network. Adaptive CSS/XPath/extract calls are the exception to statelessness:
they persist relocation identities in the configured SQLite database. They pair naturally with the session functions above
(navigate, read the page, then parse it), but they don't need one.

```bash
iii trigger browser::css --payload '{
  "html": "<ul><li><a class=\"product\" href=\"/sku/1\">Widget</a></li><li><a class=\"product\" href=\"/sku/2\">Gadget</a></li></ul>",
  "query": "a.product",
  "attr": "href",
  "first": true
}'
# → { "result": "/sku/1" }
```

`first` defaults to `false`, in which case `result` is an array of every match
instead of just the first.

```bash
iii trigger browser::extract --payload '{
  "html": "<div class=\"card\"><h3>Widget</h3><span class=\"price\">$19.99</span><a href=\"/sku/1\">buy</a></div>",
  "selectors": [
    { "name": "title", "css": "h3" },
    { "name": "price", "css": ".price" },
    { "name": "url", "css": "a", "attr": "href" }
  ]
}'
# → { "extracted": { "title": "Widget", "price": "$19.99", "url": "/sku/1" } }
```

The 10 parse functions: `extract`, `css`, `xpath`, `regex`, `find`,
`find-by-text`, `find-by-regex`, `find-similar`, `describe`, `to-markdown`.
Non-adaptive parsing has no operator-tunable defaults. The fixed limit,
`find` / `find-by-text` / `find-by-regex` capping
at 100 items per call (`limit` clamps to `[0, 100]`), mirrors the python
worker's hardcoded cap.

### Fetching, sessions and crawl

Nine more functions go out to the network (plus `browser::crawl::items`,
which reads back what a crawl retained). They share one response envelope —
`{status, url, headers, cookies, encoding}` plus, on request, `extracted`
(from `selectors`), `content`+`format` (`markdown`/`text`) and `html` — so the
parse layer above is reachable inline, without a second call.

Three fetch tiers, cheapest first; escalate only when the cheaper one fails:

| | engine | use when |
|---|---|---|
| `fetch` | safe: reqwest/rustls; compat: frozen curl-impersonate | static pages, APIs — no browser, fastest |
| `dynamic-fetch` | frozen Chrome over raw CDP | the page needs JavaScript to render |
| `stealthy-fetch` | frozen Chrome with the Patchright command/launch sequence | the site sniffs for automation |

```bash
iii trigger browser::fetch --json '{
  "url": "https://example.com/",
  "selectors": [{ "name": "title", "css": "h1" }],
  "format": "text"
}'
# → { "status": 200, "url": "...", "extracted": { "title": "Example Domain" }, ... }
```

All three take a single `url` or a bulk `urls` list (bulk returns
`{results: [...]}`, where a failed URL contributes `{url, error}` instead of
sinking the batch). `dynamic-fetch` and `stealthy-fetch` additionally accept
`wait_selector` (+ `wait_selector_state`), `network_idle`, and `wait`.

`browser::screenshot-url` captures a page as image content blocks the console renders
inline — downscaled to 1024px wide and split into at most six 1536px tiles,
with the caption saying so when a page is taller than that.

`session-open` / `session-fetch` / `session-close` / `session-list` keep state
in a private Scrapling registry. HTTP sessions retain one cookie jar/transport;
dynamic and stealthy sessions retain one browser process and context. All use
UUID4 hex ids and serialize requests FIFO per session. They never appear in
`browser::sessions::list`, and interactive ids are not accepted. One-shot
browser calls get a fresh process/profile; retries get a fresh page in that
process. Compat mode supports request proxies, remote `cdp_url`, and
`solve_cloudflare` on stealthy calls.

`crawl` walks links breadth-first from `start_urls`, extracting per page. It
stays on the seed domain by default (`www.` folded), strips URL fragments when
deduping, and stops at `max_pages` (20) or `max_depth` (2). The RPC response carries
`stats` and only a ≤10-item sample; `crawl: { id, items_function,
trigger_type, retained, dropped_events }` says where the rest is:

- **All items, after the call:** `browser::crawl::items { crawl_id, after?,
  limit? }` pages through every retained item in crawl order (`seq` 1..n,
  `next_after` to continue, ≤100 per page). Retention is worker-owned and
  in memory: the last 32 crawls, for 1 h after they finish, 32 MiB per crawl
  and 128 MiB in total, not across worker restarts. A crawl that hit a cap
  reports `retained` below `stats.crawled` and the read says `truncated`.
- **Live, while it runs:** bind the `browser::crawl-item` trigger type
  (filter `{ crawl_id }`) *before* calling `browser::crawl { crawl_id }`.
  Each bound function receives `{ crawl_id, event: "item", seq, item,
  truncated }` per page, in `seq` order, then one `{ event: "done", seq: n+1,
  stats, retained, dropped_events }`. Event items cut page content to 64 KiB
  (`truncated: true`); the full item is in `browser::crawl::items`. Delivery
  goes through a bounded per-crawl queue and never slows the crawl: when a
  slow consumer fills it, events are dropped and counted in `dropped_events`,
  and the consumer reads the missing `seq`s back with `browser::crawl::items`.

`crawl_id` is optional (default: a random id) and must not name a crawl that
is still running. The legacy `group_id` input still works as the crawl id.
`stream_name` is deprecated and ignored: items are no longer written to a
stream, the call still succeeds, and the response gains a `warnings` entry.
`stream: { name, group_id }` is still echoed for older callers; read
`crawl.id` instead. The crawl works on an engine without any stream worker.

**These functions take a caller-supplied URL, so they are an SSRF surface.**
Safe mode rejects caller proxies and checks every connection against private,
loopback, link-local (including cloud metadata), CGNAT, multicast and reserved
ranges. Set `browser.scrapling.allow_loopback: true` to scrape a local dev
server; every other private range stays blocked. Compat mode intentionally
reproduces the Python wrapper's unrestricted network behavior and should be
enabled only for trusted calls. All nine functions remain at the
`needs_approval` default in `iii-permissions.yaml`, unlike the ten parse
functions.

The guarantee differs by tier, and the difference is worth knowing:

- **`fetch` (HTTP) — checked before every hop.** Redirects are followed by
  hand precisely so each hop is validated *before* the request is made, and
  each connection is pinned to the address that was validated, closing the DNS
  rebinding window between check and connect. `Authorization` and `Cookie` are
  dropped on a cross-origin redirect, as curl has done since CVE-2018-1000007.
- **Browser tiers — checked at the socket boundary.** Safe-mode Chrome is
  forced through an in-process HTTP/CONNECT gate. The gate resolves, checks,
  and pins every destination before dialing, including redirect destinations;
  direct bypass, QUIC and WebRTC are disabled.

Two more safe-mode limits worth stating: response bodies are bounded at 32 MiB
whether or not the server declares a content length, and a `fetch` call is
capped at three times its `timeout` in total. Compat mode preserves the frozen
worker's unbounded response and retry/redirect quirks.

### Compatibility modes and certification

Request/response schemas are golden-pinned to the Python wrapper this
surface replaced. Every call is `browser::<leaf>`: the wrapper's
`scrapling::screenshot` is `browser::screenshot-url` here, while
`browser::screenshot` is the interactive session screenshot. The
`stream_name` crawl input is accepted for compatibility but ignored.

`security_mode: safe` is the default. It keeps SSRF checks and resource
ceilings, refuses network options the safe engine cannot enforce, rejects
`verify: false`, and bounds adaptive storage. `security_mode: compat` is only
eligible on Tier-1 Linux x86_64/aarch64 builds produced with the certified
curl-impersonate and Chromium artifacts. Other targets reject compat instead
of silently degrading. Eligibility is not a claim that an arbitrary local
build is certified: builds without the frozen artifacts return a capability
error, and callers should keep using safe mode.

The parser/query core, CSS-to-XPath translation, XPath 1.0 evaluation, Python
regex behavior, Markdown conversion, selector generation, and adaptive
relocation are repository-owned compatibility implementations covered by
exact differential fixtures. Adaptive queries persist element identities in
SQLite at `adaptive_storage_path`; parse functions remain auto-allowed, so
operators should treat that path as durable worker state. Safe mode enforces
`adaptive_max_bytes` (256 MiB by default) and rolls back a write that would
exceed it. Compat mode keeps the frozen worker's unbounded behavior.

Safe HTTP uses the bounded native engine. Compat HTTP is linked to the frozen
curl-impersonate archive; compat browser calls use the certified Chrome build
through raw pipe/WebSocket CDP and reproduce the frozen Playwright/Patchright
sequences. Persistent browser sessions, proxy rotation, remote CDP,
Cloudflare handling and screenshot transforms use that same private runtime.
Certified builds fail when pinned artifacts are absent or mismatched; there is
no silent fallback from compat to safe.

The standalone `scrapling` worker was the oracle and the production
fallback during rollout. It has been removed, and with it the Python
differentials that compared the two implementations call by call.

### The parse goldens

`tests/golden/schemas/browser.*.json` and `tests/golden/behavior/**` are the
frozen record of what the Python implementation answered, captured while both
ran side by side. They are no longer regenerable — the generator ran against
that implementation — so they are now ordinary regression fixtures: a test
failure means this worker's behavior moved, and the fixture is only ever
updated by hand, deliberately, with the change explained.

## Configuration

Stored in the `configuration` worker under the `browser` key. `data_dir` is
read at startup; `engine`, `executable`, `headless`, and the viewport apply
the next time the browser process launches (the first live tab after boot,
or after every tab went to sleep). Scrapling settings live in an isolated nested
block: bulk/default policy can be read per call, while the session cap, idle
timeout, and adaptive database path are snapshotted at worker startup.
Restart after changing a startup-snapshotted value.

```yaml
browser:
  engine: chromium          # chromium | lightpanda (see Engines above)
  executable: ''            # empty = auto-detect Chrome/Chromium/Edge (then the browser::chromium::install copy), or `lightpanda` on PATH
  data_dir: ./data/browser  # profile/ (cookies, logins), downloads/, tabs.json; startup setting
  headless: true            # false shows a real window locally
  max_sessions: 4           # tabs with a page open at once; the LRU unwatched tab sleeps past it
  console_buffer: 500       # per-session console ring buffer (entries)
  network_buffer: 500       # per-session network ring buffer (entries)
  viewport_width: 1280
  viewport_height: 800
  default_timeout_ms: 30000 # navigation/act/evaluate default
  max_timeout_ms: 120000    # ceiling; caller timeout_ms clamped DOWN to this
  inactive_after_ms: 1800000 # unused, unwatched tabs sleep after this (incognito closes); 0 disables
  screenshot_quality: 60    # JPEG quality 1-100
  allowed_schemes: [http, https, file]  # `file` lets a local document be rendered; see below
  max_snapshot_nodes: 2000  # a11y outline size cap
  default_origin_policy:    # omitted fields default to allow
    access: allow
    downloads: allow
    uploads: allow
    scripting: allow
  origin_policies:
    'https://app.example.com:8443':
      uploads: deny
    app.example.com:
      scripting: deny
  allow_history_access: true
  allow_cookie_import: true
  allow_attach: false       # true = allow sessions::attach into a running browser's real profile

  scrapling:
    security_mode: safe        # safe | compat; compat is Tier-1 certified builds only
    chromium_executable: ''    # certified Chrome path; empty = discovery
    allow_loopback: false      # true = permit 127.0.0.1 / ::1 in outbound calls

    defaults:
      impersonate: chrome
      headless: true
      network_idle: false
      proxy: ''
      include_html: false

    max_bulk_concurrency: 5
    max_sessions: 8
    session_idle_timeout_s: 900
    adaptive_storage_path: data/scrapling/elements.db # relative to III_COMPOSE_DIR
    adaptive_max_bytes: 268435456 # safe only; compat preserves the unbounded wrapper behavior
```

`file` is on the default scheme list so a local document can be opened and
rendered, which is how `document::ocr` gets pixels out of a scanned PDF. It is
worth knowing what that permits: navigation is not checked against a session's
filesystem scope the way the workers that read files directly are, so anything
that can reach `browser::navigate` can open any file this process can read.
Narrow the list on a shared machine.

Origin policy keys do not accept wildcards. An exact origin, including its
scheme and non-default port, wins over a bare host; a bare host matches any
scheme or port. Origin keys are URL-normalized before matching, including
lowercased hosts and removal of explicit default ports; bare-host keys match
case-insensitively. URLs with no matching key use `default_origin_policy`.
Each policy field defaults to `allow` when omitted.

Sessions started while any origin policy is configured reload the policy on
every top-document request, so edits apply to their later navigations. A
session started with no origin policy does not enable interception; adding the
first policy later applies the navigation gate to new sessions.

The compatibility fields are part of the stable configuration surface.
Non-Tier-1 or artifact-free builds retain safe mode and reject compat
explicitly instead of approximating it.

The declared production envelope is 4 GiB memory and 2 CPUs. Tier-1 release
validation budgets for five concurrent browser processes; that is a release
test envelope, not permission to exceed configured session caps.

## Custom trigger types

Sibling workers (and the console UI) can subscribe to session activity. All
session bindings accept an optional `{ "session_id": "..." }` filter;
`browser::crawl-item` takes `{ "crawl_id": "..." }` instead.

| Trigger type | Fires when | Payload to subscribers |
|---|---|---|
| `browser::session-started` | A tab opened and is ready | `{ session_id, url, headless, preview, timestamp }` — `preview: false` when the opener passed `preview: false` to `sessions::start` (the console's own tab controls do) |
| `browser::session-stopped` | A tab closed for good | `{ session_id, reason: "stopped" \| "idle" \| "expired" \| "crashed", timestamp }` |
| `browser::session-updated` | A tab woke (`active: true`), went to sleep (`active: false`), or its live page's title changed (`active: true`) | `{ session_id, active, url, title, timestamp }` |
| `browser::navigated` | The page committed a navigation | `{ session_id, url, timestamp }` |
| `browser::console-event` | A console/log/exception entry was captured | `{ session_id, entry }` |
| `browser::picked` | The human picked an element in inspect mode | `{ session_id, element, timestamp }` |
| `browser::handoff-requested` | A session paused for a human step (CAPTCHA, 2FA, payment) | `{ session_id, handoff_id, instructions, timestamp }` |
| `browser::frame-event` | Internal: a live screencast frame of a watched tab (console viewport plumbing) | `{ session_id, frame, width, height, frame_seq, timestamp }` |
| `browser::chromium-install-progress` | A `browser::chromium::install` job entered a phase or moved ~1 % (at most every 250 ms) | `{ job_id, phase: "resolving" \| "downloading" \| "extracting" \| "verifying" \| "done" \| "failed", version?, bytes_done, bytes_total?, path?, error?, hint?, timestamp }` — not a session event: bind it without a `session_id` filter |
| `browser::crawl-item` | A `browser::crawl` page was crawled, or the crawl ended | `{ crawl_id, event: "item", seq, item, truncated }` per page in `seq` order, then `{ crawl_id, event: "done", seq, stats, retained, dropped_events }` — filter is `{ crawl_id }` instead of `session_id`; see Fetching, sessions and crawl |

`browser::console-event` is high-volume; bind it with a `session_id` filter
and treat `browser::console::read` as the durable record. `browser::picked`
elements carry a `ref` that `browser::act` accepts directly, so a human pick
flows straight into agent action.

## Element picking

`browser::pick::start` puts the page in DevTools inspect mode (native hover
highlight); the human's click resolves to tag, attributes, outer HTML, text,
bounds, and recent console errors, emitted as `browser::picked`. The pick,
hint, screencast, and frame functions are internal: console-UI plumbing, not
agent surface, and they stay out of agent tool lists.

## skills

This version ships 1 skill file for agents, also browsable on the worker page:

- [`SKILL.md`](https://workers.iii.dev/workers/browser?tab=skills&skill=SKILL.md&version=0.2.31)

### SKILL.md

# browser

The browser worker does two things. It runs real Chromium sessions on the bus
(`browser::*`), and it parses HTML natively without a browser
(`browser::*` — CSS/XPath/regex queries, element search,
HTML→Markdown, over any HTML string you already have).

Start a session,
navigate, and the page becomes data: `browser::snapshot` returns an
accessibility outline whose `[ref=eN]` handles feed straight into
`browser::act`, and everything the page logs (console calls, uncaught
exceptions, failed requests) is captured into per-session ring buffers you can
query. This is the difference from one-shot fetching: the session stays alive,
so you can act, observe the result, and read what the page said about it.

Sessions are headless by default and cost a Chromium process each; the
configured session cap is small. Stop sessions when a task is done. Refs die
on navigation; re-snapshot before acting after any page change.

## When to Use

- A web app misbehaves and you need the page's own evidence: read console
  errors and failed requests with `browser::console::read` /
  `browser::network::read` instead of guessing from source.
- Verifying frontend work end to end: navigate to the dev server, act on the
  UI, snapshot the result.
- Multi-step flows on a real page: forms, logins, anything that needs state
  to persist between steps.
- Reading a page that only renders with JavaScript, when you also need to
  interact with it afterwards.
- Live style experiments: `browser::styles::read` and
  `browser::styles::write` inspect and change an element's CSS in the running
  page without touching source files.

## Boundaries

- Do not start a browser session just to read a page once. One-shot fetching
  is `browser::fetch` (no browser) or `browser::dynamic-fetch`
  (Chromium, when the page needs JS). Sessions are for flows that need state
  between steps.
- Parsing HTML you already have needs neither a session nor a fetch: use the
  `browser::*` parse function below. Starting Chromium to run a
  CSS selector over a string you are already holding is pure waste.
- `solve_cloudflare` is available on `browser::stealthy-fetch` and stealthy
  Scrapling sessions. Use `browser::handoff` for challenges in an interactive
  session or when automated solving does not clear the page.
- Attach mode reaches the user's real browser profile with its logged-in
  sessions. It is disabled unless `allow_attach` is set, and adoption is
  exclusive (one session per tab) so two sessions never fight over a tab.
  Reach for a launched session when you do not specifically need the user's
  existing logins.
- `browser::styles::write` edits are visual experiments only: they die on the
  next navigation and never touch source files. Use them to find the right
  value, then edit the codebase.
- `browser::pick::*`, `browser::screencast::*`, and `browser::frame` are console-UI plumbing, not agent surface.
- The ghost cursor and session-status badge are in-page overlays for a human
  watching the streamed viewport; they appear only while screencast is
  active and never affect page content or the accessibility snapshot.
- Navigation is limited to the configured URL schemes (http/https by
  default).

## Functions

- `browser::sessions::start` — open a browser tab; returns the session_id
  every other function needs. Tabs share one browser profile (cookies,
  logins) and stay open until stopped: an unused tab sleeps after a while
  and wakes on the next call, so keep using the same session_id instead of
  opening new ones. `read_only: true` opens an inspection-only tab. `ttl_ms`
  gives the tab a lifetime. `incognito: true` opens a PRIVATE tab: its own
  browser context, no shared logins, nothing saved to disk, no history, and
  inactivity closes it for good — use it when a login must not be kept.
- `browser::sessions::list` — every tab, live or asleep (`active`), with its
  current URL and whether it is incognito.
- `browser::sessions::stop` — close a tab for good; idempotent. An attached
  session closes only a tab it opened and releases an adopted user tab
  untouched.
- `browser::sessions::attach` — bind a session to an already-running browser
  over CDP (start Chrome with `--remote-debugging-port`): open a fresh tab
  the session owns, or adopt an existing logged-in tab by URL substring.
  Off unless `allow_attach` is set in config.
- `browser::tabs::list` — open tabs of a running browser at a CDP endpoint,
  with which are already adopted; read-only.
- `browser::doctor` — read-only environment report: which Chromium would
  launch, its version, capacity, whether attach and recording are available,
  and anything degraded with how to enable it.
- `browser::chromium::status` — read-only: whether a Chromium is found, its
  path, version and source, and whether a download is available here.
- `browser::chromium::install` — download Chromium (~200 MB) when the
  machine has none; returns a `job_id` at once, progress on
  `browser::chromium-install-progress`. A session error starting with
  `chromium_missing:` means exactly this: tell the user, and offer the ADE's
  Set up the harness → Browser step or this call (it needs their approval).
- `browser::recording::start` / `browser::recording::stop` — capture a
  session's live viewport to a webm or mp4 file via ffmpeg; stop returns the
  path, duration, and frame count. Requires ffmpeg on PATH.
- `browser::navigate` — go to a URL and wait for the load. Like a browser, a
  network failure or an empty HTTP error response leaves Chromium's error
  page in the tab: the call returns `ok: false` with `error` set instead of
  failing, and the tab stays usable.
- `browser::snapshot` — the page as an accessibility outline with `[ref=eN]`
  handles; the default way to read a page. `diff: true` returns only what
  changed since the previous snapshot.
- `browser::elements` — the visible, enabled controls in the viewport as a
  flat table (`n` ref, role, label, current value, checked/expanded state,
  which `act` operations apply, `<select>` options) plus the visible text,
  in one read. The cheap way to drive a form and to read what an action
  changed.
- `browser::act` — click, hover, type, select a native `<select>` option,
  press, or scroll, addressed by ref or viewport coordinates. A ref is
  scrolled into view, and a click/type/select on a disabled, hidden or
  covered element is refused (the error names what covers it) instead of
  landing on the overlay. Typing into an input or textarea by ref replaces
  its value.
- `browser::run` — reach a goal on the current page in one call: the judge
  worker picks each step (click, type, select, scroll, wait) from the element
  table and the worker executes it, until done, blocked, a field needs text
  you did not pass in `inputs` (`needs_text`), no progress, or the budget
  runs out. Returns the steps and the final page table. Without a judge
  worker it returns `judge_unavailable` plus the table, and changes nothing.
- `browser::screenshot` — viewable JPEG of the viewport, for when layout or
  rendering matters.
- `browser::evaluate` — run a JavaScript expression in the page and get the
  completion value.
- `browser::execute` — run a multi-step async script in the page: top-level
  await and return, `log(...)`, `sleep(ms)`, `waitFor(selector)`, and a
  `state` object persisted across execute calls for the session. One call
  replaces a chain of act/evaluate round-trips.
- `browser::handoff` — pause the session for a human-only step (CAPTCHA,
  2FA, payment): show an in-page continue banner and block until the human
  clicks it, a `browser::handoff::confirm` call resolves it, or the timeout
  elapses. Verify the expected page state after it returns.
- `browser::handoff::confirm` — resolve a paused handoff from outside the
  page (by handoff_id, or the one pending handoff for a session_id).
- `browser::console::read` — captured console entries; filter with
  pattern/level and page with since_seq.
- `browser::network::read` — captured requests; failed_only=true is the fast
  path for what broke.
- `browser::history` — back, forward, or reload; the tab's history survives
  it sleeping and the worker restarting.
- `browser::clear-data` — clear the cookies, storage and cache of the site the
  tab is on; other sites stay signed in. `browser::clear-browser-data` wipes
  the whole profile (every tab signed out).
- `browser::dom::read` — DOM tag outline with refs, for structure the
  accessibility tree hides.
- `browser::styles::read` / `browser::styles::write` — computed styles and
  live inline CSS edits on one element.

### Fetching, sessions and crawl

These reach the network, so they need approval. All return one envelope —
`{status, url, headers, cookies, encoding}` — and can extract or render inline
via `selectors` / `format: markdown|text` / `include_html`, so you rarely need
a second call to parse what you fetched. Each takes a single `url` or a bulk
`urls` list.

- `browser::fetch` — plain HTTP, no browser. The default choice: fastest,
  cheapest. Safe mode uses bounded native HTTP; certified compat mode uses the
  frozen curl-impersonate wire behavior.
- `browser::dynamic-fetch` — real Chromium over CDP, for pages that
  need JavaScript. Supports `wait_selector` (+ `wait_selector_state`),
  `network_idle`, and a plain `wait`.
- `browser::stealthy-fetch` — same, plus masking of the automation
  tells a page can read. Escalate here only when `dynamic-fetch` is detected.
- `browser::screenshot-url` — page as image tiles (≤1024px wide, ≤6
  tiles); says so in the caption when a page is taller than the budget.
- `browser::session-open` / `session-fetch` / `session-close` /
  `session-list` — keep cookies and browser state across fetches.
  HTTP, dynamic and stealthy types are private FIFO sessions with UUID4 hex
  ids; they never appear in `browser::sessions::list` and reject interactive
  ids (`session-close` on a tab id fails: tabs close with
  `browser::sessions::stop`). Close sessions when done.
- `browser::crawl` — breadth-first from `start_urls`, same-domain by
  default, capped by `max_pages` (20) and `max_depth` (2). The response holds
  only a ≤10-item sample; page through every item with
  `browser::crawl::items { crawl_id: <crawl.id>, after?, limit? }` (retained
  in memory for 1 h, last 32 crawls). For items live while it runs, bind
  `browser::crawl-item` with `{ crawl_id }` first, then call
  `browser::crawl { crawl_id }`; missed or dropped events (`seq` gaps,
  `dropped_events`) are read back with `browser::crawl::items`.
  `stream_name` is deprecated and ignored (a `warnings` entry says so).

Safe mode refuses private, loopback and cloud-metadata addresses on every one
of these connections (including redirects and crawl hops). To scrape a local
dev server the operator must set `browser.scrapling.allow_loopback` in worker
config. Compat mode reproduces the Python wrapper's unrestricted network
behavior and is for trusted calls.

### HTML parsing — no session, no browser, no network

These take an `html` string and never touch Chromium. Use them on HTML from
any source (a fetch body, a file, a page you already read).

- `browser::extract` — declarative selector list in one call:
  each entry names a `css`/`xpath`/`regex` plus optional `attr`/`html`/`all`,
  and the response is a `{name: value}` map. The right default when pulling
  several fields off one document.
- `browser::css` / `browser::xpath` — one query;
  `first: true` returns a scalar, otherwise an array. `attr` pulls an
  attribute instead of text.
- `browser::regex` — regex over the document's visible text.
- `browser::find` — element search by tag/attribute filters
  (+ optional text regex), BeautifulSoup-style.
- `browser::find-by-text` / `browser::find-by-regex` —
  find elements by their visible text. Responses carry generated css/xpath
  selectors for each hit, so you can feed one straight back into a query.
- `browser::find-similar` — give one example element, get its
  structural siblings. The fast path for "extract every card/row on this
  page" without hand-writing a selector.
- `browser::describe` — inspect the first match: attributes,
  class list, generated selectors, parent/child/sibling counts.
- `browser::to-markdown` — HTML → compact Markdown (or text), with
  an optional CSS scope and a main-content cleaner. Use it to shrink a page
  before putting it in context.

`adaptive: true` persists element identities in the configured SQLite file.
Parse calls are auto-allowed, so do not assume parsing is side-effect-free
when adaptive tracking is enabled. Safe mode enforces the configured database
quota; compat mode preserves the Python wrapper's unbounded behavior.

### Safe and compat modes

`browser.scrapling.security_mode` defaults to `safe`. Safe mode keeps SSRF,
TLS, proxy, response-size, timeout, and adaptive-database policy checks; an
option the safe backend cannot enforce is refused with an actionable error.
Compat is eligible only on certified Linux x86_64/aarch64 builds containing
the frozen curl-impersonate and Chromium artifacts. Other targets reject it,
and a Tier-1 build missing an artifact reports a capability error instead of
silently using the safe transport.

Every id is `browser::<leaf>`. `browser::screenshot-url` screenshots a url,
while `browser::screenshot` is the interactive-session function.

## Workflow: inspect before acting

1. Read first; act on refs from the latest read, never from memory of an
   earlier one. For forms and controls, `browser::elements` is the cheap
   read; `browser::snapshot` shows the whole structure. Refs die on
   navigation, so a stale ref fails with an error instead of clicking the
   wrong element.
2. After an action that changes the page, re-read with `browser::elements`
   (or `browser::snapshot { diff: true }`, which returns only added and
   removed lines). Set a native `<select>` with `act` `select`, not a
   script.
3. Use `browser::execute` when a step needs waiting or several dependent
   reads and writes; use `browser::act` for single trusted input events
   (in-page script clicks are not trusted events).
4. Pure inspection tasks (audits, scraping a logged-in page you must not
   touch) belong in a `read_only: true` session.
5. When a flow hits a step only a human can do (CAPTCHA, 2FA, payment
   confirmation), call `browser::handoff` and wait, rather than trying to
   automate it. After it returns confirmed, re-read the page and verify the
   step actually landed — a human clicking Continue is not proof the step
   succeeded.

## Workflow: drive a goal with browser::run

A multi-step form or flow is one call instead of an act/read round trip per
click:

1. `browser::run { session_id, goal, inputs }`: state the end state in
   `goal` ("logged in: the dashboard shows"), not the clicks, and give every
   value to type in `inputs` (`email`, `password` or the field labels work).
   Values go to the page only; the judge sees the keys. The run waits for
   the requests each click starts, so the page it returns is the outcome.
2. `done`: the judge chose it and a separate "is the goal complete on this
   page?" check agreed (fields filled but not saved do not pass). Still check
   `page` (the final table) against the goal before you report success; the
   judge's done is not proof (a login can end on "invalid password"). `page.busy` means it was still loading. `needs_text`: type the named field with
   `browser::act` or rerun with that input. `blocked`/`stalled`/`max_steps`:
   read `steps` (each has `error` when an action was refused) and continue
   by hand.
3. `judge_unavailable`: no judge worker is deployed (or it is failing and
   paused for 30 s). Drive the same flow with `browser::elements` +
   `browser::act`; `page` already holds the table.

Do not use `run` for destructive actions; they need the two-phase approval
below.

## Workflow: destructive UI actions

For destructive page actions (deleting records, cancelling subscriptions)
use two phases, never one script:

1. Discovery: read candidates and return their exact stable text or ids for
   the user to approve. Do not act in this call.
2. Action: operate only on the approved identifiers, read any confirmation
   dialog text, and abort unless it matches the approved action.

## Keeping context small

Browser outputs are large and land in the transcript, so a few careless reads
fill the context window and force a compaction. Read economically:

- Prefer `browser::snapshot` (a compact text outline) over
  `browser::screenshot` (a whole image) for reading a page; take a screenshot
  only when layout or rendering is the actual question.
- Filter every read instead of dumping: `browser::console::read` takes
  `pattern`/`level`, `browser::network::read` takes `failed_only`, and both
  page with `since_seq` so a follow-up read returns only what is new.
- On a big page, read a subtree: pass a `ref` to `browser::dom::read` rather
  than snapshotting the whole document repeatedly.
- Reuse one session across steps and `browser::sessions::stop` it when done;
  do not re-snapshot after every action, only after the page actually
  changed.

## Reactive triggers

Bind a `browser::*` trigger when another function should react to session
activity as it happens instead of polling the read functions. The types:
`browser::session-started`, `browser::session-stopped`, `browser::navigated`,
`browser::console-event` (one captured entry per firing; high volume),
`browser::picked` (a human picked an element in the console UI; the payload
carries a ref that `browser::act` accepts directly), and
`browser::handoff-requested` (a session is paused waiting for a human; the
console surfaces it beside the live viewport). `browser::crawl-item` is the
one non-session type: one event per crawled page of a `browser::crawl`,
filtered by `crawl_id` instead of `session_id`.

If you just ran `browser::navigate` yourself, its return value already tells
you the outcome; bind triggers when a different worker needs to observe
sessions it does not drive.

### How to bind

1. Register a handler: `registerFunction('mywatcher::on-console', handler)`.
2. Register the trigger:

```typescript
iii.registerTrigger({
  type: 'browser::console-event',
  function_id: 'mywatcher::on-console',
  config: { session_id: 'b1' },
})
```

Every session `browser::*` binding accepts the optional `session_id`
equality filter; omit it to receive events for all sessions
(`browser::crawl-item` takes `crawl_id` instead). `browser::console-event` fires
per entry, so filter by session and treat `browser::console::read` as the
durable record. For event payload shapes, call `get function info` on the
trigger type.

## api reference

```json
{
  "functions": [
    {
      "description": "Interact with the page: click (left/right/middle, single or double), hover, type, select a native <select> option, press, scroll, or drag (press at the start point, glide to x2/y2, release). Address elements with a ref from browser::elements (`n4`), browser::snapshot (`e3`) or a pick, or raw viewport coordinates. A ref is scrolled into view first, and a click, type or select on a disabled, hidden or covered element is refused instead of landing on whatever is on top. Typing into an input or textarea by ref replaces its value. Returns once the page had a moment to react.",
      "metadata": {},
      "name": "browser::act",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "action": {
            "description": "`click`, `hover`, `type`, `select`, `press`, `scroll`, or `drag`.",
            "type": "string"
          },
          "button": {
            "default": null,
            "description": "Mouse button for `click`: `left` (default), `right`, or `middle`.",
            "type": [
              "string",
              "null"
            ]
          },
          "click_count": {
            "default": null,
            "description": "Clicks in the gesture: 2 double-clicks (`click` only, default 1).",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "delta_y": {
            "default": null,
            "description": "Scroll distance in pixels; positive scrolls down (`scroll`).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "key": {
            "default": null,
            "description": "Key name for `press`: Enter, Tab, Escape, Backspace, Delete, ArrowUp/Down/Left/Right, Home, End, PageUp, PageDown.",
            "type": [
              "string",
              "null"
            ]
          },
          "option": {
            "default": null,
            "description": "Option to choose (`select`, native `<select>` only): its value or visible label.",
            "type": [
              "string",
              "null"
            ]
          },
          "ref": {
            "default": null,
            "description": "Element ref from `browser::elements` (`n4`), `browser::snapshot` (`e3`), `browser::dom::read` (`d7`) or `browser::picked` (`p1`). A click, type or select by ref is refused when the element is disabled, hidden or covered. Refs die on navigation; re-read the page after.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "text": {
            "default": null,
            "description": "Text to insert (`type`). With a ref to an `<input>` or `<textarea>` it replaces the current value; otherwise it is inserted at the caret.",
            "type": [
              "string",
              "null"
            ]
          },
          "x": {
            "default": null,
            "description": "Viewport x, when acting by coordinates instead of ref.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "x2": {
            "default": null,
            "description": "Drag end x, in viewport pixels (`drag`). The start is `x`/`y` or a `ref`; the end is `x2`/`y2`.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "y": {
            "default": null,
            "description": "Viewport y, when acting by coordinates instead of ref.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "y2": {
            "default": null,
            "description": "Drag end y, in viewport pixels (`drag`).",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "action",
          "session_id"
        ],
        "title": "ActInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "detail": {
            "description": "What was done, for the transcript.",
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "detail",
          "ok"
        ],
        "title": "ActOutput",
        "type": "object"
      }
    },
    {
      "description": "Download Chromium (Chrome for Testing, Stable, about 200 MB) into the worker's cache (~/.cache/iii/browser/chrome) for a machine with no Chrome or Chromium. Returns at once with a job_id; progress arrives on the browser::chromium-install-progress trigger and in browser::chromium::status. A Chrome installed on the system still takes precedence. Does nothing when a Chromium is already found, unless force=true.",
      "metadata": {},
      "name": "browser::chromium::install",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "force": {
            "default": false,
            "description": "Download even when a Chromium is already found (a system one, or the same version already downloaded).",
            "type": "boolean"
          }
        },
        "title": "ChromiumInstallInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "InstallStatus": {
            "oneOf": [
              {
                "description": "A new background job started; follow `job_id`.",
                "enum": [
                  "started"
                ],
                "type": "string"
              },
              {
                "description": "A job was already running; `job_id` is that job.",
                "enum": [
                  "running"
                ],
                "type": "string"
              },
              {
                "description": "Nothing to do: a Chromium is already found (`path`, `version`).",
                "enum": [
                  "already-installed"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "job_id": {
            "description": "The install job; its progress arrives on the `browser::chromium-install-progress` trigger and in `browser::chromium::status` (`job`). Absent for `already-installed`.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/definitions/InstallStatus"
          },
          "version": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "status"
        ],
        "title": "ChromiumInstallOutput",
        "type": "object"
      }
    },
    {
      "description": "Whether this machine has a Chromium the worker can launch: the binary, its version, where it came from (config, $CHROME, a system install, the copy browser::chromium::install downloaded, or a Playwright/Puppeteer cache), every place searched, whether a download is available here, and the latest install job. Read-only; never starts a browser.",
      "metadata": {},
      "name": "browser::chromium::status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ChromiumStatusInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "InstallProgress": {
            "description": "`browser::chromium-install-progress` — one install job moved: a new phase, or about 1 % more bytes (at most every 250 ms). Also the `job` field of `browser::chromium::status`.",
            "properties": {
              "bytes_done": {
                "description": "Bytes downloaded (downloading) or unpacked (extracting) so far.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "bytes_total": {
                "description": "Total for `bytes_done`, when known.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "error": {
                "description": "What went wrong (failed).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "hint": {
                "description": "How to fix it, when known (failed) — e.g. the system packages a Linux machine is missing.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "job_id": {
                "type": "string"
              },
              "path": {
                "description": "The installed binary (done).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "phase": {
                "$ref": "#/definitions/Phase"
              },
              "timestamp": {
                "format": "int64",
                "type": "integer"
              },
              "version": {
                "description": "Chrome for Testing version being installed, once known.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "bytes_done",
              "job_id",
              "phase",
              "timestamp"
            ],
            "type": "object"
          },
          "InstallSupport": {
            "description": "Whether `browser::chromium::install` can download a Chromium here.",
            "properties": {
              "approx_download_mb": {
                "description": "Roughly how big the download is.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "dir": {
                "description": "Where the download is kept (`III_BROWSER_CACHE_DIR`, else `${XDG_CACHE_HOME:-~/.cache}/iii/browser`, plus `/chrome`).",
                "type": "string"
              },
              "platform": {
                "description": "Chrome for Testing platform: `linux64`, `linux-arm64`, `mac-arm64`, `mac-x64`, or `win64`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "reason": {
                "description": "Why there is no download here (unsupported platforms).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "supported": {
                "description": "A Chrome for Testing build exists for this platform.",
                "type": "boolean"
              }
            },
            "required": [
              "approx_download_mb",
              "dir",
              "supported"
            ],
            "type": "object"
          },
          "Phase": {
            "oneOf": [
              {
                "description": "Reading the Chrome for Testing manifest.",
                "enum": [
                  "resolving"
                ],
                "type": "string"
              },
              {
                "description": "Streaming the zip; `bytes_done`/`bytes_total` are download bytes.",
                "enum": [
                  "downloading"
                ],
                "type": "string"
              },
              {
                "description": "Unpacking; `bytes_done`/`bytes_total` are unpacked bytes.",
                "enum": [
                  "extracting"
                ],
                "type": "string"
              },
              {
                "description": "Running the new binary with `--version`.",
                "enum": [
                  "verifying"
                ],
                "type": "string"
              },
              {
                "description": "Installed; `path` and `version` are set.",
                "enum": [
                  "done"
                ],
                "type": "string"
              },
              {
                "description": "Stopped; `error` (and maybe `hint`) say why.",
                "enum": [
                  "failed"
                ],
                "type": "string"
              }
            ]
          },
          "Source": {
            "description": "Where the resolved binary came from.",
            "oneOf": [
              {
                "description": "The worker config `executable`.",
                "enum": [
                  "configured"
                ],
                "type": "string"
              },
              {
                "description": "The `CHROME` environment variable.",
                "enum": [
                  "env"
                ],
                "type": "string"
              },
              {
                "description": "A system Chrome/Chromium/Edge.",
                "enum": [
                  "system"
                ],
                "type": "string"
              },
              {
                "description": "The copy `browser::chromium::install` downloaded.",
                "enum": [
                  "managed"
                ],
                "type": "string"
              },
              {
                "description": "A Playwright-installed Chromium.",
                "enum": [
                  "playwright"
                ],
                "type": "string"
              },
              {
                "description": "A Puppeteer-installed Chrome.",
                "enum": [
                  "puppeteer"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "engine": {
            "description": "The configured interactive engine (`chromium` or `lightpanda`). Scrapling's browser tiers use Chromium either way.",
            "type": "string"
          },
          "found": {
            "description": "A Chromium the worker can launch exists.",
            "type": "boolean"
          },
          "install": {
            "$ref": "#/definitions/InstallSupport"
          },
          "job": {
            "anyOf": [
              {
                "$ref": "#/definitions/InstallProgress"
              },
              {
                "type": "null"
              }
            ],
            "description": "This worker's latest install job, running or finished."
          },
          "path": {
            "description": "The binary sessions launch.",
            "type": [
              "string",
              "null"
            ]
          },
          "sandboxed": {
            "description": "False when sessions launch this Chromium with `--no-sandbox` (a cache copy on a Linux that blocks the user-namespace sandbox).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "searched": {
            "description": "The places looked, in order.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "source": {
            "anyOf": [
              {
                "$ref": "#/definitions/Source"
              },
              {
                "type": "null"
              }
            ],
            "description": "Where it came from: `configured`, `env`, `system`, `managed` (the download), `playwright`, or `puppeteer`."
          },
          "version": {
            "description": "`<binary> --version`, first line.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "engine",
          "found",
          "install",
          "searched"
        ],
        "title": "ChromiumStatusOutput",
        "type": "object"
      }
    },
    {
      "description": "Clear ALL browser data: closes every tab's page, quits Chromium, and deletes the profile (every site's cookies, logins, storage, cache) and the downloads on disk. Tabs stay and reopen signed out. Incognito tabs are closed.",
      "metadata": {},
      "name": "browser::clear-browser-data",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ClearBrowserDataInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "closed_pages": {
            "description": "Tabs whose page was closed to release the profile; they reopen on the next call, signed out.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          },
          "profile_dir": {
            "description": "The profile directory that was deleted.",
            "type": "string"
          }
        },
        "required": [
          "closed_pages",
          "ok",
          "profile_dir"
        ],
        "title": "ClearBrowserDataOutput",
        "type": "object"
      }
    },
    {
      "description": "Clear the browsing data of the site the tab is on: its cookies, its storage, and the shared cache — like a browser's per-site 'Clear cookies and site data'. Other sites keep their logins; browser::clear-browser-data wipes everything.",
      "metadata": {},
      "name": "browser::clear-data",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cache": {
            "default": null,
            "description": "Clear the HTTP cache (shared by every tab). Default true.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "cookies": {
            "default": null,
            "description": "Delete the cookies the current page can see (its site's cookies). Default true.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "storage": {
            "default": null,
            "description": "Clear localStorage / sessionStorage / IndexedDB for the current origin. Default true.",
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ClearDataInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cleared": {
            "description": "What was cleared, for the confirmation message.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "cleared",
          "ok"
        ],
        "title": "ClearDataOutput",
        "type": "object"
      }
    },
    {
      "description": "Read the session's captured console: console.* calls, uncaught exceptions, and browser-level log entries. Filter with pattern/level and page with since_seq instead of dumping everything.",
      "metadata": {},
      "name": "browser::console::read",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "level": {
            "default": null,
            "description": "Only entries at this level: `log`, `info`, `warning`, `error`, `debug`, `exception`. `error` also matches `exception`.",
            "type": [
              "string",
              "null"
            ]
          },
          "limit": {
            "default": null,
            "description": "Maximum entries returned, newest kept (default 100).",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "pattern": {
            "default": null,
            "description": "Regex applied to entry text. Use it: dumping an unfiltered console wastes the caller's context.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "since_seq": {
            "default": null,
            "description": "Only entries with `seq` greater than this; resume from the cursor returned as `last_seq`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ConsoleReadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ConsoleEntry": {
            "description": "One captured console/log/exception entry.",
            "properties": {
              "level": {
                "description": "`log`, `info`, `warning`, `error`, `debug`, or `exception`.",
                "type": "string"
              },
              "seq": {
                "description": "Monotonic per-session cursor; pass back as `since_seq`.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "source": {
                "description": "`url:line` of the emitting frame, when known.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "text": {
                "type": "string"
              },
              "timestamp": {
                "format": "int64",
                "type": "integer"
              }
            },
            "required": [
              "level",
              "seq",
              "text",
              "timestamp"
            ],
            "type": "object"
          }
        },
        "properties": {
          "dropped": {
            "description": "Entries evicted from the ring buffer since session start.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "entries": {
            "items": {
              "$ref": "#/definitions/ConsoleEntry"
            },
            "type": "array"
          },
          "last_seq": {
            "description": "Cursor for the next `since_seq`.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "dropped",
          "entries",
          "last_seq"
        ],
        "title": "ConsoleReadOutput",
        "type": "object"
      }
    },
    {
      "description": "Delete the cookies the tab's current page can see (its site's cookies). Other sites keep theirs; browser::clear-browser-data removes everything.",
      "metadata": {},
      "name": "browser::cookies::clear",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "CookiesClearInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "CookiesClearOutput",
        "type": "object"
      }
    },
    {
      "description": "List the cookies on the session's current page (name, value, domain, path, flags).",
      "metadata": {},
      "name": "browser::cookies::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "CookiesListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CookieSpec": {
            "description": "One cookie as read from or written to the session. A subset of the CDP cookie shape: what a person setting a cookie actually provides.",
            "properties": {
              "domain": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "expires": {
                "description": "Seconds since the Unix epoch; omitted for a session cookie.",
                "format": "double",
                "type": [
                  "number",
                  "null"
                ]
              },
              "http_only": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              },
              "path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "same_site": {
                "description": "`Strict`, `Lax`, or `None`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "secure": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "value"
            ],
            "type": "object"
          }
        },
        "properties": {
          "cookies": {
            "items": {
              "$ref": "#/definitions/CookieSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "cookies"
        ],
        "title": "CookiesListOutput",
        "type": "object"
      }
    },
    {
      "description": "Set cookies on the session, like importing a cookie file. A cookie without a domain is scoped to the current page's URL. same_site is Strict, Lax, or None.",
      "metadata": {},
      "name": "browser::cookies::set",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CookieSpec": {
            "description": "One cookie as read from or written to the session. A subset of the CDP cookie shape: what a person setting a cookie actually provides.",
            "properties": {
              "domain": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "expires": {
                "description": "Seconds since the Unix epoch; omitted for a session cookie.",
                "format": "double",
                "type": [
                  "number",
                  "null"
                ]
              },
              "http_only": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              },
              "path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "same_site": {
                "description": "`Strict`, `Lax`, or `None`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "secure": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "value"
            ],
            "type": "object"
          }
        },
        "properties": {
          "cookies": {
            "description": "Cookies to set. A cookie without a domain is scoped to the current page's URL.",
            "items": {
              "$ref": "#/definitions/CookieSpec"
            },
            "type": "array"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "cookies",
          "session_id"
        ],
        "title": "CookiesSetInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "count": {
            "description": "How many cookies were sent.",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "count",
          "ok"
        ],
        "title": "CookiesSetOutput",
        "type": "object"
      }
    },
    {
      "description": "Crawl a site from start_urls (same-domain links), extract per page; all items via browser::crawl::items.",
      "metadata": {},
      "name": "browser::crawl",
      "request_schema": {
        "properties": {
          "allowed_domains": {
            "description": "only follow links on these hosts",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "concurrency": {
            "type": "integer"
          },
          "crawl_id": {
            "description": "id for this crawl (default: random). Bind browser::crawl-item with this crawl_id before calling to receive items live; must not match a running crawl",
            "type": "string"
          },
          "css_selector": {
            "description": "scope the render to this CSS subtree (e.g. a page's content div)",
            "type": "string"
          },
          "download_delay": {
            "description": "seconds to wait between crawl rounds",
            "type": "number"
          },
          "fetcher": {
            "enum": [
              "http",
              "stealthy",
              "dynamic"
            ],
            "type": "string"
          },
          "format": {
            "description": "render page body to this format",
            "enum": [
              "markdown",
              "text"
            ],
            "type": "string"
          },
          "impersonate": {
            "type": "string"
          },
          "include_html": {
            "type": "boolean"
          },
          "main_content_only": {
            "description": "strip nav/scripts/hidden before rendering",
            "type": "boolean"
          },
          "max_depth": {
            "type": "integer"
          },
          "max_pages": {
            "type": "integer"
          },
          "same_domain": {
            "description": "follow only same-host links (default true)",
            "type": "boolean"
          },
          "selectors": {
            "items": {
              "properties": {
                "all": {
                  "description": "return every match as a list",
                  "type": "boolean"
                },
                "attr": {
                  "description": "extract this attribute instead of text",
                  "type": "string"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "description": "extract inner HTML instead of text",
                  "type": "boolean"
                },
                "name": {
                  "type": "string"
                },
                "regex": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "start_urls": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "stream_name": {
            "description": "deprecated, ignored (adds a warning): items are no longer written to a stream; use browser::crawl::items or the browser::crawl-item trigger",
            "type": "string"
          },
          "url": {
            "description": "single start URL (alternative to start_urls)",
            "type": "string"
          }
        },
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "crawl": {
            "properties": {
              "dropped_events": {
                "description": "live events dropped because a slow consumer filled the bounded queue; read those items back",
                "type": "integer"
              },
              "id": {
                "description": "pass to browser::crawl::items, or as the browser::crawl-item crawl_id filter",
                "type": "string"
              },
              "items_function": {
                "description": "browser::crawl::items: paginated read of every retained item",
                "type": "string"
              },
              "retained": {
                "description": "items readable with browser::crawl::items (less than stats.crawled when a retention cap was hit)",
                "type": "integer"
              },
              "trigger_type": {
                "description": "browser::crawl-item: live per-item events",
                "type": "string"
              }
            },
            "type": "object"
          },
          "items": {
            "description": "a sample (at most 10) of the crawl's items; read all of them with browser::crawl::items",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "stats": {
            "properties": {
              "crawled": {
                "type": "integer"
              },
              "errors": {
                "type": "integer"
              },
              "items": {
                "type": "integer"
              },
              "stopped": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "stream": {
            "description": "deprecated echo kept for older callers: stream_name and the crawl id; nothing is written to a stream. Use crawl.id",
            "properties": {
              "group_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "warnings": {
            "description": "present when the request used deprecated input such as stream_name",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Page through every item a recent browser::crawl retained (by crawl.id, in seq order).",
      "metadata": {},
      "name": "browser::crawl::items",
      "request_schema": {
        "properties": {
          "after": {
            "description": "return items with seq greater than this (default 0); pass next_after to continue",
            "type": "integer"
          },
          "crawl_id": {
            "description": "crawl.id from browser::crawl",
            "type": "string"
          },
          "limit": {
            "description": "items per page, 1-100 (default 20)",
            "type": "integer"
          }
        },
        "required": [
          "crawl_id"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "crawl_id": {
            "type": "string"
          },
          "items": {
            "description": "retained items in crawl order; seq matches the browser::crawl-item events",
            "items": {
              "properties": {
                "item": {
                  "type": "object"
                },
                "seq": {
                  "type": "integer"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "next_after": {
            "description": "present when more retained items follow",
            "type": "integer"
          },
          "retained": {
            "type": "integer"
          },
          "running": {
            "description": "the crawl is still in progress",
            "type": "boolean"
          },
          "stats": {
            "description": "the crawl's final stats, once it finished",
            "type": "object"
          },
          "truncated": {
            "description": "a retention cap was hit; items after the retained ones were not kept",
            "type": "boolean"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Query HTML with a CSS selector; first-or-all; `attr` pulls an attribute.",
      "metadata": {},
      "name": "browser::css",
      "request_schema": {
        "properties": {
          "adaptive": {
            "description": "relocate elements after a site change via saved identities",
            "type": "boolean"
          },
          "adaptive_domain": {
            "description": "page URL/domain that keys saved identities",
            "type": "string"
          },
          "attr": {
            "type": "string"
          },
          "auto_save": {
            "description": "save matched identities (defaults on when adaptive)",
            "type": "boolean"
          },
          "first": {
            "type": "boolean"
          },
          "html": {
            "type": "string"
          },
          "identifier": {
            "description": "stable key for the saved element",
            "type": "string"
          },
          "query": {
            "type": "string"
          }
        },
        "required": [
          "html",
          "query"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "result": {
            "items": {
              "type": [
                "string",
                "null"
              ]
            },
            "type": [
              "array",
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Describe the first css/xpath match: attrs, generated selectors, class list, DOM context.",
      "metadata": {},
      "name": "browser::describe",
      "request_schema": {
        "properties": {
          "html": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "css",
              "xpath"
            ],
            "type": "string"
          },
          "query": {
            "type": "string"
          }
        },
        "required": [
          "html",
          "query"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "element": {
            "properties": {
              "attrs": {
                "type": "object"
              },
              "children": {
                "type": "integer"
              },
              "classes": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "css": {
                "type": "string"
              },
              "full_css": {
                "type": "string"
              },
              "full_xpath": {
                "type": "string"
              },
              "html": {
                "type": "string"
              },
              "parent_tag": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "siblings": {
                "type": "integer"
              },
              "tag": {
                "type": "string"
              },
              "text": {
                "type": "string"
              },
              "xpath": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "found": {
            "type": "boolean"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Diagnose the browser environment: which Chromium the worker would launch, its version, session capacity, and any degraded capability with how to enable it. Never starts a browser.",
      "metadata": {},
      "name": "browser::doctor",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "DoctorInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "DoctorIssue": {
            "description": "One degraded capability plus the way to enable it.",
            "properties": {
              "enable_how": {
                "type": "string"
              },
              "what": {
                "type": "string"
              }
            },
            "required": [
              "enable_how",
              "what"
            ],
            "type": "object"
          },
          "Source": {
            "description": "Where the resolved binary came from.",
            "oneOf": [
              {
                "description": "The worker config `executable`.",
                "enum": [
                  "configured"
                ],
                "type": "string"
              },
              {
                "description": "The `CHROME` environment variable.",
                "enum": [
                  "env"
                ],
                "type": "string"
              },
              {
                "description": "A system Chrome/Chromium/Edge.",
                "enum": [
                  "system"
                ],
                "type": "string"
              },
              {
                "description": "The copy `browser::chromium::install` downloaded.",
                "enum": [
                  "managed"
                ],
                "type": "string"
              },
              {
                "description": "A Playwright-installed Chromium.",
                "enum": [
                  "playwright"
                ],
                "type": "string"
              },
              {
                "description": "A Puppeteer-installed Chrome.",
                "enum": [
                  "puppeteer"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "active_sessions": {
            "description": "Tabs with a page open right now.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "allow_cookie_import": {
            "type": "boolean"
          },
          "allow_history_access": {
            "type": "boolean"
          },
          "allowed_schemes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "attach_enabled": {
            "description": "Whether attach mode is enabled (allow_attach).",
            "type": "boolean"
          },
          "browser_running": {
            "description": "Whether the shared Chromium process is running.",
            "type": "boolean"
          },
          "chromium_path": {
            "description": "The engine binary the worker would launch (a Chromium/Chrome, or the `lightpanda` binary). The field name predates the `engine` setting.",
            "type": [
              "string",
              "null"
            ]
          },
          "chromium_source": {
            "anyOf": [
              {
                "$ref": "#/definitions/Source"
              },
              {
                "type": "null"
              }
            ],
            "description": "Where the Chromium came from: `configured`, `env`, `system`, `managed` (downloaded by browser::chromium::install), `playwright`, or `puppeteer`. Absent for Lightpanda or when nothing was found."
          },
          "chromium_version": {
            "description": "`<binary> --version`, first line.",
            "type": [
              "string",
              "null"
            ]
          },
          "configured_origin_policies": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "data_dir": {
            "description": "Where the profile, downloads and tab list live.",
            "type": "string"
          },
          "default_origin_policy_set": {
            "type": "boolean"
          },
          "engine": {
            "description": "Configured engine: `chromium` or `lightpanda`.",
            "type": "string"
          },
          "headless_default": {
            "type": "boolean"
          },
          "issues": {
            "items": {
              "$ref": "#/definitions/DoctorIssue"
            },
            "type": "array"
          },
          "max_sessions": {
            "description": "Live-tab cap (`max_sessions`).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "description": "True when sessions can start right now.",
            "type": "boolean"
          },
          "open_tabs": {
            "description": "Every tab, live or asleep.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "recording_available": {
            "description": "Whether ffmpeg is on PATH, which browser::recording requires.",
            "type": "boolean"
          }
        },
        "required": [
          "active_sessions",
          "allow_cookie_import",
          "allow_history_access",
          "allowed_schemes",
          "attach_enabled",
          "browser_running",
          "configured_origin_policies",
          "data_dir",
          "default_origin_policy_set",
          "engine",
          "headless_default",
          "issues",
          "max_sessions",
          "ok",
          "open_tabs",
          "recording_available"
        ],
        "title": "DoctorOutput",
        "type": "object"
      }
    },
    {
      "description": "Read the DOM as a tree of tags with id/class and refs. Structure-oriented complement to browser::snapshot; read deep subtrees by passing a ref.",
      "metadata": {},
      "name": "browser::dom::read",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "depth": {
            "default": null,
            "description": "Levels of children to include (default 3).",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "ref": {
            "default": null,
            "description": "Subtree root from an earlier ref (`e3`/`p1`) or dom node. Omit for the document root.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "DomReadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "DomNode": {
            "description": "One DOM node in the outline. `ref` resolves in `browser::act`, `browser::styles::read`, and `browser::styles::write`.",
            "properties": {
              "child_count": {
                "description": "Total children in the document, which may exceed `children` returned at this depth.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "children": {
                "items": {
                  "$ref": "#/definitions/DomNode"
                },
                "type": "array"
              },
              "classes": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ref": {
                "type": "string"
              },
              "tag": {
                "description": "Lowercase tag (`div`, `button`) or node name (`#text`).",
                "type": "string"
              },
              "text": {
                "description": "Trimmed text content for text nodes.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "child_count",
              "ref",
              "tag"
            ],
            "type": "object"
          }
        },
        "properties": {
          "root": {
            "$ref": "#/definitions/DomNode"
          },
          "truncated": {
            "description": "True when the node cap cut the tree short; read a subtree via `ref`.",
            "type": "boolean"
          }
        },
        "required": [
          "root",
          "truncated"
        ],
        "title": "DomReadOutput",
        "type": "object"
      }
    },
    {
      "description": "Read a downloaded file's bytes for saving or attaching to the chat. Base64; guid from browser::downloads::list.",
      "metadata": {},
      "name": "browser::download",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "guid": {
            "description": "The download's CDP guid, from `browser::downloads::list`.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "guid",
          "session_id"
        ],
        "title": "DownloadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "data": {
            "description": "The file, base64.",
            "type": "string"
          },
          "file_name": {
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "size_bytes": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "data",
          "file_name",
          "ok",
          "size_bytes"
        ],
        "title": "DownloadOutput",
        "type": "object"
      }
    },
    {
      "description": "Forget a download and delete its file from the session's download dir.",
      "metadata": {},
      "name": "browser::download::remove",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "guid": {
            "description": "The download to forget, and delete from disk.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "guid",
          "session_id"
        ],
        "title": "DownloadRemoveInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "DownloadRemoveOutput",
        "type": "object"
      }
    },
    {
      "description": "List the files this session downloaded (name, url, size, state), newest first. Read one with browser::download.",
      "metadata": {},
      "name": "browser::downloads::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "DownloadsListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "DownloadRecord": {
            "description": "A download Chromium started, tracked by its CDP guid.",
            "properties": {
              "file_name": {
                "type": "string"
              },
              "guid": {
                "type": "string"
              },
              "received_bytes": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "started_ms": {
                "format": "int64",
                "type": "integer"
              },
              "state": {
                "description": "`in_progress`, `completed`, or `canceled`.",
                "type": "string"
              },
              "total_bytes": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "file_name",
              "guid",
              "received_bytes",
              "started_ms",
              "state",
              "total_bytes",
              "url"
            ],
            "type": "object"
          }
        },
        "properties": {
          "downloads": {
            "description": "Newest first.",
            "items": {
              "$ref": "#/definitions/DownloadRecord"
            },
            "type": "array"
          }
        },
        "required": [
          "downloads"
        ],
        "title": "DownloadsListOutput",
        "type": "object"
      }
    },
    {
      "description": "Scrape a JS-rendered URL with Playwright/Chromium: waits, XHR capture, CDP.",
      "metadata": {},
      "name": "browser::dynamic-fetch",
      "request_schema": {
        "properties": {
          "block_ads": {
            "type": "boolean"
          },
          "blocked_domains": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "capture_xhr": {
            "type": "string"
          },
          "cdp_url": {
            "type": "string"
          },
          "cookies": {
            "type": "object"
          },
          "css_selector": {
            "description": "scope the render to this CSS subtree (e.g. a page's content div)",
            "type": "string"
          },
          "disable_resources": {
            "type": "boolean"
          },
          "dns_over_https": {
            "type": "boolean"
          },
          "extra_flags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "extra_headers": {
            "type": "object"
          },
          "format": {
            "description": "render page body to this format",
            "enum": [
              "markdown",
              "text"
            ],
            "type": "string"
          },
          "google_search": {
            "type": "boolean"
          },
          "headless": {
            "type": "boolean"
          },
          "include_html": {
            "type": "boolean"
          },
          "load_dom": {
            "type": "boolean"
          },
          "locale": {
            "type": "string"
          },
          "main_content_only": {
            "description": "strip nav/scripts/hidden before rendering",
            "type": "boolean"
          },
          "max_pages": {
            "type": "integer"
          },
          "network_idle": {
            "type": "boolean"
          },
          "proxy": {
            "type": "string"
          },
          "real_chrome": {
            "type": "boolean"
          },
          "retries": {
            "type": "integer"
          },
          "retry_delay": {
            "type": "number"
          },
          "selectors": {
            "items": {
              "properties": {
                "all": {
                  "description": "return every match as a list",
                  "type": "boolean"
                },
                "attr": {
                  "description": "extract this attribute instead of text",
                  "type": "string"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "description": "extract inner HTML instead of text",
                  "type": "boolean"
                },
                "name": {
                  "type": "string"
                },
                "regex": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "timeout": {
            "description": "milliseconds (browser fetcher)",
            "type": "number"
          },
          "timezone_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "urls": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "useragent": {
            "type": "string"
          },
          "wait": {
            "description": "extra ms to wait after load",
            "type": "number"
          },
          "wait_selector": {
            "type": "string"
          },
          "wait_selector_state": {
            "enum": [
              "attached",
              "detached",
              "visible",
              "hidden"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "captured_xhr": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "content": {
            "description": "markdown/text render when `format` requested",
            "type": "string"
          },
          "cookies": {
            "type": "object"
          },
          "encoding": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": "string"
          },
          "extracted": {
            "type": "object"
          },
          "format": {
            "type": "string"
          },
          "headers": {
            "type": "object"
          },
          "html": {
            "type": "string"
          },
          "results": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "The page's visible, enabled controls in the viewport as an indexed table (ref, role, label, current value, checked/expanded state, what browser::act can do with each, and <select> options), plus the visible text, from one read. Cheaper and flatter than browser::snapshot: use it to drive forms and read what an action changed. Refs (`n4`) stay valid while the element stays in the document.",
      "metadata": {},
      "name": "browser::elements",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ElementsInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Element": {
            "description": "One control. `operations` says what `browser::act` can do with it.",
            "properties": {
              "autocomplete": {
                "description": "The field's `autocomplete` hint (`username`, `current-password`, …).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "checked": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "expanded": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "input_type": {
                "description": "`<input>` type when not `text` (`email`, `password`, `tel`, …).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "label": {
                "description": "Accessible name (aria-labelledby, aria-label, label, text, title, or placeholder).",
                "type": "string"
              },
              "more_options": {
                "description": "Options beyond the listed 50; `select` still accepts them by label.",
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "name": {
                "description": "The field's `name` (or `id`).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "operations": {
                "description": "`click`, `type` and/or `select`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "options": {
                "description": "Enabled `<select>` option labels (first 50), for `act` `select`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "ref": {
                "description": "`n` ref `browser::act` accepts; valid while the element stays in this document.",
                "type": "string"
              },
              "role": {
                "type": "string"
              },
              "selected": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "value": {
                "description": "Current value of a field or dropdown; a set password reads `(set)`.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "label",
              "operations",
              "ref",
              "role"
            ],
            "type": "object"
          }
        },
        "properties": {
          "busy": {
            "default": false,
            "description": "The page is still loading: the document, a region marked `aria-busy`, or (in `browser::run`) requests an action started that had not finished when the run stopped waiting.",
            "type": "boolean"
          },
          "can_scroll_down": {
            "type": "boolean"
          },
          "can_scroll_up": {
            "type": "boolean"
          },
          "elements": {
            "description": "Visible, enabled controls inside the viewport, in document order.",
            "items": {
              "$ref": "#/definitions/Element"
            },
            "type": "array"
          },
          "generation": {
            "default": 0,
            "description": "Document generation; navigation advances it and every `n` ref dies.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "omitted": {
            "description": "Controls past the 250-element cap, not listed.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "text": {
            "description": "Visible text in the viewport, up to 6000 characters.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "can_scroll_down",
          "can_scroll_up",
          "elements",
          "omitted",
          "text",
          "title",
          "url"
        ],
        "title": "ElementsOutput",
        "type": "object"
      }
    },
    {
      "description": "Evaluate a JavaScript expression in the page and return its completion value. Use for reads the snapshot can't express; prefer browser::act for interactions.",
      "metadata": {},
      "name": "browser::evaluate",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "expression": {
            "description": "JavaScript expression evaluated in the page. The completion value is returned by value; wrap statements in an IIFE.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "timeout_ms": {
            "default": null,
            "description": "Upper bound on evaluation; clamped to `max_timeout_ms`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "expression",
          "session_id"
        ],
        "title": "EvaluateInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "error": {
            "description": "Exception text when not `ok`.",
            "type": [
              "string",
              "null"
            ]
          },
          "ok": {
            "type": "boolean"
          },
          "value": {
            "description": "JSON completion value when `ok`."
          }
        },
        "required": [
          "ok"
        ],
        "title": "EvaluateOutput",
        "type": "object"
      }
    },
    {
      "description": "Run a multi-step async JavaScript script in the page: top-level await and return work, with log(...), sleep(ms), waitFor(selector), and a state object that persists across execute calls for the session. One call replaces a chain of act/evaluate round-trips; returns { result, logs, state }.",
      "metadata": {},
      "name": "browser::execute",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "code": {
            "description": "Async JavaScript body run in the page; `state`, `log(...)`, `sleep(ms)` and `waitFor(selector, { timeout })` are in scope. Return plain JSON.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "timeout_ms": {
            "default": null,
            "description": "Upper bound on the run; clamped to `max_timeout_ms`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "code",
          "session_id"
        ],
        "title": "ExecuteInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "error": {
            "description": "Exception text when not `ok`. A \"context destroyed\" error usually means the script navigated; split the script at the navigation.",
            "type": [
              "string",
              "null"
            ]
          },
          "logs": {
            "description": "`log(...)` output collected during the run, in order.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ok": {
            "type": "boolean"
          },
          "result": {
            "description": "The script's return value when `ok`."
          },
          "state": {
            "description": "Session state after the run; the next execute call sees this as `state`."
          }
        },
        "required": [
          "logs",
          "ok",
          "state"
        ],
        "title": "ExecuteOutput",
        "type": "object"
      }
    },
    {
      "description": "Parse HTML with a selector list (css/xpath/regex, text/attr/html, all-or-first).",
      "metadata": {},
      "name": "browser::extract",
      "request_schema": {
        "properties": {
          "adaptive": {
            "description": "relocate elements after a site change via saved identities",
            "type": "boolean"
          },
          "adaptive_domain": {
            "description": "page URL/domain that keys saved identities",
            "type": "string"
          },
          "auto_save": {
            "description": "save matched identities (defaults on when adaptive)",
            "type": "boolean"
          },
          "html": {
            "type": "string"
          },
          "selectors": {
            "items": {
              "properties": {
                "all": {
                  "description": "return every match as a list",
                  "type": "boolean"
                },
                "attr": {
                  "description": "extract this attribute instead of text",
                  "type": "string"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "description": "extract inner HTML instead of text",
                  "type": "boolean"
                },
                "name": {
                  "type": "string"
                },
                "regex": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "html",
          "selectors"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "extracted": {
            "type": "object"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Scrape a URL over HTTP with TLS impersonation: get/post/put/delete, extraction, bulk.",
      "metadata": {},
      "name": "browser::fetch",
      "request_schema": {
        "properties": {
          "cookies": {
            "type": "object"
          },
          "css_selector": {
            "description": "scope the render to this CSS subtree (e.g. a page's content div)",
            "type": "string"
          },
          "data": {
            "type": "object"
          },
          "follow_redirects": {
            "type": "boolean"
          },
          "format": {
            "description": "render page body to this format",
            "enum": [
              "markdown",
              "text"
            ],
            "type": "string"
          },
          "headers": {
            "type": "object"
          },
          "http3": {
            "type": "boolean"
          },
          "impersonate": {
            "description": "TLS/UA fingerprint, e.g. 'chrome'",
            "type": "string"
          },
          "include_html": {
            "type": "boolean"
          },
          "json": {
            "type": "object"
          },
          "main_content_only": {
            "description": "strip nav/scripts/hidden before rendering",
            "type": "boolean"
          },
          "max_redirects": {
            "type": "integer"
          },
          "method": {
            "enum": [
              "get",
              "post",
              "put",
              "delete"
            ],
            "type": "string"
          },
          "params": {
            "type": "object"
          },
          "proxies": {
            "description": "per-scheme proxies, e.g. {\"https\": \"http://...\"}",
            "type": "object"
          },
          "proxy": {
            "type": "string"
          },
          "proxy_auth": {
            "description": "[user, password]",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "retries": {
            "type": "integer"
          },
          "retry_delay": {
            "type": "number"
          },
          "selectors": {
            "items": {
              "properties": {
                "all": {
                  "description": "return every match as a list",
                  "type": "boolean"
                },
                "attr": {
                  "description": "extract this attribute instead of text",
                  "type": "string"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "description": "extract inner HTML instead of text",
                  "type": "boolean"
                },
                "name": {
                  "type": "string"
                },
                "regex": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "stealthy_headers": {
            "type": "boolean"
          },
          "timeout": {
            "description": "seconds (HTTP fetcher)",
            "type": "number"
          },
          "url": {
            "type": "string"
          },
          "urls": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "verify": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "captured_xhr": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "content": {
            "description": "markdown/text render when `format` requested",
            "type": "string"
          },
          "cookies": {
            "type": "object"
          },
          "encoding": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": "string"
          },
          "extracted": {
            "type": "object"
          },
          "format": {
            "type": "string"
          },
          "headers": {
            "type": "object"
          },
          "html": {
            "type": "string"
          },
          "results": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Find elements by tag/attribute filters (+ optional text regex); BeautifulSoup-style.",
      "metadata": {},
      "name": "browser::find",
      "request_schema": {
        "properties": {
          "attrs": {
            "description": "attribute filters, e.g. {\"class\": \"card\"}",
            "type": "object"
          },
          "first": {
            "type": "boolean"
          },
          "html": {
            "type": "string"
          },
          "limit": {
            "type": "integer"
          },
          "tag": {
            "description": "tag name or list of tag names",
            "items": {
              "type": "string"
            },
            "type": [
              "string",
              "array"
            ]
          },
          "text_regex": {
            "description": "keep only elements whose text matches this regex",
            "type": "string"
          }
        },
        "required": [
          "html"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "count": {
            "type": "integer"
          },
          "items": {
            "items": {
              "properties": {
                "attrs": {
                  "type": "object"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "type": "string"
                },
                "tag": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Find elements whose text matches a regex pattern.",
      "metadata": {},
      "name": "browser::find-by-regex",
      "request_schema": {
        "properties": {
          "case_sensitive": {
            "type": "boolean"
          },
          "clean_match": {
            "type": "boolean"
          },
          "first": {
            "type": "boolean"
          },
          "html": {
            "type": "string"
          },
          "limit": {
            "type": "integer"
          },
          "pattern": {
            "type": "string"
          }
        },
        "required": [
          "html",
          "pattern"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "count": {
            "type": "integer"
          },
          "items": {
            "items": {
              "properties": {
                "attrs": {
                  "type": "object"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "type": "string"
                },
                "tag": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Find elements with this exact text, or `partial` for a substring.",
      "metadata": {},
      "name": "browser::find-by-text",
      "request_schema": {
        "properties": {
          "case_sensitive": {
            "type": "boolean"
          },
          "clean_match": {
            "description": "ignore surrounding/collapsing whitespace",
            "type": "boolean"
          },
          "first": {
            "type": "boolean"
          },
          "html": {
            "type": "string"
          },
          "limit": {
            "type": "integer"
          },
          "partial": {
            "description": "match elements that contain the text",
            "type": "boolean"
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "html",
          "text"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "count": {
            "type": "integer"
          },
          "items": {
            "items": {
              "properties": {
                "attrs": {
                  "type": "object"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "type": "string"
                },
                "tag": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Find text in the page like the browser's find bar: highlights every match in the live document, scrolls the current one into view, and steps with next / previous. close clears the highlights. Returns count and the 1-based index.",
      "metadata": {},
      "name": "browser::find-in-page",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "action": {
            "default": null,
            "description": "`search` (default: run the query from the top), `next`, `previous`, or `close`.",
            "type": [
              "string",
              "null"
            ]
          },
          "case_sensitive": {
            "default": null,
            "description": "Match case. Default false.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "query": {
            "default": "",
            "description": "Text to look for. Empty with `action: close` clears the search.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "FindInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "count": {
            "description": "Number of matches in the visible text of the page.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "index": {
            "description": "1-based index of the highlighted match; 0 when there is none.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          },
          "query": {
            "description": "The query the counts refer to.",
            "type": "string"
          }
        },
        "required": [
          "count",
          "index",
          "ok",
          "query"
        ],
        "title": "FindOutput",
        "type": "object"
      }
    },
    {
      "description": "Find elements structurally similar to one example element, plus that element.",
      "metadata": {},
      "name": "browser::find-similar",
      "request_schema": {
        "properties": {
          "anchor": {
            "description": "CSS selector to one example element",
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "match_text": {
            "type": "boolean"
          },
          "selectors": {
            "items": {
              "properties": {
                "all": {
                  "description": "return every match as a list",
                  "type": "boolean"
                },
                "attr": {
                  "description": "extract this attribute instead of text",
                  "type": "string"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "description": "extract inner HTML instead of text",
                  "type": "boolean"
                },
                "name": {
                  "type": "string"
                },
                "regex": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "similarity_threshold": {
            "type": "number"
          }
        },
        "required": [
          "html",
          "anchor"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "count": {
            "type": "integer"
          },
          "items": {
            "items": {
              "type": "object"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Internal: newest screencast frame, or nothing when since_frame is still current. No capture round-trip: a viewer reads it once for its first paint, then frames arrive on browser::frame-event. Not an agent function.",
      "metadata": {
        "internal": true
      },
      "name": "browser::frame",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "since_frame": {
            "default": null,
            "description": "Frame cursor from the previous read; the response omits `frame` while the newest frame still has this seq.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "FrameInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "active": {
            "description": "False when no screencast is running (call screencast::start first).",
            "type": "boolean"
          },
          "frame": {
            "description": "Base64 JPEG of the newest frame; absent when `since_frame` is still current or no frame has arrived yet.",
            "type": [
              "string",
              "null"
            ]
          },
          "frame_seq": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "height": {
            "description": "Page-viewport height the frame maps to.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "timestamp": {
            "format": "int64",
            "type": "integer"
          },
          "width": {
            "description": "Page-viewport width the frame maps to (input coordinate space).",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "active",
          "frame_seq",
          "height",
          "timestamp",
          "width"
        ],
        "title": "FrameOutput",
        "type": "object"
      }
    },
    {
      "description": "Pause a session for a step only a human can do (CAPTCHA, 2FA, payment): show an in-page continue banner and block until the human clicks it, a browser::handoff::confirm call resolves it, or the timeout elapses. Human acknowledgment is not proof — verify the expected page state after it returns.",
      "metadata": {},
      "name": "browser::handoff",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "instructions": {
            "description": "What the human must do before the call continues. Shown in the in-page banner and the handoff-requested event.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "timeout_ms": {
            "default": null,
            "description": "Give up after this many ms and return `via: \"timeout\"` (clamped to `max_timeout_ms`); set generously, a human is slow.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "instructions",
          "session_id"
        ],
        "title": "HandoffInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "confirmed": {
            "description": "True when a human confirmed; false when the wait timed out.",
            "type": "boolean"
          },
          "handoff_id": {
            "type": "string"
          },
          "url": {
            "description": "The page URL when the wait ended, so the caller can verify the step actually landed (human acknowledgment is not proof).",
            "type": "string"
          },
          "via": {
            "description": "How the confirmation arrived: `in_page`, `confirm_call`, or `timeout`.",
            "type": "string"
          }
        },
        "required": [
          "confirmed",
          "handoff_id",
          "url",
          "via"
        ],
        "title": "HandoffOutput",
        "type": "object"
      }
    },
    {
      "description": "Resolve a paused browser::handoff by handoff_id, or the one pending handoff for a session_id. The console calls this when the human confirms outside the page.",
      "metadata": {},
      "name": "browser::handoff::confirm",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "handoff_id": {
            "default": null,
            "description": "Confirm a specific handoff by id. Omit to confirm the one pending handoff for `session_id`.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "HandoffConfirmInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "handoff_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "ok": {
            "description": "True when a pending handoff matched and was resolved.",
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "HandoffConfirmOutput",
        "type": "object"
      }
    },
    {
      "description": "Go back, go forward, or reload the tab's page. History survives the tab sleeping and the worker restarting. Back/forward at the history edge is a no-op with moved=false.",
      "metadata": {},
      "name": "browser::history",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "action": {
            "description": "`back`, `forward`, or `reload`.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "action",
          "session_id"
        ],
        "title": "HistoryInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "moved": {
            "description": "False when back/forward had no entry to move to.",
            "type": "boolean"
          },
          "ok": {
            "type": "boolean"
          },
          "url": {
            "description": "URL after the action. `back`/`forward` at the history edge is a no-op with ok=true.",
            "type": "string"
          }
        },
        "required": [
          "moved",
          "ok",
          "url"
        ],
        "title": "HistoryOutput",
        "type": "object"
      }
    },
    {
      "description": "List the pages this session visited, newest first, for a history panel or address-bar suggestions. Filter with query. browser::history moves back / forward / reloads instead.",
      "metadata": {},
      "name": "browser::history::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "limit": {
            "default": null,
            "description": "Cap on returned entries. Default 100.",
            "format": "uint",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "query": {
            "default": null,
            "description": "Only entries whose url or title contains this (case-insensitive).",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "HistoryListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "HistoryVisit": {
            "description": "One entry in a tab's navigation history.",
            "properties": {
              "timestamp": {
                "format": "int64",
                "type": "integer"
              },
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "timestamp",
              "title",
              "url"
            ],
            "type": "object"
          }
        },
        "properties": {
          "visits": {
            "items": {
              "$ref": "#/definitions/HistoryVisit"
            },
            "type": "array"
          }
        },
        "required": [
          "visits"
        ],
        "title": "HistoryListOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: appends browser::* scraping and HTML parsing guidance to the agent system prompt.",
      "metadata": {
        "internal": true
      },
      "name": "browser::inject-guidance",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "GenerateContext": {
            "properties": {
              "system_prompt": {
                "default": "",
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "generate": {
            "$ref": "#/definitions/GenerateContext"
          }
        },
        "title": "PreGenerateEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PreGenerateMutations": {
            "properties": {
              "system_prompt": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "mutations": {
            "$ref": "#/definitions/PreGenerateMutations"
          }
        },
        "required": [
          "mutations"
        ],
        "title": "PreGenerateResponse",
        "type": "object"
      }
    },
    {
      "description": "Navigate a tab to a URL and wait for the page to load. Like a browser, a network failure or an empty HTTP error response leaves Chromium's error page in the tab and is reported in `error` rather than failing the call. Element refs from earlier snapshots are invalidated by navigation.",
      "metadata": {},
      "name": "browser::navigate",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "timeout_ms": {
            "default": null,
            "description": "Upper bound on the navigation wait; clamped to `max_timeout_ms`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "description": "Absolute URL; scheme must be on the configured allowlist.",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "url"
        ],
        "title": "NavigateInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "error": {
            "description": "Chromium's error text when the navigation ended on the browser's own error page: a network failure (`net::ERR_NAME_NOT_RESOLVED`), or an empty HTTP error response (`net::ERR_HTTP_RESPONSE_CODE_FAILURE`, a 4xx/5xx with no body). Like a browser, the tab still shows that page and stays usable.",
            "type": [
              "string",
              "null"
            ]
          },
          "ok": {
            "description": "False when the tab shows Chromium's error page instead of the site; see `error`.",
            "type": "boolean"
          },
          "timed_out": {
            "description": "True when the load event did not fire inside the timeout; the page may still be usable; snapshot to check.",
            "type": "boolean"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "description": "URL after redirects.",
            "type": "string"
          }
        },
        "required": [
          "ok",
          "timed_out",
          "url"
        ],
        "title": "NavigateOutput",
        "type": "object"
      }
    },
    {
      "description": "Read the session's captured network requests (method, URL, status, failures). failed_only=true is the fast path for 'what broke'.",
      "metadata": {},
      "name": "browser::network::read",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "failed_only": {
            "default": null,
            "description": "Only failed requests (network error or status >= 400).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "limit": {
            "default": null,
            "description": "Maximum entries returned, newest kept (default 100).",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "pattern": {
            "default": null,
            "description": "Regex applied to the request URL.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "since_seq": {
            "default": null,
            "description": "Only entries with `seq` greater than this.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "NetworkReadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "NetworkEntry": {
            "description": "One captured network request.",
            "properties": {
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "failed": {
                "type": "boolean"
              },
              "method": {
                "type": "string"
              },
              "mime_type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "seq": {
                "description": "Monotonic per-session cursor; pass back as `since_seq`.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "status": {
                "format": "int64",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "timestamp": {
                "format": "int64",
                "type": "integer"
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "failed",
              "method",
              "seq",
              "timestamp",
              "url"
            ],
            "type": "object"
          }
        },
        "properties": {
          "dropped": {
            "description": "Entries evicted from the ring buffer since session start.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "entries": {
            "items": {
              "$ref": "#/definitions/NetworkEntry"
            },
            "type": "array"
          },
          "last_seq": {
            "description": "Cursor for the next `since_seq`.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "dropped",
          "entries",
          "last_seq"
        ],
        "title": "NetworkReadOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: reload browser settings from the authoritative configuration on change.",
      "metadata": {
        "internal": true
      },
      "name": "browser::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "OnConfigChangeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Print the page to a PDF (the browser's Print -> Save as PDF). Returns it base64 with a file name from the title.",
      "metadata": {},
      "name": "browser::pdf",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "landscape": {
            "default": null,
            "description": "Landscape orientation. Default portrait.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "print_background": {
            "default": null,
            "description": "Print background colours and images. Default true (what the page looks like, not what a printer would save ink on).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "scale": {
            "default": null,
            "description": "Page scale, 0.1–2. Default 1.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "PdfInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "data": {
            "description": "The PDF, base64.",
            "type": "string"
          },
          "file_name": {
            "description": "Suggested file name, from the page title.",
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "size_bytes": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "data",
          "file_name",
          "ok",
          "size_bytes",
          "url"
        ],
        "title": "PdfOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: element preview at a viewport point (tag, id, classes, bounds) so the console UI can draw a hover highlight in pick mode. Not an agent function.",
      "metadata": {
        "internal": true
      },
      "name": "browser::pick::hint",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "x": {
            "description": "Viewport x of the cursor.",
            "format": "double",
            "type": "number"
          },
          "y": {
            "description": "Viewport y of the cursor.",
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "session_id",
          "x",
          "y"
        ],
        "title": "PickHintInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Bounds": {
            "properties": {
              "height": {
                "format": "double",
                "type": "number"
              },
              "width": {
                "format": "double",
                "type": "number"
              },
              "x": {
                "format": "double",
                "type": "number"
              },
              "y": {
                "format": "double",
                "type": "number"
              }
            },
            "required": [
              "height",
              "width",
              "x",
              "y"
            ],
            "type": "object"
          }
        },
        "properties": {
          "bounds": {
            "anyOf": [
              {
                "$ref": "#/definitions/Bounds"
              },
              {
                "type": "null"
              }
            ],
            "description": "Viewport-space box to draw the highlight over."
          },
          "classes": {
            "type": [
              "string",
              "null"
            ]
          },
          "hit": {
            "description": "False when no element sits at the point.",
            "type": "boolean"
          },
          "id": {
            "type": [
              "string",
              "null"
            ]
          },
          "tag": {
            "description": "Lowercase tag name.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "hit"
        ],
        "title": "PickHintOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: resolve the element at a clicked viewport point and emit browser::picked. The console calls this on a pick-mode click. Not an agent function.",
      "metadata": {
        "internal": true
      },
      "name": "browser::pick::resolve",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "x": {
            "description": "Viewport x of the click.",
            "format": "double",
            "type": "number"
          },
          "y": {
            "description": "Viewport y of the click.",
            "format": "double",
            "type": "number"
          }
        },
        "required": [
          "session_id",
          "x",
          "y"
        ],
        "title": "PickResolveInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Neutral acknowledgement returned by the fire-and-forget internal functions (pick start/stop/resolve, screencast start/stop): they emit their result as a trigger event, so the direct return is just `{ ok }`.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "AckOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: enter pick mode so the human can select an element in the console UI. Not an agent function.",
      "metadata": {
        "internal": true
      },
      "name": "browser::pick::start",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "PickStartInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Neutral acknowledgement returned by the fire-and-forget internal functions (pick start/stop/resolve, screencast start/stop): they emit their result as a trigger event, so the direct return is just `{ ok }`.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "AckOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: leave DevTools inspect mode without picking. Idempotent. Not an agent function.",
      "metadata": {
        "internal": true
      },
      "name": "browser::pick::stop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "description": "Cancelling pick mode on an unknown session succeeds.",
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "PickStopInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Neutral acknowledgement returned by the fire-and-forget internal functions (pick start/stop/resolve, screencast start/stop): they emit their result as a trigger event, so the direct return is just `{ ok }`.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "AckOutput",
        "type": "object"
      }
    },
    {
      "description": "Record a session's live viewport to a video file (webm or mp4). Turns the screencast on if needed and pipes it through ffmpeg, which must be on PATH; browser::doctor reports whether it is available.",
      "metadata": {},
      "name": "browser::recording::start",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "format": {
            "default": null,
            "description": "`webm` (VP9) or `mp4` (H.264). Defaults to webm.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "description": "Output file path. The extension should match `format`.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "path",
          "session_id"
        ],
        "title": "RecordingStartInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "format": {
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "format",
          "ok",
          "path"
        ],
        "title": "RecordingStartOutput",
        "type": "object"
      }
    },
    {
      "description": "Stop a session's recording, finalize the file, and return its path, duration, and frame count. Idempotent: stopping when nothing is recording returns ok=false.",
      "metadata": {},
      "name": "browser::recording::stop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "RecordingStopInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "duration_ms": {
            "description": "Wall-clock duration captured, milliseconds.",
            "format": "int64",
            "type": "integer"
          },
          "frames": {
            "description": "Frames written to the encoder.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "description": "False when no recording was running.",
            "type": "boolean"
          },
          "path": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "duration_ms",
          "frames",
          "ok"
        ],
        "title": "RecordingStopOutput",
        "type": "object"
      }
    },
    {
      "description": "Run a regex over the visible text of provided HTML; `first` returns the first match, else all.",
      "metadata": {},
      "name": "browser::regex",
      "request_schema": {
        "properties": {
          "first": {
            "type": "boolean"
          },
          "html": {
            "type": "string"
          },
          "pattern": {
            "type": "string"
          }
        },
        "required": [
          "html",
          "pattern"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "result": {
            "items": {
              "type": [
                "string",
                "null"
              ]
            },
            "type": [
              "array",
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Set the session's live viewport size (CSS pixels). The console calls this as its browser pane resizes so the streamed frame fills the pane with no letterboxing and clicks map 1:1; the device toolbar calls it with a preset. Clamped 200..4000.",
      "metadata": {},
      "name": "browser::resize",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "device_scale_factor": {
            "default": null,
            "description": "Device pixel ratio. Default 1.",
            "format": "double",
            "type": [
              "number",
              "null"
            ]
          },
          "fit": {
            "default": null,
            "description": "Mark this resize as a pane auto-fit; a fit is refused (current size returned) while more than one viewer watches the session.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "height": {
            "description": "Viewport height in CSS pixels (clamped 200..4000).",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "mobile": {
            "default": null,
            "description": "Emulate a mobile device (viewport meta, overlay scrollbars, touch). Default false. The device toolbar sets this for phone presets.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "width": {
            "description": "Viewport width in CSS pixels (clamped 200..4000).",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "height",
          "session_id",
          "width"
        ],
        "title": "ResizeInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "height": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          },
          "width": {
            "description": "The clamped size actually applied.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "height",
          "ok",
          "width"
        ],
        "title": "ResizeOutput",
        "type": "object"
      }
    },
    {
      "description": "Drive the page toward a goal in one call (fill and submit a form, log in, pick filters, step through a wizard): each step reads the visible controls, asks the judge worker which operation (click, type, select, scroll, wait, done, blocked) and which element, acts, and waits for the requests the action started, until the judge says done or blocked, a field needs text you did not give in `inputs`, nothing changes three times, or the step/time budget runs out. State the goal as the end state to reach (\"logged in: the dashboard shows\"), not the clicks. Returns the steps taken and the final page table; read it before reporting success, done is the judge's reading, not proof (a login can end on an error). Without a judge worker it returns status judge_unavailable with the page table: drive with browser::elements and browser::act instead.",
      "metadata": {},
      "name": "browser::run",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "goal": {
            "description": "The end state to reach, as the page would show it (\"logged in: the dashboard shows\", \"the bug is saved and listed\"), not the clicks to make. Name every value the outcome needs.",
            "type": "string"
          },
          "inputs": {
            "additionalProperties": {
              "type": "string"
            },
            "default": {},
            "description": "Text for fields the goal needs filled: `{\"email\": \"…\", \"password\": \"…\"}`. A key reaches a field by its `n` ref, by its label (case, accents and punctuation ignored: `email` for \"E-mail\"), by what the field is (input type, `name`, `autocomplete`: `password` for a password field labelled \"Senha\"), or as the only key the label contains. Only the keys are shown to the judge.",
            "type": "object"
          },
          "max_steps": {
            "default": null,
            "description": "Most actions to execute (default 10, at most 60).",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "timeout_ms": {
            "default": null,
            "description": "Whole-run budget in ms (default 30000), clamped to `max_timeout_ms`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "goal",
          "session_id"
        ],
        "title": "RunInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Element": {
            "description": "One control. `operations` says what `browser::act` can do with it.",
            "properties": {
              "autocomplete": {
                "description": "The field's `autocomplete` hint (`username`, `current-password`, …).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "checked": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "expanded": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "input_type": {
                "description": "`<input>` type when not `text` (`email`, `password`, `tel`, …).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "label": {
                "description": "Accessible name (aria-labelledby, aria-label, label, text, title, or placeholder).",
                "type": "string"
              },
              "more_options": {
                "description": "Options beyond the listed 50; `select` still accepts them by label.",
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "name": {
                "description": "The field's `name` (or `id`).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "operations": {
                "description": "`click`, `type` and/or `select`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "options": {
                "description": "Enabled `<select>` option labels (first 50), for `act` `select`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "ref": {
                "description": "`n` ref `browser::act` accepts; valid while the element stays in this document.",
                "type": "string"
              },
              "role": {
                "type": "string"
              },
              "selected": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "value": {
                "description": "Current value of a field or dropdown; a set password reads `(set)`.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "label",
              "operations",
              "ref",
              "role"
            ],
            "type": "object"
          },
          "ElementsOutput": {
            "properties": {
              "busy": {
                "default": false,
                "description": "The page is still loading: the document, a region marked `aria-busy`, or (in `browser::run`) requests an action started that had not finished when the run stopped waiting.",
                "type": "boolean"
              },
              "can_scroll_down": {
                "type": "boolean"
              },
              "can_scroll_up": {
                "type": "boolean"
              },
              "elements": {
                "description": "Visible, enabled controls inside the viewport, in document order.",
                "items": {
                  "$ref": "#/definitions/Element"
                },
                "type": "array"
              },
              "generation": {
                "default": 0,
                "description": "Document generation; navigation advances it and every `n` ref dies.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "omitted": {
                "description": "Controls past the 250-element cap, not listed.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "text": {
                "description": "Visible text in the viewport, up to 6000 characters.",
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "can_scroll_down",
              "can_scroll_up",
              "elements",
              "omitted",
              "text",
              "title",
              "url"
            ],
            "type": "object"
          },
          "NeedsText": {
            "properties": {
              "input_keys": {
                "description": "The `inputs` keys the run was given, none of which matched this field.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "label": {
                "type": "string"
              },
              "ref": {
                "type": "string"
              }
            },
            "required": [
              "input_keys",
              "label",
              "ref"
            ],
            "type": "object"
          },
          "RunStatus": {
            "oneOf": [
              {
                "enum": [
                  "max_steps",
                  "deadline"
                ],
                "type": "string"
              },
              {
                "description": "The judge chose DONE and its completion check rated the goal complete on the page. Verify on `page`; it is the judge's reading, not proof.",
                "enum": [
                  "done"
                ],
                "type": "string"
              },
              {
                "description": "The judge found no operation that makes progress.",
                "enum": [
                  "blocked"
                ],
                "type": "string"
              },
              {
                "description": "Three executed steps in a row changed nothing.",
                "enum": [
                  "stalled"
                ],
                "type": "string"
              },
              {
                "description": "A field must be typed and no `inputs` entry matches it: type it with `browser::act` (or rerun with `inputs`), then run again.",
                "enum": [
                  "needs_text"
                ],
                "type": "string"
              },
              {
                "description": "No usable judge (not deployed, no provider, or failing). Drive with `browser::elements` + `browser::act`; `page` is the current table.",
                "enum": [
                  "judge_unavailable"
                ],
                "type": "string"
              }
            ]
          },
          "RunStep": {
            "properties": {
              "error": {
                "description": "Why the action was refused or failed; the page was re-read and the judge asked again.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "judge_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "label": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "operation": {
                "description": "CLICK, TYPE_TEXT, SELECT, SCROLL_UP, SCROLL_DOWN or WAIT.",
                "type": "string"
              },
              "option": {
                "description": "The option chosen (SELECT).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "page_changed": {
                "type": "boolean"
              },
              "probability": {
                "description": "The judge's probability for the executed choice.",
                "format": "double",
                "type": "number"
              },
              "ref": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "judge_ms",
              "operation",
              "page_changed",
              "probability"
            ],
            "type": "object"
          }
        },
        "properties": {
          "elapsed_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "judge_requests": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "needs_text": {
            "anyOf": [
              {
                "$ref": "#/definitions/NeedsText"
              },
              {
                "type": "null"
              }
            ]
          },
          "page": {
            "allOf": [
              {
                "$ref": "#/definitions/ElementsOutput"
              }
            ],
            "description": "The page as the run left it (`browser::elements` shape)."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/definitions/RunStatus"
          },
          "steps": {
            "description": "Executed actions, in order.",
            "items": {
              "$ref": "#/definitions/RunStep"
            },
            "type": "array"
          }
        },
        "required": [
          "elapsed_ms",
          "judge_requests",
          "page",
          "status",
          "steps"
        ],
        "title": "RunOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: start the live viewport feed — frames arrive on the browser::frame-event trigger and browser::frame reads the newest. Console-UI plumbing; agents use browser::screenshot. Not an agent function.",
      "metadata": {
        "internal": true
      },
      "name": "browser::screencast::start",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "preview": {
            "default": null,
            "description": "A corner thumbnail rather than a pane: streams frames but does not count as a viewer for `browser::resize` fit arbitration.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ScreencastStartInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Neutral acknowledgement returned by the fire-and-forget internal functions (pick start/stop/resolve, screencast start/stop): they emit their result as a trigger event, so the direct return is just `{ ok }`.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "AckOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: stop the live frame push. Idempotent. Not an agent function.",
      "metadata": {
        "internal": true
      },
      "name": "browser::screencast::stop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "preview": {
            "default": null,
            "description": "Must match the `preview` the start was made with.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "session_id": {
            "description": "Stopping the screencast on an unknown session succeeds.",
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ScreencastStopInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Neutral acknowledgement returned by the fire-and-forget internal functions (pick start/stop/resolve, screencast start/stop): they emit their result as a trigger event, so the direct return is just `{ ok }`.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "AckOutput",
        "type": "object"
      }
    },
    {
      "description": "Screenshot the session's current page as a JPEG (or a lossless PNG with format=png). Use browser::snapshot for machine-readable structure; screenshot when layout or rendering matters.",
      "metadata": {
        "display": true
      },
      "name": "browser::screenshot",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "format": {
            "default": null,
            "description": "`jpeg` (default, compressed with the configured quality) or `png` (lossless; what pixel comparisons need).",
            "type": [
              "string",
              "null"
            ]
          },
          "full_page": {
            "default": null,
            "description": "Capture the full scrollable page instead of the viewport.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ScreenshotInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentBlock": {
            "description": "One block of a viewable response: an image block plus a text line.",
            "properties": {
              "data": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "mime": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "text": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          "ScreenshotDetails": {
            "properties": {
              "height": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "session_id": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "width": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "height",
              "session_id",
              "url",
              "width"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "items": {
              "$ref": "#/definitions/ContentBlock"
            },
            "type": "array"
          },
          "details": {
            "$ref": "#/definitions/ScreenshotDetails"
          }
        },
        "required": [
          "content",
          "details"
        ],
        "title": "ScreenshotOutput",
        "type": "object"
      }
    },
    {
      "description": "Screenshot a URL with a browser fetcher (dynamic or stealthy); returns image content blocks.",
      "metadata": {},
      "name": "browser::screenshot-url",
      "request_schema": {
        "properties": {
          "fetcher": {
            "enum": [
              "dynamic",
              "stealthy"
            ],
            "type": "string"
          },
          "format": {
            "enum": [
              "png",
              "jpeg"
            ],
            "type": "string"
          },
          "full_page": {
            "type": "boolean"
          },
          "headless": {
            "type": "boolean"
          },
          "network_idle": {
            "type": "boolean"
          },
          "proxy": {
            "type": "string"
          },
          "timeout": {
            "type": "number"
          },
          "url": {
            "type": "string"
          },
          "wait_selector": {
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "content": {
            "description": "image blocks (one per tile, width<=1024/height<=1536) + a text caption",
            "items": {
              "properties": {
                "data": {
                  "description": "base64 image bytes (image blocks)",
                  "type": "string"
                },
                "mime": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "type": {
                  "enum": [
                    "image",
                    "text"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "type"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "mime": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Close a scraping session opened with browser::session-open and free its browser/connection. Interactive tabs from browser::sessions::start are not scraping sessions: close those with browser::sessions::stop (passing a tab id here fails). Returns closed=false for an id it does not hold.",
      "metadata": {},
      "name": "browser::session-close",
      "request_schema": {
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "closed": {
            "type": "boolean"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Fetch a URL on an open scraping session (reuses its cookies/state); returns page content, shows nothing. For a page the user should see, use browser::sessions::start + browser::navigate.",
      "metadata": {},
      "name": "browser::session-fetch",
      "request_schema": {
        "properties": {
          "css_selector": {
            "description": "scope the render to this CSS subtree (e.g. a page's content div)",
            "type": "string"
          },
          "data": {
            "type": "object"
          },
          "format": {
            "description": "render page body to this format",
            "enum": [
              "markdown",
              "text"
            ],
            "type": "string"
          },
          "headers": {
            "type": "object"
          },
          "include_html": {
            "type": "boolean"
          },
          "json": {
            "type": "object"
          },
          "main_content_only": {
            "description": "strip nav/scripts/hidden before rendering",
            "type": "boolean"
          },
          "method": {
            "enum": [
              "get",
              "post",
              "put",
              "delete"
            ],
            "type": "string"
          },
          "params": {
            "type": "object"
          },
          "selectors": {
            "items": {
              "properties": {
                "all": {
                  "description": "return every match as a list",
                  "type": "boolean"
                },
                "attr": {
                  "description": "extract this attribute instead of text",
                  "type": "string"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "description": "extract inner HTML instead of text",
                  "type": "boolean"
                },
                "name": {
                  "type": "string"
                },
                "regex": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "session_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "wait_selector": {
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "url"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "captured_xhr": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "content": {
            "description": "markdown/text render when `format` requested",
            "type": "string"
          },
          "cookies": {
            "type": "object"
          },
          "encoding": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": "string"
          },
          "extracted": {
            "type": "object"
          },
          "format": {
            "type": "string"
          },
          "headers": {
            "type": "object"
          },
          "html": {
            "type": "string"
          },
          "results": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "List open scraping sessions: type and idle time.",
      "metadata": {},
      "name": "browser::session-list",
      "request_schema": {
        "properties": {
          "type": {
            "description": "filter by type",
            "enum": [
              "http",
              "dynamic",
              "stealthy"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "sessions": {
            "items": {
              "properties": {
                "created_at": {
                  "type": "number"
                },
                "idle_s": {
                  "type": "number"
                },
                "last_used": {
                  "type": "number"
                },
                "session_id": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Open a persistent scraping session (HTTP or headless fetcher) whose session_id reuses cookies and state across fetches. Renders nothing the user can see; to open a page in the visible browser use browser::sessions::start.",
      "metadata": {},
      "name": "browser::session-open",
      "request_schema": {
        "properties": {
          "capture_xhr": {
            "description": "regex; capture matching XHRs (browser sessions)",
            "type": "string"
          },
          "headers": {
            "type": "object"
          },
          "headless": {
            "type": "boolean"
          },
          "impersonate": {
            "type": "string"
          },
          "proxies": {
            "type": "object"
          },
          "proxy": {
            "type": "string"
          },
          "real_chrome": {
            "type": "boolean"
          },
          "solve_cloudflare": {
            "type": "boolean"
          },
          "timeout": {
            "type": "number"
          },
          "type": {
            "description": "session engine",
            "enum": [
              "http",
              "dynamic",
              "stealthy"
            ],
            "type": "string"
          },
          "useragent": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "session_id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Attach a session to an already-running browser over CDP. Opens a fresh tab the session owns, or adopts a user tab by URL substring and releases it untouched on stop. Reaches the real profile and its logins; needs Chrome started with --remote-debugging-port and allow_attach in config.",
      "metadata": {},
      "name": "browser::sessions::attach",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "adopt_url_substring": {
            "default": null,
            "description": "Adopt the one open tab whose URL contains this substring (released untouched on stop); omit to open a fresh tab the session owns.",
            "type": [
              "string",
              "null"
            ]
          },
          "cdp_url": {
            "description": "CDP endpoint of the running browser: `http://127.0.0.1:9222` (resolved via `/json/version`) or a `ws://` debugger URL.",
            "type": "string"
          },
          "read_only": {
            "default": null,
            "description": "Inspection-only session; see browser::sessions::start.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "url": {
            "default": null,
            "description": "URL to open in the fresh tab (ignored when adopting). Omit for about:blank.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "cdp_url"
        ],
        "title": "AttachInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "adopted": {
            "description": "True when the session adopted an existing user tab (released, not closed, on stop); false when it opened a fresh tab it owns.",
            "type": "boolean"
          },
          "read_only": {
            "type": "boolean"
          },
          "session_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "adopted",
          "read_only",
          "session_id",
          "url"
        ],
        "title": "AttachOutput",
        "type": "object"
      }
    },
    {
      "description": "List every browser tab, live or asleep, with its current URL, title, and activity.",
      "metadata": {},
      "name": "browser::sessions::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SessionInfo": {
            "properties": {
              "active": {
                "description": "True while the tab has its page open. A sleeping tab (false) is listed and usable; the next call on it reopens the page at `url`.",
                "type": "boolean"
              },
              "console_entries": {
                "description": "Console entries captured since the page opened; 0 while asleep.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "created_ms": {
                "format": "int64",
                "type": "integer"
              },
              "headless": {
                "type": "boolean"
              },
              "incognito": {
                "description": "Private tab: nothing persisted, closes instead of sleeping.",
                "type": "boolean"
              },
              "last_used_ms": {
                "format": "int64",
                "type": "integer"
              },
              "read_only": {
                "type": "boolean"
              },
              "session_id": {
                "type": "string"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ttl_ms": {
                "description": "Lifetime the tab was opened with, when any.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "active",
              "console_entries",
              "created_ms",
              "headless",
              "incognito",
              "last_used_ms",
              "read_only",
              "session_id",
              "url"
            ],
            "type": "object"
          }
        },
        "properties": {
          "sessions": {
            "items": {
              "$ref": "#/definitions/SessionInfo"
            },
            "type": "array"
          }
        },
        "required": [
          "sessions"
        ],
        "title": "ListOutput",
        "type": "object"
      }
    },
    {
      "description": "Open a browser tab (a session) and return its session_id. Tabs share one browser profile (cookies, logins) and stay open until stopped or until an optional ttl_ms; an unused tab sleeps and wakes on the next call. incognito=true opens a PRIVATE tab: nothing it does is saved, and inactivity closes it for good.",
      "metadata": {},
      "name": "browser::sessions::start",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "headful": {
            "default": null,
            "description": "Force a visible window, overriding the configured `headless` default. Applies when this call launches the browser process; a browser that is already running keeps its mode.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "incognito": {
            "default": null,
            "description": "INCOGNITO TAB. Opens the tab in a private browser context: it shares no cookies, logins, or storage with the regular tabs, nothing it does is saved to disk (no cookies, no history, no tab record), it does not come back after a restart, and inactivity closes it for good instead of putting it to sleep. Everything lives in memory for as long as the tab does. Use it for logins you do not want kept, or to see a site signed out.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "preview": {
            "default": null,
            "description": "Whether consoles pop a live preview of the new tab (default true). The console's own tab controls pass `false`: the page that opened the tab already shows it, in every console window. Agents leave it.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "read_only": {
            "default": null,
            "description": "Inspection-only session for its whole lifetime: act, evaluate, execute and styles::write are rejected; navigation and reads work.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "ttl_ms": {
            "default": null,
            "description": "Optional lifetime in milliseconds: the tab closes on its own this long after it opened, even while in use. Omit for a tab that stays until stopped (what the console does).",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "default": null,
            "description": "URL to open immediately. Omit to start on about:blank.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "StartInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "error": {
            "description": "Chromium's error text when the requested url did not load and the tab shows the browser's error page instead (a network failure, or an empty HTTP error response such as a 400). Absent when the page came up. The tab is open either way.",
            "type": [
              "string",
              "null"
            ]
          },
          "headless": {
            "type": "boolean"
          },
          "incognito": {
            "description": "True for a private tab; see `incognito` on the request.",
            "type": "boolean"
          },
          "read_only": {
            "type": "boolean"
          },
          "session_id": {
            "description": "Pass this to every other browser function.",
            "type": "string"
          },
          "timed_out": {
            "description": "True when the requested url's load event did not fire inside the default navigation timeout; the tab is open and the page may still be usable; snapshot to check.",
            "type": "boolean"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "headless",
          "incognito",
          "read_only",
          "session_id",
          "timed_out",
          "url"
        ],
        "title": "StartOutput",
        "type": "object"
      }
    },
    {
      "description": "Close an interactive browser tab (a session from browser::sessions::start or attach) for good; do this when the task is done, since every tab holds a Chromium page. Idempotent: closing an unknown or already-closed tab succeeds with was_running=false. Scraping sessions from browser::session-open close with browser::session-close.",
      "metadata": {},
      "name": "browser::sessions::stop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "description": "Tab to close. Closing an unknown or already-closed id succeeds.",
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "StopInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "was_running": {
            "description": "False when the session was already gone.",
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "was_running"
        ],
        "title": "StopOutput",
        "type": "object"
      }
    },
    {
      "description": "Read the page as an accessibility-tree outline. Lines carry [ref=eN] handles that browser::act accepts; refs stay valid until the next navigation. Prefer this over browser::screenshot; it is cheaper and machine-readable.",
      "metadata": {},
      "name": "browser::snapshot",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "diff": {
            "default": null,
            "description": "Return only what changed since the previous snapshot; a full outline when there is no baseline (first snapshot or after a navigation).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "SnapshotInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SnapshotDiff": {
            "description": "Changes since the previous snapshot. Lines are compared without their `[ref=eN]` suffix (ref names are unique per snapshot); `added` lines carry current refs and are directly actionable.",
            "properties": {
              "added": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "removed": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "unchanged": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "added",
              "removed",
              "unchanged"
            ],
            "type": "object"
          }
        },
        "properties": {
          "diff": {
            "anyOf": [
              {
                "$ref": "#/definitions/SnapshotDiff"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present when the caller asked for `diff: true` and a baseline existed. Covers only the emitted nodes of both snapshots; check `truncated` before trusting it as a complete change set."
          },
          "generation": {
            "description": "Document generation the refs belong to; navigation advances it and kills every ref from earlier generations.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "tree": {
            "description": "Indented outline; lines carry `[ref=eN]` handles for `browser::act`. Empty when `diff` is populated.",
            "type": "string"
          },
          "truncated": {
            "description": "True when the tree hit `max_snapshot_nodes` and was cut short. Also the signal that a `diff` may be incomplete: the diff is computed over emitted nodes only, so when either snapshot was truncated a node that was emitted before and capped out now can show up in `removed` even though it still exists (and vice versa for `added`).",
            "type": "boolean"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "generation",
          "tree",
          "truncated",
          "url"
        ],
        "title": "SnapshotOutput",
        "type": "object"
      }
    },
    {
      "description": "Scrape a bot-protected URL: Camoufox stealth solves Cloudflare, hardens WebRTC/canvas.",
      "metadata": {},
      "name": "browser::stealthy-fetch",
      "request_schema": {
        "properties": {
          "allow_webgl": {
            "type": "boolean"
          },
          "block_ads": {
            "type": "boolean"
          },
          "block_webrtc": {
            "type": "boolean"
          },
          "blocked_domains": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "capture_xhr": {
            "type": "string"
          },
          "cookies": {
            "type": "object"
          },
          "css_selector": {
            "description": "scope the render to this CSS subtree (e.g. a page's content div)",
            "type": "string"
          },
          "disable_resources": {
            "type": "boolean"
          },
          "dns_over_https": {
            "type": "boolean"
          },
          "extra_flags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "extra_headers": {
            "type": "object"
          },
          "format": {
            "description": "render page body to this format",
            "enum": [
              "markdown",
              "text"
            ],
            "type": "string"
          },
          "google_search": {
            "type": "boolean"
          },
          "headless": {
            "type": "boolean"
          },
          "hide_canvas": {
            "type": "boolean"
          },
          "include_html": {
            "type": "boolean"
          },
          "load_dom": {
            "type": "boolean"
          },
          "locale": {
            "type": "string"
          },
          "main_content_only": {
            "description": "strip nav/scripts/hidden before rendering",
            "type": "boolean"
          },
          "max_pages": {
            "type": "integer"
          },
          "network_idle": {
            "type": "boolean"
          },
          "proxy": {
            "type": "string"
          },
          "retries": {
            "type": "integer"
          },
          "retry_delay": {
            "type": "number"
          },
          "selectors": {
            "items": {
              "properties": {
                "all": {
                  "description": "return every match as a list",
                  "type": "boolean"
                },
                "attr": {
                  "description": "extract this attribute instead of text",
                  "type": "string"
                },
                "css": {
                  "type": "string"
                },
                "html": {
                  "description": "extract inner HTML instead of text",
                  "type": "boolean"
                },
                "name": {
                  "type": "string"
                },
                "regex": {
                  "type": "string"
                },
                "xpath": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "solve_cloudflare": {
            "type": "boolean"
          },
          "timeout": {
            "description": "milliseconds (browser fetcher)",
            "type": "number"
          },
          "timezone_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "urls": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "useragent": {
            "type": "string"
          },
          "wait": {
            "description": "extra ms to wait after load",
            "type": "number"
          },
          "wait_selector": {
            "type": "string"
          },
          "wait_selector_state": {
            "enum": [
              "attached",
              "detached",
              "visible",
              "hidden"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "captured_xhr": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "content": {
            "description": "markdown/text render when `format` requested",
            "type": "string"
          },
          "cookies": {
            "type": "object"
          },
          "encoding": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": "string"
          },
          "extracted": {
            "type": "object"
          },
          "format": {
            "type": "string"
          },
          "headers": {
            "type": "object"
          },
          "html": {
            "type": "string"
          },
          "results": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Read an element's computed styles (curated design set by default, or named properties) plus its inline style attribute.",
      "metadata": {},
      "name": "browser::styles::read",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "properties": {
            "default": null,
            "description": "Computed property names to return. Omit for a curated design-panel set; pass `[\"*\"]` for every computed property.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "ref": {
            "description": "Element ref from `browser::snapshot`, `browser::dom::read`, or a pick.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "ref",
          "session_id"
        ],
        "title": "StylesReadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StyleProperty": {
            "properties": {
              "name": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "value"
            ],
            "type": "object"
          }
        },
        "properties": {
          "inline_style": {
            "description": "The element's inline `style` attribute, when present.",
            "type": [
              "string",
              "null"
            ]
          },
          "properties": {
            "items": {
              "$ref": "#/definitions/StyleProperty"
            },
            "type": "array"
          },
          "ref": {
            "type": "string"
          }
        },
        "required": [
          "properties",
          "ref"
        ],
        "title": "StylesReadOutput",
        "type": "object"
      }
    },
    {
      "description": "Set one inline CSS property on an element, live in the page. Visual experiment only: the page's source files are untouched, and the edit dies with the next navigation.",
      "metadata": {},
      "name": "browser::styles::write",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "important": {
            "default": null,
            "description": "Apply with `!important`.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "property": {
            "description": "CSS property name (`background-color`).",
            "type": "string"
          },
          "ref": {
            "description": "Element ref to edit.",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "value": {
            "description": "CSS value (`#101418`). Empty string removes the inline property.",
            "type": "string"
          }
        },
        "required": [
          "property",
          "ref",
          "session_id",
          "value"
        ],
        "title": "StylesWriteInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "inline_style": {
            "description": "The element's inline `style` attribute after the edit.",
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "inline_style",
          "ok"
        ],
        "title": "StylesWriteOutput",
        "type": "object"
      }
    },
    {
      "description": "List the open tabs of a running browser reachable at a CDP endpoint (url, title, and whether a session already adopted each). Read-only; adopt one with browser::sessions::attach.",
      "metadata": {},
      "name": "browser::tabs::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cdp_url": {
            "description": "CDP endpoint of the running browser, as in browser::sessions::attach.",
            "type": "string"
          }
        },
        "required": [
          "cdp_url"
        ],
        "title": "TabsListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "TabInfo": {
            "description": "One open tab reported by `browser::tabs::list`.",
            "properties": {
              "adopted": {
                "description": "True when a session already adopted this tab; it cannot be adopted again until that session stops.",
                "type": "boolean"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "adopted",
              "url"
            ],
            "type": "object"
          }
        },
        "properties": {
          "tabs": {
            "items": {
              "$ref": "#/definitions/TabInfo"
            },
            "type": "array"
          }
        },
        "required": [
          "tabs"
        ],
        "title": "TabsListOutput",
        "type": "object"
      }
    },
    {
      "description": "Convert HTML to compact Markdown (or text/html); optional CSS scope + main-content clean.",
      "metadata": {},
      "name": "browser::to-markdown",
      "request_schema": {
        "properties": {
          "css_selector": {
            "description": "convert only the subtree matching this CSS selector",
            "type": "string"
          },
          "format": {
            "enum": [
              "markdown",
              "text",
              "html"
            ],
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "main_content_only": {
            "description": "strip nav/scripts/hidden nodes first",
            "type": "boolean"
          }
        },
        "required": [
          "html"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "content": {
            "type": "string"
          },
          "format": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Serve the browser worker's injected console UI assets (content function for its console:script / console:style / console:module triggers).",
      "metadata": {
        "internal": true
      },
      "name": "browser::ui-content",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Input of the content function: the console asks for one asset by path.",
        "properties": {
          "path": {
            "description": "The asset path from the trigger config (e.g. `state/page.js`).",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "UiContentInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Output of the content function.",
        "properties": {
          "content": {
            "description": "The asset source, verbatim.",
            "type": "string"
          },
          "content_type": {
            "description": "MIME type the console should serve the asset with.",
            "type": "string"
          }
        },
        "required": [
          "content",
          "content_type"
        ],
        "title": "UiContentResult",
        "type": "object"
      }
    },
    {
      "description": "Upload up to eight base64 files to exactly one input[type=file] selected by CSS. Files are staged privately for the session and removed when it stops.",
      "metadata": {},
      "name": "browser::upload",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "UploadFile": {
            "properties": {
              "data": {
                "description": "File bytes, base64.",
                "type": "string"
              },
              "name": {
                "description": "File name exposed to the page. Path components are rejected.",
                "type": "string"
              }
            },
            "required": [
              "data",
              "name"
            ],
            "type": "object"
          }
        },
        "properties": {
          "files": {
            "description": "Up to eight files, each at most 25 MB decoded.",
            "items": {
              "$ref": "#/definitions/UploadFile"
            },
            "type": "array"
          },
          "selector": {
            "description": "CSS selector that must match exactly one input[type=file].",
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "files",
          "selector",
          "session_id"
        ],
        "title": "UploadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "attached": {
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "file_names": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "attached",
          "file_names",
          "ok"
        ],
        "title": "UploadOutput",
        "type": "object"
      }
    },
    {
      "description": "Query HTML with an XPath expression; first-or-all; `attr` pulls an attribute.",
      "metadata": {},
      "name": "browser::xpath",
      "request_schema": {
        "properties": {
          "adaptive": {
            "description": "relocate elements after a site change via saved identities",
            "type": "boolean"
          },
          "adaptive_domain": {
            "description": "page URL/domain that keys saved identities",
            "type": "string"
          },
          "attr": {
            "type": "string"
          },
          "auto_save": {
            "description": "save matched identities (defaults on when adaptive)",
            "type": "boolean"
          },
          "first": {
            "type": "boolean"
          },
          "html": {
            "type": "string"
          },
          "identifier": {
            "description": "stable key for the saved element",
            "type": "string"
          },
          "query": {
            "type": "string"
          }
        },
        "required": [
          "html",
          "query"
        ],
        "type": "object"
      },
      "response_schema": {
        "properties": {
          "result": {
            "items": {
              "type": [
                "string",
                "null"
              ]
            },
            "type": [
              "array",
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      }
    },
    {
      "description": "Zoom the page in, out, to a level (50-200 %) or back to 100 %, the way the browser's zoom menu does. The viewport keeps its size; the page scales inside it. The level belongs to the loaded document and resets on navigation.",
      "metadata": {},
      "name": "browser::zoom",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "action": {
            "default": null,
            "description": "`in`, `out`, `reset`, `set` (default when `level` is given), or `read` to report the current level without changing it.",
            "type": [
              "string",
              "null"
            ]
          },
          "level": {
            "default": null,
            "description": "Explicit level in percent (50–200); snapped to the ladder.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ZoomInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "level": {
            "description": "Level in percent now applied to the document.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "level",
          "ok"
        ],
        "title": "ZoomOutput",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "A browser::chromium::install job moved: a new phase (resolving, downloading, extracting, verifying, done, failed) or about 1% more bytes. Not a session event: bind without a session_id filter.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::chromium-install-progress",
      "return_schema": {}
    },
    {
      "description": "A console/log/exception entry was captured on a session's page.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::console-event",
      "return_schema": {}
    },
    {
      "description": "One page of a browser::crawl was crawled (event: item, in seq order), or the crawl ended (event: done). Filter with crawl_id; bind before calling browser::crawl. Dropped or missed items are read back with browser::crawl::items.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config of a `browser::crawl-item` binding. Without `crawl_id` the binding receives every crawl's items. Unknown keys are rejected so a misspelled filter fails at registration instead of silently matching everything.",
        "properties": {
          "crawl_id": {
            "description": "Only deliver events of this crawl (the `crawl_id` passed to, or returned as `crawl.id` by, `browser::crawl`).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "CrawlItemBindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::crawl-item",
      "return_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "What a `browser::crawl-item` binding receives.",
        "properties": {
          "crawl_id": {
            "type": "string"
          },
          "dropped_events": {
            "description": "`done` only: item events dropped because this crawl's queue was full.",
            "format": "uint",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "event": {
            "description": "`item` for one crawled page, `done` once when the crawl ended.",
            "type": "string"
          },
          "item": {
            "description": "`item` only: the crawl item, page-derived fields cut to 64 KiB."
          },
          "retained": {
            "description": "`done` only: items retained for `browser::crawl::items`.",
            "format": "uint",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "seq": {
            "description": "1-based position in the crawl. Items carry 1..=n in completion order; `done` carries n + 1. A gap means a dropped event: read the missing positions with `browser::crawl::items`.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "stats": {
            "description": "`done` only: the crawl's final `stats`."
          },
          "truncated": {
            "description": "`item` only: true when `item` was cut; the full one is retained.",
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "crawl_id",
          "event",
          "seq"
        ],
        "title": "CrawlItemEvent",
        "type": "object"
      }
    },
    {
      "description": "A download started, progressed, or finished in a session.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::download-changed",
      "return_schema": {}
    },
    {
      "description": "Internal: a live screencast frame of a watched tab (console viewport plumbing, high volume).",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::frame-event",
      "return_schema": {}
    },
    {
      "description": "A session is paused waiting for a human to complete a step (CAPTCHA, 2FA, payment).",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::handoff-requested",
      "return_schema": {}
    },
    {
      "description": "A paused handoff finished (confirmed in page, by call, or timed out).",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::handoff-resolved",
      "return_schema": {}
    },
    {
      "description": "The session's page committed a navigation.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::navigated",
      "return_schema": {}
    },
    {
      "description": "A network request was captured (completed or failed) on a session's page.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::network-event",
      "return_schema": {}
    },
    {
      "description": "The human picked an element in inspect mode.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::picked",
      "return_schema": {}
    },
    {
      "description": "A browser tab opened and is ready.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::session-started",
      "return_schema": {}
    },
    {
      "description": "A browser tab closed for good (stopped, idle, expired, or crashed).",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::session-stopped",
      "return_schema": {}
    },
    {
      "description": "A browser tab woke up (page open again), went to sleep (page closed, tab kept), or its page's title changed.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `browser::*` trigger binding. The only filter is an optional session-id equality match; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "session_id": {
            "description": "Only deliver events for this browser session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "browser::session-updated",
      "return_schema": {}
    }
  ]
}
```
