dsh-browser-plus 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -144
- package/README.en.md +27 -8
- package/README.md +27 -8
- package/client/index.js +185 -0
- package/cordis.patch.yml +24 -0
- package/docs/README.md +1 -1
- package/docs/SOAK-CHECKLIST.md +1 -1
- package/docs/tool-reference.md +5 -3
- package/docs/user-guide.md +20 -4
- package/lib/browser/runtime.d.ts +14 -1
- package/lib/browser/runtime.js +28 -0
- package/lib/browser/types.d.ts +96 -6
- package/lib/browser-electron/chrome-state.d.ts +24 -0
- package/lib/browser-electron/entry.d.ts +12 -5
- package/lib/browser-electron/entry.js +5 -2
- package/lib/browser-electron/host-main.d.ts +2 -1
- package/lib/browser-electron/host-main.js +205 -8
- package/lib/browser-electron/page-chrome.js +249 -14
- package/lib/browser-electron/provider.d.ts +60 -2
- package/lib/browser-electron/provider.js +300 -50
- package/lib/browser-electron/remote-host.d.ts +3 -1
- package/lib/browser-electron/remote-host.js +42 -1
- package/lib/client.js +185 -0
- package/lib/command-browser/index.d.ts +20 -0
- package/lib/command-browser/index.js +35 -0
- package/lib/http-browser/index.d.ts +28 -0
- package/lib/http-browser/index.js +110 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +11 -0
- package/lib/task-todos/index.d.ts +25 -0
- package/lib/task-todos/index.js +100 -0
- package/lib/tool-browser/index.js +190 -95
- package/package.json +27 -2
- package/scripts/build-client.mjs +20 -0
- package/scripts/smoke-browser-tools.mjs +84 -1
- package/scripts/smoke-chrome-world.mjs +6 -4
- package/scripts/test-orb-drag.mjs +83 -0
- package/src/browser/runtime.ts +36 -0
- package/src/browser/types.ts +98 -6
- package/src/browser-electron/chrome-state.ts +18 -0
- package/src/browser-electron/entry.ts +17 -7
- package/src/browser-electron/host-main.ts +207 -11
- package/src/browser-electron/page-chrome.ts +249 -14
- package/src/browser-electron/provider.ts +328 -50
- package/src/browser-electron/remote-host.ts +49 -2
- package/src/command-browser/index.ts +61 -0
- package/src/http-browser/index.ts +139 -0
- package/src/index.ts +13 -0
- package/src/task-todos/index.ts +114 -0
- package/src/tool-browser/index.ts +193 -96
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import type { Context } from '@deepseek-ai/cordis'
|
|
18
18
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
19
19
|
import type {} from '@deepseek-ai/dsh-system-prompt'
|
|
20
|
-
import type { BrowserSessionId } from '../browser/types.ts'
|
|
20
|
+
import type { BrowserSessionId, BrowserSnapshotResult } from '../browser/types.ts'
|
|
21
21
|
|
|
22
22
|
/** Plugin name used by loader diagnostics. */
|
|
23
23
|
export const name = 'tool-browser'
|
|
@@ -280,6 +280,88 @@ function formatSnapshot(snapshot: {
|
|
|
280
280
|
return `${header}\n\n${body}${tail}${banner}${user}`
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
/**
|
|
284
|
+
* The snapshot shape both `browser_open` and `browser_snapshot` return.
|
|
285
|
+
*
|
|
286
|
+
* Shared on purpose: a tool output schema rejects unknown properties, so a
|
|
287
|
+
* field declared on only one of the two schemas is silently dropped before the
|
|
288
|
+
* model ever sees it — which is exactly what happened to `frames` and the
|
|
289
|
+
* element `frame` index when iframe support landed (declared on browser_open,
|
|
290
|
+
* missing from browser_snapshot, and dropped from browser_open's own mapper).
|
|
291
|
+
*/
|
|
292
|
+
const SNAPSHOT_OUTPUT_SCHEMA = {
|
|
293
|
+
type: 'object',
|
|
294
|
+
additionalProperties: false,
|
|
295
|
+
properties: {
|
|
296
|
+
snapshotId: { type: 'string', required: true },
|
|
297
|
+
url: { type: 'string', required: true },
|
|
298
|
+
title: { type: 'string' },
|
|
299
|
+
truncated: { type: 'boolean' },
|
|
300
|
+
frames: {
|
|
301
|
+
type: 'array',
|
|
302
|
+
description: 'Every iframe on the page; readable:false marks the cross-origin ones whose contents cannot be read.',
|
|
303
|
+
items: {
|
|
304
|
+
type: 'object',
|
|
305
|
+
additionalProperties: false,
|
|
306
|
+
properties: {
|
|
307
|
+
index: { type: 'number', required: true },
|
|
308
|
+
url: { type: 'string', required: true },
|
|
309
|
+
readable: { type: 'boolean', required: true },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
elements: {
|
|
314
|
+
type: 'array',
|
|
315
|
+
required: true,
|
|
316
|
+
items: {
|
|
317
|
+
type: 'object',
|
|
318
|
+
additionalProperties: false,
|
|
319
|
+
properties: {
|
|
320
|
+
ref: { type: 'number', required: true },
|
|
321
|
+
kind: { type: 'string', required: true },
|
|
322
|
+
label: { type: 'string', required: true },
|
|
323
|
+
x: { type: 'number', required: true },
|
|
324
|
+
y: { type: 'number', required: true },
|
|
325
|
+
loc: { type: 'string', required: true },
|
|
326
|
+
frame: { type: 'number', description: 'Index into frames when this element lives inside an iframe.' },
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
challenge: {
|
|
331
|
+
type: 'object',
|
|
332
|
+
additionalProperties: false,
|
|
333
|
+
properties: {
|
|
334
|
+
blocked: { type: 'boolean', required: true },
|
|
335
|
+
kind: { type: 'string' },
|
|
336
|
+
reason: { type: 'string' },
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
userControlling: { type: 'boolean' },
|
|
340
|
+
},
|
|
341
|
+
} as const
|
|
342
|
+
|
|
343
|
+
/** Project a provider snapshot onto that shared model-facing shape. */
|
|
344
|
+
function snapshotOutput(snapshot: BrowserSnapshotResult) {
|
|
345
|
+
return {
|
|
346
|
+
snapshotId: snapshot.snapshotId,
|
|
347
|
+
url: snapshot.url,
|
|
348
|
+
...snapshot.title !== undefined ? { title: snapshot.title } : {},
|
|
349
|
+
...snapshot.frames !== undefined ? { frames: snapshot.frames.map(frame => ({ index: frame.index, url: frame.url, readable: frame.readable })) } : {},
|
|
350
|
+
elements: snapshot.elements.map(el => ({
|
|
351
|
+
ref: el.ref,
|
|
352
|
+
kind: el.kind,
|
|
353
|
+
label: el.label,
|
|
354
|
+
x: el.x,
|
|
355
|
+
y: el.y,
|
|
356
|
+
loc: el.loc,
|
|
357
|
+
...el.frame !== undefined ? { frame: el.frame } : {},
|
|
358
|
+
})),
|
|
359
|
+
truncated: snapshot.truncated,
|
|
360
|
+
...snapshot.challenge !== undefined ? { challenge: snapshot.challenge } : {},
|
|
361
|
+
...snapshot.userControlling !== undefined ? { userControlling: snapshot.userControlling } : {},
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
283
365
|
/** Register all browser tools with `ctx.tools`. */
|
|
284
366
|
export function apply(ctx: Context, config: Config = {}): void {
|
|
285
367
|
const timeoutMs = config.timeoutMs ?? 60_000
|
|
@@ -311,42 +393,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
311
393
|
space: { type: 'string', description: 'Optional browser-task label shown in the task manager and active window title.' },
|
|
312
394
|
},
|
|
313
395
|
output: {
|
|
314
|
-
schema:
|
|
315
|
-
type: 'object',
|
|
316
|
-
additionalProperties: false,
|
|
317
|
-
properties: {
|
|
318
|
-
snapshotId: { type: 'string', required: true },
|
|
319
|
-
url: { type: 'string', required: true },
|
|
320
|
-
title: { type: 'string' },
|
|
321
|
-
truncated: { type: 'boolean' },
|
|
322
|
-
elements: {
|
|
323
|
-
type: 'array',
|
|
324
|
-
required: true,
|
|
325
|
-
items: {
|
|
326
|
-
type: 'object',
|
|
327
|
-
additionalProperties: false,
|
|
328
|
-
properties: {
|
|
329
|
-
ref: { type: 'number', required: true },
|
|
330
|
-
kind: { type: 'string', required: true },
|
|
331
|
-
label: { type: 'string', required: true },
|
|
332
|
-
x: { type: 'number', required: true },
|
|
333
|
-
y: { type: 'number', required: true },
|
|
334
|
-
loc: { type: 'string', required: true },
|
|
335
|
-
},
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
challenge: {
|
|
339
|
-
type: 'object',
|
|
340
|
-
additionalProperties: false,
|
|
341
|
-
properties: {
|
|
342
|
-
blocked: { type: 'boolean', required: true },
|
|
343
|
-
kind: { type: 'string' },
|
|
344
|
-
reason: { type: 'string' },
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
userControlling: { type: 'boolean' },
|
|
348
|
-
},
|
|
349
|
-
},
|
|
396
|
+
schema: SNAPSHOT_OUTPUT_SCHEMA,
|
|
350
397
|
render: (_args, value) => [{ type: 'text', text: formatSnapshot(value) }],
|
|
351
398
|
},
|
|
352
399
|
timeoutMs,
|
|
@@ -362,15 +409,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
362
409
|
...args.newTab === true ? { newTab: true } : {},
|
|
363
410
|
}, exec.signal)
|
|
364
411
|
const snapshot = await browser.snapshot(session, {}, exec.signal)
|
|
365
|
-
return
|
|
366
|
-
snapshotId: snapshot.snapshotId,
|
|
367
|
-
url: snapshot.url,
|
|
368
|
-
...snapshot.title !== undefined ? { title: snapshot.title } : {},
|
|
369
|
-
elements: snapshot.elements.map(el => ({ ref: el.ref, kind: el.kind, label: el.label, x: el.x, y: el.y, loc: el.loc })),
|
|
370
|
-
truncated: snapshot.truncated,
|
|
371
|
-
...snapshot.challenge !== undefined ? { challenge: snapshot.challenge } : {},
|
|
372
|
-
...snapshot.userControlling !== undefined ? { userControlling: snapshot.userControlling } : {},
|
|
373
|
-
}
|
|
412
|
+
return snapshotOutput(snapshot)
|
|
374
413
|
}, args.space)
|
|
375
414
|
},
|
|
376
415
|
}))
|
|
@@ -597,42 +636,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
597
636
|
limit: { type: 'number', description: 'Maximum number of elements to return (1-1000; default 60).' },
|
|
598
637
|
},
|
|
599
638
|
output: {
|
|
600
|
-
schema:
|
|
601
|
-
type: 'object',
|
|
602
|
-
additionalProperties: false,
|
|
603
|
-
properties: {
|
|
604
|
-
snapshotId: { type: 'string', required: true },
|
|
605
|
-
url: { type: 'string', required: true },
|
|
606
|
-
title: { type: 'string' },
|
|
607
|
-
truncated: { type: 'boolean' },
|
|
608
|
-
elements: {
|
|
609
|
-
type: 'array',
|
|
610
|
-
required: true,
|
|
611
|
-
items: {
|
|
612
|
-
type: 'object',
|
|
613
|
-
additionalProperties: false,
|
|
614
|
-
properties: {
|
|
615
|
-
ref: { type: 'number', required: true },
|
|
616
|
-
kind: { type: 'string', required: true },
|
|
617
|
-
label: { type: 'string', required: true },
|
|
618
|
-
x: { type: 'number', required: true },
|
|
619
|
-
y: { type: 'number', required: true },
|
|
620
|
-
loc: { type: 'string', required: true },
|
|
621
|
-
},
|
|
622
|
-
},
|
|
623
|
-
},
|
|
624
|
-
challenge: {
|
|
625
|
-
type: 'object',
|
|
626
|
-
additionalProperties: false,
|
|
627
|
-
properties: {
|
|
628
|
-
blocked: { type: 'boolean', required: true },
|
|
629
|
-
kind: { type: 'string' },
|
|
630
|
-
reason: { type: 'string' },
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
|
-
userControlling: { type: 'boolean' },
|
|
634
|
-
},
|
|
635
|
-
},
|
|
639
|
+
schema: SNAPSHOT_OUTPUT_SCHEMA,
|
|
636
640
|
render: (_args, value) => [{ type: 'text', text: formatSnapshot(value) }],
|
|
637
641
|
},
|
|
638
642
|
timeoutMs,
|
|
@@ -645,15 +649,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
645
649
|
...args.query !== undefined ? { query: args.query } : {},
|
|
646
650
|
...args.limit !== undefined ? { limit: args.limit } : {},
|
|
647
651
|
}, exec.signal))
|
|
648
|
-
return
|
|
649
|
-
snapshotId: snapshot.snapshotId,
|
|
650
|
-
url: snapshot.url,
|
|
651
|
-
...snapshot.title !== undefined ? { title: snapshot.title } : {},
|
|
652
|
-
elements: snapshot.elements.map(el => ({ ref: el.ref, kind: el.kind, label: el.label, x: el.x, y: el.y, loc: el.loc })),
|
|
653
|
-
truncated: snapshot.truncated,
|
|
654
|
-
...snapshot.challenge !== undefined ? { challenge: snapshot.challenge } : {},
|
|
655
|
-
...snapshot.userControlling !== undefined ? { userControlling: snapshot.userControlling } : {},
|
|
656
|
-
}
|
|
652
|
+
return snapshotOutput(snapshot)
|
|
657
653
|
},
|
|
658
654
|
}))
|
|
659
655
|
|
|
@@ -1224,9 +1220,11 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
1224
1220
|
|
|
1225
1221
|
ctx.tools.register(defineTool({
|
|
1226
1222
|
name: 'browser_wait_for',
|
|
1227
|
-
description: 'Wait until
|
|
1223
|
+
description: 'Wait until a selector, some text, or both reaches a state, polling every 250ms. `state` is visible (default), attached, hidden or detached — hidden/detached are how you wait for something to go away. Use before clicking or reading, instead of sleeping.',
|
|
1228
1224
|
parameters: {
|
|
1229
|
-
selector: { type: 'string',
|
|
1225
|
+
selector: { type: 'string', description: 'CSS selector to wait for. Omit to watch the document text (give `text`).' },
|
|
1226
|
+
text: { type: 'string', description: 'Text that must be present: inside the matched element when a selector is given, otherwise anywhere in the document.' },
|
|
1227
|
+
state: { type: 'string', enum: ['visible', 'attached', 'hidden', 'detached'], description: 'Which state to wait for. visible (default) needs a 4x4 px element that is not display:none / visibility:hidden; attached only needs it to exist; hidden and detached wait for it to go away.' },
|
|
1230
1228
|
timeoutMs: { type: 'number', description: `Total budget in ms (default 15000), capped below this tool's ${String(timeoutMs)}ms budget.` },
|
|
1231
1229
|
visible: { type: 'boolean', description: 'Require visibility (>4x4 px, not display:none). Default true.' },
|
|
1232
1230
|
},
|
|
@@ -1236,12 +1234,18 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
1236
1234
|
additionalProperties: false,
|
|
1237
1235
|
properties: {
|
|
1238
1236
|
found: { type: 'boolean', required: true },
|
|
1237
|
+
state: { type: 'string', required: true },
|
|
1239
1238
|
selector: { type: 'string', required: true },
|
|
1240
1239
|
tag: { type: 'string', required: true },
|
|
1241
1240
|
text: { type: 'string' },
|
|
1242
1241
|
},
|
|
1243
1242
|
},
|
|
1244
|
-
render: (_args, value) => [{
|
|
1243
|
+
render: (_args, value) => [{
|
|
1244
|
+
type: 'text',
|
|
1245
|
+
text: value.selector === ''
|
|
1246
|
+
? `Found text "${(value.text ?? '').slice(0, 80)}" (state "${value.state}").`
|
|
1247
|
+
: `Found <${value.tag}> ${value.selector}${value.text !== undefined ? ` — "${value.text.slice(0, 80)}"` : ''}.`,
|
|
1248
|
+
}],
|
|
1245
1249
|
},
|
|
1246
1250
|
timeoutMs,
|
|
1247
1251
|
isConcurrencySafe: () => true,
|
|
@@ -1250,11 +1254,13 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
1250
1254
|
const browser = ctx.get('browser')
|
|
1251
1255
|
if (browser === undefined) throw new Error('tool-browser: browser service unavailable')
|
|
1252
1256
|
const result = await withTaskAction(browser, taskKey(exec), 'wait for element', exec, session => browser.waitForElement(session, {
|
|
1253
|
-
selector: args.selector,
|
|
1257
|
+
...args.selector !== undefined ? { selector: args.selector } : {},
|
|
1258
|
+
...args.text !== undefined ? { text: args.text } : {},
|
|
1259
|
+
...args.state !== undefined ? { state: args.state as 'visible' | 'attached' | 'hidden' | 'detached' } : {},
|
|
1254
1260
|
timeoutMs: withinToolBudget(args.timeoutMs, 15_000),
|
|
1255
1261
|
...args.visible !== undefined ? { visible: args.visible } : {},
|
|
1256
1262
|
}, exec.signal))
|
|
1257
|
-
return { found: true, selector: result.selector, tag: result.tag, text: result.text }
|
|
1263
|
+
return { found: true, state: result.state, selector: result.selector, tag: result.tag, text: result.text }
|
|
1258
1264
|
},
|
|
1259
1265
|
}))
|
|
1260
1266
|
|
|
@@ -1492,6 +1498,97 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
1492
1498
|
},
|
|
1493
1499
|
}))
|
|
1494
1500
|
|
|
1501
|
+
ctx.tools.register(defineTool({
|
|
1502
|
+
name: 'browser_pdf',
|
|
1503
|
+
description: 'Print the active tab to a PDF file — Chrome\'s "Save as PDF". The path must be inside the browser write roots. Returns the path and byte size.',
|
|
1504
|
+
parameters: {
|
|
1505
|
+
savePath: { type: 'string', required: true, description: 'Absolute path of the .pdf to write. Must be inside the browser write roots.' },
|
|
1506
|
+
landscape: { type: 'boolean', description: 'Landscape orientation. Default portrait.' },
|
|
1507
|
+
printBackground: { type: 'boolean', description: 'Include background colours and images. Default true — dark pages would otherwise print as white paper.' },
|
|
1508
|
+
paperWidth: { type: 'number', description: 'Paper width in inches. Default 8.5.' },
|
|
1509
|
+
paperHeight: { type: 'number', description: 'Paper height in inches. Default 11.' },
|
|
1510
|
+
},
|
|
1511
|
+
output: {
|
|
1512
|
+
schema: {
|
|
1513
|
+
type: 'object',
|
|
1514
|
+
additionalProperties: false,
|
|
1515
|
+
properties: {
|
|
1516
|
+
path: { type: 'string', required: true },
|
|
1517
|
+
bytes: { type: 'number', required: true },
|
|
1518
|
+
},
|
|
1519
|
+
},
|
|
1520
|
+
render: (_args, value) => [{ type: 'text', text: `Wrote ${value.bytes} bytes to ${value.path}` }],
|
|
1521
|
+
},
|
|
1522
|
+
timeoutMs,
|
|
1523
|
+
isConcurrencySafe: () => true,
|
|
1524
|
+
async execute(args, exec) {
|
|
1525
|
+
assertAllowed('browser_pdf', exec)
|
|
1526
|
+
const browser = ctx.get('browser')
|
|
1527
|
+
if (browser === undefined) throw new Error('tool-browser: browser service unavailable')
|
|
1528
|
+
const result = await withTaskAction(browser, taskKey(exec), 'print to pdf', exec, session => browser.pdf(session, {
|
|
1529
|
+
savePath: args.savePath,
|
|
1530
|
+
...args.landscape !== undefined ? { landscape: args.landscape } : {},
|
|
1531
|
+
...args.printBackground !== undefined ? { printBackground: args.printBackground } : {},
|
|
1532
|
+
...args.paperWidth !== undefined ? { paperWidth: args.paperWidth } : {},
|
|
1533
|
+
...args.paperHeight !== undefined ? { paperHeight: args.paperHeight } : {},
|
|
1534
|
+
}, exec.signal))
|
|
1535
|
+
return { path: result.path, bytes: result.bytes }
|
|
1536
|
+
},
|
|
1537
|
+
}))
|
|
1538
|
+
|
|
1539
|
+
ctx.tools.register(defineTool({
|
|
1540
|
+
name: 'browser_highlight',
|
|
1541
|
+
description: 'Draw the DevTools-style highlight box over the first element matching a CSS selector, so a human watching the window can see what the Agent is about to touch. It goes through the CDP overlay, so the page DOM is never modified. Pass clear: true to remove it.',
|
|
1542
|
+
parameters: {
|
|
1543
|
+
selector: { type: 'string', description: 'CSS selector whose first match to highlight. Required unless clear is true.' },
|
|
1544
|
+
clear: { type: 'boolean', description: 'Remove the highlight instead of drawing one.' },
|
|
1545
|
+
},
|
|
1546
|
+
output: {
|
|
1547
|
+
schema: {
|
|
1548
|
+
type: 'object',
|
|
1549
|
+
additionalProperties: false,
|
|
1550
|
+
properties: {
|
|
1551
|
+
matched: { type: 'boolean', required: true },
|
|
1552
|
+
cleared: { type: 'boolean', required: true },
|
|
1553
|
+
nodeId: { type: 'number' },
|
|
1554
|
+
box: {
|
|
1555
|
+
type: 'object',
|
|
1556
|
+
additionalProperties: false,
|
|
1557
|
+
properties: {
|
|
1558
|
+
x: { type: 'number', required: true },
|
|
1559
|
+
y: { type: 'number', required: true },
|
|
1560
|
+
width: { type: 'number', required: true },
|
|
1561
|
+
height: { type: 'number', required: true },
|
|
1562
|
+
},
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
},
|
|
1566
|
+
render: (_args, value) => [{
|
|
1567
|
+
type: 'text',
|
|
1568
|
+
text: value.matched
|
|
1569
|
+
? `Highlighted node ${String(value.nodeId)} at ${Math.round(value.box?.x ?? 0)},${Math.round(value.box?.y ?? 0)} (${Math.round(value.box?.width ?? 0)}x${Math.round(value.box?.height ?? 0)})`
|
|
1570
|
+
: value.cleared ? 'Highlight cleared' : 'No element matched that selector',
|
|
1571
|
+
}],
|
|
1572
|
+
},
|
|
1573
|
+
timeoutMs,
|
|
1574
|
+
isConcurrencySafe: () => true,
|
|
1575
|
+
async execute(args, exec) {
|
|
1576
|
+
assertAllowed('browser_highlight', exec)
|
|
1577
|
+
const browser = ctx.get('browser')
|
|
1578
|
+
if (browser === undefined) throw new Error('tool-browser: browser service unavailable')
|
|
1579
|
+
const result = await withTaskAction(browser, taskKey(exec), 'highlight element', exec, session => browser.highlight(session, {
|
|
1580
|
+
...args.selector !== undefined ? { selector: args.selector } : {},
|
|
1581
|
+
...args.clear !== undefined ? { clear: args.clear } : {},
|
|
1582
|
+
}, exec.signal))
|
|
1583
|
+
return {
|
|
1584
|
+
matched: result.matched,
|
|
1585
|
+
cleared: result.cleared,
|
|
1586
|
+
...result.nodeId !== undefined ? { nodeId: result.nodeId } : {},
|
|
1587
|
+
...result.box !== undefined ? { box: result.box } : {},
|
|
1588
|
+
}
|
|
1589
|
+
},
|
|
1590
|
+
}))
|
|
1591
|
+
|
|
1495
1592
|
if (config.tabTools !== false) {
|
|
1496
1593
|
ctx.tools.register(defineTool({
|
|
1497
1594
|
name: 'browser_list_tabs',
|