@browserbridge/bbx 1.0.0 → 1.0.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.
Files changed (52) hide show
  1. package/README.md +3 -1
  2. package/docs/api-reference.md +33 -33
  3. package/docs/mcp-vs-cli.md +104 -104
  4. package/docs/publishing.md +1 -3
  5. package/docs/quickstart.md +6 -6
  6. package/docs/unpacked-extension.md +72 -0
  7. package/manifest.json +3 -17
  8. package/package.json +44 -42
  9. package/packages/agent-client/src/cli-helpers.js +10 -5
  10. package/packages/agent-client/src/cli.js +65 -135
  11. package/packages/agent-client/src/client.js +37 -17
  12. package/packages/agent-client/src/command-registry.js +101 -69
  13. package/packages/agent-client/src/detect.js +3 -6
  14. package/packages/agent-client/src/install.js +10 -27
  15. package/packages/agent-client/src/mcp-config.js +11 -30
  16. package/packages/agent-client/src/runtime.js +41 -20
  17. package/packages/agent-client/src/setup-status.js +13 -28
  18. package/packages/extension/src/background-helpers.js +51 -36
  19. package/packages/extension/src/background-routing.js +11 -13
  20. package/packages/extension/src/background.js +562 -299
  21. package/packages/extension/src/content-script-helpers.js +17 -16
  22. package/packages/extension/src/content-script.js +175 -109
  23. package/packages/extension/src/sidepanel-helpers.js +3 -1
  24. package/packages/extension/ui/popup.js +39 -20
  25. package/packages/extension/ui/sidepanel.js +108 -191
  26. package/packages/extension/ui/ui.css +2 -1
  27. package/packages/mcp-server/src/handlers.js +546 -250
  28. package/packages/mcp-server/src/server.js +558 -257
  29. package/packages/native-host/bin/bridge-daemon.js +6 -2
  30. package/packages/native-host/bin/install-manifest.js +2 -2
  31. package/packages/native-host/bin/postinstall.js +4 -2
  32. package/packages/native-host/src/config.js +11 -7
  33. package/packages/native-host/src/daemon.js +143 -92
  34. package/packages/native-host/src/install-manifest.js +73 -22
  35. package/packages/native-host/src/native-host.js +55 -40
  36. package/packages/protocol/src/budget.js +3 -7
  37. package/packages/protocol/src/capabilities.js +3 -3
  38. package/packages/protocol/src/errors.js +11 -11
  39. package/packages/protocol/src/protocol.js +104 -71
  40. package/packages/protocol/src/registry.js +300 -45
  41. package/packages/protocol/src/summary.js +249 -106
  42. package/packages/protocol/src/types.js +1 -1
  43. package/skills/browser-bridge/SKILL.md +1 -1
  44. package/skills/browser-bridge/agents/openai.yaml +3 -3
  45. package/skills/browser-bridge/references/interaction.md +33 -11
  46. package/skills/browser-bridge/references/patch-workflow.md +3 -0
  47. package/skills/browser-bridge/references/protocol.md +125 -70
  48. package/skills/browser-bridge/references/tailwind.md +12 -11
  49. package/skills/browser-bridge/references/token-efficiency.md +23 -22
  50. package/skills/browser-bridge/references/ui-workflows.md +8 -0
  51. package/packages/extension/ui/offscreen.html +0 -6
  52. package/packages/extension/ui/offscreen.js +0 -61
@@ -32,11 +32,9 @@ import {
32
32
  normalizeViewportResizeParams,
33
33
  normalizeWaitForLoadStateParams,
34
34
  normalizeWaitForParams,
35
- SUPPORTED_VERSIONS
36
- } from '../../protocol/src/index.js';
37
- import {
38
- summarizeBridgeResponse,
35
+ SUPPORTED_VERSIONS,
39
36
  } from '../../protocol/src/index.js';
37
+ import { summarizeBridgeResponse } from '../../protocol/src/index.js';
40
38
  import {
41
39
  enforceTokenBudget,
42
40
  getResponseDiagnostics,
@@ -47,13 +45,13 @@ import {
47
45
  shouldLogAction,
48
46
  simplifyAXNode,
49
47
  summarizeActionResult,
50
- summarizeTabResult
48
+ summarizeTabResult,
51
49
  } from './background-helpers.js';
52
50
  import {
53
51
  isRestrictedAutomationUrl,
54
52
  normalizeRequestedAccessTab,
55
53
  resolveWindowScopedTab,
56
- selectRequestTabCandidate
54
+ selectRequestTabCandidate,
57
55
  } from './background-routing.js';
58
56
  import { TabDebuggerCoordinator } from './debugger-coordinator.js';
59
57
 
@@ -212,7 +210,9 @@ function sendIdentity(port) {
212
210
  void getProfileLabel().then((profileLabel) => {
213
211
  try {
214
212
  port.postMessage({ type: 'host.identity', browserName, profileLabel });
215
- } catch { /* port may have disconnected */ }
213
+ } catch {
214
+ /* port may have disconnected */
215
+ }
216
216
  });
217
217
  }
218
218
 
@@ -227,7 +227,9 @@ function sendActivityUpdate(port = state.nativePort) {
227
227
  if (!port) return;
228
228
  try {
229
229
  port.postMessage({ type: 'host.activity', at: Date.now() });
230
- } catch { /* port may have disconnected */ }
230
+ } catch {
231
+ /* port may have disconnected */
232
+ }
231
233
  }
232
234
 
233
235
  /**
@@ -239,8 +241,13 @@ function sendActivityUpdate(port = state.nativePort) {
239
241
  function sendAccessUpdate(enabled) {
240
242
  if (!state.nativePort) return;
241
243
  try {
242
- state.nativePort.postMessage({ type: 'host.access_update', accessEnabled: enabled });
243
- } catch { /* port may have disconnected */ }
244
+ state.nativePort.postMessage({
245
+ type: 'host.access_update',
246
+ accessEnabled: enabled,
247
+ });
248
+ } catch {
249
+ /* port may have disconnected */
250
+ }
244
251
  }
245
252
 
246
253
  const NATIVE_APP_NAME = 'com.browserbridge.browser_bridge';
@@ -296,7 +303,7 @@ function getVersionNegotiationPayload(requestedVersion) {
296
303
  ...(localIsNewer ? { deprecated_since: latestSupported } : {}),
297
304
  migration_hint: localIsNewer
298
305
  ? `Browser Bridge extension is newer than the client protocol ${requestedVersion}. Update the Browser Bridge CLI/npm package to ${latestSupported} or later.`
299
- : `Browser Bridge extension is older than the client protocol ${requestedVersion}. Update the extension to a build that supports ${requestedVersion}.`
306
+ : `Browser Bridge extension is older than the client protocol ${requestedVersion}. Update the extension to a build that supports ${requestedVersion}.`,
300
307
  };
301
308
  }
302
309
 
@@ -322,13 +329,13 @@ const state = {
322
329
  setupInstallPendingRequestId: null,
323
330
  setupInstallPendingAction: null,
324
331
  setupInstallPendingKey: null,
325
- setupInstallError: null
332
+ setupInstallError: null,
326
333
  };
327
334
 
328
335
  const tabDebugger = new TabDebuggerCoordinator({
329
336
  attach: (target, protocolVersion) => chrome.debugger.attach(target, protocolVersion),
330
337
  detach: (target) => chrome.debugger.detach(target),
331
- protocolVersion: DEBUGGER_PROTOCOL_VERSION
338
+ protocolVersion: DEBUGGER_PROTOCOL_VERSION,
332
339
  });
333
340
 
334
341
  void initializeState().catch(reportAsyncError);
@@ -388,27 +395,35 @@ chrome.runtime.onConnect.addListener((port) => {
388
395
  });
389
396
 
390
397
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
391
- if (message?.type === 'bridge.open-sidepanel' && typeof message.tabId === 'number' && typeof message.windowId === 'number') {
392
- void openSidePanelForTab(message.tabId, message.windowId).then(() => {
393
- sendResponse({ ok: true });
394
- }).catch((error) => {
395
- sendResponse({
396
- ok: false,
397
- error: error instanceof Error ? error.message : String(error)
398
+ if (
399
+ message?.type === 'bridge.open-sidepanel' &&
400
+ typeof message.tabId === 'number' &&
401
+ typeof message.windowId === 'number'
402
+ ) {
403
+ void openSidePanelForTab(message.tabId, message.windowId)
404
+ .then(() => {
405
+ sendResponse({ ok: true });
406
+ })
407
+ .catch((error) => {
408
+ sendResponse({
409
+ ok: false,
410
+ error: error instanceof Error ? error.message : String(error),
411
+ });
398
412
  });
399
- });
400
413
  return true;
401
414
  }
402
415
 
403
416
  if (message?.type === 'bridge.open-sidepanel' && sender.tab?.id && sender.tab.windowId) {
404
- void openSidePanelForTab(sender.tab.id, sender.tab.windowId).then(() => {
405
- sendResponse({ ok: true });
406
- }).catch((error) => {
407
- sendResponse({
408
- ok: false,
409
- error: error instanceof Error ? error.message : String(error)
417
+ void openSidePanelForTab(sender.tab.id, sender.tab.windowId)
418
+ .then(() => {
419
+ sendResponse({ ok: true });
420
+ })
421
+ .catch((error) => {
422
+ sendResponse({
423
+ ok: false,
424
+ error: error instanceof Error ? error.message : String(error),
425
+ });
410
426
  });
411
- });
412
427
  return true;
413
428
  }
414
429
 
@@ -453,9 +468,8 @@ function clearNativeReconnectTimer() {
453
468
  */
454
469
  function scheduleNativeReconnect(errorMessage, options = {}) {
455
470
  const method = typeof options.method === 'string' ? options.method : 'native.disconnect';
456
- const summaryPrefix = typeof options.summaryPrefix === 'string'
457
- ? options.summaryPrefix
458
- : 'Native host disconnected';
471
+ const summaryPrefix =
472
+ typeof options.summaryPrefix === 'string' ? options.summaryPrefix : 'Native host disconnected';
459
473
  const updateDisconnectedUi = options.updateDisconnectedUi === true;
460
474
 
461
475
  state.nativeReconnectAttempts += 1;
@@ -467,7 +481,7 @@ function scheduleNativeReconnect(errorMessage, options = {}) {
467
481
  broadcastUi({
468
482
  type: 'native.status',
469
483
  connected: false,
470
- error: errorMessage
484
+ error: errorMessage,
471
485
  });
472
486
  }
473
487
  void emitUiState().catch(reportAsyncError);
@@ -476,7 +490,7 @@ function scheduleNativeReconnect(errorMessage, options = {}) {
476
490
  method,
477
491
  source: 'extension',
478
492
  ok: false,
479
- summary: `${summaryPrefix} (attempt ${reconnectAttempt}): ${errorMessage}. Reconnecting in ${nativeReconnectDelay}ms.`
493
+ summary: `${summaryPrefix} (attempt ${reconnectAttempt}): ${errorMessage}. Reconnecting in ${nativeReconnectDelay}ms.`,
480
494
  });
481
495
 
482
496
  clearNativeReconnectTimer();
@@ -514,7 +528,7 @@ function connectNative() {
514
528
  method: 'native.reconnect',
515
529
  source: 'extension',
516
530
  ok: true,
517
- summary: `Native host reconnected after ${reconnectAttempts} attempt${reconnectAttempts === 1 ? '' : 's'}.`
531
+ summary: `Native host reconnected after ${reconnectAttempts} attempt${reconnectAttempts === 1 ? '' : 's'}.`,
518
532
  });
519
533
  }
520
534
  }, 500);
@@ -530,14 +544,14 @@ function connectNative() {
530
544
  scheduleNativeReconnect(disconnectError, {
531
545
  method: 'native.disconnect',
532
546
  summaryPrefix: 'Native host disconnected',
533
- updateDisconnectedUi: state.nativePort === candidatePort
547
+ updateDisconnectedUi: state.nativePort === candidatePort,
534
548
  });
535
549
  });
536
550
  } catch (error) {
537
551
  scheduleNativeReconnect(getErrorMessage(error), {
538
552
  method: 'native.connect',
539
553
  summaryPrefix: 'Native host connection failed',
540
- updateDisconnectedUi: !state.nativePort
554
+ updateDisconnectedUi: !state.nativePort,
541
555
  });
542
556
  }
543
557
  }
@@ -550,9 +564,7 @@ function connectNative() {
550
564
  * @returns {Promise<void>}
551
565
  */
552
566
  async function handleBridgeRequest(request) {
553
- const actionContext = shouldLogAction(request.method)
554
- ? await getActionContext(request)
555
- : null;
567
+ const actionContext = shouldLogAction(request.method) ? await getActionContext(request) : null;
556
568
  /** @type {BridgeResponse} */
557
569
  let response;
558
570
 
@@ -585,15 +597,21 @@ async function handleBridgeRequest(request) {
585
597
  async function dispatchBridgeRequest(request) {
586
598
  switch (request.method) {
587
599
  case 'health.ping':
588
- return createSuccess(request.id, {
589
- extension: 'ok',
590
- access: await getAccessStatus(),
591
- ...getVersionNegotiationPayload(request.meta?.protocol_version)
592
- }, { method: request.method });
600
+ return createSuccess(
601
+ request.id,
602
+ {
603
+ extension: 'ok',
604
+ access: await getAccessStatus(),
605
+ ...getVersionNegotiationPayload(request.meta?.protocol_version),
606
+ },
607
+ { method: request.method }
608
+ );
593
609
  case 'access.request':
594
610
  return handleAccessRequest(request);
595
611
  case 'skill.get_runtime_context':
596
- return createSuccess(request.id, createRuntimeContext(), { method: request.method });
612
+ return createSuccess(request.id, createRuntimeContext(), {
613
+ method: request.method,
614
+ });
597
615
  case 'tabs.list':
598
616
  return handleListTabs(request);
599
617
  case 'tabs.create':
@@ -659,7 +677,11 @@ async function dispatchBridgeRequest(request) {
659
677
  case 'cdp.get_computed_styles_for_node':
660
678
  return handleCdpRequest(request);
661
679
  default:
662
- return createFailure(request.id, ERROR_CODES.INVALID_REQUEST, `Unhandled method ${request.method}`);
680
+ return createFailure(
681
+ request.id,
682
+ ERROR_CODES.INVALID_REQUEST,
683
+ `Unhandled method ${request.method}`
684
+ );
663
685
  }
664
686
  }
665
687
 
@@ -739,7 +761,9 @@ async function clearEnabledWindowIfGone() {
739
761
  if (msg.includes('no window') || msg.includes('not found') || msg.includes('window closed')) {
740
762
  gone = true;
741
763
  } else {
742
- await new Promise((r) => { setTimeout(r, 300); });
764
+ await new Promise((r) => {
765
+ setTimeout(r, 300);
766
+ });
743
767
  try {
744
768
  await chrome.windows.get(state.enabledWindow.windowId);
745
769
  } catch (_e2) {
@@ -842,10 +866,14 @@ async function getAccessStatus() {
842
866
  */
843
867
  async function handleListTabs(request) {
844
868
  if (!state.enabledWindow) {
845
- return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_WINDOW_OFF, null, { method: request.method });
869
+ return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_WINDOW_OFF, null, {
870
+ method: request.method,
871
+ });
846
872
  }
847
873
 
848
- const tabs = await chrome.tabs.query({ windowId: state.enabledWindow.windowId });
874
+ const tabs = await chrome.tabs.query({
875
+ windowId: state.enabledWindow.windowId,
876
+ });
849
877
  const summarized = tabs
850
878
  .map((tab) => {
851
879
  if (!isNumber(tab.id) || typeof tab.url !== 'string') {
@@ -857,7 +885,7 @@ async function handleListTabs(request) {
857
885
  active: Boolean(tab.active),
858
886
  title: tab.title ?? '',
859
887
  origin: safeOrigin(tab.url),
860
- url: tab.url
888
+ url: tab.url,
861
889
  };
862
890
  })
863
891
  .filter((tab) => tab !== null);
@@ -893,7 +921,7 @@ const TAB_BOUND_NORMALIZERS = {
893
921
  'patch.rollback': normalizePatchOperation,
894
922
  'patch.commit_session_baseline': normalizePatchOperation,
895
923
  'page.get_storage': normalizeStorageParams,
896
- 'page.get_text': normalizePageTextParams
924
+ 'page.get_text': normalizePageTextParams,
897
925
  };
898
926
 
899
927
  /**
@@ -915,11 +943,15 @@ async function handleTabBoundRequest(request) {
915
943
  }
916
944
 
917
945
  const timeoutMs = getContentScriptTimeout(request.method, payload);
918
- const response = await sendTabMessage(target.tabId, {
919
- type: 'bridge.execute',
920
- method: request.method,
921
- params: payload
922
- }, timeoutMs);
946
+ const response = await sendTabMessage(
947
+ target.tabId,
948
+ {
949
+ type: 'bridge.execute',
950
+ method: request.method,
951
+ params: payload,
952
+ },
953
+ timeoutMs
954
+ );
923
955
  if (response?.error) {
924
956
  return toFailureResponse(request, response.error);
925
957
  }
@@ -955,7 +987,9 @@ async function handleNavigationRequest(request) {
955
987
  : await chrome.tabs.get(target.tabId);
956
988
  await emitUiState();
957
989
 
958
- return createSuccess(request.id, summarizeTabResult(tab, request.method), { method: request.method });
990
+ return createSuccess(request.id, summarizeTabResult(tab, request.method), {
991
+ method: request.method,
992
+ });
959
993
  }
960
994
 
961
995
  /**
@@ -987,7 +1021,9 @@ async function handlePageEvaluate(request) {
987
1021
  const target = await resolveRequestTarget(request);
988
1022
  const params = normalizeEvaluateParams(request.params);
989
1023
  if (!params.expression) {
990
- return createFailure(request.id, ERROR_CODES.INVALID_REQUEST, 'expression is required.', null, { method: request.method });
1024
+ return createFailure(request.id, ERROR_CODES.INVALID_REQUEST, 'expression is required.', null, {
1025
+ method: request.method,
1026
+ });
991
1027
  }
992
1028
  return tabDebugger.run(target.tabId, async (debugTarget) => {
993
1029
  const result = await chrome.debugger.sendCommand(debugTarget, 'Runtime.evaluate', {
@@ -997,19 +1033,29 @@ async function handlePageEvaluate(request) {
997
1033
  timeout: params.timeoutMs,
998
1034
  userGesture: true,
999
1035
  generatePreview: false,
1000
- replMode: true
1036
+ replMode: true,
1001
1037
  });
1002
- const cdpResult = /** @type {{ result?: { type?: string, value?: unknown, description?: string }, exceptionDetails?: { text?: string, exception?: { description?: string } } }} */ (result);
1038
+ const cdpResult =
1039
+ /** @type {{ result?: { type?: string, value?: unknown, description?: string }, exceptionDetails?: { text?: string, exception?: { description?: string } } }} */ (
1040
+ result
1041
+ );
1003
1042
  if (cdpResult.exceptionDetails) {
1004
- const errText = cdpResult.exceptionDetails.exception?.description
1005
- || cdpResult.exceptionDetails.text
1006
- || 'Evaluation failed.';
1007
- return createFailure(request.id, ERROR_CODES.INTERNAL_ERROR, errText, null, { method: request.method });
1043
+ const errText =
1044
+ cdpResult.exceptionDetails.exception?.description ||
1045
+ cdpResult.exceptionDetails.text ||
1046
+ 'Evaluation failed.';
1047
+ return createFailure(request.id, ERROR_CODES.INTERNAL_ERROR, errText, null, {
1048
+ method: request.method,
1049
+ });
1008
1050
  }
1009
- return createSuccess(request.id, {
1010
- value: cdpResult.result?.value ?? null,
1011
- type: cdpResult.result?.type ?? 'undefined'
1012
- }, { method: request.method });
1051
+ return createSuccess(
1052
+ request.id,
1053
+ {
1054
+ value: cdpResult.result?.value ?? null,
1055
+ type: cdpResult.result?.type ?? 'undefined',
1056
+ },
1057
+ { method: request.method }
1058
+ );
1013
1059
  });
1014
1060
  }
1015
1061
 
@@ -1026,12 +1072,19 @@ async function handlePageGetConsole(request) {
1026
1072
 
1027
1073
  await primeTabConsoleCapture(target.tabId);
1028
1074
  const { entries, dropped } = await readConsoleBuffer(target.tabId, params.clear);
1029
- const filtered = params.level === 'all'
1030
- ? entries
1031
- : entries.filter((/** @type {{ level: string }} */ e) => matchesConsoleLevel(params.level, e.level));
1075
+ const filtered =
1076
+ params.level === 'all'
1077
+ ? entries
1078
+ : entries.filter((/** @type {{ level: string }} */ e) =>
1079
+ matchesConsoleLevel(params.level, e.level)
1080
+ );
1032
1081
  const limited = filtered.slice(-params.limit);
1033
1082
 
1034
- return createSuccess(request.id, { entries: limited, count: limited.length, total: entries.length, dropped }, { method: request.method });
1083
+ return createSuccess(
1084
+ request.id,
1085
+ { entries: limited, count: limited.length, total: entries.length, dropped },
1086
+ { method: request.method }
1087
+ );
1035
1088
  }
1036
1089
 
1037
1090
  /**
@@ -1042,15 +1095,19 @@ async function handlePageGetConsole(request) {
1042
1095
  */
1043
1096
  async function handleCreateTab(request) {
1044
1097
  if (!state.enabledWindow) {
1045
- return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_WINDOW_OFF, null, { method: request.method });
1098
+ return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_WINDOW_OFF, null, {
1099
+ method: request.method,
1100
+ });
1046
1101
  }
1047
1102
  const params = normalizeTabCreateParams(request.params);
1048
1103
  const tab = await chrome.tabs.create({
1049
1104
  url: params.url,
1050
1105
  active: params.active,
1051
- windowId: state.enabledWindow.windowId
1106
+ windowId: state.enabledWindow.windowId,
1107
+ });
1108
+ return createSuccess(request.id, summarizeTabResult(tab, request.method), {
1109
+ method: request.method,
1052
1110
  });
1053
- return createSuccess(request.id, summarizeTabResult(tab, request.method), { method: request.method });
1054
1111
  }
1055
1112
 
1056
1113
  /**
@@ -1062,19 +1119,33 @@ async function handleCreateTab(request) {
1062
1119
  async function handleCloseTab(request) {
1063
1120
  const params = normalizeTabCloseParams(request.params);
1064
1121
  if (!state.enabledWindow) {
1065
- return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_WINDOW_OFF, null, { method: request.method });
1122
+ return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_WINDOW_OFF, null, {
1123
+ method: request.method,
1124
+ });
1066
1125
  }
1067
1126
  let tab;
1068
1127
  try {
1069
1128
  tab = await chrome.tabs.get(params.tabId);
1070
1129
  } catch {
1071
- return createFailure(request.id, ERROR_CODES.TAB_MISMATCH, `Tab ${params.tabId} not found.`, null, { method: request.method });
1130
+ return createFailure(
1131
+ request.id,
1132
+ ERROR_CODES.TAB_MISMATCH,
1133
+ `Tab ${params.tabId} not found.`,
1134
+ null,
1135
+ { method: request.method }
1136
+ );
1072
1137
  }
1073
1138
  if (tab.windowId !== state.enabledWindow.windowId) {
1074
- return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_TAB_CLOSE, null, { method: request.method });
1139
+ return createFailure(request.id, ERROR_CODES.ACCESS_DENIED, ACCESS_DENIED_TAB_CLOSE, null, {
1140
+ method: request.method,
1141
+ });
1075
1142
  }
1076
1143
  await chrome.tabs.remove(params.tabId);
1077
- return createSuccess(request.id, { closed: true, tabId: params.tabId }, { method: request.method });
1144
+ return createSuccess(
1145
+ request.id,
1146
+ { closed: true, tabId: params.tabId },
1147
+ { method: request.method }
1148
+ );
1078
1149
  }
1079
1150
 
1080
1151
  /**
@@ -1091,18 +1162,22 @@ async function handleAccessibilityTree(request) {
1091
1162
  return tabDebugger.run(target.tabId, async (debugTarget) => {
1092
1163
  await chrome.debugger.sendCommand(debugTarget, 'Accessibility.enable', {});
1093
1164
  const result = await chrome.debugger.sendCommand(debugTarget, 'Accessibility.getFullAXTree', {
1094
- depth: params.maxDepth
1165
+ depth: params.maxDepth,
1095
1166
  });
1096
1167
  const cdpResult = /** @type {{ nodes?: Array<Record<string, unknown>> }} */ (result);
1097
1168
  const rawNodes = cdpResult.nodes || [];
1098
1169
  const pruned = rawNodes.slice(0, params.maxNodes).map(simplifyAXNode);
1099
1170
  await chrome.debugger.sendCommand(debugTarget, 'Accessibility.disable', {});
1100
- return createSuccess(request.id, {
1101
- nodes: pruned,
1102
- count: pruned.length,
1103
- total: rawNodes.length,
1104
- truncated: rawNodes.length > params.maxNodes
1105
- }, { method: request.method });
1171
+ return createSuccess(
1172
+ request.id,
1173
+ {
1174
+ nodes: pruned,
1175
+ count: pruned.length,
1176
+ total: rawNodes.length,
1177
+ truncated: rawNodes.length > params.maxNodes,
1178
+ },
1179
+ { method: request.method }
1180
+ );
1106
1181
  });
1107
1182
  }
1108
1183
 
@@ -1123,7 +1198,11 @@ async function handleGetNetwork(request) {
1123
1198
  ? entries.filter((/** @type {{ url: string }} */ e) => e.url.includes(urlPattern))
1124
1199
  : entries;
1125
1200
  const limited = filtered.slice(-params.limit);
1126
- return createSuccess(request.id, { entries: limited, count: limited.length, total: entries.length, dropped }, { method: request.method });
1201
+ return createSuccess(
1202
+ request.id,
1203
+ { entries: limited, count: limited.length, total: entries.length, dropped },
1204
+ { method: request.method }
1205
+ );
1127
1206
  }
1128
1207
 
1129
1208
  /**
@@ -1155,7 +1234,15 @@ async function ensureNetworkInterceptor(tabId) {
1155
1234
  globalThis.fetch = async function (...args) {
1156
1235
  // @ts-ignore
1157
1236
  const req = new Request(...args);
1158
- const entry = { method: req.method, url: req.url, status: 0, duration: 0, type: 'fetch', ts: Date.now(), size: 0 };
1237
+ const entry = {
1238
+ method: req.method,
1239
+ url: req.url,
1240
+ status: 0,
1241
+ duration: 0,
1242
+ type: 'fetch',
1243
+ ts: Date.now(),
1244
+ size: 0,
1245
+ };
1159
1246
  const startTime = performance.now();
1160
1247
  try {
1161
1248
  const resp = await origFetch.apply(globalThis, args);
@@ -1171,8 +1258,10 @@ async function ensureNetworkInterceptor(tabId) {
1171
1258
  } finally {
1172
1259
  buffer.push(entry);
1173
1260
  if (buffer.length > MAX) {
1174
- // @ts-ignore
1175
- globalThis.__bb_network_dropped = (globalThis.__bb_network_dropped || 0) + (buffer.length - MAX);
1261
+ const dropped =
1262
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_network_dropped;
1263
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_network_dropped =
1264
+ (typeof dropped === 'number' ? dropped : 0) + (buffer.length - MAX);
1176
1265
  buffer.splice(0, buffer.length - MAX);
1177
1266
  }
1178
1267
  }
@@ -1201,7 +1290,15 @@ async function ensureNetworkInterceptor(tabId) {
1201
1290
  */
1202
1291
  XMLHttpRequest.prototype.send = function (...args) {
1203
1292
  // @ts-ignore
1204
- const entry = { method: this.__bb_method || 'GET', url: this.__bb_url || '', status: 0, duration: 0, type: 'xhr', ts: Date.now(), size: 0 };
1293
+ const entry = {
1294
+ method: this.__bb_method || 'GET',
1295
+ url: this.__bb_url || '',
1296
+ status: 0,
1297
+ duration: 0,
1298
+ type: 'xhr',
1299
+ ts: Date.now(),
1300
+ size: 0,
1301
+ };
1205
1302
  const startTime = performance.now();
1206
1303
  this.addEventListener('loadend', () => {
1207
1304
  entry.status = this.status;
@@ -1210,14 +1307,16 @@ async function ensureNetworkInterceptor(tabId) {
1210
1307
  if (cl) entry.size = Number(cl);
1211
1308
  buffer.push(entry);
1212
1309
  if (buffer.length > MAX) {
1213
- // @ts-ignore
1214
- globalThis.__bb_network_dropped = (globalThis.__bb_network_dropped || 0) + (buffer.length - MAX);
1310
+ const dropped =
1311
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_network_dropped;
1312
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_network_dropped =
1313
+ (typeof dropped === 'number' ? dropped : 0) + (buffer.length - MAX);
1215
1314
  buffer.splice(0, buffer.length - MAX);
1216
1315
  }
1217
1316
  });
1218
1317
  return /** @type {any} */ (origSend).apply(this, args);
1219
1318
  };
1220
- }
1319
+ },
1221
1320
  });
1222
1321
  }
1223
1322
 
@@ -1246,7 +1345,7 @@ async function readNetworkBuffer(tabId, clear) {
1246
1345
  }
1247
1346
  return { entries: copy, dropped };
1248
1347
  },
1249
- args: [clear]
1348
+ args: [clear],
1250
1349
  });
1251
1350
  return /** @type {any} */ (results?.[0]?.result) || { entries: [], dropped: 0 };
1252
1351
  }
@@ -1269,16 +1368,20 @@ async function handleViewportResize(request) {
1269
1368
  width: params.width,
1270
1369
  height: params.height,
1271
1370
  deviceScaleFactor: params.deviceScaleFactor,
1272
- mobile: params.width < 768
1371
+ mobile: params.width < 768,
1273
1372
  });
1274
1373
  }
1275
- return createSuccess(request.id, {
1276
- resized: true,
1277
- width: params.width,
1278
- height: params.height,
1279
- deviceScaleFactor: params.deviceScaleFactor,
1280
- reset: params.reset
1281
- }, { method: request.method });
1374
+ return createSuccess(
1375
+ request.id,
1376
+ {
1377
+ resized: true,
1378
+ width: params.width,
1379
+ height: params.height,
1380
+ deviceScaleFactor: params.deviceScaleFactor,
1381
+ reset: params.reset,
1382
+ },
1383
+ { method: request.method }
1384
+ );
1282
1385
  });
1283
1386
  }
1284
1387
 
@@ -1291,7 +1394,9 @@ async function handleViewportResize(request) {
1291
1394
  async function handlePerformanceMetrics(request) {
1292
1395
  const target = await resolveRequestTarget(request);
1293
1396
  return tabDebugger.run(target.tabId, async (debugTarget) => {
1294
- await chrome.debugger.sendCommand(debugTarget, 'Performance.enable', { timeDomain: 'timeTicks' });
1397
+ await chrome.debugger.sendCommand(debugTarget, 'Performance.enable', {
1398
+ timeDomain: 'timeTicks',
1399
+ });
1295
1400
  const result = await chrome.debugger.sendCommand(debugTarget, 'Performance.getMetrics', {});
1296
1401
  await chrome.debugger.sendCommand(debugTarget, 'Performance.disable', {});
1297
1402
  const cdpResult = /** @type {{ metrics?: Array<{ name: string, value: number }> }} */ (result);
@@ -1315,7 +1420,9 @@ async function handleWaitForLoadState(request) {
1315
1420
  const tab = params.waitForLoad
1316
1421
  ? await waitForTabComplete(target.tabId, params.timeoutMs)
1317
1422
  : await chrome.tabs.get(target.tabId);
1318
- return createSuccess(request.id, summarizeTabResult(tab, request.method), { method: request.method });
1423
+ return createSuccess(request.id, summarizeTabResult(tab, request.method), {
1424
+ method: request.method,
1425
+ });
1319
1426
  }
1320
1427
 
1321
1428
  /**
@@ -1343,21 +1450,31 @@ async function ensureConsoleInterceptor(tabId) {
1343
1450
  globalThis.__bb_console_dropped = 0;
1344
1451
  const MAX = 200;
1345
1452
  const orig = /** @type {Record<string, Function>} */ ({});
1346
- const consoleMethods = /** @type {Record<string, (...args: unknown[]) => void>} */ (/** @type {unknown} */ (console));
1453
+ const consoleMethods =
1454
+ /** @type {Record<string, (...args: unknown[]) => void>} */ (
1455
+ /** @type {unknown} */ (console)
1456
+ );
1347
1457
  for (const level of ['log', 'warn', 'error', 'info', 'debug']) {
1348
1458
  orig[level] = consoleMethods[level];
1349
1459
  consoleMethods[level] = (...args) => {
1350
1460
  buffer.push({
1351
1461
  level,
1352
1462
  args: args.map((a) => {
1353
- try { return typeof a === 'object' ? JSON.stringify(a).slice(0, 500) : String(a).slice(0, 500); }
1354
- catch { return String(a).slice(0, 500); }
1463
+ try {
1464
+ return typeof a === 'object'
1465
+ ? JSON.stringify(a).slice(0, 500)
1466
+ : String(a).slice(0, 500);
1467
+ } catch {
1468
+ return String(a).slice(0, 500);
1469
+ }
1355
1470
  }),
1356
- ts: Date.now()
1471
+ ts: Date.now(),
1357
1472
  });
1358
1473
  if (buffer.length > MAX) {
1359
- // @ts-ignore
1360
- globalThis.__bb_console_dropped = (globalThis.__bb_console_dropped || 0) + (buffer.length - MAX);
1474
+ const dropped =
1475
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_console_dropped;
1476
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_console_dropped =
1477
+ (typeof dropped === 'number' ? dropped : 0) + (buffer.length - MAX);
1361
1478
  buffer.splice(0, buffer.length - MAX);
1362
1479
  }
1363
1480
  orig[level].apply(console, args);
@@ -1366,12 +1483,16 @@ async function ensureConsoleInterceptor(tabId) {
1366
1483
  globalThis.addEventListener('error', (e) => {
1367
1484
  buffer.push({
1368
1485
  level: 'exception',
1369
- args: [e.message || 'Unknown error', e.filename ? `${e.filename}:${e.lineno}:${e.colno}` : ''],
1370
- ts: Date.now()
1486
+ args: [
1487
+ e.message || 'Unknown error',
1488
+ e.filename ? `${e.filename}:${e.lineno}:${e.colno}` : '',
1489
+ ],
1490
+ ts: Date.now(),
1371
1491
  });
1372
1492
  if (buffer.length > MAX) {
1373
- // @ts-ignore
1374
- globalThis.__bb_console_dropped = (globalThis.__bb_console_dropped || 0) + (buffer.length - MAX);
1493
+ const dropped = /** @type {Record<string, unknown>} */ (globalThis).__bb_console_dropped;
1494
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_console_dropped =
1495
+ (typeof dropped === 'number' ? dropped : 0) + (buffer.length - MAX);
1375
1496
  buffer.splice(0, buffer.length - MAX);
1376
1497
  }
1377
1498
  });
@@ -1379,15 +1500,16 @@ async function ensureConsoleInterceptor(tabId) {
1379
1500
  buffer.push({
1380
1501
  level: 'rejection',
1381
1502
  args: [String(e.reason).slice(0, 500)],
1382
- ts: Date.now()
1503
+ ts: Date.now(),
1383
1504
  });
1384
1505
  if (buffer.length > MAX) {
1385
- // @ts-ignore
1386
- globalThis.__bb_console_dropped = (globalThis.__bb_console_dropped || 0) + (buffer.length - MAX);
1506
+ const dropped = /** @type {Record<string, unknown>} */ (globalThis).__bb_console_dropped;
1507
+ /** @type {Record<string, unknown>} */ (globalThis).__bb_console_dropped =
1508
+ (typeof dropped === 'number' ? dropped : 0) + (buffer.length - MAX);
1387
1509
  buffer.splice(0, buffer.length - MAX);
1388
1510
  }
1389
1511
  });
1390
- }
1512
+ },
1391
1513
  });
1392
1514
  }
1393
1515
 
@@ -1419,15 +1541,17 @@ async function primeTabConsoleCapture(tabId, resetBuffer = false) {
1419
1541
  */
1420
1542
  function isRecoverableInstrumentationError(error) {
1421
1543
  const message = normalizeRuntimeErrorMessage(getErrorMessage(error));
1422
- return message === ERROR_CODES.TAB_MISMATCH
1423
- || /Cannot access contents of/i.test(message)
1424
- || /The extensions gallery cannot be scripted/i.test(message)
1425
- || /Cannot access a chrome:\/\//i.test(message)
1426
- || /Cannot script/i.test(message)
1427
- || /CONTENT_SCRIPT_UNAVAILABLE/i.test(message)
1428
- || /No tab with id/i.test(message)
1429
- || /Cannot attach to this target/i.test(message)
1430
- || /Another debugger is already attached/i.test(message);
1544
+ return (
1545
+ message === ERROR_CODES.TAB_MISMATCH ||
1546
+ /Cannot access contents of/i.test(message) ||
1547
+ /The extensions gallery cannot be scripted/i.test(message) ||
1548
+ /Cannot access a chrome:\/\//i.test(message) ||
1549
+ /Cannot script/i.test(message) ||
1550
+ /CONTENT_SCRIPT_UNAVAILABLE/i.test(message) ||
1551
+ /No tab with id/i.test(message) ||
1552
+ /Cannot attach to this target/i.test(message) ||
1553
+ /Another debugger is already attached/i.test(message)
1554
+ );
1431
1555
  }
1432
1556
 
1433
1557
  /**
@@ -1455,7 +1579,7 @@ async function readConsoleBuffer(tabId, clear) {
1455
1579
  }
1456
1580
  return { entries: copy, dropped };
1457
1581
  },
1458
- args: [clear]
1582
+ args: [clear],
1459
1583
  });
1460
1584
  return /** @type {any} */ (results?.[0]?.result) || { entries: [], dropped: 0 };
1461
1585
  }
@@ -1469,7 +1593,9 @@ async function readConsoleBuffer(tabId, clear) {
1469
1593
  */
1470
1594
  async function primeWindowConsoleCapture(windowId, resetBuffer = false) {
1471
1595
  const tabs = await chrome.tabs.query({ windowId });
1472
- const tabIds = tabs.map((tab) => (isNumber(tab.id) ? tab.id : null)).filter((tabId) => tabId !== null);
1596
+ const tabIds = tabs
1597
+ .map((tab) => (isNumber(tab.id) ? tab.id : null))
1598
+ .filter((tabId) => tabId !== null);
1473
1599
  await Promise.allSettled(tabIds.map((tabId) => primeTabConsoleCapture(tabId, resetBuffer)));
1474
1600
  }
1475
1601
 
@@ -1527,27 +1653,36 @@ async function clearWindowBridgeState(windowId) {
1527
1653
  async function rollbackAllPatchesForTab(tabId) {
1528
1654
  try {
1529
1655
  await ensureContentScript(tabId);
1530
- const listed = await sendTabMessage(tabId, {
1531
- type: 'bridge.execute',
1532
- method: 'patch.list',
1533
- params: {}
1534
- }, CONTENT_SCRIPT_TIMEOUT_MS);
1656
+ const listed = await sendTabMessage(
1657
+ tabId,
1658
+ {
1659
+ type: 'bridge.execute',
1660
+ method: 'patch.list',
1661
+ params: {},
1662
+ },
1663
+ CONTENT_SCRIPT_TIMEOUT_MS
1664
+ );
1535
1665
  const patches = Array.isArray(listed) ? listed : listed?.patches;
1536
1666
  if (!Array.isArray(patches)) {
1537
1667
  return;
1538
1668
  }
1539
1669
  for (const patch of patches) {
1540
- const patchId = patch && typeof patch === 'object'
1541
- ? /** @type {Record<string, unknown>} */ (patch).patchId
1542
- : null;
1670
+ const patchId =
1671
+ patch && typeof patch === 'object'
1672
+ ? /** @type {Record<string, unknown>} */ (patch).patchId
1673
+ : null;
1543
1674
  if (typeof patchId !== 'string' || !patchId) {
1544
1675
  continue;
1545
1676
  }
1546
- await sendTabMessage(tabId, {
1547
- type: 'bridge.execute',
1548
- method: 'patch.rollback',
1549
- params: { patchId }
1550
- }, CONTENT_SCRIPT_TIMEOUT_MS);
1677
+ await sendTabMessage(
1678
+ tabId,
1679
+ {
1680
+ type: 'bridge.execute',
1681
+ method: 'patch.rollback',
1682
+ params: { patchId },
1683
+ },
1684
+ CONTENT_SCRIPT_TIMEOUT_MS
1685
+ );
1551
1686
  }
1552
1687
  } catch (error) {
1553
1688
  if (!isRecoverableInstrumentationError(error)) {
@@ -1588,16 +1723,28 @@ async function handleScreenshot(target, method, params) {
1588
1723
  if (method === 'screenshot.capture_element') {
1589
1724
  await ensureContentScript(target.tabId);
1590
1725
  try {
1591
- clip = await sendTabMessage(target.tabId, {
1592
- type: 'bridge.execute', method, params
1593
- }, CONTENT_SCRIPT_TIMEOUT_MS);
1726
+ clip = await sendTabMessage(
1727
+ target.tabId,
1728
+ {
1729
+ type: 'bridge.execute',
1730
+ method,
1731
+ params,
1732
+ },
1733
+ CONTENT_SCRIPT_TIMEOUT_MS
1734
+ );
1594
1735
  } catch (err) {
1595
1736
  // Retry once after a brief pause - the page may have been mid-render
1596
1737
  if (err instanceof Error && /stale/i.test(err.message)) {
1597
- await new Promise(r => setTimeout(r, 250));
1598
- clip = await sendTabMessage(target.tabId, {
1599
- type: 'bridge.execute', method, params
1600
- }, CONTENT_SCRIPT_TIMEOUT_MS);
1738
+ await new Promise((r) => setTimeout(r, 250));
1739
+ clip = await sendTabMessage(
1740
+ target.tabId,
1741
+ {
1742
+ type: 'bridge.execute',
1743
+ method,
1744
+ params,
1745
+ },
1746
+ CONTENT_SCRIPT_TIMEOUT_MS
1747
+ );
1601
1748
  } else {
1602
1749
  throw err;
1603
1750
  }
@@ -1609,19 +1756,24 @@ async function handleScreenshot(target, method, params) {
1609
1756
  y: Math.max(0, Number(clip.y) || 0),
1610
1757
  width: Math.max(0, Number(clip.width) || 0),
1611
1758
  height: Math.max(0, Number(clip.height) || 0),
1612
- scale: Number(clip.scale) || 1
1759
+ scale: Number(clip.scale) || 1,
1613
1760
  };
1614
1761
  } else if (method === 'screenshot.capture_full_page') {
1615
1762
  await ensureContentScript(target.tabId);
1616
- const dims = /** @type {{ scrollWidth: number, scrollHeight: number, devicePixelRatio: number }} */ (
1617
- await sendTabMessage(target.tabId, { type: 'bridge.execute', method, params }, CONTENT_SCRIPT_TIMEOUT_MS)
1618
- );
1763
+ const dims =
1764
+ /** @type {{ scrollWidth: number, scrollHeight: number, devicePixelRatio: number }} */ (
1765
+ await sendTabMessage(
1766
+ target.tabId,
1767
+ { type: 'bridge.execute', method, params },
1768
+ CONTENT_SCRIPT_TIMEOUT_MS
1769
+ )
1770
+ );
1619
1771
  clip = {
1620
1772
  x: 0,
1621
1773
  y: 0,
1622
1774
  width: Math.min(Math.max(1, Number(dims.scrollWidth) || 1), 16384),
1623
1775
  height: Math.min(Math.max(1, Number(dims.scrollHeight) || 1), 16384),
1624
- scale: Number(dims.devicePixelRatio) || 1
1776
+ scale: Number(dims.devicePixelRatio) || 1,
1625
1777
  };
1626
1778
  } else {
1627
1779
  // capture_region: params already carry viewport coordinates
@@ -1631,14 +1783,14 @@ async function handleScreenshot(target, method, params) {
1631
1783
  y: Number(params.y) || 0,
1632
1784
  width: Math.max(1, Number(params.width) || 1),
1633
1785
  height: Math.max(1, Number(params.height) || 1),
1634
- scale
1786
+ scale,
1635
1787
  };
1636
1788
  }
1637
1789
 
1638
1790
  if (clip.width < 1 || clip.height < 1) {
1639
1791
  throw new Error(
1640
1792
  `Capture target has no visible area (${clip.width}\u00d7${clip.height}px). ` +
1641
- 'It may be hidden, collapsed, or not yet rendered.'
1793
+ 'It may be hidden, collapsed, or not yet rendered.'
1642
1794
  );
1643
1795
  }
1644
1796
 
@@ -1654,9 +1806,9 @@ async function handleScreenshot(target, method, params) {
1654
1806
  y: Math.max(0, clip.y),
1655
1807
  width: clip.width,
1656
1808
  height: clip.height,
1657
- scale: dpr
1809
+ scale: dpr,
1658
1810
  },
1659
- captureBeyondViewport: method === 'screenshot.capture_full_page'
1811
+ captureBeyondViewport: method === 'screenshot.capture_full_page',
1660
1812
  })
1661
1813
  );
1662
1814
  if (!cdpResult?.data) {
@@ -1664,7 +1816,7 @@ async function handleScreenshot(target, method, params) {
1664
1816
  }
1665
1817
  return {
1666
1818
  rect: clip,
1667
- image: `data:image/png;base64,${cdpResult.data}`
1819
+ image: `data:image/png;base64,${cdpResult.data}`,
1668
1820
  };
1669
1821
  });
1670
1822
  }
@@ -1681,7 +1833,11 @@ async function sendTabMessage(tabId, message, timeoutMs) {
1681
1833
  /** @type {ReturnType<typeof setTimeout> | undefined} */
1682
1834
  let timeoutId;
1683
1835
  const timeout = new Promise((_, reject) => {
1684
- timeoutId = setTimeout(() => reject(new Error(`Timed out waiting for content script response after ${timeoutMs}ms.`)), timeoutMs);
1836
+ timeoutId = setTimeout(
1837
+ () =>
1838
+ reject(new Error(`Timed out waiting for content script response after ${timeoutMs}ms.`)),
1839
+ timeoutMs
1840
+ );
1685
1841
  });
1686
1842
  try {
1687
1843
  return await Promise.race([chrome.tabs.sendMessage(tabId, message), timeout]);
@@ -1702,7 +1858,9 @@ async function injectContentScriptsForWindow(windowId) {
1702
1858
  const tabs = await chrome.tabs.query({ windowId });
1703
1859
  await Promise.allSettled(
1704
1860
  tabs
1705
- .map((tab) => (isNumber(tab.id) && tab.url && !isRestrictedAutomationUrl(tab.url) ? tab.id : null))
1861
+ .map((tab) =>
1862
+ isNumber(tab.id) && tab.url && !isRestrictedAutomationUrl(tab.url) ? tab.id : null
1863
+ )
1706
1864
  .filter((tabId) => tabId !== null)
1707
1865
  .map((tabId) => ensureContentScript(tabId))
1708
1866
  );
@@ -1715,10 +1873,12 @@ async function injectContentScriptsForWindow(windowId) {
1715
1873
  * @returns {boolean}
1716
1874
  */
1717
1875
  function isRestrictedScriptingError(message) {
1718
- return /Cannot access contents of/i.test(message)
1719
- || /The extensions gallery cannot be scripted/i.test(message)
1720
- || /Cannot access a chrome:\/\//i.test(message)
1721
- || /Cannot script/i.test(message);
1876
+ return (
1877
+ /Cannot access contents of/i.test(message) ||
1878
+ /The extensions gallery cannot be scripted/i.test(message) ||
1879
+ /Cannot access a chrome:\/\//i.test(message) ||
1880
+ /Cannot script/i.test(message)
1881
+ );
1722
1882
  }
1723
1883
 
1724
1884
  /**
@@ -1739,8 +1899,8 @@ async function ensureContentScript(tabId) {
1739
1899
  target: { tabId },
1740
1900
  files: [
1741
1901
  'packages/extension/src/content-script-helpers.js',
1742
- 'packages/extension/src/content-script.js'
1743
- ]
1902
+ 'packages/extension/src/content-script.js',
1903
+ ],
1744
1904
  });
1745
1905
  } catch (injectError) {
1746
1906
  const msg = injectError instanceof Error ? injectError.message : String(injectError);
@@ -1776,14 +1936,26 @@ async function handleCdpRequest(request) {
1776
1936
  } else if (request.method === 'cdp.get_box_model') {
1777
1937
  const nodeId = request.params?.nodeId;
1778
1938
  if (typeof nodeId !== 'number' || !Number.isFinite(nodeId)) {
1779
- return createFailure(request.id, ERROR_CODES.INVALID_REQUEST, 'nodeId must be a finite number.', null, { method: request.method });
1939
+ return createFailure(
1940
+ request.id,
1941
+ ERROR_CODES.INVALID_REQUEST,
1942
+ 'nodeId must be a finite number.',
1943
+ null,
1944
+ { method: request.method }
1945
+ );
1780
1946
  }
1781
1947
  command = 'DOM.getBoxModel';
1782
1948
  params = { nodeId };
1783
1949
  } else {
1784
1950
  const nodeId = request.params?.nodeId;
1785
1951
  if (typeof nodeId !== 'number' || !Number.isFinite(nodeId)) {
1786
- return createFailure(request.id, ERROR_CODES.INVALID_REQUEST, 'nodeId must be a finite number.', null, { method: request.method });
1952
+ return createFailure(
1953
+ request.id,
1954
+ ERROR_CODES.INVALID_REQUEST,
1955
+ 'nodeId must be a finite number.',
1956
+ null,
1957
+ { method: request.method }
1958
+ );
1787
1959
  }
1788
1960
  command = 'CSS.getComputedStyleForNode';
1789
1961
  params = { nodeId };
@@ -1814,7 +1986,9 @@ async function waitForTabComplete(tabId, timeoutMs) {
1814
1986
  let finished = false;
1815
1987
  const timeoutId = setTimeout(() => {
1816
1988
  cleanup();
1817
- reject(new Error(`Timed out waiting for tab ${tabId} to finish loading after ${timeoutMs}ms.`));
1989
+ reject(
1990
+ new Error(`Timed out waiting for tab ${tabId} to finish loading after ${timeoutMs}ms.`)
1991
+ );
1818
1992
  }, timeoutMs);
1819
1993
 
1820
1994
  /**
@@ -1896,7 +2070,9 @@ async function resolveRequestTarget(request, options = {}) {
1896
2070
  });
1897
2071
  const tab = selectRequestTabCandidate(request.tab_id, explicitTab, activeTab ?? null);
1898
2072
 
1899
- return resolveWindowScopedTab(tab, state.enabledWindow.windowId, { requireScriptable });
2073
+ return resolveWindowScopedTab(tab, state.enabledWindow.windowId, {
2074
+ requireScriptable,
2075
+ });
1900
2076
  }
1901
2077
 
1902
2078
  /**
@@ -1906,7 +2082,10 @@ async function resolveRequestTarget(request, options = {}) {
1906
2082
  * @returns {Promise<CurrentTabState | null>}
1907
2083
  */
1908
2084
  async function getCurrentTabState() {
1909
- const [activeTab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
2085
+ const [activeTab] = await chrome.tabs.query({
2086
+ active: true,
2087
+ lastFocusedWindow: true,
2088
+ });
1910
2089
  if (!activeTab?.id || typeof activeTab.windowId !== 'number' || !activeTab.url) {
1911
2090
  return null;
1912
2091
  }
@@ -1918,7 +2097,7 @@ async function getCurrentTabState() {
1918
2097
  url: activeTab.url,
1919
2098
  enabled: isWindowEnabled(activeTab.windowId),
1920
2099
  accessRequested: isAccessRequestedWindow(activeTab.windowId),
1921
- restricted: isRestrictedAutomationUrl(activeTab.url)
2100
+ restricted: isRestrictedAutomationUrl(activeTab.url),
1922
2101
  };
1923
2102
  }
1924
2103
 
@@ -1946,7 +2125,7 @@ async function getTabState(tabId) {
1946
2125
  url: tab.url,
1947
2126
  enabled: isWindowEnabled(tab.windowId),
1948
2127
  accessRequested: isAccessRequestedWindow(tab.windowId),
1949
- restricted: isRestrictedAutomationUrl(tab.url)
2128
+ restricted: isRestrictedAutomationUrl(tab.url),
1950
2129
  };
1951
2130
  } catch {
1952
2131
  return null;
@@ -1981,13 +2160,13 @@ async function setWindowEnabled(windowId, title, enabled) {
1981
2160
  const access = {
1982
2161
  windowId,
1983
2162
  title,
1984
- enabledAt: Date.now()
2163
+ enabledAt: Date.now(),
1985
2164
  };
1986
2165
 
1987
2166
  if (enabled) {
1988
2167
  state.enabledWindow = access;
1989
2168
  await chrome.storage.session.set({
1990
- [ENABLED_WINDOW_STORAGE_KEY]: access
2169
+ [ENABLED_WINDOW_STORAGE_KEY]: access,
1991
2170
  });
1992
2171
  } else {
1993
2172
  if (state.enabledWindow && state.enabledWindow.windowId === windowId) {
@@ -1998,7 +2177,9 @@ async function setWindowEnabled(windowId, title, enabled) {
1998
2177
 
1999
2178
  try {
2000
2179
  await refreshActionIndicators();
2001
- } catch { /* Badge updates can fail for closed or restricted tabs. */ }
2180
+ } catch {
2181
+ /* Badge updates can fail for closed or restricted tabs. */
2182
+ }
2002
2183
  await emitUiState();
2003
2184
 
2004
2185
  if (enabled) {
@@ -2006,7 +2187,7 @@ async function setWindowEnabled(windowId, title, enabled) {
2006
2187
  await chrome.alarms.create(KEEPALIVE_ALARM_NAME, { periodInMinutes: 0.4 });
2007
2188
  await Promise.allSettled([
2008
2189
  injectContentScriptsForWindow(access.windowId),
2009
- primeWindowConsoleCapture(access.windowId, true)
2190
+ primeWindowConsoleCapture(access.windowId, true),
2010
2191
  ]);
2011
2192
  } else {
2012
2193
  sendAccessUpdate(false);
@@ -2029,18 +2210,30 @@ async function setWindowEnabled(windowId, title, enabled) {
2029
2210
  * @returns {Promise<void>}
2030
2211
  */
2031
2212
  async function handleTabUpdated(tabId, changeInfo, tab) {
2032
- if (typeof changeInfo.title === 'string' && state.enabledWindow && tab.windowId === state.enabledWindow.windowId) {
2213
+ if (
2214
+ typeof changeInfo.title === 'string' &&
2215
+ state.enabledWindow &&
2216
+ tab.windowId === state.enabledWindow.windowId
2217
+ ) {
2033
2218
  state.enabledWindow = {
2034
2219
  ...state.enabledWindow,
2035
- title: changeInfo.title
2220
+ title: changeInfo.title,
2036
2221
  };
2037
2222
  await chrome.storage.session.set({
2038
- [ENABLED_WINDOW_STORAGE_KEY]: state.enabledWindow
2223
+ [ENABLED_WINDOW_STORAGE_KEY]: state.enabledWindow,
2039
2224
  });
2040
2225
  }
2041
2226
 
2042
- if (typeof changeInfo.url === 'string' || typeof changeInfo.title === 'string' || changeInfo.status === 'complete') {
2043
- if (changeInfo.status === 'complete' && state.enabledWindow && tab.windowId === state.enabledWindow.windowId) {
2227
+ if (
2228
+ typeof changeInfo.url === 'string' ||
2229
+ typeof changeInfo.title === 'string' ||
2230
+ changeInfo.status === 'complete'
2231
+ ) {
2232
+ if (
2233
+ changeInfo.status === 'complete' &&
2234
+ state.enabledWindow &&
2235
+ tab.windowId === state.enabledWindow.windowId
2236
+ ) {
2044
2237
  await primeTabConsoleCapture(tabId);
2045
2238
  }
2046
2239
  await updateActionIndicatorForTab(tabId);
@@ -2056,7 +2249,11 @@ async function handleTabUpdated(tabId, changeInfo, tab) {
2056
2249
  * @returns {Promise<void>}
2057
2250
  */
2058
2251
  async function handleTabRemoved(tabId, removeInfo) {
2059
- if (state.enabledWindow && removeInfo.isWindowClosing && removeInfo.windowId === state.enabledWindow.windowId) {
2252
+ if (
2253
+ state.enabledWindow &&
2254
+ removeInfo.isWindowClosing &&
2255
+ removeInfo.windowId === state.enabledWindow.windowId
2256
+ ) {
2060
2257
  state.enabledWindow = null;
2061
2258
  await chrome.storage.session.remove(ENABLED_WINDOW_STORAGE_KEY);
2062
2259
  sendAccessUpdate(false);
@@ -2074,11 +2271,11 @@ async function handleTabRemoved(tabId, removeInfo) {
2074
2271
  * @returns {Promise<void>}
2075
2272
  */
2076
2273
  async function refreshActionIndicators() {
2077
- const query = state.enabledWindow
2078
- ? { windowId: state.enabledWindow.windowId }
2079
- : {};
2274
+ const query = state.enabledWindow ? { windowId: state.enabledWindow.windowId } : {};
2080
2275
  const tabs = await chrome.tabs.query(query);
2081
- const tabIds = tabs.map((tab) => (isNumber(tab.id) ? tab.id : null)).filter((tabId) => tabId !== null);
2276
+ const tabIds = tabs
2277
+ .map((tab) => (isNumber(tab.id) ? tab.id : null))
2278
+ .filter((tabId) => tabId !== null);
2082
2279
  await Promise.allSettled(tabIds.map((tabId) => updateActionIndicatorForTab(tabId)));
2083
2280
 
2084
2281
  // Some Chromium-based browsers (e.g. Edge) do not visually refresh the toolbar
@@ -2096,24 +2293,43 @@ async function refreshActionIndicators() {
2096
2293
  */
2097
2294
  async function syncGlobalBadgeToActiveTab() {
2098
2295
  try {
2099
- const [activeTab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
2296
+ const [activeTab] = await chrome.tabs.query({
2297
+ active: true,
2298
+ lastFocusedWindow: true,
2299
+ });
2100
2300
  if (!activeTab?.id) return;
2101
2301
  const enabled = await isTabEnabled(activeTab.id);
2102
- const accessRequested = !enabled && await isAccessRequestedTab(activeTab.id);
2302
+ const accessRequested = !enabled && (await isAccessRequestedTab(activeTab.id));
2103
2303
  const restricted = enabled && isRestrictedAutomationUrl(activeTab.url ?? '');
2104
2304
  const text = enabled
2105
- ? (restricted ? RESTRICTED_BADGE_TEXT : ENABLED_BADGE_TEXT)
2106
- : accessRequested ? ACCESS_REQUEST_BADGE_TEXT : '';
2305
+ ? restricted
2306
+ ? RESTRICTED_BADGE_TEXT
2307
+ : ENABLED_BADGE_TEXT
2308
+ : accessRequested
2309
+ ? ACCESS_REQUEST_BADGE_TEXT
2310
+ : '';
2107
2311
  const bgColor = enabled
2108
- ? (restricted ? '#e07020' : '#787878')
2109
- : accessRequested ? '#f2cf2f' : '#464646';
2110
- const textColor = enabled
2111
- ? '#ffffff'
2112
- : accessRequested ? '#000000' : '#ffffff';
2312
+ ? restricted
2313
+ ? '#e07020'
2314
+ : '#787878'
2315
+ : accessRequested
2316
+ ? '#f2cf2f'
2317
+ : '#464646';
2318
+ const textColor = enabled ? '#ffffff' : accessRequested ? '#000000' : '#ffffff';
2113
2319
  await chrome.action.setBadgeText({ text });
2114
- try { await chrome.action.setBadgeBackgroundColor({ color: bgColor }); } catch { /* unsupported */ }
2115
- try { await chrome.action.setBadgeTextColor({ color: textColor }); } catch { /* unsupported */ }
2116
- } catch { /* non-critical */ }
2320
+ try {
2321
+ await chrome.action.setBadgeBackgroundColor({ color: bgColor });
2322
+ } catch {
2323
+ /* unsupported */
2324
+ }
2325
+ try {
2326
+ await chrome.action.setBadgeTextColor({ color: textColor });
2327
+ } catch {
2328
+ /* unsupported */
2329
+ }
2330
+ } catch {
2331
+ /* non-critical */
2332
+ }
2117
2333
  }
2118
2334
 
2119
2335
  /**
@@ -2125,40 +2341,64 @@ async function syncGlobalBadgeToActiveTab() {
2125
2341
  */
2126
2342
  async function updateActionIndicatorForTab(tabId) {
2127
2343
  const enabled = await isTabEnabled(tabId);
2128
- const accessRequested = !enabled && await isAccessRequestedTab(tabId);
2344
+ const accessRequested = !enabled && (await isAccessRequestedTab(tabId));
2129
2345
  let restricted = false;
2130
2346
  if (enabled) {
2131
2347
  try {
2132
2348
  const tab = await chrome.tabs.get(tabId);
2133
2349
  restricted = isRestrictedAutomationUrl(tab.url ?? '');
2134
- } catch { /* ignore */ }
2350
+ } catch {
2351
+ /* ignore */
2352
+ }
2135
2353
  }
2136
2354
  const badgeText = enabled
2137
- ? (restricted ? RESTRICTED_BADGE_TEXT : ENABLED_BADGE_TEXT)
2138
- : accessRequested ? ACCESS_REQUEST_BADGE_TEXT : '';
2355
+ ? restricted
2356
+ ? RESTRICTED_BADGE_TEXT
2357
+ : ENABLED_BADGE_TEXT
2358
+ : accessRequested
2359
+ ? ACCESS_REQUEST_BADGE_TEXT
2360
+ : '';
2139
2361
  const bgColor = enabled
2140
- ? (restricted ? '#e07020' : '#787878')
2141
- : accessRequested ? '#f2cf2f' : '#464646';
2142
- const textColor = enabled
2143
- ? '#ffffff'
2144
- : accessRequested ? '#000000' : '#ffffff';
2362
+ ? restricted
2363
+ ? '#e07020'
2364
+ : '#787878'
2365
+ : accessRequested
2366
+ ? '#f2cf2f'
2367
+ : '#464646';
2368
+ const textColor = enabled ? '#ffffff' : accessRequested ? '#000000' : '#ffffff';
2145
2369
  try {
2146
2370
  await chrome.action.setBadgeBackgroundColor({ tabId, color: bgColor });
2147
- } catch { /* color APIs may be unsupported */ }
2371
+ } catch {
2372
+ /* color APIs may be unsupported */
2373
+ }
2148
2374
  try {
2149
2375
  await chrome.action.setBadgeTextColor({ tabId, color: textColor });
2150
- } catch { /* setBadgeTextColor not supported everywhere */ }
2376
+ } catch {
2377
+ /* setBadgeTextColor not supported everywhere */
2378
+ }
2151
2379
  try {
2152
2380
  if (enabled && restricted) {
2153
- await chrome.action.setTitle({ tabId, title: 'Browser Bridge is enabled, but this page cannot be interacted with.' });
2381
+ await chrome.action.setTitle({
2382
+ tabId,
2383
+ title: 'Browser Bridge is enabled, but this page cannot be interacted with.',
2384
+ });
2154
2385
  } else if (enabled) {
2155
- await chrome.action.setTitle({ tabId, title: 'Browser Bridge is enabled for this window.' });
2386
+ await chrome.action.setTitle({
2387
+ tabId,
2388
+ title: 'Browser Bridge is enabled for this window.',
2389
+ });
2156
2390
  } else if (accessRequested) {
2157
- await chrome.action.setTitle({ tabId, title: 'Agent requested Browser Bridge access for this window. Click to open Browser Bridge, then click Enable.' });
2391
+ await chrome.action.setTitle({
2392
+ tabId,
2393
+ title:
2394
+ 'Agent requested Browser Bridge access for this window. Click to open Browser Bridge, then click Enable.',
2395
+ });
2158
2396
  } else {
2159
2397
  await chrome.action.setTitle({ tabId, title: 'Browser Bridge' });
2160
2398
  }
2161
- } catch { /* title can fail for closed tabs */ }
2399
+ } catch {
2400
+ /* title can fail for closed tabs */
2401
+ }
2162
2402
  try {
2163
2403
  await chrome.action.setBadgeText({ tabId, text: badgeText });
2164
2404
  } catch (error) {
@@ -2256,13 +2496,15 @@ function checkAccessRequestAvailability(target) {
2256
2496
  if (state.requestedAccessWindowId === target.windowId) {
2257
2497
  return {
2258
2498
  allowed: false,
2259
- message: 'Browser Bridge access is already pending for this window. Ask the user to click Enable before requesting access again.'
2499
+ message:
2500
+ 'Browser Bridge access is already pending for this window. Ask the user to click Enable before requesting access again.',
2260
2501
  };
2261
2502
  }
2262
2503
 
2263
2504
  return {
2264
2505
  allowed: false,
2265
- message: 'Browser Bridge access is already pending for another window. Ask the user to click Enable for that window before requesting access again.'
2506
+ message:
2507
+ 'Browser Bridge access is already pending for another window. Ask the user to click Enable for that window before requesting access again.',
2266
2508
  };
2267
2509
  }
2268
2510
 
@@ -2279,10 +2521,14 @@ async function handleAccessRequest(request) {
2279
2521
 
2280
2522
  if (state.enabledWindow) {
2281
2523
  const access = await getAccessStatus();
2282
- return createSuccess(request.id, {
2283
- enabled: true,
2284
- access
2285
- }, { method: request.method });
2524
+ return createSuccess(
2525
+ request.id,
2526
+ {
2527
+ enabled: true,
2528
+ access,
2529
+ },
2530
+ { method: request.method }
2531
+ );
2286
2532
  }
2287
2533
 
2288
2534
  if (!target) {
@@ -2304,7 +2550,7 @@ async function handleAccessRequest(request) {
2304
2550
  {
2305
2551
  requestedWindowId: state.requestedAccessWindowId,
2306
2552
  requestedTargetWindowId: target.windowId,
2307
- requestedTargetTabId: target.tabId
2553
+ requestedTargetTabId: target.tabId,
2308
2554
  },
2309
2555
  { method: request.method }
2310
2556
  );
@@ -2315,14 +2561,18 @@ async function handleAccessRequest(request) {
2315
2561
  await emitUiState();
2316
2562
  await openRequestedAccessUi(target);
2317
2563
 
2318
- return createSuccess(request.id, {
2319
- enabled: false,
2320
- requested: true,
2321
- windowId: target.windowId,
2322
- tabId: target.tabId,
2323
- title: target.title,
2324
- url: target.url
2325
- }, { method: request.method });
2564
+ return createSuccess(
2565
+ request.id,
2566
+ {
2567
+ enabled: false,
2568
+ requested: true,
2569
+ windowId: target.windowId,
2570
+ tabId: target.tabId,
2571
+ title: target.title,
2572
+ url: target.url,
2573
+ },
2574
+ { method: request.method }
2575
+ );
2326
2576
  }
2327
2577
 
2328
2578
  /**
@@ -2385,7 +2635,9 @@ async function openRequestedAccessPopupWindow(target) {
2385
2635
 
2386
2636
  if (state.requestedAccessPopupWindowId != null) {
2387
2637
  try {
2388
- const existingWindow = await chrome.windows.get(state.requestedAccessPopupWindowId, { populate: true });
2638
+ const existingWindow = await chrome.windows.get(state.requestedAccessPopupWindowId, {
2639
+ populate: true,
2640
+ });
2389
2641
  const existingWindowId = typeof existingWindow.id === 'number' ? existingWindow.id : null;
2390
2642
  const popupTabId = existingWindow.tabs?.find((tab) => typeof tab.id === 'number')?.id ?? null;
2391
2643
  if (existingWindowId == null || popupTabId == null) {
@@ -2394,7 +2646,7 @@ async function openRequestedAccessPopupWindow(target) {
2394
2646
  await chrome.tabs.update(popupTabId, { url: popupUrl });
2395
2647
  await chrome.windows.update(existingWindowId, {
2396
2648
  focused: true,
2397
- ...(popupPlacement ?? {})
2649
+ ...(popupPlacement ?? {}),
2398
2650
  });
2399
2651
  return;
2400
2652
  } catch {
@@ -2407,20 +2659,18 @@ async function openRequestedAccessPopupWindow(target) {
2407
2659
  type: 'popup',
2408
2660
  focused: true,
2409
2661
  width: popupWidth,
2410
- height: popupHeight
2662
+ height: popupHeight,
2411
2663
  });
2412
2664
 
2413
2665
  if (popupPlacement) {
2414
2666
  createData = {
2415
2667
  ...createData,
2416
- ...popupPlacement
2668
+ ...popupPlacement,
2417
2669
  };
2418
2670
  }
2419
2671
 
2420
2672
  const popupWindow = await chrome.windows.create(createData);
2421
- state.requestedAccessPopupWindowId = typeof popupWindow?.id === 'number'
2422
- ? popupWindow.id
2423
- : null;
2673
+ state.requestedAccessPopupWindowId = typeof popupWindow?.id === 'number' ? popupWindow.id : null;
2424
2674
  }
2425
2675
 
2426
2676
  /**
@@ -2432,13 +2682,13 @@ async function getRequestedAccessPopupPlacement(targetWindowId, popupWidth) {
2432
2682
  try {
2433
2683
  const browserWindow = await chrome.windows.get(targetWindowId);
2434
2684
  if (
2435
- typeof browserWindow.left === 'number'
2436
- && typeof browserWindow.top === 'number'
2437
- && typeof browserWindow.width === 'number'
2685
+ typeof browserWindow.left === 'number' &&
2686
+ typeof browserWindow.top === 'number' &&
2687
+ typeof browserWindow.width === 'number'
2438
2688
  ) {
2439
2689
  return {
2440
2690
  left: browserWindow.left + Math.max(24, browserWindow.width - popupWidth - 40),
2441
- top: browserWindow.top + 72
2691
+ top: browserWindow.top + 72,
2442
2692
  };
2443
2693
  }
2444
2694
  } catch {
@@ -2498,18 +2748,18 @@ async function getActionContext(request) {
2498
2748
  const tab = await chrome.tabs.get(params.tabId);
2499
2749
  return {
2500
2750
  tabId: params.tabId,
2501
- url: tab.url ?? ''
2751
+ url: tab.url ?? '',
2502
2752
  };
2503
2753
  }
2504
2754
  if (!bridgeMethodNeedsTab(request.method)) {
2505
2755
  return null;
2506
2756
  }
2507
2757
  const tab = await resolveRequestTarget(request, {
2508
- requireScriptable: request.method !== 'tabs.create'
2758
+ requireScriptable: request.method !== 'tabs.create',
2509
2759
  });
2510
2760
  return {
2511
2761
  tabId: tab.tabId,
2512
- url: tab.url
2762
+ url: tab.url,
2513
2763
  };
2514
2764
  } catch {
2515
2765
  return null;
@@ -2552,9 +2802,8 @@ async function logBridgeAction(request, response, actionContext) {
2552
2802
  overBudget: response.meta?.budget_truncated === true,
2553
2803
  hasScreenshot: diagnostics.hasScreenshot,
2554
2804
  nodeCount: diagnostics.nodeCount,
2555
- continuationHint: typeof response.meta?.continuation_hint === 'string'
2556
- ? response.meta.continuation_hint
2557
- : null,
2805
+ continuationHint:
2806
+ typeof response.meta?.continuation_hint === 'string' ? response.meta.continuation_hint : null,
2558
2807
  });
2559
2808
  await emitUiState();
2560
2809
  }
@@ -2607,14 +2856,14 @@ async function appendActionLogEntry(entry) {
2607
2856
  overBudget: entry.overBudget === true,
2608
2857
  hasScreenshot: entry.hasScreenshot ?? false,
2609
2858
  nodeCount: entry.nodeCount ?? null,
2610
- continuationHint: entry.continuationHint ?? null
2859
+ continuationHint: entry.continuationHint ?? null,
2611
2860
  });
2612
2861
  while (state.actionLog.length > MAX_ACTION_LOG_ENTRIES) {
2613
2862
  state.actionLog.shift();
2614
2863
  }
2615
2864
 
2616
2865
  await chrome.storage.session.set({
2617
- [ACTION_LOG_STORAGE_KEY]: state.actionLog
2866
+ [ACTION_LOG_STORAGE_KEY]: state.actionLog,
2618
2867
  });
2619
2868
  }
2620
2869
 
@@ -2652,20 +2901,27 @@ function normalizeActionLogEntry(entry) {
2652
2901
  responseBytes: Number(candidate.responseBytes) || 0,
2653
2902
  approxTokens: Number(candidate.approxTokens) || 0,
2654
2903
  imageApproxTokens: Number(candidate.imageApproxTokens) || 0,
2655
- costClass: candidate.costClass === 'moderate' || candidate.costClass === 'heavy' || candidate.costClass === 'extreme'
2656
- ? candidate.costClass
2657
- : 'cheap',
2904
+ costClass:
2905
+ candidate.costClass === 'moderate' ||
2906
+ candidate.costClass === 'heavy' ||
2907
+ candidate.costClass === 'extreme'
2908
+ ? candidate.costClass
2909
+ : 'cheap',
2658
2910
  imageBytes: Number(candidate.imageBytes) || 0,
2659
2911
  summaryBytes: Number(candidate.summaryBytes) || 0,
2660
2912
  summaryTokens: Number(candidate.summaryTokens) || 0,
2661
- summaryCostClass: candidate.summaryCostClass === 'moderate' || candidate.summaryCostClass === 'heavy' || candidate.summaryCostClass === 'extreme'
2662
- ? candidate.summaryCostClass
2663
- : 'cheap',
2913
+ summaryCostClass:
2914
+ candidate.summaryCostClass === 'moderate' ||
2915
+ candidate.summaryCostClass === 'heavy' ||
2916
+ candidate.summaryCostClass === 'extreme'
2917
+ ? candidate.summaryCostClass
2918
+ : 'cheap',
2664
2919
  debuggerBacked: candidate.debuggerBacked === true,
2665
2920
  overBudget: candidate.overBudget === true,
2666
2921
  hasScreenshot: candidate.hasScreenshot === true,
2667
2922
  nodeCount: typeof candidate.nodeCount === 'number' ? candidate.nodeCount : null,
2668
- continuationHint: typeof candidate.continuationHint === 'string' ? candidate.continuationHint : null
2923
+ continuationHint:
2924
+ typeof candidate.continuationHint === 'string' ? candidate.continuationHint : null,
2669
2925
  };
2670
2926
  }
2671
2927
 
@@ -2686,7 +2942,9 @@ function toFailureResponse(request, error) {
2686
2942
  ? ERROR_CODES.ELEMENT_STALE
2687
2943
  : ERROR_CODES.INTERNAL_ERROR;
2688
2944
 
2689
- return createFailure(request.id, code, message, null, { method: request.method });
2945
+ return createFailure(request.id, code, message, null, {
2946
+ method: request.method,
2947
+ });
2690
2948
  }
2691
2949
 
2692
2950
  /**
@@ -2698,11 +2956,7 @@ function toFailureResponse(request, error) {
2698
2956
  * @returns {BridgeResponse}
2699
2957
  */
2700
2958
  function enrichBridgeResponse(request, response) {
2701
- const budgetedResponse = enforceTokenBudget(
2702
- request.method,
2703
- response,
2704
- request.meta?.token_budget,
2705
- );
2959
+ const budgetedResponse = enforceTokenBudget(request.method, response, request.meta?.token_budget);
2706
2960
  const diagnostics = getResponseDiagnostics(request.method, budgetedResponse);
2707
2961
  return {
2708
2962
  ...budgetedResponse,
@@ -2804,8 +3058,8 @@ async function emitUiStateForPort(port) {
2804
3058
  setupInstallError: state.setupInstallError,
2805
3059
  actionLog: [...state.actionLog]
2806
3060
  .filter((entry) => scopedTabId == null || entry.tabId === scopedTabId)
2807
- .reverse()
2808
- }
3061
+ .reverse(),
3062
+ },
2809
3063
  });
2810
3064
  }
2811
3065
 
@@ -2820,9 +3074,10 @@ function handleHostStatusMessage(message) {
2820
3074
 
2821
3075
  const candidate = /** @type {Record<string, unknown>} */ (message);
2822
3076
  if (candidate.type === 'host.bridge_response') {
2823
- const response = candidate.response && typeof candidate.response === 'object'
2824
- ? /** @type {BridgeResponse} */ (candidate.response)
2825
- : null;
3077
+ const response =
3078
+ candidate.response && typeof candidate.response === 'object'
3079
+ ? /** @type {BridgeResponse} */ (candidate.response)
3080
+ : null;
2826
3081
  if (response?.id === state.setupInstallPendingRequestId) {
2827
3082
  const action = state.setupInstallPendingAction;
2828
3083
  state.setupInstallPendingRequestId = null;
@@ -2833,7 +3088,7 @@ function handleHostStatusMessage(message) {
2833
3088
  void appendActionLogEntry({
2834
3089
  method: getSetupActionMethodLabel(action),
2835
3090
  ok: true,
2836
- summary: getSetupActionSuccessSummary(action)
3091
+ summary: getSetupActionSuccessSummary(action),
2837
3092
  }).catch(reportAsyncError);
2838
3093
  }
2839
3094
  refreshSetupStatus(true);
@@ -2843,7 +3098,7 @@ function handleHostStatusMessage(message) {
2843
3098
  void appendActionLogEntry({
2844
3099
  method: getSetupActionMethodLabel(action),
2845
3100
  ok: false,
2846
- summary: getSetupActionErrorSummary(action, response.error.message)
3101
+ summary: getSetupActionErrorSummary(action, response.error.message),
2847
3102
  }).catch(reportAsyncError);
2848
3103
  }
2849
3104
  state.setupInstallPendingKey = null;
@@ -2874,16 +3129,17 @@ function handleHostStatusMessage(message) {
2874
3129
  state.setupInstallPendingRequestId = null;
2875
3130
  state.setupInstallPendingAction = null;
2876
3131
  state.setupInstallPendingKey = null;
2877
- state.setupInstallError = typeof candidate.error === 'object'
2878
- && candidate.error
2879
- && typeof /** @type {Record<string, unknown>} */ (candidate.error).message === 'string'
2880
- ? /** @type {Record<string, string>} */ (candidate.error).message
2881
- : 'Could not install host setup.';
3132
+ state.setupInstallError =
3133
+ typeof candidate.error === 'object' &&
3134
+ candidate.error &&
3135
+ typeof (/** @type {Record<string, unknown>} */ (candidate.error).message) === 'string'
3136
+ ? /** @type {Record<string, string>} */ (candidate.error).message
3137
+ : 'Could not install host setup.';
2882
3138
  if (action) {
2883
3139
  void appendActionLogEntry({
2884
3140
  method: getSetupActionMethodLabel(action),
2885
3141
  ok: false,
2886
- summary: getSetupActionErrorSummary(action, state.setupInstallError)
3142
+ summary: getSetupActionErrorSummary(action, state.setupInstallError),
2887
3143
  }).catch(reportAsyncError);
2888
3144
  }
2889
3145
  void emitUiState().catch(reportAsyncError);
@@ -2895,11 +3151,12 @@ function handleHostStatusMessage(message) {
2895
3151
  state.setupStatusPendingRequestId = null;
2896
3152
  state.setupInstallPendingAction = null;
2897
3153
  state.setupInstallPendingKey = null;
2898
- state.setupStatusError = typeof candidate.error === 'object'
2899
- && candidate.error
2900
- && typeof /** @type {Record<string, unknown>} */ (candidate.error).message === 'string'
2901
- ? /** @type {Record<string, string>} */ (candidate.error).message
2902
- : 'Could not inspect host setup.';
3154
+ state.setupStatusError =
3155
+ typeof candidate.error === 'object' &&
3156
+ candidate.error &&
3157
+ typeof (/** @type {Record<string, unknown>} */ (candidate.error).message) === 'string'
3158
+ ? /** @type {Record<string, string>} */ (candidate.error).message
3159
+ : 'Could not inspect host setup.';
2903
3160
  void emitUiState().catch(reportAsyncError);
2904
3161
  }
2905
3162
  return true;
@@ -2927,11 +3184,12 @@ function handleHostStatusMessage(message) {
2927
3184
  state.setupStatusPendingRequestId = null;
2928
3185
  state.setupInstallPendingAction = null;
2929
3186
  state.setupInstallPendingKey = null;
2930
- state.setupStatusError = typeof candidate.error === 'object'
2931
- && candidate.error
2932
- && typeof /** @type {Record<string, unknown>} */ (candidate.error).message === 'string'
2933
- ? /** @type {Record<string, string>} */ (candidate.error).message
2934
- : 'Could not inspect host setup.';
3187
+ state.setupStatusError =
3188
+ typeof candidate.error === 'object' &&
3189
+ candidate.error &&
3190
+ typeof (/** @type {Record<string, unknown>} */ (candidate.error).message) === 'string'
3191
+ ? /** @type {Record<string, string>} */ (candidate.error).message
3192
+ : 'Could not inspect host setup.';
2935
3193
  void emitUiState().catch(reportAsyncError);
2936
3194
  }
2937
3195
  return true;
@@ -2950,8 +3208,9 @@ function refreshSetupStatus(force = false) {
2950
3208
  return;
2951
3209
  }
2952
3210
 
2953
- const isFresh = state.setupStatusUpdatedAt > 0
2954
- && (Date.now() - state.setupStatusUpdatedAt) < SETUP_STATUS_STALE_MS;
3211
+ const isFresh =
3212
+ state.setupStatusUpdatedAt > 0 &&
3213
+ Date.now() - state.setupStatusUpdatedAt < SETUP_STATUS_STALE_MS;
2955
3214
  if (state.setupStatusPending || (!force && isFresh && !state.setupStatusError)) {
2956
3215
  return;
2957
3216
  }
@@ -2965,8 +3224,8 @@ function refreshSetupStatus(force = false) {
2965
3224
  type: 'host.bridge_request',
2966
3225
  request: createRequest({
2967
3226
  id: requestId,
2968
- method: 'setup.get_status'
2969
- })
3227
+ method: 'setup.get_status',
3228
+ }),
2970
3229
  });
2971
3230
  state.setupStatusTimeoutId = setTimeout(() => {
2972
3231
  if (state.setupStatusPendingRequestId !== requestId) {
@@ -3038,7 +3297,7 @@ async function handleUiMessage(port, message) {
3038
3297
  }
3039
3298
  state.uiPorts.set(port, {
3040
3299
  surface: currentPortState.surface,
3041
- scopeTabId: Number.isFinite(scopeTabId) && scopeTabId > 0 ? scopeTabId : null
3300
+ scopeTabId: Number.isFinite(scopeTabId) && scopeTabId > 0 ? scopeTabId : null,
3042
3301
  });
3043
3302
  refreshSetupStatus();
3044
3303
  await emitUiStateForPort(port);
@@ -3055,7 +3314,7 @@ async function handleUiMessage(port, message) {
3055
3314
  const requestedTabId = Number(message.tabId);
3056
3315
  try {
3057
3316
  // ── DEBUG: simulate slow/error toggles. Set to "delay", "error", or "" ──
3058
- const _TOGGLE_SIM = /** @type {string} */ (''); // "delay" | "error" | ""
3317
+ const _TOGGLE_SIM = /** @type {'delay' | 'error' | ''} */ ('');
3059
3318
  if (_TOGGLE_SIM === 'delay') {
3060
3319
  await new Promise((r) => setTimeout(r, 6000));
3061
3320
  } else if (_TOGGLE_SIM === 'error') {
@@ -3075,7 +3334,11 @@ async function handleUiMessage(port, message) {
3075
3334
  }
3076
3335
  } catch (error) {
3077
3336
  const errorMessage = error instanceof Error ? error.message : String(error);
3078
- try { port.postMessage({ type: 'toggle.error', error: errorMessage }); } catch { /* port may have disconnected */ }
3337
+ try {
3338
+ port.postMessage({ type: 'toggle.error', error: errorMessage });
3339
+ } catch {
3340
+ /* port may have disconnected */
3341
+ }
3079
3342
  throw error;
3080
3343
  }
3081
3344
  return;
@@ -3096,7 +3359,7 @@ async function handleSetupInstallAction(message) {
3096
3359
  await appendActionLogEntry({
3097
3360
  method: 'Host setup',
3098
3361
  ok: false,
3099
- summary: 'Install failed: Native host is not connected.'
3362
+ summary: 'Install failed: Native host is not connected.',
3100
3363
  });
3101
3364
  await emitUiState();
3102
3365
  return;
@@ -3113,15 +3376,15 @@ async function handleSetupInstallAction(message) {
3113
3376
  await appendActionLogEntry({
3114
3377
  method: getSetupActionMethodLabel(action),
3115
3378
  ok: true,
3116
- summary: getSetupActionStartSummary(action)
3379
+ summary: getSetupActionStartSummary(action),
3117
3380
  });
3118
3381
  state.nativePort.postMessage({
3119
3382
  type: 'host.bridge_request',
3120
3383
  request: createRequest({
3121
3384
  id: requestId,
3122
3385
  method: 'setup.install',
3123
- params: action
3124
- })
3386
+ params: action,
3387
+ }),
3125
3388
  });
3126
3389
  await emitUiState();
3127
3390
  }
@@ -3204,11 +3467,11 @@ async function openSidePanelForTab(tabId, windowId) {
3204
3467
  await chrome.sidePanel.setOptions({
3205
3468
  tabId,
3206
3469
  path: `${SIDEPANEL_PATH}?tabId=${encodeURIComponent(String(tabId))}`,
3207
- enabled: true
3470
+ enabled: true,
3208
3471
  });
3209
3472
  await chrome.sidePanel.open({
3210
3473
  tabId,
3211
- windowId
3474
+ windowId,
3212
3475
  });
3213
3476
  }
3214
3477