@browserbridge/bbx 1.7.5 → 1.7.7
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/README.md +1 -1
- package/package.json +6 -6
- package/packages/agent-client/src/cli-args.js +152 -0
- package/packages/agent-client/src/cli-batch.js +140 -0
- package/packages/agent-client/src/cli-output.js +57 -0
- package/packages/agent-client/src/cli-proxy-remote.js +350 -0
- package/packages/agent-client/src/cli-setup-commands.js +404 -0
- package/packages/agent-client/src/cli.js +22 -1062
- package/packages/agent-client/src/runtime.js +1 -2
- package/packages/mcp-server/src/guidance.js +4 -232
- package/packages/mcp-server/src/server.js +1 -3
- package/packages/native-host/bin/bridge-daemon.js +2 -1
- package/packages/native-host/src/auth-token.js +23 -2
- package/packages/native-host/src/config.js +59 -19
- package/packages/native-host/src/daemon-process.js +226 -11
- package/packages/native-host/src/daemon.js +2 -6
- package/packages/protocol/src/capabilities.js +33 -110
- package/packages/protocol/src/capability-values.js +45 -0
- package/packages/protocol/src/defaults.js +17 -2
- package/packages/protocol/src/registry.js +299 -86
|
@@ -260,8 +260,9 @@ export async function stopBridgeDaemon(options = {}) {
|
|
|
260
260
|
previousPid = endpointOwnerPid;
|
|
261
261
|
previouslyRunning = true;
|
|
262
262
|
} else if (reachable) {
|
|
263
|
-
//
|
|
264
|
-
// reachability is the evidence
|
|
263
|
+
// When the endpoint owner is not discoverable (no verified TCP listener,
|
|
264
|
+
// lsof and ss both unavailable), daemon reachability is the evidence
|
|
265
|
+
// supporting the recorded pid.
|
|
265
266
|
previousPid = pidFromFile;
|
|
266
267
|
previouslyRunning = true;
|
|
267
268
|
}
|
|
@@ -407,10 +408,17 @@ async function restartBridgeDaemonAfterStop(stopResult, options = {}) {
|
|
|
407
408
|
throw new Error('Timed out waiting for Browser Bridge daemon to start.');
|
|
408
409
|
}
|
|
409
410
|
|
|
411
|
+
let pid = await readPidFn(pidPath);
|
|
412
|
+
if (pid === null) {
|
|
413
|
+
// The daemon starts listening immediately before it persists its PID.
|
|
414
|
+
await sleepFn(pollIntervalMs);
|
|
415
|
+
pid = await readPidFn(pidPath);
|
|
416
|
+
}
|
|
417
|
+
|
|
410
418
|
return {
|
|
411
419
|
...stopResult,
|
|
412
420
|
pidPath,
|
|
413
|
-
pid
|
|
421
|
+
pid,
|
|
414
422
|
};
|
|
415
423
|
}
|
|
416
424
|
|
|
@@ -435,28 +443,40 @@ function resolveDaemonTransport(options) {
|
|
|
435
443
|
return getBridgeTransport(env);
|
|
436
444
|
}
|
|
437
445
|
|
|
446
|
+
/**
|
|
447
|
+
* @typedef {{
|
|
448
|
+
* execFileFn?: typeof execFileAsync,
|
|
449
|
+
* platform?: NodeJS.Platform,
|
|
450
|
+
* readFileFn?: typeof fs.promises.readFile,
|
|
451
|
+
* }} FindDaemonPidOptions
|
|
452
|
+
*/
|
|
453
|
+
|
|
438
454
|
/**
|
|
439
455
|
* @param {BridgeTransport} transport
|
|
456
|
+
* @param {FindDaemonPidOptions} [options={}]
|
|
440
457
|
* @returns {Promise<number | null>}
|
|
441
458
|
*/
|
|
442
|
-
export async function findDaemonPidByTransport(transport) {
|
|
443
|
-
if (transport.type
|
|
444
|
-
return
|
|
459
|
+
export async function findDaemonPidByTransport(transport, options = {}) {
|
|
460
|
+
if (transport.type === 'socket') {
|
|
461
|
+
return findDaemonPidBySocket(transport.socketPath, options);
|
|
445
462
|
}
|
|
446
|
-
return
|
|
463
|
+
return findDaemonPidByTcpPort(transport.port, options);
|
|
447
464
|
}
|
|
448
465
|
|
|
449
466
|
/**
|
|
450
467
|
* @param {string} socketPath
|
|
468
|
+
* @param {FindDaemonPidOptions} [options={}]
|
|
451
469
|
* @returns {Promise<number | null>}
|
|
452
470
|
*/
|
|
453
|
-
export async function findDaemonPidBySocket(socketPath) {
|
|
454
|
-
|
|
471
|
+
export async function findDaemonPidBySocket(socketPath, options = {}) {
|
|
472
|
+
const { execFileFn = execFileAsync, platform = process.platform } = options;
|
|
473
|
+
if (platform === 'win32') {
|
|
474
|
+
// Named pipes do not expose an owner pid through a filesystem path.
|
|
455
475
|
return null;
|
|
456
476
|
}
|
|
457
477
|
|
|
458
478
|
try {
|
|
459
|
-
const { stdout } = await
|
|
479
|
+
const { stdout } = await execFileFn('lsof', ['-t', '--', socketPath]);
|
|
460
480
|
const pid = Number.parseInt(
|
|
461
481
|
stdout
|
|
462
482
|
.split(/\r?\n/u)
|
|
@@ -466,13 +486,208 @@ export async function findDaemonPidBySocket(socketPath) {
|
|
|
466
486
|
);
|
|
467
487
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
468
488
|
} catch (error) {
|
|
469
|
-
if (isCommandNotFoundError(error)
|
|
489
|
+
if (isCommandNotFoundError(error)) {
|
|
490
|
+
// macOS always ships lsof; minimal Linux installs often do not, so fall
|
|
491
|
+
// back to iproute2's ss which is effectively universal there.
|
|
492
|
+
return platform === 'linux' ? findLinuxSocketOwnerPidWithSs(socketPath, execFileFn) : null;
|
|
493
|
+
}
|
|
494
|
+
if (isLsofNoResultsError(error)) {
|
|
470
495
|
return null;
|
|
471
496
|
}
|
|
472
497
|
throw error;
|
|
473
498
|
}
|
|
474
499
|
}
|
|
475
500
|
|
|
501
|
+
/**
|
|
502
|
+
* @param {string} socketPath
|
|
503
|
+
* @param {typeof execFileAsync} execFileFn
|
|
504
|
+
* @returns {Promise<number | null>}
|
|
505
|
+
*/
|
|
506
|
+
async function findLinuxSocketOwnerPidWithSs(socketPath, execFileFn) {
|
|
507
|
+
try {
|
|
508
|
+
const { stdout } = await execFileFn('ss', ['-x', '-l', '-p', 'src', socketPath]);
|
|
509
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
510
|
+
if (!line.includes(socketPath)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
const match = /pid=(\d+)/u.exec(line);
|
|
514
|
+
const pid = match ? Number.parseInt(match[1], 10) : Number.NaN;
|
|
515
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
516
|
+
return pid;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
} catch {
|
|
521
|
+
// ss is a best-effort fallback; the caller degrades to the pid file.
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Find the pid of the bridge daemon listening on a local TCP port (the default
|
|
528
|
+
* transport on Windows and the opt-in proxy transport elsewhere). Unlike a
|
|
529
|
+
* bridge-owned socket path, a TCP port could be held by an unrelated process,
|
|
530
|
+
* so a candidate pid is only returned after its command line is confirmed to
|
|
531
|
+
* reference the bridge daemon entrypoint. Discovery is opportunistic: any
|
|
532
|
+
* tooling failure yields null and the caller degrades to the pid file.
|
|
533
|
+
*
|
|
534
|
+
* @param {number} port
|
|
535
|
+
* @param {FindDaemonPidOptions} [options={}]
|
|
536
|
+
* @returns {Promise<number | null>}
|
|
537
|
+
*/
|
|
538
|
+
export async function findDaemonPidByTcpPort(port, options = {}) {
|
|
539
|
+
const {
|
|
540
|
+
execFileFn = execFileAsync,
|
|
541
|
+
platform = process.platform,
|
|
542
|
+
readFileFn = fs.promises.readFile,
|
|
543
|
+
} = options;
|
|
544
|
+
|
|
545
|
+
if (platform === 'win32') {
|
|
546
|
+
return findWindowsTcpDaemonPid(port, execFileFn);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const candidatePids = await findPosixTcpListenerPids(port, execFileFn, platform);
|
|
550
|
+
for (const pid of candidatePids) {
|
|
551
|
+
const command = await getPosixProcessCommand(pid, execFileFn, platform, readFileFn);
|
|
552
|
+
if (command.includes(DAEMON_ENTRY_BASENAME)) {
|
|
553
|
+
return pid;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const DAEMON_ENTRY_BASENAME = 'bridge-daemon';
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* @returns {string}
|
|
563
|
+
*/
|
|
564
|
+
function getWindowsPowerShellExe() {
|
|
565
|
+
const systemRoot = process.env.SystemRoot || process.env.WINDIR;
|
|
566
|
+
return systemRoot
|
|
567
|
+
? path.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
|
568
|
+
: 'powershell.exe';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Resolve and verify the listening daemon pid in a single PowerShell call.
|
|
573
|
+
* Get-NetTCPConnection is locale-independent, unlike netstat's state column.
|
|
574
|
+
* `$listenerPid` deliberately avoids PowerShell's reserved `$PID` variable.
|
|
575
|
+
*
|
|
576
|
+
* @param {number} port
|
|
577
|
+
* @param {typeof execFileAsync} execFileFn
|
|
578
|
+
* @returns {Promise<number | null>}
|
|
579
|
+
*/
|
|
580
|
+
async function findWindowsTcpDaemonPid(port, execFileFn) {
|
|
581
|
+
const script = [
|
|
582
|
+
`$listenerPids = @(Get-NetTCPConnection -State Listen -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { $_.OwningProcess } | Sort-Object -Unique)`,
|
|
583
|
+
'foreach ($listenerPid in $listenerPids) {',
|
|
584
|
+
` $commandLine = (Get-CimInstance Win32_Process -Filter "ProcessId=$listenerPid" -ErrorAction SilentlyContinue).CommandLine`,
|
|
585
|
+
` if ($commandLine -like '*${DAEMON_ENTRY_BASENAME}*') { Write-Output $listenerPid; break }`,
|
|
586
|
+
'}',
|
|
587
|
+
].join(' ');
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
const { stdout } = await execFileFn(getWindowsPowerShellExe(), [
|
|
591
|
+
'-NoLogo',
|
|
592
|
+
'-NoProfile',
|
|
593
|
+
'-NonInteractive',
|
|
594
|
+
'-Command',
|
|
595
|
+
script,
|
|
596
|
+
]);
|
|
597
|
+
const pid = Number.parseInt(
|
|
598
|
+
stdout
|
|
599
|
+
.split(/\r?\n/u)
|
|
600
|
+
.map((line) => line.trim())
|
|
601
|
+
.find(Boolean) ?? '',
|
|
602
|
+
10
|
|
603
|
+
);
|
|
604
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
605
|
+
} catch {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* @param {number} port
|
|
612
|
+
* @param {typeof execFileAsync} execFileFn
|
|
613
|
+
* @param {NodeJS.Platform} platform
|
|
614
|
+
* @returns {Promise<number[]>}
|
|
615
|
+
*/
|
|
616
|
+
async function findPosixTcpListenerPids(port, execFileFn, platform) {
|
|
617
|
+
try {
|
|
618
|
+
const { stdout } = await execFileFn('lsof', ['-nP', '-t', `-iTCP:${port}`, '-sTCP:LISTEN']);
|
|
619
|
+
return parsePidLines(stdout);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
if (isCommandNotFoundError(error) && platform === 'linux') {
|
|
622
|
+
return findLinuxTcpListenerPidsWithSs(port, execFileFn);
|
|
623
|
+
}
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* @param {number} port
|
|
630
|
+
* @param {typeof execFileAsync} execFileFn
|
|
631
|
+
* @returns {Promise<number[]>}
|
|
632
|
+
*/
|
|
633
|
+
async function findLinuxTcpListenerPidsWithSs(port, execFileFn) {
|
|
634
|
+
try {
|
|
635
|
+
const { stdout } = await execFileFn('ss', ['-t', '-l', '-n', '-p', 'sport', '=', `:${port}`]);
|
|
636
|
+
/** @type {number[]} */
|
|
637
|
+
const pids = [];
|
|
638
|
+
for (const match of stdout.matchAll(/pid=(\d+)/gu)) {
|
|
639
|
+
const pid = Number.parseInt(match[1], 10);
|
|
640
|
+
if (Number.isInteger(pid) && pid > 0 && !pids.includes(pid)) {
|
|
641
|
+
pids.push(pid);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return pids;
|
|
645
|
+
} catch {
|
|
646
|
+
return [];
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* @param {number} pid
|
|
652
|
+
* @param {typeof execFileAsync} execFileFn
|
|
653
|
+
* @param {NodeJS.Platform} platform
|
|
654
|
+
* @param {typeof fs.promises.readFile} readFileFn
|
|
655
|
+
* @returns {Promise<string>}
|
|
656
|
+
*/
|
|
657
|
+
async function getPosixProcessCommand(pid, execFileFn, platform, readFileFn) {
|
|
658
|
+
if (platform === 'linux') {
|
|
659
|
+
// /proc avoids depending on a full procps ps (busybox lacks -ww/command=).
|
|
660
|
+
try {
|
|
661
|
+
const cmdline = await readFileFn(`/proc/${pid}/cmdline`, 'utf8');
|
|
662
|
+
return String(cmdline).replaceAll('\0', ' ');
|
|
663
|
+
} catch {
|
|
664
|
+
return '';
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
const { stdout } = await execFileFn('ps', ['-ww', '-p', String(pid), '-o', 'command=']);
|
|
669
|
+
return stdout;
|
|
670
|
+
} catch {
|
|
671
|
+
return '';
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* @param {string} stdout
|
|
677
|
+
* @returns {number[]}
|
|
678
|
+
*/
|
|
679
|
+
function parsePidLines(stdout) {
|
|
680
|
+
/** @type {number[]} */
|
|
681
|
+
const pids = [];
|
|
682
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
683
|
+
const pid = Number.parseInt(line.trim(), 10);
|
|
684
|
+
if (Number.isInteger(pid) && pid > 0 && !pids.includes(pid)) {
|
|
685
|
+
pids.push(pid);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return pids;
|
|
689
|
+
}
|
|
690
|
+
|
|
476
691
|
/**
|
|
477
692
|
* @param {{
|
|
478
693
|
* transport: BridgeTransport,
|
|
@@ -39,11 +39,7 @@ import {
|
|
|
39
39
|
getBridgeTransport,
|
|
40
40
|
getSocketPath,
|
|
41
41
|
} from './config.js';
|
|
42
|
-
import {
|
|
43
|
-
ensureBridgeAuthToken,
|
|
44
|
-
normalizeBridgeAuthToken,
|
|
45
|
-
readBridgeAuthToken,
|
|
46
|
-
} from './auth-token.js';
|
|
42
|
+
import { bridgeAuthTokensEqual, ensureBridgeAuthToken, readBridgeAuthToken } from './auth-token.js';
|
|
47
43
|
import { normalizeDaemonLogger } from './daemon-logger.js';
|
|
48
44
|
import { writeJsonLine } from './framing.js';
|
|
49
45
|
|
|
@@ -341,7 +337,7 @@ export class BridgeDaemon {
|
|
|
341
337
|
return;
|
|
342
338
|
}
|
|
343
339
|
|
|
344
|
-
if (this.isAuthRequired() &&
|
|
340
|
+
if (this.isAuthRequired() && !bridgeAuthTokensEqual(message.authToken, this.authToken)) {
|
|
345
341
|
this.logger.error('socket registration rejected', { role: message.role ?? null });
|
|
346
342
|
void writeJsonLine(socket, {
|
|
347
343
|
type: 'registration_failed',
|
|
@@ -1,122 +1,45 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import { CAPABILITIES, DEFAULT_CAPABILITIES } from './capability-values.js';
|
|
4
|
+
import { BRIDGE_METHODS, BRIDGE_METHOD_REGISTRY } from './registry.js';
|
|
5
|
+
|
|
3
6
|
/** @typedef {import('./types.js').BridgeMethod} CapabilityMethod */
|
|
4
7
|
/** @typedef {import('./types.js').Capability} Capability */
|
|
5
8
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
TABS_MANAGE: 'tabs.manage',
|
|
23
|
-
PERFORMANCE_READ: 'performance.read',
|
|
24
|
-
NETWORK_READ: 'network.read',
|
|
25
|
-
NETWORK_INTERCEPT: 'network.intercept',
|
|
26
|
-
});
|
|
9
|
+
export { CAPABILITIES, DEFAULT_CAPABILITIES };
|
|
10
|
+
|
|
11
|
+
/** @type {readonly CapabilityMethod[]} */
|
|
12
|
+
const LEGACY_METHOD_CAPABILITY_PREFIX = [
|
|
13
|
+
'access.request',
|
|
14
|
+
'tabs.list',
|
|
15
|
+
'tabs.create',
|
|
16
|
+
'tabs.close',
|
|
17
|
+
'tabs.activate',
|
|
18
|
+
'skill.get_runtime_context',
|
|
19
|
+
'setup.get_status',
|
|
20
|
+
'setup.install',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/** @type {readonly CapabilityMethod[]} */
|
|
24
|
+
const LEGACY_METHOD_CAPABILITY_SUFFIX = ['log.tail', 'health.ping', 'daemon.metrics'];
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
CAPABILITIES.DOM_READ,
|
|
32
|
-
CAPABILITIES.STYLES_READ,
|
|
33
|
-
CAPABILITIES.LAYOUT_READ,
|
|
34
|
-
CAPABILITIES.VIEWPORT_CONTROL,
|
|
35
|
-
CAPABILITIES.NAVIGATION_CONTROL,
|
|
36
|
-
CAPABILITIES.SCREENSHOT_PARTIAL,
|
|
37
|
-
CAPABILITIES.PATCH_DOM,
|
|
38
|
-
CAPABILITIES.PATCH_STYLES,
|
|
39
|
-
CAPABILITIES.AUTOMATION_INPUT,
|
|
40
|
-
CAPABILITIES.CDP_DOM_SNAPSHOT,
|
|
41
|
-
CAPABILITIES.CDP_BOX_MODEL,
|
|
42
|
-
CAPABILITIES.CDP_STYLES,
|
|
43
|
-
CAPABILITIES.CDP_INPUT,
|
|
44
|
-
CAPABILITIES.TABS_MANAGE,
|
|
45
|
-
CAPABILITIES.PERFORMANCE_READ,
|
|
46
|
-
CAPABILITIES.NETWORK_READ,
|
|
47
|
-
CAPABILITIES.NETWORK_INTERCEPT,
|
|
26
|
+
const LEGACY_POSITIONED_METHODS = new Set([
|
|
27
|
+
...LEGACY_METHOD_CAPABILITY_PREFIX,
|
|
28
|
+
...LEGACY_METHOD_CAPABILITY_SUFFIX,
|
|
48
29
|
]);
|
|
49
30
|
|
|
50
31
|
/** @type {Readonly<Record<CapabilityMethod, Capability | null>>} */
|
|
51
|
-
export const METHOD_CAPABILITIES = Object.freeze(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
'page.get_console': CAPABILITIES.PAGE_READ,
|
|
63
|
-
'page.wait_for_load_state': CAPABILITIES.PAGE_READ,
|
|
64
|
-
'page.get_storage': CAPABILITIES.PAGE_READ,
|
|
65
|
-
'page.get_text': CAPABILITIES.PAGE_READ,
|
|
66
|
-
'page.get_network': CAPABILITIES.NETWORK_READ,
|
|
67
|
-
// Interception mutates traffic (block/forge responses) - distinct from
|
|
68
|
-
// passive network reads so hosts can gate it independently.
|
|
69
|
-
'network.intercept.add': CAPABILITIES.NETWORK_INTERCEPT,
|
|
70
|
-
'network.intercept.remove': CAPABILITIES.NETWORK_INTERCEPT,
|
|
71
|
-
'network.intercept.list': CAPABILITIES.NETWORK_INTERCEPT,
|
|
72
|
-
'network.intercept.clear': CAPABILITIES.NETWORK_INTERCEPT,
|
|
73
|
-
'navigation.navigate': CAPABILITIES.NAVIGATION_CONTROL,
|
|
74
|
-
'navigation.reload': CAPABILITIES.NAVIGATION_CONTROL,
|
|
75
|
-
'navigation.go_back': CAPABILITIES.NAVIGATION_CONTROL,
|
|
76
|
-
'navigation.go_forward': CAPABILITIES.NAVIGATION_CONTROL,
|
|
77
|
-
'dom.query': CAPABILITIES.DOM_READ,
|
|
78
|
-
'dom.describe': CAPABILITIES.DOM_READ,
|
|
79
|
-
'dom.get_text': CAPABILITIES.DOM_READ,
|
|
80
|
-
'dom.get_attributes': CAPABILITIES.DOM_READ,
|
|
81
|
-
'dom.wait_for': CAPABILITIES.DOM_READ,
|
|
82
|
-
'dom.find_by_text': CAPABILITIES.DOM_READ,
|
|
83
|
-
'dom.find_by_role': CAPABILITIES.DOM_READ,
|
|
84
|
-
'dom.get_html': CAPABILITIES.DOM_READ,
|
|
85
|
-
'dom.get_accessibility_tree': CAPABILITIES.DOM_READ,
|
|
86
|
-
'layout.get_box_model': CAPABILITIES.LAYOUT_READ,
|
|
87
|
-
'layout.hit_test': CAPABILITIES.LAYOUT_READ,
|
|
88
|
-
'styles.get_computed': CAPABILITIES.STYLES_READ,
|
|
89
|
-
'styles.get_matched_rules': CAPABILITIES.STYLES_READ,
|
|
90
|
-
'viewport.scroll': CAPABILITIES.VIEWPORT_CONTROL,
|
|
91
|
-
'viewport.resize': CAPABILITIES.VIEWPORT_CONTROL,
|
|
92
|
-
'input.click': CAPABILITIES.AUTOMATION_INPUT,
|
|
93
|
-
'input.focus': CAPABILITIES.AUTOMATION_INPUT,
|
|
94
|
-
'input.type': CAPABILITIES.AUTOMATION_INPUT,
|
|
95
|
-
'input.fill': CAPABILITIES.AUTOMATION_INPUT,
|
|
96
|
-
'input.press_key': CAPABILITIES.AUTOMATION_INPUT,
|
|
97
|
-
'input.set_checked': CAPABILITIES.AUTOMATION_INPUT,
|
|
98
|
-
'input.select_option': CAPABILITIES.AUTOMATION_INPUT,
|
|
99
|
-
'input.hover': CAPABILITIES.AUTOMATION_INPUT,
|
|
100
|
-
'input.drag': CAPABILITIES.AUTOMATION_INPUT,
|
|
101
|
-
'input.scroll_into_view': CAPABILITIES.AUTOMATION_INPUT,
|
|
102
|
-
'screenshot.capture_region': CAPABILITIES.SCREENSHOT_PARTIAL,
|
|
103
|
-
'screenshot.capture_element': CAPABILITIES.SCREENSHOT_PARTIAL,
|
|
104
|
-
'screenshot.capture_full_page': CAPABILITIES.SCREENSHOT_PARTIAL,
|
|
105
|
-
'patch.apply_styles': CAPABILITIES.PATCH_STYLES,
|
|
106
|
-
'patch.apply_dom': CAPABILITIES.PATCH_DOM,
|
|
107
|
-
'patch.list': CAPABILITIES.PATCH_DOM,
|
|
108
|
-
'patch.rollback': CAPABILITIES.PATCH_DOM,
|
|
109
|
-
'patch.commit_session_baseline': CAPABILITIES.PATCH_DOM,
|
|
110
|
-
'cdp.get_document': CAPABILITIES.CDP_DOM_SNAPSHOT,
|
|
111
|
-
'cdp.get_dom_snapshot': CAPABILITIES.CDP_DOM_SNAPSHOT,
|
|
112
|
-
'cdp.get_box_model': CAPABILITIES.CDP_BOX_MODEL,
|
|
113
|
-
'cdp.get_computed_styles_for_node': CAPABILITIES.CDP_STYLES,
|
|
114
|
-
'cdp.dispatch_key_event': CAPABILITIES.CDP_INPUT,
|
|
115
|
-
'performance.get_metrics': CAPABILITIES.PERFORMANCE_READ,
|
|
116
|
-
'log.tail': null,
|
|
117
|
-
'health.ping': null,
|
|
118
|
-
'daemon.metrics': null,
|
|
119
|
-
});
|
|
32
|
+
export const METHOD_CAPABILITIES = Object.freeze(
|
|
33
|
+
/** @type {Record<CapabilityMethod, Capability | null>} */ (
|
|
34
|
+
Object.fromEntries(
|
|
35
|
+
[
|
|
36
|
+
...LEGACY_METHOD_CAPABILITY_PREFIX,
|
|
37
|
+
...BRIDGE_METHODS.filter((method) => !LEGACY_POSITIONED_METHODS.has(method)),
|
|
38
|
+
...LEGACY_METHOD_CAPABILITY_SUFFIX,
|
|
39
|
+
].map((method) => [method, BRIDGE_METHOD_REGISTRY[method].capability])
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
);
|
|
120
43
|
|
|
121
44
|
/**
|
|
122
45
|
* @param {unknown} value
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
export const CAPABILITIES = Object.freeze({
|
|
4
|
+
PAGE_READ: 'page.read',
|
|
5
|
+
PAGE_EVALUATE: 'page.evaluate',
|
|
6
|
+
DOM_READ: 'dom.read',
|
|
7
|
+
STYLES_READ: 'styles.read',
|
|
8
|
+
LAYOUT_READ: 'layout.read',
|
|
9
|
+
VIEWPORT_CONTROL: 'viewport.control',
|
|
10
|
+
NAVIGATION_CONTROL: 'navigation.control',
|
|
11
|
+
SCREENSHOT_PARTIAL: 'screenshot.partial',
|
|
12
|
+
PATCH_DOM: 'patch.dom',
|
|
13
|
+
PATCH_STYLES: 'patch.styles',
|
|
14
|
+
CDP_DOM_SNAPSHOT: 'cdp.dom_snapshot',
|
|
15
|
+
CDP_BOX_MODEL: 'cdp.box_model',
|
|
16
|
+
CDP_STYLES: 'cdp.styles',
|
|
17
|
+
CDP_INPUT: 'cdp.input',
|
|
18
|
+
AUTOMATION_INPUT: 'automation.input',
|
|
19
|
+
TABS_MANAGE: 'tabs.manage',
|
|
20
|
+
PERFORMANCE_READ: 'performance.read',
|
|
21
|
+
NETWORK_READ: 'network.read',
|
|
22
|
+
NETWORK_INTERCEPT: 'network.intercept',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_CAPABILITIES = Object.freeze([
|
|
26
|
+
CAPABILITIES.PAGE_READ,
|
|
27
|
+
CAPABILITIES.PAGE_EVALUATE,
|
|
28
|
+
CAPABILITIES.DOM_READ,
|
|
29
|
+
CAPABILITIES.STYLES_READ,
|
|
30
|
+
CAPABILITIES.LAYOUT_READ,
|
|
31
|
+
CAPABILITIES.VIEWPORT_CONTROL,
|
|
32
|
+
CAPABILITIES.NAVIGATION_CONTROL,
|
|
33
|
+
CAPABILITIES.SCREENSHOT_PARTIAL,
|
|
34
|
+
CAPABILITIES.PATCH_DOM,
|
|
35
|
+
CAPABILITIES.PATCH_STYLES,
|
|
36
|
+
CAPABILITIES.AUTOMATION_INPUT,
|
|
37
|
+
CAPABILITIES.CDP_DOM_SNAPSHOT,
|
|
38
|
+
CAPABILITIES.CDP_BOX_MODEL,
|
|
39
|
+
CAPABILITIES.CDP_STYLES,
|
|
40
|
+
CAPABILITIES.CDP_INPUT,
|
|
41
|
+
CAPABILITIES.TABS_MANAGE,
|
|
42
|
+
CAPABILITIES.PERFORMANCE_READ,
|
|
43
|
+
CAPABILITIES.NETWORK_READ,
|
|
44
|
+
CAPABILITIES.NETWORK_INTERCEPT,
|
|
45
|
+
]);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import { BRIDGE_METHODS, BRIDGE_METHOD_REGISTRY } from './registry.js';
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Shared Browser Bridge defaults used by the protocol, MCP layer, and runtime
|
|
5
7
|
* guidance. Keep this as the source of truth for default limits and presets.
|
|
@@ -70,8 +72,8 @@ export const BUDGET_PRESETS = Object.freeze({
|
|
|
70
72
|
deep: { maxNodes: 100, maxDepth: 8, textBudget: 2000, tokenBudget: 4000 },
|
|
71
73
|
});
|
|
72
74
|
|
|
73
|
-
/** @type {
|
|
74
|
-
|
|
75
|
+
/** @type {readonly import('./types.js').BridgeMethod[]} */
|
|
76
|
+
const LEGACY_DEBUGGER_BACKED_METHOD_ORDER = [
|
|
75
77
|
'page.evaluate',
|
|
76
78
|
'dom.get_accessibility_tree',
|
|
77
79
|
'viewport.resize',
|
|
@@ -88,6 +90,19 @@ export const DEBUGGER_BACKED_METHODS = new Set([
|
|
|
88
90
|
'cdp.get_box_model',
|
|
89
91
|
'cdp.get_computed_styles_for_node',
|
|
90
92
|
'cdp.dispatch_key_event',
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const LEGACY_DEBUGGER_BACKED_METHODS = new Set(LEGACY_DEBUGGER_BACKED_METHOD_ORDER);
|
|
96
|
+
|
|
97
|
+
/** @type {ReadonlySet<string>} */
|
|
98
|
+
export const DEBUGGER_BACKED_METHODS = new Set([
|
|
99
|
+
...LEGACY_DEBUGGER_BACKED_METHOD_ORDER.filter(
|
|
100
|
+
(method) => BRIDGE_METHOD_REGISTRY[method].debuggerBacked
|
|
101
|
+
),
|
|
102
|
+
...BRIDGE_METHODS.filter(
|
|
103
|
+
(method) =>
|
|
104
|
+
BRIDGE_METHOD_REGISTRY[method].debuggerBacked && !LEGACY_DEBUGGER_BACKED_METHODS.has(method)
|
|
105
|
+
),
|
|
91
106
|
]);
|
|
92
107
|
|
|
93
108
|
/**
|