@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.
- package/package.json +14 -8
- package/packages/agent-client/src/client.js +18 -1
- package/packages/agent-client/src/types.ts +2 -0
- package/packages/mcp-server/src/handlers-capture.js +19 -7
- package/packages/mcp-server/src/handlers-dom.js +6 -1
- package/packages/mcp-server/src/handlers-utils.js +3 -0
- package/packages/mcp-server/src/server.js +59 -3
- package/packages/protocol/src/defaults.js +16 -0
- package/packages/protocol/src/protocol.js +366 -12
- package/packages/protocol/src/registry.js +34 -10
- package/packages/protocol/src/summary.js +249 -38
- package/packages/protocol/src/types.ts +157 -7
- package/skills/browser-bridge/SKILL.md +23 -13
- package/skills/browser-bridge/references/interaction.md +86 -21
- package/skills/browser-bridge/references/protocol.md +39 -10
- package/skills/browser-bridge/references/token-efficiency.md +12 -6
|
@@ -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
|
-
|
|
62
|
-
text: (r) =>
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
700
|
-
|
|
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
|
-
|
|
707
|
-
|
|
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
|
-
|
|
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:
|
|
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 {
|