dsh-browser-playwright-codex 0.5.1

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +486 -0
  2. package/LICENSE +21 -0
  3. package/README.md +322 -0
  4. package/SECURITY.md +114 -0
  5. package/cordis.patch.yml +66 -0
  6. package/lib/cdp-policy.d.ts +69 -0
  7. package/lib/cdp-policy.js +105 -0
  8. package/lib/compat.d.ts +37 -0
  9. package/lib/compat.js +31 -0
  10. package/lib/contract.d.ts +74 -0
  11. package/lib/contract.js +117 -0
  12. package/lib/errors.d.ts +25 -0
  13. package/lib/errors.js +21 -0
  14. package/lib/index.d.ts +20 -0
  15. package/lib/index.js +12 -0
  16. package/lib/injected.d.ts +35 -0
  17. package/lib/injected.js +350 -0
  18. package/lib/playwright.d.ts +647 -0
  19. package/lib/playwright.js +2515 -0
  20. package/lib/runtime-state.d.ts +45 -0
  21. package/lib/runtime-state.js +181 -0
  22. package/lib/service.d.ts +56 -0
  23. package/lib/service.js +96 -0
  24. package/lib/snapshot-aria.d.ts +121 -0
  25. package/lib/snapshot-aria.js +485 -0
  26. package/lib/snapshot-render.d.ts +28 -0
  27. package/lib/snapshot-render.js +100 -0
  28. package/lib/tool.d.ts +77 -0
  29. package/lib/tool.js +907 -0
  30. package/lib/types.d.ts +242 -0
  31. package/lib/types.js +5 -0
  32. package/locale/en.json +6 -0
  33. package/locale/zh.json +6 -0
  34. package/package.json +108 -0
  35. package/scripts/analysis/hl-compare.mjs +149 -0
  36. package/scripts/analysis/live-handoff-probe.mjs +97 -0
  37. package/scripts/analysis/needs-human-probe.mjs +83 -0
  38. package/scripts/analysis/repro-minimize.mjs +112 -0
  39. package/scripts/autocommit.ps1 +23 -0
  40. package/scripts/check-doc-links.py +112 -0
  41. package/scripts/doctor.mjs +144 -0
  42. package/scripts/dsh-browser-verify.mjs +475 -0
  43. package/scripts/measure-prompt-cost.mjs +57 -0
  44. package/scripts/prompt-cost.mjs +73 -0
  45. package/scripts/shipped-gates.mjs +43 -0
@@ -0,0 +1,45 @@
1
+ /** How a page is read into a snapshot tree. */
2
+ export type SnapshotEngine = 'legacy' | 'aria';
3
+ /** Current enable state (loads from disk on first call). */
4
+ export declare function getEnabled(): boolean;
5
+ /** Set the enable state. Returns true if the value actually changed. */
6
+ export declare function setEnabled(next: boolean): boolean;
7
+ /** Subscribe to enable-state transitions. Returns an unsubscribe function. */
8
+ export declare function subscribe(fn: (enabled: boolean) => void): () => void;
9
+ /**
10
+ * The page-reading mode the user picked, or undefined when they never picked
11
+ * one — the configured default then applies.
12
+ */
13
+ export declare function getSnapshotEngine(): SnapshotEngine | undefined;
14
+ /** Pick the page-reading mode. Returns true if the value actually changed. */
15
+ export declare function setSnapshotEngine(next: SnapshotEngine): boolean;
16
+ /**
17
+ * Report the mode this deployment was configured with, so the settings page can
18
+ * display what is actually in effect instead of guessing. Called by the provider
19
+ * when it is constructed; not persisted (it comes from config).
20
+ * @param engine - the configured default mode.
21
+ */
22
+ export declare function publishConfiguredEngine(engine: SnapshotEngine): void;
23
+ /**
24
+ * What a capture will use right now: the user's pick, else the configured
25
+ * default, else 'legacy'. This is what the settings page shows.
26
+ */
27
+ export declare function getEffectiveEngine(): SnapshotEngine;
28
+ /** Whether the current mode is the user's own pick rather than the default. */
29
+ export declare function isEngineUserChosen(): boolean;
30
+ /**
31
+ * The tool family's effective gates, published at registration. The Settings page
32
+ * needs to show what is actually in effect — including whatever a profile patch
33
+ * or a home-level patch decided — instead of guessing at the package default.
34
+ * Not persisted: it is derived from config every time the plugin is applied.
35
+ * @param gates - whether browser_evaluate / browser_cdp are registered.
36
+ */
37
+ export declare function publishGates(gates: {
38
+ allowEvaluate: boolean;
39
+ allowCdp: boolean;
40
+ }): void;
41
+ /** The published gates, or undefined while the tool family has not been applied. */
42
+ export declare function getGates(): {
43
+ allowEvaluate: boolean;
44
+ allowCdp: boolean;
45
+ } | undefined;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Shared user settings for dsh-browser-playwright-codex, used by the three host
3
+ * entries (browser / browser-playwright / browser-tool) and by the settings-page
4
+ * switches in dsh-browser-toggle.
5
+ *
6
+ * Two settings, both user-facing:
7
+ * - `enabled` (default true) — are the browser tools available at all.
8
+ * - `engine` (default: unset) — how a page is read into the snapshot tree.
9
+ * 'legacy' = the plugin's own DOM walk (most tested).
10
+ * 'aria' = the browser's official accessibility tree (what a screen
11
+ * reader sees), usually more faithful on complex pages.
12
+ * Unset means "follow the configured default", which is 'legacy'.
13
+ *
14
+ * State persists to ~/.dsh/dsh-browser-playwright.state.json so both settings
15
+ * survive a DSH restart. The FILE name deliberately keeps the pre-rename
16
+ * spelling: it is persisted user state, and renaming it would silently reset the
17
+ * user's switches.
18
+ *
19
+ * The file is re-read on every call (no read-once caching), so a change made on
20
+ * one side (web or desktop) is visible to the other immediately, and flipping a
21
+ * switch needs no restart and no config edit.
22
+ *
23
+ * `DSH_BROWSER_STATE_FILE` redirects the file; tests use it so they never touch
24
+ * real user state.
25
+ * @module dsh-browser-playwright-codex/runtime-state
26
+ */
27
+ import fs from 'node:fs';
28
+ import os from 'node:os';
29
+ import path from 'node:path';
30
+ // Shared layer: the web and desktop sides read the same switch file, so one
31
+ // agent can never see two different states depending on which side is open.
32
+ const STATE_DIR = path.join(os.homedir(), '.dsh');
33
+ const DEFAULT_STATE_FILE = path.join(STATE_DIR, 'dsh-browser-playwright.state.json');
34
+ /** The state file in use, resolved per call so tests can redirect it. */
35
+ function stateFile() {
36
+ const override = process.env.DSH_BROWSER_STATE_FILE;
37
+ return override !== undefined && override !== '' ? override : DEFAULT_STATE_FILE;
38
+ }
39
+ let _enabled = true;
40
+ let _engine = undefined;
41
+ /** Default pushed in by the provider that is actually loaded (display only). */
42
+ let _configuredEngine = undefined;
43
+ /** Effective tool gates, pushed in by the tool family when it is applied (display only). */
44
+ let _gates = undefined;
45
+ const _subscribers = new Set();
46
+ function load() {
47
+ // Re-read the file on every call: whichever side flips the switch, the
48
+ // other side follows immediately (no read-once caching).
49
+ const previous = _enabled;
50
+ try {
51
+ const txt = fs.readFileSync(stateFile(), 'utf8');
52
+ const obj = JSON.parse(txt);
53
+ if (obj !== null && typeof obj === 'object') {
54
+ if (typeof obj.enabled === 'boolean')
55
+ _enabled = obj.enabled;
56
+ // The file is authoritative for the page-reading mode: an absent or
57
+ // unknown value means "follow the configured default" (undefined here).
58
+ // Files written before this setting existed therefore stay valid.
59
+ _engine = obj.engine === 'legacy' || obj.engine === 'aria' ? obj.engine : undefined;
60
+ }
61
+ }
62
+ catch {
63
+ // Missing file / parse error / unreadable: keep the current values.
64
+ }
65
+ if (_enabled !== previous) {
66
+ for (const fn of [..._subscribers]) {
67
+ try {
68
+ fn(_enabled);
69
+ }
70
+ catch {
71
+ // One subscriber failing must not affect the others.
72
+ }
73
+ }
74
+ }
75
+ }
76
+ function persist() {
77
+ try {
78
+ const target = stateFile();
79
+ fs.mkdirSync(path.dirname(target), { recursive: true });
80
+ // Atomic write: rename(2) is atomic on the same filesystem, so a crash
81
+ // mid-write leaves either the old file or the new file, never half of
82
+ // both.
83
+ const tmp = target + '.tmp';
84
+ // Keep the historical shape until the user actually picks a page-reading
85
+ // mode: an absent `engine` means "use the configured default".
86
+ const payload = _engine === undefined ? { enabled: _enabled } : { enabled: _enabled, engine: _engine };
87
+ fs.writeFileSync(tmp, JSON.stringify(payload, null, 2));
88
+ fs.renameSync(tmp, target);
89
+ }
90
+ catch {
91
+ // Persist failure must not break the in-memory switches; the next set
92
+ // will retry.
93
+ }
94
+ }
95
+ /** Current enable state (loads from disk on first call). */
96
+ export function getEnabled() {
97
+ load();
98
+ return _enabled;
99
+ }
100
+ /** Set the enable state. Returns true if the value actually changed. */
101
+ export function setEnabled(next) {
102
+ load();
103
+ if (typeof next !== 'boolean')
104
+ return false;
105
+ if (_enabled === next)
106
+ return false;
107
+ _enabled = next;
108
+ persist();
109
+ for (const fn of [..._subscribers]) {
110
+ try {
111
+ fn(next);
112
+ }
113
+ catch {
114
+ // One subscriber failing must not prevent others from reacting.
115
+ }
116
+ }
117
+ return true;
118
+ }
119
+ /** Subscribe to enable-state transitions. Returns an unsubscribe function. */
120
+ export function subscribe(fn) {
121
+ _subscribers.add(fn);
122
+ return () => {
123
+ _subscribers.delete(fn);
124
+ };
125
+ }
126
+ /**
127
+ * The page-reading mode the user picked, or undefined when they never picked
128
+ * one — the configured default then applies.
129
+ */
130
+ export function getSnapshotEngine() {
131
+ load();
132
+ return _engine;
133
+ }
134
+ /** Pick the page-reading mode. Returns true if the value actually changed. */
135
+ export function setSnapshotEngine(next) {
136
+ load();
137
+ if (next !== 'legacy' && next !== 'aria')
138
+ return false;
139
+ if (_engine === next)
140
+ return false;
141
+ _engine = next;
142
+ persist();
143
+ return true;
144
+ }
145
+ /**
146
+ * Report the mode this deployment was configured with, so the settings page can
147
+ * display what is actually in effect instead of guessing. Called by the provider
148
+ * when it is constructed; not persisted (it comes from config).
149
+ * @param engine - the configured default mode.
150
+ */
151
+ export function publishConfiguredEngine(engine) {
152
+ if (engine === 'legacy' || engine === 'aria')
153
+ _configuredEngine = engine;
154
+ }
155
+ /**
156
+ * What a capture will use right now: the user's pick, else the configured
157
+ * default, else 'legacy'. This is what the settings page shows.
158
+ */
159
+ export function getEffectiveEngine() {
160
+ load();
161
+ return _engine ?? _configuredEngine ?? 'legacy';
162
+ }
163
+ /** Whether the current mode is the user's own pick rather than the default. */
164
+ export function isEngineUserChosen() {
165
+ load();
166
+ return _engine !== undefined;
167
+ }
168
+ /**
169
+ * The tool family's effective gates, published at registration. The Settings page
170
+ * needs to show what is actually in effect — including whatever a profile patch
171
+ * or a home-level patch decided — instead of guessing at the package default.
172
+ * Not persisted: it is derived from config every time the plugin is applied.
173
+ * @param gates - whether browser_evaluate / browser_cdp are registered.
174
+ */
175
+ export function publishGates(gates) {
176
+ _gates = { allowEvaluate: gates.allowEvaluate === true, allowCdp: gates.allowCdp === true };
177
+ }
178
+ /** The published gates, or undefined while the tool family has not been applied. */
179
+ export function getGates() {
180
+ return _gates === undefined ? undefined : { ..._gates };
181
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Browser capability seam: the provider registry and selection service.
3
+ * Providers implement browser backends; consumers acquire sessions through
4
+ * ctx.browser without importing a concrete implementation.
5
+ * @module dsh-browser-playwright-codex/service
6
+ */
7
+ import { Context, Service } from '@deepseek-ai/cordis';
8
+ import z from '@deepseek-ai/schemastery';
9
+ import type { BrowserProvider, BrowserSession } from './types.ts';
10
+ declare module '@deepseek-ai/cordis' {
11
+ interface Context {
12
+ browser: BrowserRuntime;
13
+ }
14
+ }
15
+ /** Selection config for the browser seam. */
16
+ export interface BrowserRuntimeConfig {
17
+ /** Explicit provider id. Omitted = auto-select when exactly one is registered. */
18
+ readonly provider?: string;
19
+ }
20
+ /** Schemastery validation for {@link BrowserRuntimeConfig}. */
21
+ export declare const Config: z<BrowserRuntimeConfig>;
22
+ /**
23
+ * The browser service, registered as ctx.browser (one instance per context).
24
+ * Selection semantics (resolved at acquire time, never order-dependent):
25
+ * - A configured id that is registered → that provider.
26
+ * - A configured id not registered → CONFIGURED_PROVIDER_MISSING.
27
+ * - No id configured, exactly one registered provider → that provider.
28
+ * - No id configured, multiple registered providers → AMBIGUOUS_PROVIDER.
29
+ * - No registered provider → NO_PROVIDER.
30
+ */
31
+ export default class BrowserRuntime extends Service {
32
+ static Config: z<BrowserRuntimeConfig>;
33
+ private readonly providers;
34
+ private readonly configuredId;
35
+ constructor(ctx: Context, config?: BrowserRuntimeConfig);
36
+ /**
37
+ * Register a browser provider. Duplicate ids fail the registering plugin.
38
+ * @param provider - the provider; its id is the registry key.
39
+ * @returns the disposer that unregisters and disposes the provider.
40
+ */
41
+ registerProvider(provider: BrowserProvider): () => void;
42
+ /**
43
+ * Acquire the session owned by the owner key through the selected provider.
44
+ * @param owner - opaque caller-owned session key (a harness session id).
45
+ * @param signal - optional cancellation forwarded to the provider.
46
+ * @returns the live browser session.
47
+ */
48
+ acquire(owner: string, signal?: AbortSignal): Promise<BrowserSession>;
49
+ /**
50
+ * Release the session owned by the owner key on every registered provider;
51
+ * providers ignore unknown owners.
52
+ * @param owner - the owner key whose session should be released.
53
+ */
54
+ disposeOwner(owner: string): Promise<void>;
55
+ private resolveProvider;
56
+ }
package/lib/service.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Browser capability seam: the provider registry and selection service.
3
+ * Providers implement browser backends; consumers acquire sessions through
4
+ * ctx.browser without importing a concrete implementation.
5
+ * @module dsh-browser-playwright-codex/service
6
+ */
7
+ import { Context, Service } from '@deepseek-ai/cordis';
8
+ import z from '@deepseek-ai/schemastery';
9
+ import { BrowserError } from "./errors.js";
10
+ /** Schemastery validation for {@link BrowserRuntimeConfig}. */
11
+ export const Config = z.object({
12
+ provider: z.string(),
13
+ });
14
+ /**
15
+ * The browser service, registered as ctx.browser (one instance per context).
16
+ * Selection semantics (resolved at acquire time, never order-dependent):
17
+ * - A configured id that is registered → that provider.
18
+ * - A configured id not registered → CONFIGURED_PROVIDER_MISSING.
19
+ * - No id configured, exactly one registered provider → that provider.
20
+ * - No id configured, multiple registered providers → AMBIGUOUS_PROVIDER.
21
+ * - No registered provider → NO_PROVIDER.
22
+ */
23
+ export default class BrowserRuntime extends Service {
24
+ static Config = Config;
25
+ providers = new Map();
26
+ configuredId;
27
+ constructor(ctx, config = {}) {
28
+ super(ctx, 'browser');
29
+ this.configuredId = config.provider;
30
+ // Unloading this plugin disposes every registered provider's resources.
31
+ this.ctx.effect(() => () => {
32
+ for (const provider of this.providers.values())
33
+ void provider.dispose();
34
+ this.providers.clear();
35
+ });
36
+ }
37
+ /**
38
+ * Register a browser provider. Duplicate ids fail the registering plugin.
39
+ * @param provider - the provider; its id is the registry key.
40
+ * @returns the disposer that unregisters and disposes the provider.
41
+ */
42
+ registerProvider(provider) {
43
+ if (this.providers.has(provider.id)) {
44
+ throw new BrowserError('AMBIGUOUS_PROVIDER', 'a browser provider with id "' + provider.id + '" is already registered');
45
+ }
46
+ const runtime = this;
47
+ const dispose = this.ctx.effect(function* () {
48
+ runtime.providers.set(provider.id, provider);
49
+ yield () => {
50
+ runtime.providers.delete(provider.id);
51
+ void provider.dispose();
52
+ };
53
+ }, 'browser.registerProvider()');
54
+ return () => void dispose();
55
+ }
56
+ /**
57
+ * Acquire the session owned by the owner key through the selected provider.
58
+ * @param owner - opaque caller-owned session key (a harness session id).
59
+ * @param signal - optional cancellation forwarded to the provider.
60
+ * @returns the live browser session.
61
+ */
62
+ async acquire(owner, signal) {
63
+ return this.resolveProvider().acquire(owner, signal);
64
+ }
65
+ /**
66
+ * Release the session owned by the owner key on every registered provider;
67
+ * providers ignore unknown owners.
68
+ * @param owner - the owner key whose session should be released.
69
+ */
70
+ async disposeOwner(owner) {
71
+ for (const provider of this.providers.values()) {
72
+ await provider.disposeOwner(owner);
73
+ }
74
+ }
75
+ resolveProvider() {
76
+ const { configuredId, providers } = this;
77
+ if (configuredId !== undefined) {
78
+ const provider = providers.get(configuredId);
79
+ if (provider === undefined) {
80
+ throw new BrowserError('CONFIGURED_PROVIDER_MISSING', 'configured browser provider "' + configuredId + '" is not registered');
81
+ }
82
+ return provider;
83
+ }
84
+ const registered = [...providers.values()];
85
+ const [single] = registered;
86
+ if (single === undefined) {
87
+ throw new BrowserError('NO_PROVIDER', 'no browser provider is registered; mount the playwright entry of this package');
88
+ }
89
+ if (registered.length > 1) {
90
+ throw new BrowserError('AMBIGUOUS_PROVIDER', 'multiple browser providers are registered (' +
91
+ registered.map((p) => p.id).join(', ') +
92
+ '); configure one explicitly');
93
+ }
94
+ return single;
95
+ }
96
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Aria semantic snapshot engine: uses Playwright's official
3
+ * locator.ariaSnapshot({ mode: 'ai' }) as the semantic tree source, then
4
+ * re-aligns refs onto the project's stable data-dsh-ref scheme.
5
+ *
6
+ * Why a ref-alignment layer: ariaSnapshot's `[ref=eN]` are Playwright-internal
7
+ * per-invocation handles — they are NOT locator-addressable and their numbering
8
+ * does not survive the project's cross-snapshot stability contract. This module
9
+ * therefore (1) walks the DOM to mint/keep the same e<docNonce><seq> refs the
10
+ * legacy engine uses, (2) parses the official YAML into a semantic tree, and
11
+ * (3) assigns the DOM-minted refs to actionable nodes in document order, so
12
+ * refLocator('[data-dsh-ref=...]') and REF_PATTERN keep working unchanged.
13
+ *
14
+ * The actionable set is intentionally role-based (the same role set the
15
+ * injected engine uses) so DOM-side ref assignment and tree-side ref alignment
16
+ * agree on ordering; elements that only tabindex/contenteditable would make
17
+ * actionable in the legacy engine are out of scope for this engine.
18
+ * @module dsh-browser-playwright-codex/snapshot-aria
19
+ */
20
+ import type { Page } from 'playwright-core';
21
+ import type { BrowserNode } from './types.ts';
22
+ /**
23
+ * Page-context script: walks the DOM in document order and mints/keeps
24
+ * data-dsh-ref on every actionable element (same rule as the legacy engine,
25
+ * minus the tabindex/contenteditable escape hatches). Returns the refs in
26
+ * document order plus the per-document nonce, so the alignment layer can pair
27
+ * them with the aria tree's actionable nodes depth-first.
28
+ */
29
+ export declare const ARIA_REF_SCRIPT: string;
30
+ /** One semantic node parsed from the official ariaSnapshot YAML. */
31
+ export interface AriaNode {
32
+ readonly role: string;
33
+ readonly name: string;
34
+ /** Playwright-internal ref from the YAML ([ref=eN]); used for alignment only. */
35
+ readonly ariaRef?: string;
36
+ readonly level?: number;
37
+ readonly checked?: boolean;
38
+ readonly selected?: boolean;
39
+ readonly disabled?: boolean;
40
+ readonly href?: string;
41
+ /** `/placeholder:` attribute line: names an input the same way the legacy walker does. */
42
+ readonly placeholder?: string;
43
+ readonly children: readonly AriaNode[];
44
+ }
45
+ /** Parsed tree plus the lines the parser could not read (format drift). */
46
+ export interface AriaParseResult {
47
+ readonly roots: AriaNode[];
48
+ /** Content lines that could not be parsed — non-empty means format drift. */
49
+ readonly unparsed: string[];
50
+ }
51
+ /**
52
+ * Parse Playwright's official ariaSnapshot YAML into a semantic node tree.
53
+ *
54
+ * The real format (verified against playwright-core 1.62, 2026-09-30) is:
55
+ *
56
+ * - generic [active] [ref=e1]: ← trailing ':' = the node has children
57
+ * - link "One" [ref=e6] [cursor=pointer]:
58
+ * - /url: /one ← href rides a deeper child line
59
+ * - text: Name ← text content uses 'text: <value>'
60
+ *
61
+ * The first version of this parser assumed a hand-rolled dialect
62
+ * (`- role "name" [flags] -> href`, nothing after the flags) that real output
63
+ * never produces. Every container line ends in ':', so that regex matched none
64
+ * of them: the whole hierarchy was dropped, links and their hrefs vanished, and
65
+ * `captureAriaSnapshot` still reported success. Unreadable lines are now
66
+ * collected in `unparsed` so the capture layer can refuse the tree and let the
67
+ * provider fall back to the legacy engine.
68
+ * @param yaml - the ariaSnapshot text.
69
+ * @returns parsed roots plus any unreadable content lines.
70
+ */
71
+ export declare function parseAriaSnapshotWithStats(yaml: string): AriaParseResult;
72
+ /**
73
+ * Parse Playwright's ariaSnapshot YAML into a semantic node tree.
74
+ * @param yaml - the ariaSnapshot text.
75
+ * @returns the parsed root nodes (see {@link parseAriaSnapshotWithStats} for
76
+ * the unreadable-line report this is a thin wrapper over).
77
+ */
78
+ export declare function parseAriaSnapshot(yaml: string): AriaNode[];
79
+ /** Whether an aria role carries a ref (must match the DOM-side assignment). */
80
+ export declare function isActionableRole(role: string): boolean;
81
+ /**
82
+ * Build BrowserNode[] from the parsed aria tree, aligning DOM-minted refs onto
83
+ * actionable nodes in depth-first order, with node/name caps and truncation.
84
+ * @param nodes - parsed aria tree roots.
85
+ * @param opts - bounding options.
86
+ * @param refs - data-dsh-refs minted by the page-side walk, in document order.
87
+ * @returns the converted tree plus the truncation flag.
88
+ */
89
+ export declare function ariaTreeToBrowserNodes(nodes: readonly AriaNode[], opts: {
90
+ maxNodes: number;
91
+ maxNameLength: number;
92
+ maxTextLength: number;
93
+ interactiveOnly?: boolean;
94
+ }, refs: readonly string[], roles?: readonly string[]): {
95
+ nodes: BrowserNode[];
96
+ truncated: boolean;
97
+ totalRefs: number;
98
+ misaligned: boolean;
99
+ };
100
+ /**
101
+ * Capture one aria snapshot from a live page: official ariaSnapshot(mode:'ai')
102
+ * for the semantic tree, DOM-side ref minting for stable refs, then alignment.
103
+ * Returns the same shape the legacy capture produces, so playwright.ts can
104
+ * dispatch transparently.
105
+ *
106
+ * `missing: true` is the honest-failure contract: the official call threw, the
107
+ * YAML was empty, or the text carried lines this parser cannot read. The caller
108
+ * must treat it as "engine unavailable" (the provider falls back to the legacy
109
+ * DOM walker) and must never forward the empty node list to the model.
110
+ */
111
+ export declare function captureAriaSnapshot(page: Page, opts: {
112
+ interactiveOnly?: boolean;
113
+ maxNodes: number;
114
+ maxNameLength: number;
115
+ maxTextLength: number;
116
+ } | undefined): Promise<{
117
+ nodes: BrowserNode[];
118
+ truncated: boolean;
119
+ totalRefs: number;
120
+ missing?: boolean;
121
+ }>;