teamshare-bridge 0.26.12 → 0.26.13

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 (35) hide show
  1. package/dist/cli/commands/research.js +15 -0
  2. package/dist/cli/commands/research.js.map +1 -1
  3. package/dist/lib/browser/batch.d.ts +47 -2
  4. package/dist/lib/browser/batch.js +48 -5
  5. package/dist/lib/browser/batch.js.map +1 -1
  6. package/dist/lib/browser/browse-cdp.js +5 -1
  7. package/dist/lib/browser/browse-cdp.js.map +1 -1
  8. package/dist/lib/browser/browse-editor.js +8 -0
  9. package/dist/lib/browser/browse-editor.js.map +1 -1
  10. package/dist/lib/browser/cdp.d.ts +2 -0
  11. package/dist/lib/browser/cdp.js +8 -0
  12. package/dist/lib/browser/cdp.js.map +1 -1
  13. package/dist/lib/browser/index.d.ts +3 -1
  14. package/dist/lib/browser/index.js +12 -1
  15. package/dist/lib/browser/index.js.map +1 -1
  16. package/dist/lib/browser/login.d.ts +67 -25
  17. package/dist/lib/browser/login.js +681 -174
  18. package/dist/lib/browser/login.js.map +1 -1
  19. package/dist/lib/browser/network-log.d.ts +9 -0
  20. package/dist/lib/browser/network-log.js +21 -0
  21. package/dist/lib/browser/network-log.js.map +1 -1
  22. package/dist/lib/browser/page-context.d.ts +176 -0
  23. package/dist/lib/browser/page-context.js +637 -0
  24. package/dist/lib/browser/page-context.js.map +1 -0
  25. package/dist/lib/browser/page-events.d.ts +39 -0
  26. package/dist/lib/browser/page-events.js +128 -0
  27. package/dist/lib/browser/page-events.js.map +1 -0
  28. package/dist/lib/browser/screen-change.js +3 -0
  29. package/dist/lib/browser/screen-change.js.map +1 -1
  30. package/dist/lib/browser/session.d.ts +101 -1
  31. package/dist/lib/browser/session.js +321 -2
  32. package/dist/lib/browser/session.js.map +1 -1
  33. package/dist/lib/orchestrator/handlers/browser.js +200 -31
  34. package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
  35. package/package.json +1 -1
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Persistent page-event buffer for the debug browser (2026-10).
3
+ *
4
+ * WHY THIS EXISTS. The auto-answer browser scrapes toasts/errors at capture
5
+ * time, and the debug browser had NOTHING: a toast that appeared after a form
6
+ * submit and vanished two seconds later was invisible to an agent that only
7
+ * looked on its next tool call. In the live Soludesks QA run the agent spent
8
+ * minutes re-reading the DOM for a dialog/error that had already been shown.
9
+ *
10
+ * This module injects a tiny, idempotent MutationObserver into the page that
11
+ * records toast / alert / status / dialog events in a bounded ring buffer
12
+ * (`window.__tsPageEvents`). It is installed:
13
+ * - immediately when a session connects,
14
+ * - on every new document via `Page.addScriptToEvaluateOnNewDocument`,
15
+ * - again after a main-frame navigation (belt and braces).
16
+ *
17
+ * The script is dependency-free and returns a string so it can be injected
18
+ * with `Runtime.evaluate` or registered as a new-document script. It never
19
+ * reads field values and never logs anything.
20
+ */
21
+ /** Maximum events retained in the page ring buffer (oldest dropped). */
22
+ export declare const PAGE_EVENT_LIMIT = 80;
23
+ export interface PageEvent {
24
+ /** toast | alert | status | error | dialog | dialog-closed */
25
+ type: string;
26
+ text: string;
27
+ /** epoch ms, page clock */
28
+ at: number;
29
+ role?: string;
30
+ count?: number;
31
+ }
32
+ /**
33
+ * Build the injected page-event script.
34
+ *
35
+ * IMPORTANT: regex escapes must be DOUBLE-escaped inside this template literal
36
+ * (`\\s`), otherwise the injected JS receives `s` (the exact bug the bridge lint
37
+ * gate found in another injected script).
38
+ */
39
+ export declare function buildPageEventScript(): string;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /**
3
+ * Persistent page-event buffer for the debug browser (2026-10).
4
+ *
5
+ * WHY THIS EXISTS. The auto-answer browser scrapes toasts/errors at capture
6
+ * time, and the debug browser had NOTHING: a toast that appeared after a form
7
+ * submit and vanished two seconds later was invisible to an agent that only
8
+ * looked on its next tool call. In the live Soludesks QA run the agent spent
9
+ * minutes re-reading the DOM for a dialog/error that had already been shown.
10
+ *
11
+ * This module injects a tiny, idempotent MutationObserver into the page that
12
+ * records toast / alert / status / dialog events in a bounded ring buffer
13
+ * (`window.__tsPageEvents`). It is installed:
14
+ * - immediately when a session connects,
15
+ * - on every new document via `Page.addScriptToEvaluateOnNewDocument`,
16
+ * - again after a main-frame navigation (belt and braces).
17
+ *
18
+ * The script is dependency-free and returns a string so it can be injected
19
+ * with `Runtime.evaluate` or registered as a new-document script. It never
20
+ * reads field values and never logs anything.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.PAGE_EVENT_LIMIT = void 0;
24
+ exports.buildPageEventScript = buildPageEventScript;
25
+ /** Maximum events retained in the page ring buffer (oldest dropped). */
26
+ exports.PAGE_EVENT_LIMIT = 80;
27
+ /**
28
+ * Build the injected page-event script.
29
+ *
30
+ * IMPORTANT: regex escapes must be DOUBLE-escaped inside this template literal
31
+ * (`\\s`), otherwise the injected JS receives `s` (the exact bug the bridge lint
32
+ * gate found in another injected script).
33
+ */
34
+ function buildPageEventScript() {
35
+ return `
36
+ (() => {
37
+ if (window.__tsEventInstalled) return 'already';
38
+ window.__tsEventInstalled = true;
39
+ const MAX = ${exports.PAGE_EVENT_LIMIT};
40
+ const events = [];
41
+ window.__tsPageEvents = events;
42
+ const clean = (v) => String(v == null ? '' : v).replace(/\\s+/g, ' ').trim().slice(0, 500);
43
+ const now = () => Date.now();
44
+ const vis = (el) => {
45
+ if (!el || el.nodeType !== 1) return false;
46
+ const r = el.getBoundingClientRect();
47
+ if (r.width === 0 || r.height === 0) return false;
48
+ const cs = getComputedStyle(el);
49
+ return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
50
+ };
51
+ const push = (type, text, extra) => {
52
+ const t = clean(text);
53
+ if (!t || t.length < 2) return;
54
+ const last = events[events.length - 1];
55
+ if (last && last.type === type && last.text === t && now() - last.at < 1200) return;
56
+ const row = { type: type, text: t, at: now() };
57
+ if (extra) { if (extra.role) row.role = extra.role; if (extra.count != null) row.count = extra.count; }
58
+ events.push(row);
59
+ if (events.length > MAX) events.splice(0, events.length - MAX);
60
+ };
61
+ const TOAST_SEL = '[role="alert"],[role="status"],[aria-live],[data-sonner-toast],' +
62
+ '[class*="toast"],[class*="Toast"],[class*="snackbar"],[class*="Snackbar"],' +
63
+ '[class*="notification"],[class*="Notification"],[class*="alert"],[class*="Alert"],' +
64
+ '[class*="banner"],[class*="Banner"]';
65
+ const kindOf = (el) => {
66
+ const role = String((el.getAttribute && el.getAttribute('role')) || '').toLowerCase();
67
+ const cls = String((el.className && typeof el.className === 'string') ? el.className : '') + ' ' + String(el.id || '');
68
+ const live = String((el.getAttribute && el.getAttribute('aria-live')) || '').toLowerCase();
69
+ if (role === 'alert' || /error|danger|invalid/i.test(cls)) return 'error';
70
+ if (/toast|snackbar|notification/i.test(cls)) return 'toast';
71
+ if (role === 'status' || live === 'polite' || live === 'assertive') return 'status';
72
+ if (/alert|banner/i.test(cls)) return 'alert';
73
+ return null;
74
+ };
75
+ const record = (el) => {
76
+ if (!el || el.nodeType !== 1 || !vis(el)) return;
77
+ // A container that holds a nested toast should not be recorded as a
78
+ // second, duplicate event; use the deepest matching node.
79
+ const nested = el.querySelector && el.querySelector(TOAST_SEL);
80
+ if (nested && nested !== el) return;
81
+ const kind = kindOf(el);
82
+ if (!kind) return;
83
+ const text = el.innerText || el.textContent || el.getAttribute('aria-label') || '';
84
+ push(kind, text, { role: String((el.getAttribute && el.getAttribute('role')) || '') });
85
+ };
86
+ const scan = (root) => {
87
+ if (!root || root.nodeType !== 1) return;
88
+ if (root.matches && root.matches(TOAST_SEL)) record(root);
89
+ if (root.querySelectorAll) {
90
+ const nodes = root.querySelectorAll(TOAST_SEL);
91
+ for (let i = 0; i < nodes.length && i < 12; i++) record(nodes[i]);
92
+ }
93
+ };
94
+ const dialogs = () => [...document.querySelectorAll('dialog[open],[role="dialog"],[aria-modal="true"]')].filter(vis);
95
+ let dialogCount = dialogs().length;
96
+ const topDialogText = () => {
97
+ const list = dialogs();
98
+ const el = list[list.length - 1];
99
+ return el ? clean(el.innerText || el.textContent || '') : '';
100
+ };
101
+ const obs = new MutationObserver((mutations) => {
102
+ for (const m of mutations) {
103
+ if (m.type === 'childList') {
104
+ for (const n of m.addedNodes) if (n && n.nodeType === 1) scan(n);
105
+ } else if (m.type === 'characterData') {
106
+ const p = m.target && m.target.parentElement;
107
+ if (p) scan(p);
108
+ }
109
+ }
110
+ const count = dialogs().length;
111
+ if (count !== dialogCount) {
112
+ if (count > 0) push('dialog', topDialogText(), { count: count });
113
+ else push('dialog-closed', 'dialog closed', { count: 0 });
114
+ dialogCount = count;
115
+ }
116
+ });
117
+ const root = document.documentElement || document.body;
118
+ if (root) {
119
+ obs.observe(root, { childList: true, subtree: true, characterData: true });
120
+ // Record anything already on screen once (a login modal that opened
121
+ // before this script ran).
122
+ scan(document.body || root);
123
+ }
124
+ return 'ok';
125
+ })()
126
+ `;
127
+ }
128
+ //# sourceMappingURL=page-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-events.js","sourceRoot":"","sources":["../../../src/lib/browser/page-events.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAsBH,oDA6FC;AAjHD,wEAAwE;AAC3D,QAAA,gBAAgB,GAAG,EAAE,CAAC;AAYnC;;;;;;GAMG;AACH,SAAgB,oBAAoB;IAClC,OAAO;;;;oBAIW,wBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFjC,CAAC;AACJ,CAAC"}
@@ -117,6 +117,9 @@ function buildObserverScript() {
117
117
  });
118
118
  lastText = getText();
119
119
  window[KEY] = { observer: obs, changed: 0, mutated: 0 };
120
+ // String-addressable alias for OTHER injected scripts (the debug
121
+ // browser's waitFor reads it; a Symbol is not reachable by name).
122
+ window.__tsScreenObs = window[KEY];
120
123
  obs.observe(document.body || document.documentElement, {
121
124
  childList: true, subtree: true, characterData: true
122
125
  });
@@ -1 +1 @@
1
- {"version":3,"file":"screen-change.js","sourceRoot":"","sources":["../../../src/lib/browser/screen-change.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAcH,kDAkBC;AAOD,sDAIC;AAQD,kDA8DC;AA/GD,0EAA0E;AAC7D,QAAA,uBAAuB,GAAG,EAAE,CAAC;AAE1C;;;;;;;;GAQG;AACH,SAAgB,mBAAmB,CACjC,QAAgB,EAChB,QAAgB;IAEhB,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpB,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM;gBAAE,OAAO,EAAE,CAAC;;gBACpD,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/D,OAAO,KAAK,GAAG,EAAE,GAAG,OAAO,GAAG,EAAE,GAAG,OAAO,GAAG,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CACnC,MAA4D;IAE5D,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB;IACjC,OAAO;;;;;sCAK6B,mBAAmB,CAAC,QAAQ,EAAE;;;;;;;;;;;;;;;;qDAgBf,+BAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuCvE,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"screen-change.js","sourceRoot":"","sources":["../../../src/lib/browser/screen-change.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAcH,kDAkBC;AAOD,sDAIC;AAQD,kDAiEC;AAlHD,0EAA0E;AAC7D,QAAA,uBAAuB,GAAG,EAAE,CAAC;AAE1C;;;;;;;;GAQG;AACH,SAAgB,mBAAmB,CACjC,QAAgB,EAChB,QAAgB;IAEhB,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpB,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM;gBAAE,OAAO,EAAE,CAAC;;gBACpD,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/D,OAAO,KAAK,GAAG,EAAE,GAAG,OAAO,GAAG,EAAE,GAAG,OAAO,GAAG,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CACnC,MAA4D;IAE5D,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB;IACjC,OAAO;;;;;sCAK6B,mBAAmB,CAAC,QAAQ,EAAE;;;;;;;;;;;;;;;;qDAgBf,+BAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0CvE,CAAC;AACN,CAAC"}
@@ -2,6 +2,8 @@ import type { CdpPage, ElementInfo, PageInfo, PageText, PageLink } from './cdp';
2
2
  import { type NetworkEntry, type ConsoleEntry } from './network-log';
3
3
  import { type ReadStagedResult, type FillExcelResult } from './xlsx-template';
4
4
  import { type LoginResult, type PerformLoginOptions } from './login';
5
+ import { type NativeDialogState, type PageSnapshot, type WaitForKind } from './page-context';
6
+ import { type PageEvent } from './page-events';
5
7
  /**
6
8
  * Persistent Chrome profile for the AUTO-ANSWER browser (research reads,
7
9
  * `browse --auto-answer`, the web-auto-answer tool).
@@ -78,6 +80,13 @@ export declare class BrowserSession {
78
80
  * { action: "networkLog" }` when it needs to prove an API mismatch.
79
81
  */
80
82
  private network;
83
+ /**
84
+ * The native alert/confirm/prompt/beforeunload currently blocking the page
85
+ * (2026-10). Captured from `Page.javascriptDialogOpening`; other actions are
86
+ * refused while it is set, and `browser_action({action:"dialog"})` resolves
87
+ * it. A native dialog blocks the renderer, so a page snapshot would hang.
88
+ */
89
+ private _nativeDialog;
81
90
  constructor(id: string, opts?: BrowserSessionOptions);
82
91
  /** The stable session name the browser manager keys this session by. */
83
92
  get sessionKey(): string;
@@ -99,6 +108,8 @@ export declare class BrowserSession {
99
108
  */
100
109
  setAllowCredentials(allow: boolean): void;
101
110
  get allowCredentials(): boolean;
111
+ /** The native JS dialog currently blocking the page, if any. */
112
+ get nativeDialog(): NativeDialogState | null;
102
113
  /** Last time a command actually used this session (drives idle close). */
103
114
  private _lastUsedAt;
104
115
  /** When this session was last used — the idle-close timer reads this. */
@@ -133,6 +144,37 @@ export declare class BrowserSession {
133
144
  * Connect to a specific tab by targetId.
134
145
  */
135
146
  connectToTab(targetId: string): Promise<PageInfo>;
147
+ /**
148
+ * Install the debug browser's page instrumentation (2026-10):
149
+ *
150
+ * - the persistent toast/alert/dialog ring buffer (`page-events.ts`),
151
+ * registered for FUTURE documents via
152
+ * `Page.addScriptToEvaluateOnNewDocument` and injected into the current
153
+ * one immediately;
154
+ * - the screen-change observer (`__tsScreenObs.mutated`) so `waitFor
155
+ * {for:"change"}` knows a click had an effect without a blind sleep;
156
+ * - the native-dialog listener (`Page.javascriptDialogOpening`), because an
157
+ * alert/confirm/prompt blocks the renderer and must be surfaced and
158
+ * resolved explicitly.
159
+ *
160
+ * All pieces are idempotent in the page. The CDP listeners are bound once per
161
+ * page object (a reconnect creates a new one).
162
+ */
163
+ private instrumentPage;
164
+ /**
165
+ * Resolve a native JS dialog (alert/confirm/prompt/beforeunload).
166
+ * `accept:false` dismisses it; `promptText` is used by a prompt.
167
+ */
168
+ dialogAction(opts: {
169
+ accept?: boolean;
170
+ promptText?: string;
171
+ }): Promise<{
172
+ ok: boolean;
173
+ accepted?: boolean;
174
+ type?: string;
175
+ message?: string;
176
+ error?: string;
177
+ }>;
136
178
  /**
137
179
  * Navigate to a URL. Returns page info after navigation.
138
180
  */
@@ -167,7 +209,9 @@ export declare class BrowserSession {
167
209
  * credential for the run - the debug browser is the QA surface, the
168
210
  * auto-answer loop is a separate path that still never touches credentials.
169
211
  */
170
- type(text: string): Promise<void>;
212
+ type(text: string, opts?: {
213
+ clearFirst?: boolean;
214
+ }): Promise<void>;
171
215
  /**
172
216
  * Deterministically fill a login form (username + password) and submit.
173
217
  * Handles the common two-step email -> Continue -> password flow. Requires
@@ -311,6 +355,62 @@ export declare class BrowserSession {
311
355
  * Get current page info.
312
356
  */
313
357
  pageInfo(): Promise<PageInfo>;
358
+ /**
359
+ * Full page state (2026-10): dialogs (topmost with buttons/fields), toasts
360
+ * (persistent buffer), visible errors, form fields (lengths only), focus,
361
+ * busy, headings, optional page text, pending requests and console errors.
362
+ *
363
+ * This is the debug browser's equivalent of the auto-answer loop's rich
364
+ * `extractPageContext` - and it is what lets an agent see a login modal
365
+ * instead of guessing with raw `evaluate`.
366
+ */
367
+ snapshot(opts?: {
368
+ includeText?: boolean;
369
+ maxTextChars?: number;
370
+ maxFields?: number;
371
+ }): Promise<PageSnapshot>;
372
+ /**
373
+ * Bounded state attached to every mutating action result. Never include the
374
+ * page text (that is what `snapshot` is for); a failure returns null so an
375
+ * action result is never failed by its own observation.
376
+ */
377
+ compactSnapshot(): Promise<Record<string, unknown> | null>;
378
+ /**
379
+ * Recent toast / alert / dialog events from the persistent page buffer.
380
+ * This is how a toast that disappeared before the next tool call is still
381
+ * visible.
382
+ */
383
+ events(opts?: {
384
+ limit?: number;
385
+ clear?: boolean;
386
+ since?: number;
387
+ }): Promise<{
388
+ events: PageEvent[];
389
+ count: number;
390
+ }>;
391
+ /**
392
+ * Wait until a page condition is true, then return the current screen.
393
+ *
394
+ * This replaces the manual `evaluate` polling loop the QA agent used to run
395
+ * (and which broke on "Inspected target navigated"). On timeout it returns
396
+ * `matched:false` WITH the current screen, so the caller can see why.
397
+ */
398
+ waitFor(opts: {
399
+ for: WaitForKind;
400
+ selector?: string;
401
+ text?: string;
402
+ urlIncludes?: string;
403
+ state?: 'open' | 'closed';
404
+ loginPhase?: 'username' | 'password' | 'otp' | 'done';
405
+ timeoutMs?: number;
406
+ pollMs?: number;
407
+ }): Promise<{
408
+ matched: boolean;
409
+ elapsedMs: number;
410
+ for: WaitForKind;
411
+ screen: Record<string, unknown> | null;
412
+ nativeDialog: NativeDialogState | null;
413
+ }>;
314
414
  /**
315
415
  * Is the page actually usable right now? (2026-10)
316
416
  *