@browserbridge/bbx 1.6.1 → 1.6.2

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.
@@ -81,7 +81,7 @@ function isValidCaptureRegion(rect) {
81
81
  }
82
82
 
83
83
  /**
84
- * @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, nodeId?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
84
+ * @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, nodeId?: number, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
85
85
  * @returns {Promise<ToolResult>}
86
86
  */
87
87
  export async function handleCaptureTool(args) {
@@ -115,7 +115,7 @@ export const INPUT_ACTION_METHODS = {
115
115
  };
116
116
 
117
117
  /**
118
- * @param {{ action: string, elementRef?: string, selector?: string, button?: string, clickCount?: number, text?: string, clear?: boolean, submit?: boolean, key?: string, code?: string, modifiers?: string[], checked?: boolean, values?: string[], labels?: string[], indexes?: number[], duration?: number, sourceElementRef?: string, sourceSelector?: string, destinationElementRef?: string, destinationSelector?: string, offsetX?: number, offsetY?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
118
+ * @param {{ action: string, elementRef?: string, selector?: string, button?: string, clickCount?: number, text?: string, clear?: boolean, submit?: boolean, key?: string, code?: string, modifiers?: string[], checked?: boolean, values?: string[], labels?: string[], indexes?: number[], duration?: number, sourceElementRef?: string, sourceSelector?: string, destinationElementRef?: string, destinationSelector?: string, offsetX?: number, offsetY?: number, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
119
119
  * @returns {Promise<ToolResult>}
120
120
  */
121
121
  export async function handleInputTool(args) {
@@ -134,207 +134,210 @@ export async function handleInputTool(args) {
134
134
  return summarizeToolError('values, labels, or indexes are required for input.select_option.');
135
135
  }
136
136
 
137
- return withToolClient(async (client) => {
138
- const requestedTabId = typeof args.tabId === 'number' ? args.tabId : null;
139
- const elementTarget = async () => ({
140
- elementRef: await resolveToolRef(client, args, requestedTabId),
141
- });
137
+ return withToolClient(
138
+ async (client) => {
139
+ const requestedTabId = typeof args.tabId === 'number' ? args.tabId : null;
140
+ const elementTarget = async () => ({
141
+ elementRef: await resolveToolRef(client, args, requestedTabId),
142
+ });
142
143
 
143
- switch (args.action) {
144
- case 'click': {
145
- const response = await requestBridgeWithRetry(
146
- client,
147
- 'input.click',
148
- {
149
- target: await elementTarget(),
150
- button: args.button,
151
- clickCount: args.clickCount,
152
- modifiers: args.modifiers,
153
- },
154
- {
155
- tabId: requestedTabId,
156
- source: REQUEST_SOURCE,
157
- tokenBudget: getToolTokenBudget(args),
158
- }
159
- );
160
- return summarizeToolResponse(response, 'input.click');
161
- }
162
- case 'focus': {
163
- const response = await requestBridgeWithRetry(
164
- client,
165
- 'input.focus',
166
- {
167
- target: await elementTarget(),
168
- },
169
- {
170
- tabId: requestedTabId,
171
- source: REQUEST_SOURCE,
172
- tokenBudget: getToolTokenBudget(args),
173
- }
174
- );
175
- return summarizeToolResponse(response, 'input.focus');
176
- }
177
- case 'type': {
178
- const response = await requestBridgeWithRetry(
179
- client,
180
- 'input.type',
181
- {
182
- target: await elementTarget(),
183
- text: args.text,
184
- clear: args.clear,
185
- submit: args.submit,
186
- modifiers: args.modifiers,
187
- },
188
- {
189
- tabId: requestedTabId,
190
- source: REQUEST_SOURCE,
191
- tokenBudget: getToolTokenBudget(args),
192
- }
193
- );
194
- return summarizeToolResponse(response, 'input.type');
195
- }
196
- case 'press_key': {
197
- const target = args.elementRef || args.selector ? await elementTarget() : undefined;
198
- const response = await requestBridgeWithRetry(
199
- client,
200
- 'input.press_key',
201
- {
202
- target,
203
- key: args.key,
204
- modifiers: args.modifiers,
205
- },
206
- {
207
- tabId: requestedTabId,
208
- source: REQUEST_SOURCE,
209
- tokenBudget: getToolTokenBudget(args),
210
- }
211
- );
212
- return summarizeToolResponse(response, 'input.press_key');
213
- }
214
- case 'cdp_press_key': {
215
- const response = await requestBridgeWithRetry(
216
- client,
217
- 'cdp.dispatch_key_event',
218
- {
219
- key: args.key,
220
- code: args.code,
221
- modifiers: args.modifiers,
222
- },
223
- {
224
- tabId: requestedTabId,
225
- source: REQUEST_SOURCE,
226
- tokenBudget: getToolTokenBudget(args),
227
- }
228
- );
229
- return summarizeToolResponse(response, 'cdp.dispatch_key_event');
230
- }
231
- case 'set_checked': {
232
- const response = await requestBridgeWithRetry(
233
- client,
234
- 'input.set_checked',
235
- {
236
- target: await elementTarget(),
237
- checked: args.checked,
238
- },
239
- {
240
- tabId: requestedTabId,
241
- source: REQUEST_SOURCE,
242
- tokenBudget: getToolTokenBudget(args),
243
- }
244
- );
245
- return summarizeToolResponse(response, 'input.set_checked');
246
- }
247
- case 'select_option': {
248
- const response = await requestBridgeWithRetry(
249
- client,
250
- 'input.select_option',
251
- {
252
- target: await elementTarget(),
253
- values: args.values,
254
- labels: args.labels,
255
- indexes: args.indexes,
256
- },
257
- {
258
- tabId: requestedTabId,
259
- source: REQUEST_SOURCE,
260
- tokenBudget: getToolTokenBudget(args),
261
- }
262
- );
263
- return summarizeToolResponse(response, 'input.select_option');
264
- }
265
- case 'hover': {
266
- const response = await requestBridgeWithRetry(
267
- client,
268
- 'input.hover',
269
- {
270
- target: await elementTarget(),
271
- duration: args.duration,
272
- modifiers: args.modifiers,
273
- },
274
- {
275
- tabId: requestedTabId,
276
- source: REQUEST_SOURCE,
277
- tokenBudget: getToolTokenBudget(args),
278
- }
279
- );
280
- return summarizeToolResponse(response, 'input.hover');
281
- }
282
- case 'drag': {
283
- const source = {
284
- elementRef:
285
- args.sourceElementRef ||
286
- (args.sourceSelector
287
- ? await resolveRef(client, args.sourceSelector, requestedTabId, REQUEST_SOURCE)
288
- : ''),
289
- };
290
- const destination = {
291
- elementRef:
292
- args.destinationElementRef ||
293
- (args.destinationSelector
294
- ? await resolveRef(client, args.destinationSelector, requestedTabId, REQUEST_SOURCE)
295
- : ''),
296
- };
297
- if (!source.elementRef || !destination.elementRef) {
298
- return summarizeToolError(
299
- 'sourceElementRef/sourceSelector and destinationElementRef/destinationSelector are required for drag.'
144
+ switch (args.action) {
145
+ case 'click': {
146
+ const response = await requestBridgeWithRetry(
147
+ client,
148
+ 'input.click',
149
+ {
150
+ target: await elementTarget(),
151
+ button: args.button,
152
+ clickCount: args.clickCount,
153
+ modifiers: args.modifiers,
154
+ },
155
+ {
156
+ tabId: requestedTabId,
157
+ source: REQUEST_SOURCE,
158
+ tokenBudget: getToolTokenBudget(args),
159
+ }
300
160
  );
161
+ return summarizeToolResponse(response, 'input.click');
301
162
  }
302
- const response = await requestBridgeWithRetry(
303
- client,
304
- 'input.drag',
305
- {
306
- source,
307
- destination,
308
- offsetX: args.offsetX,
309
- offsetY: args.offsetY,
310
- },
311
- {
312
- tabId: requestedTabId,
313
- source: REQUEST_SOURCE,
314
- tokenBudget: getToolTokenBudget(args),
315
- }
316
- );
317
- return summarizeToolResponse(response, 'input.drag');
318
- }
319
- case 'scroll_into_view': {
320
- const response = await requestBridgeWithRetry(
321
- client,
322
- 'input.scroll_into_view',
323
- {
324
- target: await elementTarget(),
325
- },
326
- {
327
- tabId: requestedTabId,
328
- source: REQUEST_SOURCE,
329
- tokenBudget: getToolTokenBudget(args),
163
+ case 'focus': {
164
+ const response = await requestBridgeWithRetry(
165
+ client,
166
+ 'input.focus',
167
+ {
168
+ target: await elementTarget(),
169
+ },
170
+ {
171
+ tabId: requestedTabId,
172
+ source: REQUEST_SOURCE,
173
+ tokenBudget: getToolTokenBudget(args),
174
+ }
175
+ );
176
+ return summarizeToolResponse(response, 'input.focus');
177
+ }
178
+ case 'type': {
179
+ const response = await requestBridgeWithRetry(
180
+ client,
181
+ 'input.type',
182
+ {
183
+ target: await elementTarget(),
184
+ text: args.text,
185
+ clear: args.clear,
186
+ submit: args.submit,
187
+ modifiers: args.modifiers,
188
+ },
189
+ {
190
+ tabId: requestedTabId,
191
+ source: REQUEST_SOURCE,
192
+ tokenBudget: getToolTokenBudget(args),
193
+ }
194
+ );
195
+ return summarizeToolResponse(response, 'input.type');
196
+ }
197
+ case 'press_key': {
198
+ const target = args.elementRef || args.selector ? await elementTarget() : undefined;
199
+ const response = await requestBridgeWithRetry(
200
+ client,
201
+ 'input.press_key',
202
+ {
203
+ target,
204
+ key: args.key,
205
+ modifiers: args.modifiers,
206
+ },
207
+ {
208
+ tabId: requestedTabId,
209
+ source: REQUEST_SOURCE,
210
+ tokenBudget: getToolTokenBudget(args),
211
+ }
212
+ );
213
+ return summarizeToolResponse(response, 'input.press_key');
214
+ }
215
+ case 'cdp_press_key': {
216
+ const response = await requestBridgeWithRetry(
217
+ client,
218
+ 'cdp.dispatch_key_event',
219
+ {
220
+ key: args.key,
221
+ code: args.code,
222
+ modifiers: args.modifiers,
223
+ },
224
+ {
225
+ tabId: requestedTabId,
226
+ source: REQUEST_SOURCE,
227
+ tokenBudget: getToolTokenBudget(args),
228
+ }
229
+ );
230
+ return summarizeToolResponse(response, 'cdp.dispatch_key_event');
231
+ }
232
+ case 'set_checked': {
233
+ const response = await requestBridgeWithRetry(
234
+ client,
235
+ 'input.set_checked',
236
+ {
237
+ target: await elementTarget(),
238
+ checked: args.checked,
239
+ },
240
+ {
241
+ tabId: requestedTabId,
242
+ source: REQUEST_SOURCE,
243
+ tokenBudget: getToolTokenBudget(args),
244
+ }
245
+ );
246
+ return summarizeToolResponse(response, 'input.set_checked');
247
+ }
248
+ case 'select_option': {
249
+ const response = await requestBridgeWithRetry(
250
+ client,
251
+ 'input.select_option',
252
+ {
253
+ target: await elementTarget(),
254
+ values: args.values,
255
+ labels: args.labels,
256
+ indexes: args.indexes,
257
+ },
258
+ {
259
+ tabId: requestedTabId,
260
+ source: REQUEST_SOURCE,
261
+ tokenBudget: getToolTokenBudget(args),
262
+ }
263
+ );
264
+ return summarizeToolResponse(response, 'input.select_option');
265
+ }
266
+ case 'hover': {
267
+ const response = await requestBridgeWithRetry(
268
+ client,
269
+ 'input.hover',
270
+ {
271
+ target: await elementTarget(),
272
+ duration: args.duration,
273
+ modifiers: args.modifiers,
274
+ },
275
+ {
276
+ tabId: requestedTabId,
277
+ source: REQUEST_SOURCE,
278
+ tokenBudget: getToolTokenBudget(args),
279
+ }
280
+ );
281
+ return summarizeToolResponse(response, 'input.hover');
282
+ }
283
+ case 'drag': {
284
+ const source = {
285
+ elementRef:
286
+ args.sourceElementRef ||
287
+ (args.sourceSelector
288
+ ? await resolveRef(client, args.sourceSelector, requestedTabId, REQUEST_SOURCE)
289
+ : ''),
290
+ };
291
+ const destination = {
292
+ elementRef:
293
+ args.destinationElementRef ||
294
+ (args.destinationSelector
295
+ ? await resolveRef(client, args.destinationSelector, requestedTabId, REQUEST_SOURCE)
296
+ : ''),
297
+ };
298
+ if (!source.elementRef || !destination.elementRef) {
299
+ return summarizeToolError(
300
+ 'sourceElementRef/sourceSelector and destinationElementRef/destinationSelector are required for drag.'
301
+ );
330
302
  }
331
- );
332
- return summarizeToolResponse(response, 'input.scroll_into_view');
303
+ const response = await requestBridgeWithRetry(
304
+ client,
305
+ 'input.drag',
306
+ {
307
+ source,
308
+ destination,
309
+ offsetX: args.offsetX,
310
+ offsetY: args.offsetY,
311
+ },
312
+ {
313
+ tabId: requestedTabId,
314
+ source: REQUEST_SOURCE,
315
+ tokenBudget: getToolTokenBudget(args),
316
+ }
317
+ );
318
+ return summarizeToolResponse(response, 'input.drag');
319
+ }
320
+ case 'scroll_into_view': {
321
+ const response = await requestBridgeWithRetry(
322
+ client,
323
+ 'input.scroll_into_view',
324
+ {
325
+ target: await elementTarget(),
326
+ },
327
+ {
328
+ tabId: requestedTabId,
329
+ source: REQUEST_SOURCE,
330
+ tokenBudget: getToolTokenBudget(args),
331
+ }
332
+ );
333
+ return summarizeToolResponse(response, 'input.scroll_into_view');
334
+ }
335
+ default:
336
+ return summarizeToolError(`Unsupported input action "${args.action}".`);
333
337
  }
334
- default:
335
- return summarizeToolError(`Unsupported input action "${args.action}".`);
336
- }
337
- });
338
+ },
339
+ { destinationId: args.destinationId ?? null }
340
+ );
338
341
  }
339
342
 
340
343
  /**
@@ -1,29 +1,100 @@
1
1
  // @ts-check
2
2
 
3
- import { callBridgeTool, getToolTokenBudget, summarizeToolError } from './handlers-utils.js';
3
+ import { createBridgeClientForDestination } from '../../agent-client/src/remotes.js';
4
+ import {
5
+ callBridgeTool,
6
+ createToolResult,
7
+ getBridgeDestinations,
8
+ getToolTokenBudget,
9
+ summarizeToolError,
10
+ } from './handlers-utils.js';
4
11
 
5
12
  /** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
6
13
  /** @typedef {import('./handlers-utils.js').ToolResult} ToolResult */
7
14
 
8
15
  /**
9
- * @param {{ action: string, url?: string, active?: boolean, tabId?: number }} args
16
+ * @param {{ action: string, url?: string, active?: boolean, tabId?: number, destinationId?: string }} args
10
17
  * @returns {Promise<ToolResult>}
11
18
  */
12
19
  export async function handleTabsTool(args) {
13
20
  if (args.action === 'list') {
14
- return callBridgeTool('tabs.list');
21
+ if (typeof args.destinationId === 'string') {
22
+ return callBridgeTool('tabs.list', {}, { destinationId: args.destinationId });
23
+ }
24
+ const destinations = await getBridgeDestinations();
25
+ if (destinations.length === 1) {
26
+ return callBridgeTool('tabs.list');
27
+ }
28
+ const results = await Promise.all(
29
+ destinations.map(async (destination) => {
30
+ const client = await createBridgeClientForDestination(
31
+ destination.local ? null : destination.id
32
+ );
33
+ try {
34
+ await client.connect();
35
+ const response = await client.request({
36
+ method: 'tabs.list',
37
+ meta: { source: 'mcp' },
38
+ });
39
+ if (!response.ok) {
40
+ return {
41
+ destinationId: destination.id,
42
+ ok: false,
43
+ error: response.error,
44
+ tabs: [],
45
+ };
46
+ }
47
+ const result = /** @type {{ tabs?: Array<Record<string, unknown>> }} */ (response.result);
48
+ return {
49
+ destinationId: destination.id,
50
+ ok: true,
51
+ tabs: (result.tabs ?? []).map((tab) => ({ destinationId: destination.id, ...tab })),
52
+ };
53
+ } catch (error) {
54
+ return {
55
+ destinationId: destination.id,
56
+ ok: false,
57
+ error: { message: error instanceof Error ? error.message : String(error) },
58
+ tabs: [],
59
+ };
60
+ } finally {
61
+ await client.close();
62
+ }
63
+ })
64
+ );
65
+ const tabs = results.flatMap((result) => result.tabs);
66
+ const failures = results.filter((result) => !result.ok);
67
+ return createToolResult(
68
+ failures.length === 0
69
+ ? `Listed ${tabs.length} tab(s) across ${results.length} destination(s).`
70
+ : `Listed ${tabs.length} tab(s) with ${failures.length} destination error(s).`,
71
+ {
72
+ ok: failures.length === 0,
73
+ tabs,
74
+ destinations: results,
75
+ },
76
+ false
77
+ );
15
78
  }
16
79
  if (args.action === 'create') {
17
- return callBridgeTool('tabs.create', {
18
- url: args.url,
19
- active: args.active,
20
- });
80
+ return callBridgeTool(
81
+ 'tabs.create',
82
+ {
83
+ url: args.url,
84
+ active: args.active,
85
+ },
86
+ { destinationId: args.destinationId ?? null }
87
+ );
21
88
  }
22
89
  if (args.action === 'close') {
23
90
  if (typeof args.tabId !== 'number') {
24
91
  return summarizeToolError('tabId is required for tabs.close.');
25
92
  }
26
- return callBridgeTool('tabs.close', { tabId: args.tabId });
93
+ return callBridgeTool(
94
+ 'tabs.close',
95
+ { tabId: args.tabId },
96
+ { destinationId: args.destinationId ?? null }
97
+ );
27
98
  }
28
99
  return summarizeToolError(`Unsupported tabs action "${args.action}".`);
29
100
  }
@@ -71,7 +142,7 @@ export const NAVIGATION_ACTIONS = {
71
142
  };
72
143
 
73
144
  /**
74
- * @param {{ action: string, url?: string, waitForLoad?: boolean, timeoutMs?: number, top?: number, left?: number, behavior?: string, relative?: boolean, width?: number, height?: number, deviceScaleFactor?: number, reset?: boolean, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
145
+ * @param {{ action: string, url?: string, waitForLoad?: boolean, timeoutMs?: number, top?: number, left?: number, behavior?: string, relative?: boolean, width?: number, height?: number, deviceScaleFactor?: number, reset?: boolean, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
75
146
  * @returns {Promise<ToolResult>}
76
147
  */
77
148
  export async function handleNavigationTool(args) {
@@ -95,5 +166,6 @@ export async function handleNavigationTool(args) {
95
166
  return callBridgeTool(entry.method, entry.params(args), {
96
167
  tabId: typeof args.tabId === 'number' ? args.tabId : null,
97
168
  tokenBudget: getToolTokenBudget(args),
169
+ destinationId: args.destinationId ?? null,
98
170
  });
99
171
  }