@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
@@ -3,7 +3,13 @@
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
 
6
- import { APP_NAME, getBridgeDir, getLauncherFilename, getManifestInstallDir, PUBLISHED_EXTENSION_ID } from './config.js';
6
+ import {
7
+ APP_NAME,
8
+ getBridgeDir,
9
+ getLauncherFilename,
10
+ getManifestInstallDir,
11
+ PUBLISHED_EXTENSION_ID,
12
+ } from './config.js';
7
13
 
8
14
  export const DEFAULT_EXTENSION_ID_ENV = 'BROWSER_BRIDGE_EXTENSION_ID';
9
15
  export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
@@ -21,6 +27,7 @@ export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
21
27
  * bridgeDir?: string | undefined,
22
28
  * stdout?: Pick<NodeJS.WriteStream, 'write'>,
23
29
  * stderr?: Pick<NodeJS.WriteStream, 'write'>,
30
+ * preserveCustomExtensionId?: boolean | undefined,
24
31
  * env?: NodeJS.ProcessEnv
25
32
  * }} InstallManifestOptions
26
33
  */
@@ -62,13 +69,13 @@ export function resolveDefaultExtensionId(env = process.env) {
62
69
  const parsed = parseExtensionId(candidate);
63
70
  return {
64
71
  extensionId: parsed,
65
- source: parsed ? 'env' : 'invalid_env'
72
+ source: parsed ? 'env' : 'invalid_env',
66
73
  };
67
74
  }
68
75
 
69
76
  return {
70
77
  extensionId: PUBLISHED_EXTENSION_ID || null,
71
- source: PUBLISHED_EXTENSION_ID ? 'built_in' : 'none'
78
+ source: PUBLISHED_EXTENSION_ID ? 'built_in' : 'none',
72
79
  };
73
80
  }
74
81
 
@@ -90,9 +97,10 @@ export function getDefaultExtensionId(env = process.env) {
90
97
  * @returns {string[]}
91
98
  */
92
99
  export function getAllowedOrigins(existingManifest, extensionId) {
93
- const existing = (existingManifest && Array.isArray(existingManifest.allowed_origins))
94
- ? existingManifest.allowed_origins
95
- : [];
100
+ const existing =
101
+ existingManifest && Array.isArray(existingManifest.allowed_origins)
102
+ ? existingManifest.allowed_origins
103
+ : [];
96
104
 
97
105
  if (extensionId) {
98
106
  const origin = `chrome-extension://${extensionId}/`;
@@ -110,6 +118,25 @@ export function getAllowedOrigins(existingManifest, extensionId) {
110
118
  return ['chrome-extension://__REPLACE_WITH_EXTENSION_ID__/'];
111
119
  }
112
120
 
121
+ /**
122
+ * @param {string[] | undefined} allowedOrigins
123
+ * @returns {string[]}
124
+ */
125
+ function getExtensionIdsFromAllowedOrigins(allowedOrigins) {
126
+ if (!Array.isArray(allowedOrigins)) {
127
+ return [];
128
+ }
129
+
130
+ const ids = new Set();
131
+ for (const origin of allowedOrigins) {
132
+ const match = /^chrome-extension:\/\/([a-z]{32})\/?$/.exec(origin);
133
+ if (match?.[1]) {
134
+ ids.add(match[1]);
135
+ }
136
+ }
137
+ return [...ids];
138
+ }
139
+
113
140
  /**
114
141
  * @param {string} value
115
142
  * @returns {string}
@@ -144,7 +171,9 @@ export async function installNativeManifest(options) {
144
171
  installDir = getManifestInstallDir(browser),
145
172
  bridgeDir = getBridgeDir(),
146
173
  stdout = process.stdout,
147
- env = process.env
174
+ stderr = process.stderr,
175
+ preserveCustomExtensionId = false,
176
+ env = process.env,
148
177
  } = options;
149
178
 
150
179
  const parsedExtensionId = parseExtensionId(extensionIdArg);
@@ -160,19 +189,35 @@ export async function installNativeManifest(options) {
160
189
  `Invalid ${DEFAULT_EXTENSION_ID_ENV}: ${env[DEFAULT_EXTENSION_ID_ENV]}\nExpected 32 lowercase letters or chrome-extension://<id>/`
161
190
  );
162
191
  }
163
- const extensionId = parsedExtensionId || defaultExtensionId.extensionId;
192
+ const requestedExtensionId = parsedExtensionId || defaultExtensionId.extensionId;
164
193
  const hostPath = path.join(repoRoot, 'packages', 'native-host', 'bin', 'native-host.js');
165
194
  const launcherPath = path.join(bridgeDir, getLauncherFilename());
166
195
  const manifestPath = path.join(installDir, `${APP_NAME}.json`);
167
196
 
168
- const launcher = process.platform === 'win32'
169
- ? `@echo off\r\n"${nodePath}" "${hostPath}" %*\r\n`
170
- : `#!/bin/sh
197
+ const launcher =
198
+ process.platform === 'win32'
199
+ ? `@echo off\r\n"${nodePath}" "${hostPath}" %*\r\n`
200
+ : `#!/bin/sh
171
201
  exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
172
202
  `;
173
203
 
174
204
  const existingManifest = await readExistingManifest(manifestPath);
175
- const allowedOrigins = getAllowedOrigins(existingManifest, extensionId);
205
+ const existingExtensionIds = getExtensionIdsFromAllowedOrigins(existingManifest?.allowed_origins);
206
+ const hasStoreOrigin = existingExtensionIds.includes(PUBLISHED_EXTENSION_ID);
207
+ const customExtensionIds = existingExtensionIds.filter((id) => id !== PUBLISHED_EXTENSION_ID);
208
+ const preservedCustomExtensionId =
209
+ preserveCustomExtensionId &&
210
+ !parsedExtensionId &&
211
+ extensionIdArg == null &&
212
+ defaultExtensionId.source === BUILT_IN_EXTENSION_ID_SOURCE &&
213
+ customExtensionIds.length > 0 &&
214
+ !hasStoreOrigin;
215
+ const allowedOrigins = preservedCustomExtensionId
216
+ ? getAllowedOrigins(existingManifest, null)
217
+ : getAllowedOrigins(existingManifest, requestedExtensionId);
218
+ const extensionId = preservedCustomExtensionId
219
+ ? customExtensionIds[0] || requestedExtensionId
220
+ : requestedExtensionId;
176
221
 
177
222
  /** @type {{name: string, description: string, path: string, type: 'stdio', allowed_origins: string[]}} */
178
223
  const manifest = {
@@ -180,7 +225,7 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
180
225
  description: 'Browser Bridge native host',
181
226
  path: launcherPath,
182
227
  type: 'stdio',
183
- allowed_origins: allowedOrigins
228
+ allowed_origins: allowedOrigins,
184
229
  };
185
230
 
186
231
  await fs.promises.mkdir(installDir, { recursive: true });
@@ -194,7 +239,7 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
194
239
  stdout.write(`Wrote ${manifestPath}\n`);
195
240
  stdout.write(`Wrote ${launcherPath}\n`);
196
241
 
197
- if (!parsedExtensionId && extensionIdArg == null && extensionId) {
242
+ if (!preservedCustomExtensionId && !parsedExtensionId && extensionIdArg == null && extensionId) {
198
243
  if (defaultExtensionId.source === 'env') {
199
244
  stdout.write(`Used extension ID from ${DEFAULT_EXTENSION_ID_ENV}.\n`);
200
245
  } else if (defaultExtensionId.source === BUILT_IN_EXTENSION_ID_SOURCE) {
@@ -202,11 +247,19 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
202
247
  }
203
248
  }
204
249
 
205
- const hasPlaceholder = allowedOrigins.some((origin) => origin.includes('__REPLACE_WITH_EXTENSION_ID__'));
250
+ if (preservedCustomExtensionId) {
251
+ stderr.write(
252
+ `Warning: existing native host manifest keeps custom extension ID ${customExtensionIds.join(', ')} instead of the Browser Bridge store ID ${PUBLISHED_EXTENSION_ID}. Leaving allowed_origins unchanged.\n`
253
+ );
254
+ }
255
+
256
+ const hasPlaceholder = allowedOrigins.some((origin) =>
257
+ origin.includes('__REPLACE_WITH_EXTENSION_ID__')
258
+ );
206
259
  if (hasPlaceholder) {
207
260
  stdout.write(
208
261
  'Tip: pass the extension ID to set allowed_origins automatically:\n' +
209
- ' bbx install <extension-id>\n'
262
+ ' bbx install <extension-id>\n'
210
263
  );
211
264
  }
212
265
 
@@ -214,7 +267,7 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
214
267
  manifestPath,
215
268
  launcherPath,
216
269
  allowedOrigins,
217
- extensionId
270
+ extensionId,
218
271
  };
219
272
  }
220
273
 
@@ -228,7 +281,7 @@ export async function uninstallNativeManifest(options = {}) {
228
281
  installDir = getManifestInstallDir(browser),
229
282
  bridgeDir = getBridgeDir(),
230
283
  removeBridgeDir = false,
231
- stdout = process.stdout
284
+ stdout = process.stdout,
232
285
  } = options;
233
286
 
234
287
  const manifestPath = path.join(installDir, `${APP_NAME}.json`);
@@ -237,9 +290,7 @@ export async function uninstallNativeManifest(options = {}) {
237
290
  stdout.write(`Removed ${manifestPath}\n`);
238
291
  }
239
292
 
240
- const removedBridgeDir = removeBridgeDir
241
- ? await removePathIfExists(bridgeDir)
242
- : false;
293
+ const removedBridgeDir = removeBridgeDir ? await removePathIfExists(bridgeDir) : false;
243
294
  if (removedBridgeDir) {
244
295
  stdout.write(`Removed ${bridgeDir}\n`);
245
296
  }
@@ -248,7 +299,7 @@ export async function uninstallNativeManifest(options = {}) {
248
299
  manifestPath,
249
300
  bridgeDir,
250
301
  removedManifest,
251
- removedBridgeDir
302
+ removedBridgeDir,
252
303
  };
253
304
  }
254
305
 
@@ -54,10 +54,10 @@ const daemonEntryPath = path.resolve(__dirname, '../bin/bridge-daemon.js');
54
54
  */
55
55
  function isHostBridgeRequest(message) {
56
56
  return Boolean(
57
- message
58
- && typeof message === 'object'
59
- && /** @type {Record<string, unknown>} */ (message).type === 'host.bridge_request'
60
- && typeof /** @type {Record<string, unknown>} */ (message).request === 'object'
57
+ message &&
58
+ typeof message === 'object' &&
59
+ /** @type {Record<string, unknown>} */ (message).type === 'host.bridge_request' &&
60
+ typeof (/** @type {Record<string, unknown>} */ (message).request) === 'object'
61
61
  );
62
62
  }
63
63
 
@@ -67,10 +67,10 @@ function isHostBridgeRequest(message) {
67
67
  */
68
68
  function isHostStatusRequest(message) {
69
69
  return Boolean(
70
- message
71
- && typeof message === 'object'
72
- && /** @type {Record<string, unknown>} */ (message).type === 'host.setup_status.request'
73
- && typeof /** @type {Record<string, unknown>} */ (message).requestId === 'string'
70
+ message &&
71
+ typeof message === 'object' &&
72
+ /** @type {Record<string, unknown>} */ (message).type === 'host.setup_status.request' &&
73
+ typeof (/** @type {Record<string, unknown>} */ (message).requestId) === 'string'
74
74
  );
75
75
  }
76
76
 
@@ -80,9 +80,9 @@ function isHostStatusRequest(message) {
80
80
  */
81
81
  function isHostIdentity(message) {
82
82
  return Boolean(
83
- message
84
- && typeof message === 'object'
85
- && /** @type {Record<string, unknown>} */ (message).type === 'host.identity'
83
+ message &&
84
+ typeof message === 'object' &&
85
+ /** @type {Record<string, unknown>} */ (message).type === 'host.identity'
86
86
  );
87
87
  }
88
88
 
@@ -92,10 +92,10 @@ function isHostIdentity(message) {
92
92
  */
93
93
  function isHostAccessUpdate(message) {
94
94
  return Boolean(
95
- message
96
- && typeof message === 'object'
97
- && /** @type {Record<string, unknown>} */ (message).type === 'host.access_update'
98
- && typeof /** @type {Record<string, unknown>} */ (message).accessEnabled === 'boolean'
95
+ message &&
96
+ typeof message === 'object' &&
97
+ /** @type {Record<string, unknown>} */ (message).type === 'host.access_update' &&
98
+ typeof (/** @type {Record<string, unknown>} */ (message).accessEnabled) === 'boolean'
99
99
  );
100
100
  }
101
101
 
@@ -105,9 +105,9 @@ function isHostAccessUpdate(message) {
105
105
  */
106
106
  function isHostActivity(message) {
107
107
  return Boolean(
108
- message
109
- && typeof message === 'object'
110
- && /** @type {Record<string, unknown>} */ (message).type === 'host.activity'
108
+ message &&
109
+ typeof message === 'object' &&
110
+ /** @type {Record<string, unknown>} */ (message).type === 'host.activity'
111
111
  );
112
112
  }
113
113
 
@@ -126,7 +126,7 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
126
126
  'native_bootstrap',
127
127
  ERROR_CODES.NATIVE_HOST_UNAVAILABLE,
128
128
  error instanceof Error ? error.message : String(error)
129
- )
129
+ ),
130
130
  });
131
131
  return;
132
132
  }
@@ -159,22 +159,29 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
159
159
  if (message.type === 'agent.response') {
160
160
  await writeNativeMessage(process.stdout, {
161
161
  type: 'host.bridge_response',
162
- response: message.response
162
+ response: message.response,
163
163
  });
164
164
  return;
165
165
  }
166
- if (message.type === 'extension.setup_status.response' || message.type === 'extension.setup_status.error') {
166
+ if (
167
+ message.type === 'extension.setup_status.response' ||
168
+ message.type === 'extension.setup_status.error'
169
+ ) {
167
170
  await writeNativeMessage(process.stdout, {
168
- type: message.type === 'extension.setup_status.response'
169
- ? 'host.setup_status.response'
170
- : 'host.setup_status.error',
171
+ type:
172
+ message.type === 'extension.setup_status.response'
173
+ ? 'host.setup_status.response'
174
+ : 'host.setup_status.error',
171
175
  requestId: message.requestId,
172
176
  status: message.status,
173
- error: message.error
177
+ error: message.error,
174
178
  });
175
179
  }
176
180
  })().catch((err) => {
177
- console.error('native-host: socket message handler failed:', err instanceof Error ? err.message : err);
181
+ console.error(
182
+ 'native-host: socket message handler failed:',
183
+ err instanceof Error ? err.message : err
184
+ );
178
185
  });
179
186
  }
180
187
  });
@@ -184,14 +191,14 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
184
191
  if (isHostBridgeRequest(message)) {
185
192
  await writeJsonLine(socket, {
186
193
  type: 'agent.request',
187
- request: message.request
194
+ request: message.request,
188
195
  });
189
196
  return;
190
197
  }
191
198
  if (isHostStatusRequest(message)) {
192
199
  await writeJsonLine(socket, {
193
200
  type: 'extension.setup_status.request',
194
- requestId: message.requestId
201
+ requestId: message.requestId,
195
202
  });
196
203
  return;
197
204
  }
@@ -199,30 +206,33 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
199
206
  await writeJsonLine(socket, {
200
207
  type: 'extension.identity',
201
208
  browserName: message.browserName,
202
- profileLabel: message.profileLabel
209
+ profileLabel: message.profileLabel,
203
210
  });
204
211
  return;
205
212
  }
206
213
  if (isHostAccessUpdate(message)) {
207
214
  await writeJsonLine(socket, {
208
215
  type: 'extension.access_update',
209
- accessEnabled: message.accessEnabled
216
+ accessEnabled: message.accessEnabled,
210
217
  });
211
218
  return;
212
219
  }
213
220
  if (isHostActivity(message)) {
214
221
  await writeJsonLine(socket, {
215
222
  type: 'extension.activity',
216
- at: message.at
223
+ at: message.at,
217
224
  });
218
225
  return;
219
226
  }
220
227
  await writeJsonLine(socket, {
221
228
  type: 'extension.response',
222
- response: message
229
+ response: message,
223
230
  });
224
231
  })().catch((err) => {
225
- console.error('native-host: stdin message handler failed:', err instanceof Error ? err.message : err);
232
+ console.error(
233
+ 'native-host: stdin message handler failed:',
234
+ err instanceof Error ? err.message : err
235
+ );
226
236
  });
227
237
  });
228
238
  }
@@ -235,9 +245,12 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
235
245
  * @param {() => void} [onTerminate]
236
246
  * @returns {void}
237
247
  */
238
- export function bindBridgeSocketLifecycle(socket, onTerminate = () => {
239
- process.exit(0);
240
- }) {
248
+ export function bindBridgeSocketLifecycle(
249
+ socket,
250
+ onTerminate = () => {
251
+ process.exit(0);
252
+ }
253
+ ) {
241
254
  let terminated = false;
242
255
 
243
256
  /**
@@ -317,7 +330,7 @@ function connectSocket(socketPath) {
317
330
  function spawnBridgeDaemon() {
318
331
  const child = spawn(process.execPath, [daemonEntryPath], {
319
332
  detached: true,
320
- stdio: 'ignore'
333
+ stdio: 'ignore',
321
334
  });
322
335
  child.unref();
323
336
  }
@@ -327,9 +340,11 @@ function spawnBridgeDaemon() {
327
340
  * @returns {boolean}
328
341
  */
329
342
  function shouldBootstrap(error) {
330
- return error instanceof Error
331
- && 'code' in error
332
- && (error.code === 'ENOENT' || error.code === 'ECONNREFUSED');
343
+ return (
344
+ error instanceof Error &&
345
+ 'code' in error &&
346
+ (error.code === 'ENOENT' || error.code === 'ECONNREFUSED')
347
+ );
333
348
  }
334
349
 
335
350
  /**
@@ -4,11 +4,7 @@
4
4
  /** @typedef {import('./types.js').BudgetOptions} BudgetOptions */
5
5
  /** @typedef {import('./types.js').TruncateResult} TruncateResult */
6
6
 
7
- import {
8
- DEFAULT_MAX_DEPTH,
9
- DEFAULT_MAX_NODES,
10
- DEFAULT_TEXT_BUDGET,
11
- } from './defaults.js';
7
+ import { DEFAULT_MAX_DEPTH, DEFAULT_MAX_NODES, DEFAULT_TEXT_BUDGET } from './defaults.js';
12
8
 
13
9
  /**
14
10
  * @param {BudgetOptions} [options={}]
@@ -20,7 +16,7 @@ export function applyBudget(options = {}) {
20
16
  maxDepth: clamp(options.maxDepth ?? DEFAULT_MAX_DEPTH, 1, 20),
21
17
  textBudget: clamp(options.textBudget ?? DEFAULT_TEXT_BUDGET, 32, 10000),
22
18
  includeBbox: options.includeBbox !== false,
23
- attributeAllowlist: normalizeList(options.attributeAllowlist)
19
+ attributeAllowlist: normalizeList(options.attributeAllowlist),
24
20
  };
25
21
  }
26
22
 
@@ -41,7 +37,7 @@ export function truncateText(value, budget) {
41
37
  return {
42
38
  value: `${value.slice(0, Math.max(0, budget - 1))}\u2026`,
43
39
  truncated: true,
44
- omitted: value.length - budget
40
+ omitted: value.length - budget,
45
41
  };
46
42
  }
47
43
 
@@ -20,7 +20,7 @@ export const CAPABILITIES = Object.freeze({
20
20
  AUTOMATION_INPUT: 'automation.input',
21
21
  TABS_MANAGE: 'tabs.manage',
22
22
  PERFORMANCE_READ: 'performance.read',
23
- NETWORK_READ: 'network.read'
23
+ NETWORK_READ: 'network.read',
24
24
  });
25
25
 
26
26
  export const DEFAULT_CAPABILITIES = Object.freeze([
@@ -40,7 +40,7 @@ export const DEFAULT_CAPABILITIES = Object.freeze([
40
40
  CAPABILITIES.CDP_STYLES,
41
41
  CAPABILITIES.TABS_MANAGE,
42
42
  CAPABILITIES.PERFORMANCE_READ,
43
- CAPABILITIES.NETWORK_READ
43
+ CAPABILITIES.NETWORK_READ,
44
44
  ]);
45
45
 
46
46
  /** @type {Readonly<Record<CapabilityMethod, Capability | null>>} */
@@ -101,7 +101,7 @@ export const METHOD_CAPABILITIES = Object.freeze({
101
101
  'cdp.get_computed_styles_for_node': CAPABILITIES.CDP_STYLES,
102
102
  'performance.get_metrics': CAPABILITIES.PERFORMANCE_READ,
103
103
  'log.tail': null,
104
- 'health.ping': null
104
+ 'health.ping': null,
105
105
  });
106
106
 
107
107
  /**
@@ -12,7 +12,7 @@ export const ERROR_CODES = Object.freeze({
12
12
  INVALID_REQUEST: 'INVALID_REQUEST',
13
13
  NATIVE_HOST_UNAVAILABLE: 'NATIVE_HOST_UNAVAILABLE',
14
14
  EXTENSION_DISCONNECTED: 'EXTENSION_DISCONNECTED',
15
- TIMEOUT: 'TIMEOUT'
15
+ TIMEOUT: 'TIMEOUT',
16
16
  });
17
17
 
18
18
  /**
@@ -23,47 +23,47 @@ export const ERROR_CODES = Object.freeze({
23
23
  export const ERROR_RECOVERY = Object.freeze({
24
24
  [ERROR_CODES.ACCESS_DENIED]: {
25
25
  retry: false,
26
- hint: 'Access is off for this window. Ask the user to click Enable in the Browser Bridge popup or side panel. Do not request access again until that window is enabled.'
26
+ hint: 'Access is off for this window. Ask the user to click Enable in the Browser Bridge popup or side panel. Do not request access again until that window is enabled.',
27
27
  },
28
28
  [ERROR_CODES.ELEMENT_STALE]: {
29
29
  retry: false,
30
30
  alternativeMethod: 'dom.query',
31
- hint: 'Element was removed from the DOM. Re-query with the same selector to get a fresh elementRef.'
31
+ hint: 'Element was removed from the DOM. Re-query with the same selector to get a fresh elementRef.',
32
32
  },
33
33
  [ERROR_CODES.TAB_MISMATCH]: {
34
34
  retry: false,
35
35
  alternativeMethod: 'tabs.list',
36
- hint: 'Tab was closed or not found. Use tabs.list to find an available tab.'
36
+ hint: 'Tab was closed or not found. Use tabs.list to find an available tab.',
37
37
  },
38
38
  [ERROR_CODES.TIMEOUT]: {
39
39
  retry: true,
40
40
  retryAfterMs: 1000,
41
- hint: 'Operation exceeded the time limit. Retry once, or simplify the request (smaller maxNodes, narrower selector).'
41
+ hint: 'Operation exceeded the time limit. Retry once, or simplify the request (smaller maxNodes, narrower selector).',
42
42
  },
43
43
  [ERROR_CODES.RATE_LIMITED]: {
44
44
  retry: true,
45
45
  retryAfterMs: 2000,
46
- hint: 'Too many requests. Back off and retry after a short delay.'
46
+ hint: 'Too many requests. Back off and retry after a short delay.',
47
47
  },
48
48
  [ERROR_CODES.EXTENSION_DISCONNECTED]: {
49
49
  retry: true,
50
50
  retryAfterMs: 3000,
51
51
  alternativeMethod: 'health.ping',
52
- hint: 'Extension not connected. Check Chrome is running, then retry. Use health.ping to verify connectivity.'
52
+ hint: 'Extension not connected. Check Chrome is running, then retry. Use health.ping to verify connectivity.',
53
53
  },
54
54
  [ERROR_CODES.NATIVE_HOST_UNAVAILABLE]: {
55
55
  retry: false,
56
- hint: 'Native host not reachable. Run `bbx doctor` to diagnose the installation.'
56
+ hint: 'Native host not reachable. Run `bbx doctor` to diagnose the installation.',
57
57
  },
58
58
  [ERROR_CODES.INVALID_REQUEST]: {
59
59
  retry: false,
60
- hint: 'Malformed method or params. Check the method name and parameter types.'
60
+ hint: 'Malformed method or params. Check the method name and parameter types.',
61
61
  },
62
62
  [ERROR_CODES.INTERNAL_ERROR]: {
63
63
  retry: true,
64
64
  retryAfterMs: 1000,
65
- hint: 'Unexpected extension error. Retry once; if persistent, check page.get_console for details.'
66
- }
65
+ hint: 'Unexpected extension error. Retry once; if persistent, check page.get_console for details.',
66
+ },
67
67
  });
68
68
 
69
69
  /**