@browserbridge/bbx 1.14.0 → 1.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbridge/bbx",
3
- "version": "1.14.0",
3
+ "version": "1.14.2",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "agent-tools",
@@ -77,12 +77,12 @@
77
77
  "dependencies": {
78
78
  "@modelcontextprotocol/server": "^2.3.1",
79
79
  "@mozilla/readability": "^0.6.0",
80
- "linkedom": "^0.18.13",
80
+ "linkedom": "^0.18.14",
81
81
  "zod": "^4.6.7"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@modelcontextprotocol/client": "^2.3.1",
85
- "@types/chrome": "^0.3.5",
85
+ "@types/chrome": "^0.3.6",
86
86
  "@types/node": "^26.6.5",
87
87
  "ajv": "^8.20.0",
88
88
  "ajv-formats": "^3.0.1",
@@ -52,7 +52,7 @@ export function printCallResponse(response, method) {
52
52
  }
53
53
 
54
54
  /**
55
- * @returns {void}
55
+ * @returns {Promise<void>}
56
56
  */
57
57
  export function printUsage() {
58
58
  const blocks = ['Usage: bbx [--remote <name>] <command> [args]'];
@@ -60,5 +60,11 @@ export function printUsage() {
60
60
  blocks.push('', `${section.title}:`);
61
61
  blocks.push(...section.lines.map((line) => ` ${line}`));
62
62
  }
63
- process.stdout.write(`${blocks.join('\n')}\n`);
63
+ // Wait for piped output to flush before the CLI exits.
64
+ return new Promise((resolve, reject) => {
65
+ process.stdout.write(`${blocks.join('\n')}\n`, (error) => {
66
+ if (error) reject(error);
67
+ else resolve();
68
+ });
69
+ });
64
70
  }
@@ -101,7 +101,7 @@ const remoteDestinationId =
101
101
  : (remoteFlag.remoteId ?? process.env[REMOTE_ENV] ?? null);
102
102
 
103
103
  if (!command || ['help', '--help', '-h'].includes(command)) {
104
- printUsage();
104
+ await printUsage();
105
105
  process.exit(0);
106
106
  }
107
107
 
@@ -794,7 +794,7 @@ async function main() {
794
794
  }
795
795
 
796
796
  process.stderr.write(`Unknown command: ${command}\n`);
797
- printUsage();
797
+ await printUsage();
798
798
  process.exitCode = 1;
799
799
  } catch (error) {
800
800
  const message = error instanceof Error ? error.message : String(error);
@@ -8,6 +8,7 @@
8
8
  const SHARED_INSTRUCTIONS = Object.freeze([
9
9
  'Prefer Browser Bridge MCP tools over shelling out to bbx. Use bbx only for explicit CLI setup, doctor, logs, or raw debugging requests.',
10
10
  'Start with browser_call method health.ping. Its access status describes only the routed profile, not every window. Use connectedExtensions or unscoped tabs.list to discover profiles and existing tabs. Reuse the selected extensionId, destinationId when remote, and tabId on every follow-up call, including each browser_batch item. tabId alone does not select a profile. Never disable unrelated windows to fix routing. If a connection disappears, rediscover it instead of switching profiles or using guessed IDs.',
11
+ "Keep working in the target tab when the user switches to their own tab. Reads, input, navigation, and screenshots can run in background tabs; a different active_tab_id is informational. You may switch to tabs you opened for this task without asking while the currently active tab is also one you are using for this task. Check tabs.list active state before switching; track opened tabs from your own tabs.create results. working:true identifies routing, not ownership or foreground focus. Otherwise, activate only at the user's request or with their permission. If background work fails because a tab needs to be active, explain why and ask whether activation is allowed. Apply this rule to tabs.activate, tabs.create with active:true, and equivalent focus APIs.",
11
12
  "If the selected profile has window access disabled, call browser_call method access.request once with its extensionId, ask the user to click Enable in that profile's Browser Bridge popup or side panel, then retry once with the same target. Restricted pages and ambiguous_browser_target are routing problems, not evidence that every window needs enabling.",
12
13
  'Use structured reads first through browser_call: page.get_state, dom.query, page.get_text, page.extract_content, styles.get_computed, or layout.get_box_model. Prefer semantic extraction for articles and documentation. Use browser_batch only for independent parallel reads; run mutations in order, preferably inside one input.perform for a known flow. Keep limits tight before widening.',
13
14
  'Reuse elementRef values returned by DOM reads. Use attribute allowlists for focused DOM reads.',
@@ -323,13 +323,18 @@ export function createBridgeMcpServer(options = {}) {
323
323
  {
324
324
  title: 'Browser Tabs',
325
325
  description:
326
- "List, create, close, or activate browser tabs. Unscoped list discovers tabs across enabled browser profiles and configured destinations. Reuse each tab's extensionId, destinationId, and tabId for subsequent calls. Other actions default to local. Only create a page when the user explicitly requests it.",
326
+ "List, create, close, or activate browser tabs. Unscoped list discovers tabs across enabled browser profiles and configured destinations. Reuse each tab's extensionId, destinationId, and tabId for subsequent calls. Other actions default to local. Only create a page when the user explicitly requests it. New tabs open in the background. You may activate tabs you opened for this task while the active tab is also in use for this task. Otherwise, activate only at the user's request or with their permission. Targeting tabId does not require activation.",
327
327
  inputSchema: z.object({
328
328
  action: z
329
329
  .enum(['list', 'create', 'close', 'activate'])
330
330
  .describe('"list" (preferred), "create" (only when needed), "close", or "activate"'),
331
331
  url: z.string().optional().describe('URL for create action'),
332
- active: z.boolean().optional().describe('Focus the new tab (default: true)'),
332
+ active: z
333
+ .boolean()
334
+ .optional()
335
+ .describe(
336
+ 'Focus the new tab when the active tab is in use for this task, or with user permission (default: false)'
337
+ ),
333
338
  limit: z
334
339
  .number()
335
340
  .int()
@@ -235,6 +235,18 @@ async function ensureStoredBridgeAuthToken(tokenPath, options) {
235
235
  owner = await readFile(lockPath, 'utf8');
236
236
  } catch (error) {
237
237
  if (isMissingFileError(error)) continue;
238
+ // Windows can report EPERM while a competing owner deletes the lock.
239
+ // Retry within the same bounded acquisition loop, then recheck the winner.
240
+ if (
241
+ process.platform === 'win32' &&
242
+ error &&
243
+ typeof error === 'object' &&
244
+ 'code' in error &&
245
+ error.code === 'EPERM'
246
+ ) {
247
+ await new Promise((resolve) => setTimeout(resolve, 10));
248
+ continue;
249
+ }
238
250
  throw error;
239
251
  }
240
252
  const match = /^(\d+):([0-9a-f-]{36})\n$/u.exec(owner);
@@ -1841,7 +1841,7 @@ export function normalizeTabCreateParams(params = {}) {
1841
1841
  }
1842
1842
  return {
1843
1843
  url,
1844
- active: params.active !== false,
1844
+ active: params.active === true,
1845
1845
  };
1846
1846
  }
1847
1847
 
@@ -148,9 +148,11 @@ const BRIDGE_METHOD_DESCRIPTIONS = Object.freeze({
148
148
  'protocol.describe':
149
149
  'Describe bridge method parameters and metadata by method or registry group.',
150
150
  'tabs.list': 'List tabs in the enabled window.',
151
- 'tabs.create': 'Create a new tab in the enabled window.',
151
+ 'tabs.create':
152
+ "Create a background tab in the enabled window. Set active:true when the current active tab is in use for this task, or at the user's request or with their permission.",
152
153
  'tabs.close': 'Close a tab in the enabled window.',
153
- 'tabs.activate': 'Bring a tab to the foreground in the enabled window.',
154
+ 'tabs.activate':
155
+ "Bring a tab forward at the user's request or with their permission, or switch to a tab you opened for this task while the active tab is also in use for this task. Check active state first; if background work needs activation outside this case, ask. Targeting tabId does not require activation.",
154
156
  'skill.get_runtime_context': 'Return runtime method groups, budgets, and limits.',
155
157
  'setup.get_status': 'Return MCP and skill setup status.',
156
158
  'setup.install': 'Install or uninstall MCP or skill integration targets.',
@@ -32,7 +32,7 @@ bbx tabs # list available tabs (prefer this)
32
32
  bbx logs # recent bridge request log
33
33
  bbx tab-create [url] # open a new tab (avoid unless necessary)
34
34
  bbx tab-close <tabId> # close a tab
35
- bbx tab-activate <tabId> # bring a tab to the foreground
35
+ bbx tab-activate <tabId> # bring forward under the tab focus rules below
36
36
  bbx skill # live runtime presets + limits
37
37
  bbx protocol describe [method|group] # local method params or compact group index
38
38
  ```
@@ -176,13 +176,21 @@ After the Enable cue appears:
176
176
  After access is enabled:
177
177
 
178
178
  1. Your first tab-bound call binds your session to the active tab: that becomes your **working tab**.
179
- 2. The working tab is sticky. If the user switches to another tab (to read mail, say), your calls keep running in your working tab; they never navigate or type into the tab the user is looking at.
179
+ 2. The working tab is sticky. If the user switches to their own tab, keep running in your working tab without taking focus back. Reads, input, navigation, and screenshots can run in background tabs. A different `active_tab_id` is informational, not an error to fix.
180
180
  3. Responses report `meta.tab_id`; when the user's active tab differs, the summary says so (`Ran in working tab 12; the user's active tab is 34`). If the user asks you to work in "this tab", pass that `tabId` once; the explicit tab becomes your new working tab. `tabs.create` and `tabs.activate` also move your working tab.
181
181
  4. If your working tab was closed, calls fail with `TAB_MISMATCH` (`reason: working_tab_closed`) instead of silently using another tab; pick a tab with `tabId` or `tabs.list` (which marks `working: true`).
182
182
  5. The binding expires after 15 idle minutes.
183
183
  6. Do not stop at a generic "no access" message before making a real Browser Bridge call, because the first denied call is what triggers the UI cue.
184
184
  7. CLI agents running separate `bbx` processes share one working tab unless each sets `BBX_SESSION=<name>`; MCP sessions are separated automatically.
185
185
 
186
+ ### Tab focus
187
+
188
+ You may switch to tabs you opened for the current task without asking while the currently active tab is also one you are using for that task. Check `tabs.list` active state before switching. Track tabs you opened from your own `tabs.create` results; `working:true` identifies the routed tab, not ownership or foreground focus.
189
+
190
+ If the user has switched to their own tab, continue in the background. Activate only at their request or with their permission. If something cannot work while the target tab is inactive, explain why and ask whether you may activate it. A timeout alone does not establish that activation is needed. Permission for one activation does not allow repeatedly taking focus back after the user switches away.
191
+
192
+ These rules apply to `tabs.activate`, `active:true` on creation, and equivalent focus APIs. New tabs open in the background by default.
193
+
186
194
  ## Error Recovery
187
195
 
188
196
  | Error | Retry? | Recovery |
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: 'Browser Bridge'
3
3
  short_description: 'Token-efficient Chrome tab inspection, interaction, and patching via local bridge'
4
- default_prompt: 'Use the browser-bridge skill for Chrome tab inspection, interaction, and patching. Start with `bbx status` to confirm connectivity; if the daemon is offline, run `bbx restart`. If ACCESS_DENIED, ask the user to click Enable in the extension popup/side panel, then retry. Default routing follows the active tab; use `--tab` only for a different tab. Prefer structured reads (`dom.query`, `styles.get_computed`) over screenshots. Batch independent reads with `bbx batch`. Use `bbx skill` for runtime presets.'
4
+ default_prompt: 'Use the browser-bridge skill for Chrome tab inspection, interaction, and patching. Start with `bbx status` to confirm connectivity; if the daemon is offline, run `bbx restart`. If ACCESS_DENIED, ask the user to click Enable in the extension popup/side panel, then retry. The first page call binds to a working tab; keep using it when the user switches tabs. Use `--tab` to target another tab without activating it. Check active state before switching: you may activate tabs you opened for this task while the active tab is also in use for this task. Otherwise, get user permission before taking focus, including when background work requires activation. Prefer structured reads (`dom.query`, `styles.get_computed`) over screenshots. Batch independent reads with `bbx batch`. Use `bbx skill` for runtime presets.'
@@ -88,12 +88,16 @@ Uses CDP device emulation - the page re-renders at the new size immediately.
88
88
 
89
89
  Always start with `tabs.list` to find an appropriate existing tab before considering `tabs.create`.
90
90
 
91
+ Reads, input, navigation, and screenshots can target background tabs by `tabId`. New tabs open in the background by default. You may switch to tabs you opened for this task without asking while the active tab is also one you are using for this task. Check `tabs.list` active state before switching, and track opened tabs from your own `tabs.create` results. `working:true` identifies routing, not ownership or foreground focus.
92
+
93
+ If the user switches to their own tab, continue in the background. Activate only at their request or with their permission. If an operation cannot work while the tab is inactive, explain why and ask whether activation is allowed. A timeout or a different `active_tab_id` alone does not justify activation. Permission for one activation does not allow repeatedly taking focus back after the user switches away. Apply these rules to `tabs.activate`, `active:true`, and equivalent focus APIs.
94
+
91
95
  ```bash
92
96
  bbx tabs # list available tabs (start here)
93
97
  bbx tab-create https://example.com # open new tab (avoid unless necessary)
94
98
  bbx tab-create # open blank tab (avoid unless necessary)
95
99
  bbx tab-close 12345 # close tab by ID
96
- bbx tab-activate 12345 # bring a tab to the foreground
100
+ bbx tab-activate 12345 # bring forward under the tab focus rules above
97
101
  bbx call tabs.create '{"url":"https://example.com","active":false}'
98
102
  ```
99
103
 
@@ -126,7 +130,7 @@ The default `source: "cdp"` returns Chrome's AX tree through the debugger. Use i
126
130
  Access is window-scoped. Your first call binds your session to the active tab in the enabled window, which becomes your working tab, and later calls stay there even when the user switches tabs. Responses include `meta.tab_id`; when the user is looking at a different tab, the summary names both. `tabs.list` marks your tab with `working: true`. Passing `tabId`, `tabs.create`, or `tabs.activate` moves your working tab. A closed working tab fails with `TAB_MISMATCH` (`working_tab_closed`) rather than switching to whatever tab is active.
127
131
 
128
132
  ```bash
129
- # Default routing follows the active tab in the enabled window:
133
+ # The first page call binds to the active tab; later calls keep that working tab:
130
134
  bbx tabs
131
135
  bbx page-text
132
136
 
@@ -5,7 +5,7 @@ Prefer non-debugger methods first. `chrome.debugger`-backed methods (marked **CD
5
5
  ## Access Model
6
6
 
7
7
  1. The user turns Browser Bridge on for one browser window.
8
- 2. Default routing follows the active tab in that enabled window.
8
+ 2. The first page call binds to the active tab in that enabled window. Later calls keep the working tab when the user switches tabs. You may activate tabs you opened for this task while the active tab is also in use for this task; check active state first. Otherwise, target background tabs without activating them unless the user requests or permits activation. If background work requires activation, explain why and ask.
9
9
  3. Use `tabId` only when you intentionally need a different tab in the same enabled window.
10
10
  4. Turning Browser Bridge off removes access immediately.
11
11
 
@@ -126,7 +126,7 @@ stopped.
126
126
  ```bash
127
127
  bbx status | doctor | restart | logs | tabs | skill # no routed tab needed
128
128
  bbx protocol describe [method|group] # local signature or compact group index
129
- bbx call <method> '{"key":"val"}' # generic RPC (routes to active tab in enabled window)
129
+ bbx call <method> '{"key":"val"}' # generic RPC (routes to the session's working tab)
130
130
  bbx call --tab 123 <method> '{...}' # explicit tab target inside enabled window
131
131
  bbx batch '[{"method":"...","params":{}}]' # parallel calls
132
132
  ```