@browserbridge/bbx 1.11.3 → 1.12.0

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.
@@ -56,12 +56,192 @@ import { sanitizeIncidentalUrl } from './incidental-sanitizer.js';
56
56
  * }} BatchItemSummary
57
57
  */
58
58
 
59
+ /** Evaluate values longer than this stay out of the summary; evidence holds them. */
60
+ const MAX_EVALUATE_SUMMARY_CHARS = 160;
61
+ const EVALUATE_PREVIEW_CHARS = 80;
62
+
63
+ /**
64
+ * Keep evaluate summaries short: small values inline, larger ones as a shape
65
+ * description, because the full value is already delivered in evidence.
66
+ *
67
+ * @param {unknown} value
68
+ * @returns {string}
69
+ */
70
+ function summarizeEvaluateValue(value) {
71
+ const json = JSON.stringify(value) ?? String(value);
72
+ if (json.length <= MAX_EVALUATE_SUMMARY_CHARS) return json;
73
+ const shape = Array.isArray(value)
74
+ ? `${value.length} item(s)`
75
+ : value && typeof value === 'object'
76
+ ? `${Object.keys(value).length} key(s)`
77
+ : `${json.length} chars`;
78
+ return `${shape}, ${json.length} chars as JSON; full value in evidence.value`;
79
+ }
80
+
81
+ /**
82
+ * @param {string | undefined} method
83
+ * @param {Record<string, unknown>} result
84
+ * @returns {string}
85
+ */
86
+ function describeTabResult(method, result) {
87
+ const url = typeof result.url === 'string' && result.url ? ` (${result.url})` : '';
88
+ switch (method) {
89
+ case 'navigation.reload':
90
+ return `Reloaded tab ${result.tabId}${url}.`;
91
+ case 'navigation.go_back':
92
+ return `Navigated back in tab ${result.tabId}${url}.`;
93
+ case 'navigation.go_forward':
94
+ return `Navigated forward in tab ${result.tabId}${url}.`;
95
+ case 'tabs.activate':
96
+ return `Tab ${result.tabId} activated${url}.`;
97
+ case 'page.wait_for_load_state':
98
+ return `Tab ${result.tabId} ${typeof result.status === 'string' ? result.status : 'ready'}${url}.`;
99
+ default:
100
+ return `Tab ${result.tabId} created${url}.`;
101
+ }
102
+ }
103
+
104
+ const ROUTINE_STRATEGIES = new Set(['elementRef', 'selector-first', 'locator-first']);
105
+
106
+ /**
107
+ * Keep input evidence lean: resolution/execution metadata only when something
108
+ * notable happened (scroll, wait, ranking, recovery, CDP, fallback), plus any
109
+ * observed effects. Routine successes cost a few tokens instead of ~100.
110
+ *
111
+ * @param {Record<string, unknown>} r
112
+ * @param {Record<string, unknown>} [extra]
113
+ * @returns {Record<string, unknown>}
114
+ */
115
+ function compactInputEvidence(r, extra = {}) {
116
+ /** @type {Record<string, unknown>} */
117
+ const evidence = { elementRef: r.elementRef ?? null, ...extra };
118
+ const resolution = toRecord(r.resolution);
119
+ if (
120
+ resolution.scrolled === true ||
121
+ resolution.recovered === true ||
122
+ (typeof resolution.waitedMs === 'number' && resolution.waitedMs > 0) ||
123
+ (typeof resolution.strategy === 'string' && !ROUTINE_STRATEGIES.has(resolution.strategy))
124
+ ) {
125
+ /** @type {Record<string, unknown>} */
126
+ const notable = { strategy: resolution.strategy };
127
+ for (const key of ['scrolled', 'recovered', 'waitedMs', 'candidateCount', 'oldRef', 'newRef']) {
128
+ const value = resolution[key];
129
+ if (value !== undefined && value !== false && !(key === 'candidateCount' && value === 1)) {
130
+ notable[key] = value;
131
+ }
132
+ }
133
+ evidence.resolution = notable;
134
+ }
135
+ const execution = toRecord(r.execution);
136
+ if (execution.actualMode === 'cdp' || execution.fallbackReason) {
137
+ evidence.execution = {
138
+ actualMode: execution.actualMode,
139
+ ...(execution.fallbackReason ? { fallbackReason: execution.fallbackReason } : {}),
140
+ };
141
+ }
142
+ if (r.effects && typeof r.effects === 'object') evidence.effects = r.effects;
143
+ return Object.fromEntries(Object.entries(evidence).filter(([, value]) => value !== undefined));
144
+ }
145
+
146
+ /**
147
+ * Health evidence without idle diagnostics: zero-valued recovery counters,
148
+ * internal latency stats, and idle debugger/capture state are dropped, which
149
+ * cuts a routine health check from ~1k tokens to ~200.
150
+ *
151
+ * @param {Record<string, unknown>} result
152
+ * @returns {Record<string, unknown>}
153
+ */
154
+ function compactHealthEvidence(result) {
155
+ const { recovery, domBaselines, debugger: debuggerState, capture, ...rest } = result;
156
+ /** @type {Record<string, unknown>} */
157
+ const evidence = { ...rest };
158
+ /** @type {Record<string, Record<string, unknown>>} */
159
+ const activeRecovery = {};
160
+ for (const [scope, value] of Object.entries(toRecord(recovery))) {
161
+ const events = toRecord(toRecord(value).events);
162
+ for (const [event, stats] of Object.entries(events)) {
163
+ const record = toRecord(stats);
164
+ if (record.attempts || record.pending || record.activeLoop) {
165
+ activeRecovery[`${scope}.${event}`] = {
166
+ attempts: record.attempts,
167
+ failures: record.failures,
168
+ ...(record.activeLoop ? { activeLoop: true } : {}),
169
+ };
170
+ }
171
+ }
172
+ }
173
+ if (Object.keys(activeRecovery).length) evidence.recovery = activeRecovery;
174
+ const baselineCount = toRecord(domBaselines).baselineCount;
175
+ if (typeof baselineCount === 'number' && baselineCount > 0) {
176
+ evidence.domBaselines = { baselineCount, bytes: toRecord(domBaselines).bytes };
177
+ }
178
+ const debuggerRecord = toRecord(debuggerState);
179
+ if (debuggerState && (debuggerRecord.status !== 'idle' || debuggerRecord.attachedTabCount)) {
180
+ evidence.debugger = debuggerState;
181
+ }
182
+ const captureRecord = toRecord(capture);
183
+ if (capture && captureRecord.state !== 'stopped') evidence.capture = capture;
184
+ return evidence;
185
+ }
186
+
187
+ /**
188
+ * One-line digest of observed post-action effects.
189
+ *
190
+ * @param {unknown} value
191
+ * @returns {string}
192
+ */
193
+ function describeEffects(value) {
194
+ if (!value || typeof value !== 'object') return '';
195
+ const effects = /** @type {Record<string, unknown>} */ (value);
196
+ /** @type {string[]} */
197
+ const parts = [];
198
+ if (effects.navigation === 'unloading') parts.push('page is navigating');
199
+ if (typeof effects.url === 'string') parts.push(`URL → ${truncateUrl(effects.url)}`);
200
+ for (const dialog of Array.isArray(effects.dialogs) ? effects.dialogs : []) {
201
+ const record = toRecord(dialog);
202
+ parts.push(
203
+ `${record.role ?? 'dialog'}${record.name ? ` "${record.name}"` : ''} opened [${record.ref}]`
204
+ );
205
+ }
206
+ for (const message of Array.isArray(effects.messages) ? effects.messages : []) {
207
+ const record = toRecord(message);
208
+ parts.push(`${record.role ?? 'status'}: "${record.text}"`);
209
+ }
210
+ const focused = toRecord(effects.focused);
211
+ if (focused.ref)
212
+ parts.push(
213
+ `focus → ${focused.role ?? ''}${focused.name ? ` "${focused.name}"` : ''} [${focused.ref}]`
214
+ );
215
+ const dom = toRecord(effects.dom);
216
+ if (typeof dom.added === 'number' || typeof dom.removed === 'number') {
217
+ parts.push(`DOM +${dom.added ?? 0}/-${dom.removed ?? 0}`);
218
+ }
219
+ if (toRecord(effects.target).removed === true) parts.push('target removed');
220
+ if (!parts.length) return effects.changed === false ? ' No visible effect.' : '';
221
+ return ` Effects: ${parts.join('; ')}.`;
222
+ }
223
+
59
224
  /** @type {Record<string, ActionSummary>} */
60
225
  const ACTION_SUMMARIES = {
61
- hovered: {
62
- text: (r) => `Hover ${r.hovered ? 'active' : 'failed'} on ${r.elementRef}.`,
226
+ performed: {
227
+ text: (r) => {
228
+ const failures = Array.isArray(r.failures) ? r.failures.length : 0;
229
+ return `Performed ${r.completed ?? 0}/${r.total ?? 0} step(s) in ${r.elapsedMs ?? 0}ms${failures ? ` with ${failures} failed step(s)` : ''}.`;
230
+ },
231
+ evidence: (r) => r,
232
+ },
233
+ touched: {
234
+ text: (r) => {
235
+ const mode = toRecord(r.execution).actualMode;
236
+ return `Touched ${r.pointCount ?? 0} point(s)${typeof mode === 'string' ? ` via ${mode}` : ''}.`;
237
+ },
63
238
  evidence: (r) => r,
64
239
  },
240
+ hovered: {
241
+ text: (r) =>
242
+ `Hover ${r.hovered ? 'active' : 'failed'} on ${r.elementRef}.${describeEffects(r.effects)}`,
243
+ evidence: (r) => compactInputEvidence(r),
244
+ },
65
245
  dragged: {
66
246
  text: (r) =>
67
247
  `Drag ${r.dragged ? 'completed' : 'failed'}: ${r.sourceRef} → ${r.destinationRef}.`,
@@ -71,33 +251,25 @@ const ACTION_SUMMARIES = {
71
251
  clicked: {
72
252
  text: (r) => {
73
253
  const mode = toRecord(r.execution).actualMode;
74
- return `Clicked ${r.elementRef ?? 'element'}${typeof mode === 'string' ? ` via ${mode}` : ''}.`;
254
+ return `Clicked ${r.elementRef ?? 'element'}${typeof mode === 'string' ? ` via ${mode}` : ''}.${describeEffects(r.effects)}`;
75
255
  },
76
- evidence: (r) => ({
77
- elementRef: r.elementRef,
78
- resolution: r.resolution,
79
- execution: r.execution,
80
- }),
256
+ evidence: (r) => compactInputEvidence(r),
81
257
  },
82
258
  focused: {
83
- text: (r) => `Focused ${r.elementRef ?? 'element'}.`,
84
- evidence: (r) => ({
85
- elementRef: r.elementRef,
86
- resolution: r.resolution,
87
- execution: r.execution,
88
- }),
259
+ text: (r) => `Focused ${r.elementRef ?? 'element'}.${describeEffects(r.effects)}`,
260
+ evidence: (r) => compactInputEvidence(r),
89
261
  },
90
262
  typed: {
91
- text: (r) => `Typed into ${r.elementRef ?? 'element'}.`,
92
- evidence: (r) => ({
93
- elementRef: r.elementRef,
94
- resolution: r.resolution,
95
- execution: r.execution,
96
- }),
263
+ text: (r) => `Typed into ${r.elementRef ?? 'element'}.${describeEffects(r.effects)}`,
264
+ evidence: (r) => compactInputEvidence(r, { value: r.value }),
97
265
  },
98
266
  pressed: {
99
- text: (r) => `Key pressed${r.key ? ` (${r.key})` : ''}.`,
100
- evidence: (r) => r,
267
+ text: (r) => `Key pressed${r.key ? ` (${r.key})` : ''}.${describeEffects(r.effects)}`,
268
+ evidence: (r) => {
269
+ const { resolution: _resolution, execution: _execution, effects: _effects, ...rest } = r;
270
+ const { elementRef: _elementRef, ...compact } = compactInputEvidence(r);
271
+ return { ...rest, ...compact };
272
+ },
101
273
  },
102
274
  navigated: {
103
275
  text: (r) => `Navigated to ${r.url ?? 'page'}.`,
@@ -183,6 +355,19 @@ function getMetaCostClass(meta, field) {
183
355
  * @returns {BridgeSummary}
184
356
  */
185
357
  export function summarizeBridgeResponse(response, method) {
358
+ const summarized = summarizeBridgeResponseBody(response, method);
359
+ const routingNote = response.ok ? getTabRoutingNote(response.meta) : null;
360
+ return routingNote
361
+ ? { ...summarized, summary: `${summarized.summary} ${routingNote}` }
362
+ : summarized;
363
+ }
364
+
365
+ /**
366
+ * @param {BridgeResponse} response
367
+ * @param {string} [method]
368
+ * @returns {BridgeSummary}
369
+ */
370
+ function summarizeBridgeResponseBody(response, method) {
186
371
  const protocolWarning = getProtocolWarning(response.meta);
187
372
  if (!response.ok) {
188
373
  const recovery = response.error.recovery ?? getErrorRecovery(response.error.code);
@@ -291,7 +476,7 @@ export function summarizeBridgeResponse(response, method) {
291
476
  access == null
292
477
  ? ''
293
478
  : access.enabled
294
- ? ` Access: ${access.routeReady ? `ready on tab ${access.routeTabId}.` : `enabled${typeof access.reason === 'string' ? ` (${access.reason})` : '.'}`}`
479
+ ? ` Access: ${access.routeReady ? `ready on tab ${access.routeTabId}.` : `enabled${typeof access.reason === 'string' ? ` (${access.reason})` : '.'}`}${typeof access.workingTabId === 'number' && access.workingTabId !== access.routeTabId ? ` Your working tab is ${access.workingTabId}.` : ''}`
295
480
  : ' Access: disabled.';
296
481
  const connectedExtensions = Array.isArray(result.connectedExtensions)
297
482
  ? /** @type {Array<Record<string, unknown>>} */ (result.connectedExtensions)
@@ -310,7 +495,7 @@ export function summarizeBridgeResponse(response, method) {
310
495
  `Daemon: ${result.daemon}. Extension: ${extensionSummary}.${accessSummary}`,
311
496
  protocolWarning
312
497
  ),
313
- evidence: result,
498
+ evidence: compactHealthEvidence(result),
314
499
  };
315
500
  }
316
501
  if (Array.isArray(result.mcpClients) && Array.isArray(result.skillTargets)) {
@@ -411,6 +596,18 @@ export function summarizeBridgeResponse(response, method) {
411
596
  })),
412
597
  };
413
598
  }
599
+ if (typeof result.outline === 'string') {
600
+ const count = typeof result.count === 'number' ? result.count : 0;
601
+ const refs = result.source === 'dom' ? '; refs work as input targets' : '';
602
+ return {
603
+ ok: true,
604
+ summary: appendProtocolWarning(
605
+ `Accessibility outline: ${count} node(s)${result.truncated === true ? ', truncated' : ''}${refs}.`,
606
+ protocolWarning
607
+ ),
608
+ evidence: result.outline,
609
+ };
610
+ }
414
611
  if (
415
612
  Array.isArray(result.nodes) &&
416
613
  typeof result.total === 'number' &&
@@ -469,6 +666,7 @@ export function summarizeBridgeResponse(response, method) {
469
666
  if (Array.isArray(n.children) && n.children.length) {
470
667
  entry.childCount = n.children.length;
471
668
  }
669
+ if (n.visible === false) entry.visible = false;
472
670
  return entry;
473
671
  });
474
672
  const isFind = method === 'dom.find_by_text' || method === 'dom.find_by_role';
@@ -486,6 +684,9 @@ export function summarizeBridgeResponse(response, method) {
486
684
  } else {
487
685
  summary = `Found ${count} matching element(s)${scanned === null ? '' : ` after scanning ${scanned}`}.`;
488
686
  }
687
+ if (typeof result.hiddenMatches === 'number' && result.hiddenMatches > 0) {
688
+ summary += ` ${result.hiddenMatches} hidden match(es) omitted (includeHidden: true to list).`;
689
+ }
489
690
  return {
490
691
  ok: true,
491
692
  summary: appendProtocolWarning(summary, protocolWarning),
@@ -545,7 +746,10 @@ export function summarizeBridgeResponse(response, method) {
545
746
  } else if (isNull) {
546
747
  repr = '';
547
748
  } else if (typeof result.value === 'string') {
548
- repr = result.value.length > 200 ? `${result.value.slice(0, 199)}\u2026` : result.value;
749
+ repr =
750
+ result.value.length > MAX_EVALUATE_SUMMARY_CHARS
751
+ ? `${result.value.length} chars, starts ${JSON.stringify(result.value.slice(0, EVALUATE_PREVIEW_CHARS))}\u2026`
752
+ : result.value;
549
753
  } else if (
550
754
  typeof result.value === 'object' &&
551
755
  result.value !== null &&
@@ -553,7 +757,7 @@ export function summarizeBridgeResponse(response, method) {
553
757
  ) {
554
758
  repr = '(empty - may be a Promise, Map, or non-serializable value)';
555
759
  } else {
556
- repr = JSON.stringify(result.value);
760
+ repr = summarizeEvaluateValue(result.value);
557
761
  }
558
762
  const typeLabel = isNull ? 'null' : result.type;
559
763
  return {
@@ -692,20 +896,18 @@ export function summarizeBridgeResponse(response, method) {
692
896
  return {
693
897
  ok: true,
694
898
  summary: appendProtocolWarning(
695
- `${method} completed via ${execution.actualMode ?? 'dom'}${result.elementRef ? ` on ${result.elementRef}` : ''}.`,
899
+ `${method} completed via ${execution.actualMode ?? 'dom'}${result.elementRef ? ` on ${result.elementRef}` : ''}.${describeEffects(result.effects)}`,
696
900
  protocolWarning
697
901
  ),
698
- evidence: {
699
- elementRef: result.elementRef ?? null,
700
- sourceRef: result.sourceRef ?? null,
701
- destinationRef: result.destinationRef ?? null,
902
+ evidence: compactInputEvidence(result, {
903
+ sourceRef: result.sourceRef ?? undefined,
904
+ destinationRef: result.destinationRef ?? undefined,
702
905
  typed: result.typed,
703
906
  checked: result.checked,
704
907
  selectedValues: result.selectedValues,
705
908
  mode: result.mode,
706
- resolution: result.resolution,
707
- execution: result.execution,
708
- },
909
+ value: result.value,
910
+ }),
709
911
  };
710
912
  }
711
913
  if (typeof result.tabId === 'number' && typeof result.url === 'string') {
@@ -724,10 +926,7 @@ export function summarizeBridgeResponse(response, method) {
724
926
  }
725
927
  return {
726
928
  ok: true,
727
- summary: appendProtocolWarning(
728
- `Tab ${result.tabId} created${result.url ? ` (${result.url})` : ''}.`,
729
- protocolWarning
730
- ),
929
+ summary: appendProtocolWarning(describeTabResult(actionMethod, result), protocolWarning),
731
930
  evidence: result,
732
931
  };
733
932
  }
@@ -947,6 +1146,18 @@ function getProtocolWarning(meta) {
947
1146
  : null;
948
1147
  }
949
1148
 
1149
+ /**
1150
+ * Tell the agent when it kept working in its own tab while the user looked at
1151
+ * a different one, so "this tab" requests can be redirected explicitly.
1152
+ *
1153
+ * @param {Record<string, unknown> | null | undefined} meta
1154
+ * @returns {string | null}
1155
+ */
1156
+ function getTabRoutingNote(meta) {
1157
+ if (typeof meta?.tab_id !== 'number' || typeof meta.active_tab_id !== 'number') return null;
1158
+ return `Ran in working tab ${meta.tab_id}; the user's active tab is ${meta.active_tab_id} (pass tabId to switch).`;
1159
+ }
1160
+
950
1161
  /**
951
1162
  * @param {string} summary
952
1163
  * @param {string | null} warning
@@ -119,6 +119,8 @@ export type BridgeMethod =
119
119
  | 'input.select_option'
120
120
  | 'input.hover'
121
121
  | 'input.drag'
122
+ | 'input.touch'
123
+ | 'input.perform'
122
124
  | 'input.scroll_into_view'
123
125
  | 'screenshot.capture_region'
124
126
  | 'screenshot.capture_element'
@@ -164,6 +166,14 @@ export interface BridgeMeta {
164
166
  continuation_hint?: string | null;
165
167
  automatic_retry?: { attempt: 2; reason: 'retryable_error' };
166
168
  stale_recovery?: 'success' | 'failure';
169
+ /** Agent session for sticky working-tab routing; unset callers share a default. */
170
+ agent_session?: string;
171
+ /** Tab the request ran in. */
172
+ tab_id?: number;
173
+ /** How the tab was chosen: explicit tabId, the agent's working tab, or the active tab. */
174
+ tab_routing?: 'explicit' | 'working' | 'active';
175
+ /** Present when the user's active tab differs from the tab the request ran in. */
176
+ active_tab_id?: number;
167
177
  [key: string]: unknown;
168
178
  }
169
179
 
@@ -442,13 +452,42 @@ export interface NormalizedStyleQuery extends BridgeParams {
442
452
 
443
453
  export interface InputTarget {
444
454
  elementRef?: string;
455
+ /** CSS selector; with a locator it narrows the locator's candidates. */
445
456
  selector?: string;
457
+ /** ARIA role, e.g. "button" or "textbox". */
458
+ role?: string;
459
+ /** Accessible name (substring, case-insensitive unless exact). */
460
+ name?: string;
461
+ /** Visible text of the innermost matching element. */
462
+ text?: string;
463
+ /** Associated label text for form controls. */
464
+ label?: string;
465
+ placeholder?: string;
466
+ /** data-testid / data-test-id / data-test / data-qa value. */
467
+ testId?: string;
468
+ exact?: boolean;
469
+ /** Zero-based index among visible locator matches. */
470
+ nth?: number;
471
+ }
472
+
473
+ export interface InputWaitObserveParams {
474
+ /** Wait up to this long for the target to exist and become actionable (0 disables). */
475
+ timeoutMs?: number;
476
+ /** Report post-action effects; true or { settleMs }. Defaults on for click/press_key/select/check. */
477
+ observe?: boolean | { settleMs?: number } | null;
446
478
  }
447
479
 
448
- export type InputExecutionMode = 'dom' | 'cdp';
480
+ /** auto picks cdp only when trusted input is likely required (or the debugger is already attached). */
481
+ export type InputExecutionMode = 'dom' | 'cdp' | 'auto';
449
482
 
450
483
  export interface InputResolutionMetadata {
451
- strategy: 'elementRef' | 'selector-first' | 'selector-ranked' | 'stale-recovery';
484
+ strategy:
485
+ | 'elementRef'
486
+ | 'selector-first'
487
+ | 'selector-ranked'
488
+ | 'locator-first'
489
+ | 'locator-ranked'
490
+ | 'stale-recovery';
452
491
  candidateCount: number;
453
492
  evaluatedCount: number;
454
493
  scrolled: boolean;
@@ -458,6 +497,8 @@ export interface InputResolutionMetadata {
458
497
  newRef?: string;
459
498
  matchedFields?: string[];
460
499
  confidenceBasis?: string;
500
+ /** Time spent waiting for the target to become actionable. */
501
+ waitedMs?: number;
461
502
  }
462
503
 
463
504
  export interface InputExecutionMetadata {
@@ -468,7 +509,7 @@ export interface InputExecutionMetadata {
468
509
  targetCoordinates?: { x: number; y: number };
469
510
  }
470
511
 
471
- export interface InputActionParams {
512
+ export interface InputActionParams extends InputWaitObserveParams {
472
513
  target?: InputTarget;
473
514
  button?: 'left' | 'middle' | 'right';
474
515
  clickCount?: number;
@@ -479,6 +520,7 @@ export interface InputActionParams {
479
520
  submit?: boolean;
480
521
  key?: string;
481
522
  modifiers?: string[];
523
+ holdMs?: number;
482
524
  executionMode?: InputExecutionMode;
483
525
  recoverStale?: boolean;
484
526
  }
@@ -494,10 +536,106 @@ export interface NormalizedInputAction extends BridgeParams {
494
536
  submit: boolean;
495
537
  key: string;
496
538
  modifiers: string[];
539
+ holdMs: number;
497
540
  executionMode: InputExecutionMode;
498
541
  recoverStale: boolean;
499
542
  }
500
543
 
544
+ // Nullable fields let already-normalized params pass through normalization again.
545
+ export interface TouchPositionParams {
546
+ target?: InputTarget | null;
547
+ x?: number | null;
548
+ y?: number | null;
549
+ }
550
+
551
+ export interface TouchPointParams extends TouchPositionParams {
552
+ to?: TouchPositionParams | null;
553
+ }
554
+
555
+ export interface TouchParams {
556
+ points?: TouchPointParams[];
557
+ holdMs?: number;
558
+ moveSteps?: number;
559
+ executionMode?: InputExecutionMode;
560
+ recoverStale?: boolean;
561
+ }
562
+
563
+ export type NormalizedTouchPosition =
564
+ | { target: InputTarget; x: null; y: null }
565
+ | { target: null; x: number; y: number };
566
+
567
+ export type NormalizedTouchPoint = NormalizedTouchPosition & {
568
+ to: NormalizedTouchPosition | null;
569
+ };
570
+
571
+ export interface NormalizedTouchParams extends BridgeParams {
572
+ points: NormalizedTouchPoint[];
573
+ holdMs: number;
574
+ moveSteps: number;
575
+ executionMode: InputExecutionMode;
576
+ recoverStale: boolean;
577
+ }
578
+
579
+ export type PerformStepMethod =
580
+ | 'input.click'
581
+ | 'input.focus'
582
+ | 'input.type'
583
+ | 'input.fill'
584
+ | 'input.press_key'
585
+ | 'input.set_checked'
586
+ | 'input.select_option'
587
+ | 'input.hover'
588
+ | 'input.drag'
589
+ | 'input.touch'
590
+ | 'input.scroll_into_view'
591
+ | 'viewport.scroll'
592
+ | 'dom.wait_for'
593
+ | 'page.wait_for_load_state';
594
+
595
+ export interface PerformStepParams {
596
+ method?: string;
597
+ params?: Record<string, unknown>;
598
+ delayMs?: number | null;
599
+ atMs?: number | null;
600
+ }
601
+
602
+ export interface PerformParams {
603
+ steps?: PerformStepParams[];
604
+ executionMode?: InputExecutionMode;
605
+ timeoutMs?: number;
606
+ continueOnError?: boolean;
607
+ }
608
+
609
+ export interface NormalizedPerformStep {
610
+ method: PerformStepMethod;
611
+ params: Record<string, unknown>;
612
+ delayMs: number;
613
+ atMs: number | null;
614
+ }
615
+
616
+ export interface NormalizedPerformParams extends BridgeParams {
617
+ steps: NormalizedPerformStep[];
618
+ timeoutMs: number;
619
+ continueOnError: boolean;
620
+ }
621
+
622
+ export interface PerformStepFailure {
623
+ index: number;
624
+ method: PerformStepMethod;
625
+ code: string;
626
+ message: string;
627
+ details?: unknown;
628
+ }
629
+
630
+ export interface PerformResult {
631
+ performed: true;
632
+ completed: number;
633
+ total: number;
634
+ elapsedMs: number;
635
+ startedAtMs: number[];
636
+ failures: PerformStepFailure[];
637
+ }
638
+
501
639
  export interface CdpDispatchKeyEventParams {
502
640
  key?: string;
503
641
  code?: string;
@@ -526,7 +664,7 @@ export interface NormalizedCdpDomSnapshotParams extends BridgeParams {
526
664
  computedStyles: string[];
527
665
  }
528
666
 
529
- export interface CheckedActionParams {
667
+ export interface CheckedActionParams extends InputWaitObserveParams {
530
668
  target?: InputTarget;
531
669
  checked?: boolean;
532
670
  executionMode?: InputExecutionMode;
@@ -540,7 +678,7 @@ export interface NormalizedCheckedAction extends BridgeParams {
540
678
  recoverStale: boolean;
541
679
  }
542
680
 
543
- export interface SelectActionParams {
681
+ export interface SelectActionParams extends InputWaitObserveParams {
544
682
  target?: InputTarget;
545
683
  values?: string[];
546
684
  labels?: string[];
@@ -717,6 +855,7 @@ export interface FindByTextParams {
717
855
  exact?: boolean;
718
856
  selector?: string;
719
857
  maxResults?: number;
858
+ includeHidden?: boolean;
720
859
  }
721
860
 
722
861
  export interface NormalizedFindByTextParams extends BridgeParams {
@@ -724,20 +863,25 @@ export interface NormalizedFindByTextParams extends BridgeParams {
724
863
  exact: boolean;
725
864
  selector: string;
726
865
  maxResults: number;
866
+ includeHidden: boolean;
727
867
  }
728
868
 
729
869
  export interface FindByRoleParams {
730
870
  role?: string;
731
871
  name?: string;
872
+ exact?: boolean;
732
873
  selector?: string;
733
874
  maxResults?: number;
875
+ includeHidden?: boolean;
734
876
  }
735
877
 
736
878
  export interface NormalizedFindByRoleParams extends BridgeParams {
737
879
  role: string;
738
880
  name: string;
881
+ exact: boolean;
739
882
  selector: string;
740
883
  maxResults: number;
884
+ includeHidden: boolean;
741
885
  }
742
886
 
743
887
  export interface GetHtmlParams {
@@ -754,7 +898,7 @@ export interface NormalizedGetHtmlParams extends BridgeParams {
754
898
  maxLength: number;
755
899
  }
756
900
 
757
- export interface HoverParams {
901
+ export interface HoverParams extends InputWaitObserveParams {
758
902
  target?: InputTarget;
759
903
  duration?: number;
760
904
  modifiers?: string[];
@@ -770,7 +914,7 @@ export interface NormalizedHoverParams extends BridgeParams {
770
914
  recoverStale: boolean;
771
915
  }
772
916
 
773
- export interface DragParams {
917
+ export interface DragParams extends InputWaitObserveParams {
774
918
  source?: InputTarget;
775
919
  destination?: InputTarget;
776
920
  offsetX?: number;
@@ -954,6 +1098,10 @@ export interface AccessibilityTreeParams {
954
1098
  maxNodes?: number;
955
1099
  compact?: boolean;
956
1100
  interactiveOnly?: boolean;
1101
+ /** cdp (default): Chrome's AX tree via the debugger. dom: content-script outline with elementRefs, no debugger. */
1102
+ source?: 'cdp' | 'dom';
1103
+ /** outline: one compact `- role "name" [ref]` line per node (default for source=dom). */
1104
+ format?: 'tree' | 'outline';
957
1105
  }
958
1106
 
959
1107
  export interface NormalizedAccessibilityTreeParams extends BridgeParams {
@@ -962,6 +1110,8 @@ export interface NormalizedAccessibilityTreeParams extends BridgeParams {
962
1110
  maxNodes: number;
963
1111
  compact: boolean;
964
1112
  interactiveOnly: boolean;
1113
+ source?: 'dom';
1114
+ format?: 'outline';
965
1115
  }
966
1116
 
967
1117
  export interface AccessibilityTreeNode {