@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.
@@ -260,8 +260,9 @@ export async function stopBridgeDaemon(options = {}) {
260
260
  previousPid = endpointOwnerPid;
261
261
  previouslyRunning = true;
262
262
  } else if (reachable) {
263
- // TCP endpoints do not expose an owner pid. In that case daemon
264
- // reachability is the evidence supporting the recorded pid.
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: await readPidFn(pidPath),
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 !== 'socket') {
444
- return null;
459
+ export async function findDaemonPidByTransport(transport, options = {}) {
460
+ if (transport.type === 'socket') {
461
+ return findDaemonPidBySocket(transport.socketPath, options);
445
462
  }
446
- return findDaemonPidBySocket(transport.socketPath);
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
- if (process.platform === 'win32') {
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 execFileAsync('lsof', ['-t', '--', socketPath]);
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) || isLsofNoResultsError(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() && normalizeBridgeAuthToken(message.authToken) !== this.authToken) {
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 const CAPABILITIES = Object.freeze({
7
- PAGE_READ: 'page.read',
8
- PAGE_EVALUATE: 'page.evaluate',
9
- DOM_READ: 'dom.read',
10
- STYLES_READ: 'styles.read',
11
- LAYOUT_READ: 'layout.read',
12
- VIEWPORT_CONTROL: 'viewport.control',
13
- NAVIGATION_CONTROL: 'navigation.control',
14
- SCREENSHOT_PARTIAL: 'screenshot.partial',
15
- PATCH_DOM: 'patch.dom',
16
- PATCH_STYLES: 'patch.styles',
17
- CDP_DOM_SNAPSHOT: 'cdp.dom_snapshot',
18
- CDP_BOX_MODEL: 'cdp.box_model',
19
- CDP_STYLES: 'cdp.styles',
20
- CDP_INPUT: 'cdp.input',
21
- AUTOMATION_INPUT: 'automation.input',
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
- export const DEFAULT_CAPABILITIES = Object.freeze([
29
- CAPABILITIES.PAGE_READ,
30
- CAPABILITIES.PAGE_EVALUATE,
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
- 'access.request': null,
53
- 'tabs.list': null,
54
- 'tabs.create': CAPABILITIES.TABS_MANAGE,
55
- 'tabs.close': CAPABILITIES.TABS_MANAGE,
56
- 'tabs.activate': CAPABILITIES.TABS_MANAGE,
57
- 'skill.get_runtime_context': null,
58
- 'setup.get_status': null,
59
- 'setup.install': null,
60
- 'page.get_state': CAPABILITIES.PAGE_READ,
61
- 'page.evaluate': CAPABILITIES.PAGE_EVALUATE,
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 {ReadonlySet<string>} */
74
- export const DEBUGGER_BACKED_METHODS = new Set([
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
  /**