@orkestrel/browser 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > and publishes them as agent tools, an in-page face that drives a DOM document with native APIs,
6
6
  > and a Node runtime that finds, launches, and connects to the browser itself.
7
7
 
8
- Connect to a running browser or launch one with the `createBrowser` function, open a page, and drive it through stable element references and trusted input. Read the page as Markdown in bounded slices, and hand the whole vocabulary to an agent as `@orkestrel/tool` tools with `createBrowserToolset`. Inside a page, `createDocumentToolset` drives a DOM document with native APIs, and `createSocketCDPTransport` carries the core client over the browser's own `WebSocket`. Record a journey, one user intent kept as JSON, from a model's tool calls or a person's gestures on a page, then list, edit, replay, and forget it through the six journey tools, or compile it with `compileBrowserJourney` into a module a developer runs and customizes. Part of the `@orkestrel` line.
8
+ Connect to a running browser or launch one with the `createBrowser` function, open a page, and drive it through stable element references and trusted input. Read the whole captured page as Markdown or plain text in bounded slices, and hand the whole vocabulary to an agent as `@orkestrel/tool` tools with `createBrowserToolset`. Inside a page, `createDocumentToolset` drives a DOM document with native APIs, and `createSocketCDPTransport` carries the core client over the browser's own `WebSocket`. Record a journey, one user intent kept as JSON, from a model's tool calls or a person's gestures on a page, then list, edit, replay, and forget it through the six journey tools, or compile it with `compileBrowserJourney` into a module a developer runs and customizes. Part of the `@orkestrel` line.
9
9
 
10
10
  ## Install
11
11
 
@@ -40,7 +40,8 @@ const [save] = await page.elements.find({ role: 'button', name: 'Save' })
40
40
  await save?.click()
41
41
  await page.wait('Saved')
42
42
  const reading = await page.read()
43
- reading.markdown({ limit: 4_000 }) // { text, offset, total }
43
+ reading.markdown({ limit: 4_000 }) // { text, offset, total }; whole page by default
44
+ reading.text({ distill: true }) // main content as plain text
44
45
  await page.screenshot({ path: 'example.png' })
45
46
  await browser.destroy()
46
47
  ```
@@ -53,7 +54,9 @@ import { createToolManager } from '@orkestrel/tool'
53
54
 
54
55
  const toolset = createBrowserToolset(page, { tools: createToolManager() })
55
56
  await toolset.start()
56
- await toolset.tools.execute({ id: '1', name: 'look', arguments: { what: 'the form' } })
57
+ await toolset.tools.execute({ id: '1', name: 'look', arguments: { search: 'Email' } })
58
+ await toolset.tools.execute({ id: '2', name: 'read', arguments: { search: 'delivery' } })
59
+ await toolset.tools.execute({ id: '3', name: 'plain', arguments: { search: 'confirmation' } })
57
60
  await toolset.destroy()
58
61
  ```
59
62
 
@@ -70,7 +73,7 @@ await client.close()
70
73
 
71
74
  ## Browse binary
72
75
 
73
- The package ships the `browse` binary, which serves the browser vocabulary and the journey tools over MCP on stdio and launches Chromium on the first tool call, keeping journeys and runs under `tmp/browsers`. Register it for a checkout with `claude mcp add --scope project browse -- node node_modules/@orkestrel/browser/dist/bin/main.js`, which writes the `browse` entry of the project's `.mcp.json`, then start `claude` in the checkout and approve the server; `BROWSE_ROOT`, `BROWSE_HEADLESS`, `BROWSE_EXECUTABLE`, and `BROWSE_READONLY` configure it. See [Register the browse binary with Claude Code](guides/browser.md#register-the-browse-binary-with-claude-code) for the entry, the approval, and a recorded exchange.
76
+ The package ships the `browse` binary, which serves the browser vocabulary and the journey tools over MCP on stdio and starts Chromium when the server starts, before the client's first request, keeping journeys and runs under `tmp/browsers`. Register it for a checkout with `claude mcp add --scope project browse -- node node_modules/@orkestrel/browser/dist/bin/main.js`, which writes the `browse` entry of the project's `.mcp.json`, then start `claude` in the checkout and approve the server; `BROWSE_ROOT`, `BROWSE_HEADLESS`, `BROWSE_EXECUTABLE`, `BROWSE_READONLY`, and `BROWSE_POOL` configure it. See [Register the browse binary with Claude Code](guides/browser.md#register-the-browse-binary-with-claude-code) for the entry, the approval, the startup and recovery behavior, and a recorded exchange.
74
77
 
75
78
  ## Guide
76
79
 
package/dist/bin/main.js CHANGED
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { parseBoolean } from "@orkestrel/contract";
2
+ import { parseBoolean, parseInteger } from "@orkestrel/contract";
3
3
  import { BrowserError, isBrowserError } from "../src/core/index.js";
4
4
  import { createBrowserMCPServer } from "../src/server/index.js";
5
5
  //#region src/bin/main.ts
6
+ process.stderr.on("error", () => {
7
+ process.exitCode = 1;
8
+ });
6
9
  try {
7
- const { BROWSE_ROOT, BROWSE_HEADLESS, BROWSE_EXECUTABLE, BROWSE_READONLY } = process.env;
10
+ const { BROWSE_ROOT, BROWSE_HEADLESS, BROWSE_EXECUTABLE, BROWSE_READONLY, BROWSE_POOL } = process.env;
8
11
  const headless = parseBoolean(BROWSE_HEADLESS);
9
12
  const readonly = parseBoolean(BROWSE_READONLY);
13
+ const size = parseInteger(BROWSE_POOL);
10
14
  for (const [name, value, parsed] of [[
11
15
  "BROWSE_HEADLESS",
12
16
  BROWSE_HEADLESS,
@@ -19,11 +23,16 @@ try {
19
23
  name,
20
24
  value
21
25
  });
26
+ if (BROWSE_POOL !== void 0 && BROWSE_POOL !== "" && size === void 0) throw new BrowserError(`BROWSE_POOL must be an integer, not ${JSON.stringify(BROWSE_POOL)}`, "BROWSER_SERVER_ENVIRONMENT", {
27
+ name: "BROWSE_POOL",
28
+ value: BROWSE_POOL
29
+ });
22
30
  await createBrowserMCPServer({
23
31
  ...BROWSE_ROOT === void 0 || BROWSE_ROOT === "" ? {} : { root: BROWSE_ROOT },
24
32
  ...headless === void 0 ? {} : { headless },
25
33
  ...BROWSE_EXECUTABLE === void 0 || BROWSE_EXECUTABLE === "" ? {} : { executable: BROWSE_EXECUTABLE },
26
- ...readonly === void 0 ? {} : { readonly }
34
+ ...readonly === void 0 ? {} : { readonly },
35
+ ...size === void 0 ? {} : { pool: { size } }
27
36
  }).start();
28
37
  } catch (error) {
29
38
  if (!isBrowserError(error)) throw error;
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","names":[],"sources":["../../src/bin/main.ts"],"sourcesContent":["import { parseBoolean } from '@orkestrel/contract'\nimport { BrowserError, isBrowserError } from '@src/core'\nimport { createBrowserMCPServer } from '@src/server'\n\ntry {\n\tconst { BROWSE_ROOT, BROWSE_HEADLESS, BROWSE_EXECUTABLE, BROWSE_READONLY } = process.env\n\tconst headless = parseBoolean(BROWSE_HEADLESS)\n\tconst readonly = parseBoolean(BROWSE_READONLY)\n\t// An empty variable counts as unset, as a shell's `NAME=` leaves it.\n\tfor (const [name, value, parsed] of [\n\t\t['BROWSE_HEADLESS', BROWSE_HEADLESS, headless],\n\t\t['BROWSE_READONLY', BROWSE_READONLY, readonly],\n\t] as const) {\n\t\tif (value !== undefined && value !== '' && parsed === undefined) {\n\t\t\tthrow new BrowserError(\n\t\t\t\t`${name} must be true, false, 1, or 0, not ${JSON.stringify(value)}`,\n\t\t\t\t'BROWSER_SERVER_ENVIRONMENT',\n\t\t\t\t{ name, value },\n\t\t\t)\n\t\t}\n\t}\n\tawait createBrowserMCPServer({\n\t\t...(BROWSE_ROOT === undefined || BROWSE_ROOT === '' ? {} : { root: BROWSE_ROOT }),\n\t\t...(headless === undefined ? {} : { headless }),\n\t\t...(BROWSE_EXECUTABLE === undefined || BROWSE_EXECUTABLE === ''\n\t\t\t? {}\n\t\t\t: { executable: BROWSE_EXECUTABLE }),\n\t\t...(readonly === undefined ? {} : { readonly }),\n\t}).start()\n} catch (error) {\n\tif (!isBrowserError(error)) throw error\n\tconsole.error(`browse: ${error.code}: ${error.message.split(/\\r\\n|\\n/u).join(' ')}`)\n\tprocess.exitCode = 1\n}\n"],"mappings":";;;;;AAIA,IAAI;CACH,MAAM,EAAE,aAAa,iBAAiB,mBAAmB,oBAAoB,QAAQ;CACrF,MAAM,WAAW,aAAa,eAAe;CAC7C,MAAM,WAAW,aAAa,eAAe;CAE7C,KAAK,MAAM,CAAC,MAAM,OAAO,WAAW,CACnC;EAAC;EAAmB;EAAiB;CAAQ,GAC7C;EAAC;EAAmB;EAAiB;CAAQ,CAC9C,GACC,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,WAAW,KAAA,GACrD,MAAM,IAAI,aACT,GAAG,KAAK,qCAAqC,KAAK,UAAU,KAAK,KACjE,8BACA;EAAE;EAAM;CAAM,CACf;CAGF,MAAM,uBAAuB;EAC5B,GAAI,gBAAgB,KAAA,KAAa,gBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,YAAY;EAC/E,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;EAC7C,GAAI,sBAAsB,KAAA,KAAa,sBAAsB,KAC1D,CAAC,IACD,EAAE,YAAY,kBAAkB;EACnC,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;CAC9C,CAAC,CAAC,CAAC,MAAM;AACV,SAAS,OAAO;CACf,IAAI,CAAC,eAAe,KAAK,GAAG,MAAM;CAClC,QAAQ,MAAM,WAAW,MAAM,KAAK,IAAI,MAAM,QAAQ,MAAM,UAAU,CAAC,CAAC,KAAK,GAAG,GAAG;CACnF,QAAQ,WAAW;AACpB"}
1
+ {"version":3,"file":"main.js","names":[],"sources":["../../src/bin/main.ts"],"sourcesContent":["import { parseBoolean, parseInteger } from '@orkestrel/contract'\nimport { BrowserError, isBrowserError } from '@src/core'\nimport { createBrowserMCPServer } from '@src/server'\n\n// The entry owns stderr; a closed reader must not interrupt the server's teardown barrier.\nprocess.stderr.on('error', () => {\n\tprocess.exitCode = 1\n})\n\ntry {\n\tconst { BROWSE_ROOT, BROWSE_HEADLESS, BROWSE_EXECUTABLE, BROWSE_READONLY, BROWSE_POOL } =\n\t\tprocess.env\n\tconst headless = parseBoolean(BROWSE_HEADLESS)\n\tconst readonly = parseBoolean(BROWSE_READONLY)\n\tconst size = parseInteger(BROWSE_POOL)\n\t// An empty variable counts as unset, as a shell's `NAME=` leaves it.\n\tfor (const [name, value, parsed] of [\n\t\t['BROWSE_HEADLESS', BROWSE_HEADLESS, headless],\n\t\t['BROWSE_READONLY', BROWSE_READONLY, readonly],\n\t] as const) {\n\t\tif (value !== undefined && value !== '' && parsed === undefined) {\n\t\t\tthrow new BrowserError(\n\t\t\t\t`${name} must be true, false, 1, or 0, not ${JSON.stringify(value)}`,\n\t\t\t\t'BROWSER_SERVER_ENVIRONMENT',\n\t\t\t\t{ name, value },\n\t\t\t)\n\t\t}\n\t}\n\tif (BROWSE_POOL !== undefined && BROWSE_POOL !== '' && size === undefined)\n\t\tthrow new BrowserError(\n\t\t\t`BROWSE_POOL must be an integer, not ${JSON.stringify(BROWSE_POOL)}`,\n\t\t\t'BROWSER_SERVER_ENVIRONMENT',\n\t\t\t{ name: 'BROWSE_POOL', value: BROWSE_POOL },\n\t\t)\n\tawait createBrowserMCPServer({\n\t\t...(BROWSE_ROOT === undefined || BROWSE_ROOT === '' ? {} : { root: BROWSE_ROOT }),\n\t\t...(headless === undefined ? {} : { headless }),\n\t\t...(BROWSE_EXECUTABLE === undefined || BROWSE_EXECUTABLE === ''\n\t\t\t? {}\n\t\t\t: { executable: BROWSE_EXECUTABLE }),\n\t\t...(readonly === undefined ? {} : { readonly }),\n\t\t...(size === undefined ? {} : { pool: { size } }),\n\t}).start()\n} catch (error) {\n\tif (!isBrowserError(error)) throw error\n\tconsole.error(`browse: ${error.code}: ${error.message.split(/\\r\\n|\\n/u).join(' ')}`)\n\tprocess.exitCode = 1\n}\n"],"mappings":";;;;;AAKA,QAAQ,OAAO,GAAG,eAAe;CAChC,QAAQ,WAAW;AACpB,CAAC;AAED,IAAI;CACH,MAAM,EAAE,aAAa,iBAAiB,mBAAmB,iBAAiB,gBACzE,QAAQ;CACT,MAAM,WAAW,aAAa,eAAe;CAC7C,MAAM,WAAW,aAAa,eAAe;CAC7C,MAAM,OAAO,aAAa,WAAW;CAErC,KAAK,MAAM,CAAC,MAAM,OAAO,WAAW,CACnC;EAAC;EAAmB;EAAiB;CAAQ,GAC7C;EAAC;EAAmB;EAAiB;CAAQ,CAC9C,GACC,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,WAAW,KAAA,GACrD,MAAM,IAAI,aACT,GAAG,KAAK,qCAAqC,KAAK,UAAU,KAAK,KACjE,8BACA;EAAE;EAAM;CAAM,CACf;CAGF,IAAI,gBAAgB,KAAA,KAAa,gBAAgB,MAAM,SAAS,KAAA,GAC/D,MAAM,IAAI,aACT,uCAAuC,KAAK,UAAU,WAAW,KACjE,8BACA;EAAE,MAAM;EAAe,OAAO;CAAY,CAC3C;CACD,MAAM,uBAAuB;EAC5B,GAAI,gBAAgB,KAAA,KAAa,gBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,YAAY;EAC/E,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;EAC7C,GAAI,sBAAsB,KAAA,KAAa,sBAAsB,KAC1D,CAAC,IACD,EAAE,YAAY,kBAAkB;EACnC,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;EAC7C,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;CAChD,CAAC,CAAC,CAAC,MAAM;AACV,SAAS,OAAO;CACf,IAAI,CAAC,eAAe,KAAK,GAAG,MAAM;CAClC,QAAQ,MAAM,WAAW,MAAM,KAAK,IAAI,MAAM,QAAQ,MAAM,UAAU,CAAC,CAAC,KAAK,GAAG,GAAG;CACnF,QAAQ,WAAW;AACpB"}
@@ -2,7 +2,6 @@ import type { BrowserCallOptions } from '@orkestrel/browser';
2
2
  import type { BrowserElementInterface } from '@orkestrel/browser';
3
3
  import type { BrowserElementManagerInterface } from '@orkestrel/browser';
4
4
  import type { BrowserElementQuery } from '@orkestrel/browser';
5
- import type { BrowserElementWaitOptions } from '@orkestrel/browser';
6
5
  import type { BrowserEpochFunction } from '@orkestrel/browser';
7
6
  import type { BrowserOutline } from '@orkestrel/browser';
8
7
  import type { BrowserOutlineOptions } from '@orkestrel/browser';
@@ -11,6 +10,7 @@ import type { BrowserReadingInterface } from '@orkestrel/browser';
11
10
  import type { BrowserToolsetInterface } from '@orkestrel/browser';
12
11
  import type { BrowserToolsetOptions } from '@orkestrel/browser';
13
12
  import type { BrowserViewInterface } from '@orkestrel/browser';
13
+ import type { BrowserWaitOptions } from '@orkestrel/browser';
14
14
  import type { CDPTransportEventMap } from '@orkestrel/browser';
15
15
  import type { CDPTransportInterface } from '@orkestrel/browser';
16
16
  import type { EmitterErrorHandler } from '@orkestrel/emitter';
@@ -36,6 +36,14 @@ export declare const BROWSER_CONTEXT_TARGETS: ReadonlySet<string>;
36
36
  */
37
37
  export declare const BROWSER_DOCUMENT_TIMEOUT_MS = 5000;
38
38
 
39
+ /**
40
+ * Names the roles whose ARIA expansion state the DOM outline reads.
41
+ *
42
+ * @remarks
43
+ * The role set follows the accessibility-tree reading from Chromium 141.0.7390.37.
44
+ */
45
+ export declare const BROWSER_EXPANDED_ROLES: ReadonlySet<string>;
46
+
39
47
  /**
40
48
  * Maps an HTML element to the ARIA role the HTML Accessibility API Mappings specification gives
41
49
  * it when it carries no `role` attribute.
@@ -58,6 +66,14 @@ export declare const BROWSER_IMPLICIT_ROLES: ReadonlyMap<string, string>;
58
66
  */
59
67
  export declare const BROWSER_INTERACTIVE_CONTENT = "a[href], audio[controls], button, details, embed, iframe, img[usemap], img[controls], input:not([type=\"hidden\" i]), label, select, textarea, video[controls]";
60
68
 
69
+ /**
70
+ * Names the roles whose selection state the DOM outline reads.
71
+ *
72
+ * @remarks
73
+ * The role set follows the accessibility-tree reading from Chromium 141.0.7390.37.
74
+ */
75
+ export declare const BROWSER_SELECTED_ROLES: ReadonlySet<string>;
76
+
61
77
  /** Names the `input` type states whose value an untrusted `fill` sets as typed text. */
62
78
  export declare const BROWSER_TYPED_INPUTS: ReadonlySet<string>;
63
79
 
@@ -165,8 +181,10 @@ export declare interface BrowserDOMElementInterface extends BrowserElementInterf
165
181
  * wrapper reports that latest capture, also after the manager drops the reference. A reference
166
182
  * and a reading from any walked document record that document's epoch, which advances on its own
167
183
  * navigation events and on the view's; that document's `pagehide` drops its references. A form
168
- * carrying a `toolname` attribute renders `[tool=NAME]` after its role and name. A `select` row
169
- * is followed by one `option` row per option, named by its label. Whitespace between two texts of
184
+ * carrying a `toolname` attribute renders `[tool=NAME]` after its role and name. A row carries
185
+ * `focused` when its element is the active element of its own document or shadow root. Rows carry
186
+ * pressed, expanded, and selected states through the Chromium mapping in `readBrowserStates`.
187
+ * A `select` row is followed by one `option` row per option, named by its label. Whitespace between two texts of
170
188
  * one block renders as one space, as a rendered `br` between them does, and texts with neither
171
189
  * between them stay joined. A CSS query
172
190
  * searches the document the view drives, not its child documents or shadow trees. After the
@@ -190,7 +208,7 @@ export declare class BrowserDOMElementManager implements BrowserElementManagerIn
190
208
  constructor(input: BrowserDOMElementManagerInput);
191
209
  outline(options?: BrowserOutlineOptions): Promise<BrowserOutline>;
192
210
  find(query: BrowserElementQuery, options?: BrowserCallOptions): Promise<readonly BrowserDOMElementInterface[]>;
193
- wait(query: BrowserElementQuery, options?: BrowserElementWaitOptions): Promise<readonly BrowserDOMElementInterface[]>;
211
+ wait(query: BrowserElementQuery, options?: BrowserWaitOptions): Promise<readonly BrowserDOMElementInterface[]>;
194
212
  element(reference: string): BrowserDOMElementInterface | undefined;
195
213
  elements(): readonly BrowserDOMElementInterface[];
196
214
  clear(): void;
@@ -246,7 +264,7 @@ export declare class BrowserDOMView implements BrowserDOMViewInterface {
246
264
  get elements(): BrowserElementManagerInterface<BrowserDOMElementInterface>;
247
265
  title(options?: BrowserCallOptions): Promise<string>;
248
266
  read(options?: BrowserCallOptions): Promise<BrowserReadingInterface>;
249
- wait(text: string, options?: BrowserCallOptions): Promise<void>;
267
+ wait(text: string, options?: BrowserWaitOptions): Promise<void>;
250
268
  destroy(): void;
251
269
  }
252
270
 
@@ -272,14 +290,15 @@ export declare interface BrowserDOMViewOptions {
272
290
  }
273
291
 
274
292
  /**
275
- * Parks one condition on DOM mutations until it holds, with one deadline and no other timer.
293
+ * Parks one condition on DOM mutations and finished transitions and animations until it holds, with one deadline and coalesced wake tasks.
276
294
  *
277
295
  * @remarks
278
296
  * The wait observes every root `roots` returns, each through a `MutationObserver` of that root's
279
- * realm, and re-reads the roots after every mutation batch and every capture-phase `load` in an
280
- * observed root: a same-origin frame or an open shadow root that appears joins the observation,
297
+ * realm, and re-reads the roots after every mutation batch, every capture-phase `load`, and
298
+ * the task after a `BROWSER_WAIT_EVENTS` event in an observed root: a same-origin
299
+ * frame or an open shadow root that appears joins the observation,
281
300
  * and a root that departed (a replaced frame document, a removed shadow root) leaves it, with its
282
- * observer disconnected and its `load` listener removed. The check runs before the wait parks and
301
+ * observer disconnected and its listeners removed. The check runs before the wait parks and
283
302
  * again after every subscription, and no check runs once the deadline, counted from `start`, has
284
303
  * passed, which the wait reads again after `roots` returns. The wait settles one time: on the
285
304
  * check's first value, on the signal's abort with its reason, at the deadline with
@@ -291,9 +310,9 @@ export declare interface BrowserDOMViewOptions {
291
310
  * observer, listener, or further check behind.
292
311
  *
293
312
  * A shadow root attached after the wait began is a declared limit: attaching one fires no
294
- * mutation record and no event, so the wait discovers it at the next mutation or `load` it
295
- * observes, and a match inserted into it with no other observed change is not seen before the
296
- * deadline. The wait never polls to close that gap.
313
+ * mutation record and no event, so the wait discovers it at the next mutation batch, `load`,
314
+ * or `BROWSER_WAIT_EVENTS` event it observes, and a match inserted into it with no other
315
+ * observed change is not seen before the deadline. The wait never polls to close that gap.
297
316
  *
298
317
  * @example
299
318
  * ```ts
@@ -313,7 +332,7 @@ export declare class BrowserDOMWait<T> implements BrowserDOMWaitInterface<T> {
313
332
  execute(): Promise<T>;
314
333
  }
315
334
 
316
- /** Settles one wait parked on DOM mutations. */
335
+ /** Settles one wait parked on DOM mutations and finished transitions and animations. */
317
336
  export declare interface BrowserDOMWaitInterface<T> {
318
337
  /** The roots the wait observes at this moment; empty before it parks and after it settles. */
319
338
  readonly roots: readonly Node[];
@@ -325,11 +344,11 @@ export declare interface BrowserDOMWaitInterface<T> {
325
344
  }
326
345
 
327
346
  /**
328
- * Describes one wait parked on DOM mutations.
347
+ * Describes one wait parked on DOM mutations and finished transitions and animations.
329
348
  *
330
349
  * @remarks
331
350
  * - `roots` — returns the documents and shadow roots whose mutations re-run the check; the wait
332
- * re-reads it after every mutation batch and every `load` in an observed root, observing the
351
+ * re-reads it after every mutation batch, every `load`, and every `BROWSER_WAIT_EVENTS` event in an observed root, observing the
333
352
  * roots that appeared and releasing the ones that departed, and a `pagehide` in the first
334
353
  * root's window fails the wait
335
354
  * - `check` — returns the value the wait settles on, or `undefined` to keep waiting
@@ -506,7 +525,7 @@ export declare function createBrowserDOMView(options: BrowserDOMViewOptions): Br
506
525
  *
507
526
  * @remarks
508
527
  * The toolset runs over a view {@link createBrowserDOMView} creates for `document`, so it
509
- * advertises `look`, `read`, `click`, `type`, and `wait`, and a click or type receipt ends with
528
+ * advertises `look`, `read`, `plain`, `click`, `type`, and `wait`, and a click or type receipt ends with
510
529
  * ` (untrusted event)`. `source` supplies page tools, which the toolset adopts during `start()`
511
530
  * and again on every `change`; `toolset.native` never includes them, so publishing it to a
512
531
  * registry leaves that registry's own tools alone. The view belongs to the toolset: `destroy()`
@@ -526,7 +545,7 @@ export declare function createBrowserDOMView(options: BrowserDOMViewOptions): Br
526
545
  * ```ts
527
546
  * const toolset = createDocumentToolset({ document: frame.contentDocument, source: bridge })
528
547
  * await toolset.start()
529
- * await toolset.tools.execute({ id: '1', name: 'look', arguments: { what: 'form' } })
548
+ * await toolset.tools.execute({ id: '1', name: 'look', arguments: { search: 'form' } })
530
549
  * ```
531
550
  */
532
551
  export declare function createDocumentToolset(options: BrowserDocumentToolsetOptions): BrowserToolsetInterface;
@@ -721,15 +740,16 @@ export declare function matchesBrowserPopup(element: Element): boolean;
721
740
  export declare function readBrowserBlock(element: Element | null, root: Node): Node;
722
741
 
723
742
  /**
724
- * Reads the URL, title, and serialized markup a DOM reading is built from, refusing a capture
743
+ * Reads the URL, title, and rendered markup a DOM reading is built from, refusing a capture
725
744
  * over the result limit before any parse.
726
745
  *
727
746
  * @remarks
728
747
  * The measure is the length of `JSON.stringify({ url, title, html })`, the form the CDP capture
729
748
  * guard compares with `BROWSER_RESULT_LIMIT`, so a character JSON escapes counts as its escape
730
749
  * sequence. A reading a caller builds with `createBrowserReading` passes through no limit.
750
+ * {@link BrowserReadingInput} defines the inert capture, lowering, redaction, and limits.
731
751
  *
732
- * @param node - The element whose `outerHTML` is captured; its owner document supplies `URL`
752
+ * @param node - The root whose rendered markup is captured; its owner document supplies `URL`
733
753
  * and `title`
734
754
  * @returns The capture's `url`, `title`, and `html`
735
755
  * @throws Thrown as a `BrowserResultLimitError` when the serialized capture is longer than
@@ -778,6 +798,44 @@ export declare function readBrowserContent(root: Element | ShadowRoot, context?:
778
798
  */
779
799
  export declare function readBrowserParent(element: Element): Element | null;
780
800
 
801
+ /**
802
+ * Reads the pressed, expanded, and selected states the DOM outline assigns to an element's role.
803
+ *
804
+ * @remarks
805
+ * Only buttons carry pressed state. Expansion follows `BROWSER_EXPANDED_ROLES`, with native
806
+ * single selects always collapsed. Selection follows `BROWSER_SELECTED_ROLES`: an ARIA token
807
+ * overrides native option selectedness, and other elements without a token omit selection. Tokens are not
808
+ * trimmed; values other than `false` are true, except the pressed token `mixed`.
809
+ *
810
+ * @param element - The element whose attributes and native state are read
811
+ * @param role - Its computed accessibility role
812
+ * @returns Present states, including false, with pressed represented as a tristate string
813
+ *
814
+ * @example
815
+ * ```ts
816
+ * const button = document.createElement('button')
817
+ * button.setAttribute('aria-pressed', 'mixed')
818
+ * readBrowserStates(button, 'button') // { pressed: 'mixed' }
819
+ * ```
820
+ */
821
+ export declare function readBrowserStates(element: Element, role: string): Readonly<Record<string, string | boolean>>;
822
+
823
+ /**
824
+ * Reads a lowercased ARIA token, preserving whitespace and treating empty or undefined tokens as absent.
825
+ *
826
+ * @param element - The element carrying the attribute
827
+ * @param attribute - The ARIA attribute name
828
+ * @returns The lowercased token, or `undefined` for a missing, empty, or `undefined` attribute
829
+ *
830
+ * @example
831
+ * ```ts
832
+ * const button = document.createElement('button')
833
+ * button.setAttribute('aria-pressed', 'TRUE')
834
+ * readBrowserToken(button, 'aria-pressed') // 'true'
835
+ * ```
836
+ */
837
+ export declare function readBrowserToken(element: Element, attribute: string): string | undefined;
838
+
781
839
  /**
782
840
  * Moves a tree walker past the subtree of its current node.
783
841
  *