@browserbridge/bbx 1.3.0 → 1.5.0

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 (29) hide show
  1. package/README.md +3 -1
  2. package/package.json +2 -2
  3. package/packages/agent-client/src/cli.js +45 -16
  4. package/packages/agent-client/src/client.js +74 -20
  5. package/packages/agent-client/src/command-registry.js +2 -3
  6. package/packages/agent-client/src/mcp-config.js +30 -27
  7. package/packages/agent-client/src/runtime.js +2 -10
  8. package/packages/agent-client/src/types.ts +10 -1
  9. package/packages/mcp-server/src/guidance.js +241 -0
  10. package/packages/mcp-server/src/handlers-capture.js +74 -11
  11. package/packages/mcp-server/src/handlers-dom.js +48 -0
  12. package/packages/mcp-server/src/handlers-navigation.js +22 -2
  13. package/packages/mcp-server/src/handlers-page.js +10 -9
  14. package/packages/mcp-server/src/handlers-utils.js +47 -1
  15. package/packages/mcp-server/src/server.js +111 -29
  16. package/packages/native-host/src/auth-token.js +92 -0
  17. package/packages/native-host/src/daemon-process.js +26 -4
  18. package/packages/native-host/src/daemon.js +174 -28
  19. package/packages/native-host/src/framing.js +7 -2
  20. package/packages/native-host/src/native-host.js +18 -2
  21. package/packages/protocol/src/defaults.js +3 -0
  22. package/packages/protocol/src/json-lines.js +29 -1
  23. package/packages/protocol/src/protocol.js +6 -1
  24. package/packages/protocol/src/types.ts +2 -0
  25. package/skills/browser-bridge/SKILL.md +21 -5
  26. package/skills/browser-bridge/agents/openai.yaml +1 -1
  27. package/skills/browser-bridge/references/interaction.md +6 -6
  28. package/skills/browser-bridge/references/protocol.md +57 -54
  29. package/skills/browser-bridge/references/ui-workflows.md +1 -1
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import fs from 'node:fs';
4
+
3
5
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
6
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
7
  // zod is required at runtime by @modelcontextprotocol/sdk for tool parameter schema
@@ -38,11 +40,27 @@ import {
38
40
  getMethodsByMaxComplexity,
39
41
  } from '../../protocol/src/index.js';
40
42
  import { applyWindowsTcpTransportDefaults } from '../../native-host/src/config.js';
43
+ import { MCP_SERVER_INSTRUCTIONS, registerBridgeMcpGuidance } from './guidance.js';
41
44
 
42
45
  export const BUDGET_PRESET_DESCRIPTION = `Budget preset: "quick", "normal", or "deep" (defaults: query ${BUDGET_PRESETS.normal.maxNodes} nodes / depth ${BUDGET_PRESETS.normal.maxDepth} / text ${BUDGET_PRESETS.normal.textBudget}). Numeric fields override the preset when both are provided.`;
43
46
  export const TAB_ID_DESCRIPTION =
44
47
  'Target a specific tab instead of the active tab in the enabled window.';
45
48
 
49
+ const MCP_SERVER_VERSION = loadPackageVersion();
50
+
51
+ /**
52
+ * @returns {string}
53
+ */
54
+ function loadPackageVersion() {
55
+ try {
56
+ const raw = fs.readFileSync(new URL('../../../package.json', import.meta.url), 'utf8');
57
+ const parsed = JSON.parse(raw);
58
+ return parsed && typeof parsed.version === 'string' ? parsed.version : '0.0.0';
59
+ } catch {
60
+ return '0.0.0';
61
+ }
62
+ }
63
+
46
64
  /** @type {readonly import('../../protocol/src/types.js').BridgeMethod[]} */
47
65
  const INVESTIGATE_SUBAGENT_BRIDGE_METHODS = Object.freeze(
48
66
  getMethodsByMaxComplexity('low').filter(
@@ -75,10 +93,15 @@ const INVESTIGATE_DELEGATION_HINT = Object.freeze({
75
93
  * @returns {McpServer}
76
94
  */
77
95
  export function createBridgeMcpServer() {
78
- const server = new McpServer({
79
- name: 'browser-bridge',
80
- version: '1.0.0',
81
- });
96
+ const server = new McpServer(
97
+ {
98
+ name: 'browser-bridge',
99
+ version: MCP_SERVER_VERSION,
100
+ },
101
+ {
102
+ instructions: MCP_SERVER_INSTRUCTIONS,
103
+ }
104
+ );
82
105
 
83
106
  server.registerTool(
84
107
  'browser_status',
@@ -114,6 +137,8 @@ export function createBridgeMcpServer() {
114
137
  inputSchema: {
115
138
  limit: z
116
139
  .number()
140
+ .int()
141
+ .positive()
117
142
  .optional()
118
143
  .describe(`Maximum log entries to return (default: ${DEFAULT_CONSOLE_LIMIT})`),
119
144
  budgetPreset: z
@@ -147,7 +172,7 @@ export function createBridgeMcpServer() {
147
172
  .describe('"list" (preferred), "create" (only when needed), or "close"'),
148
173
  url: z.string().optional().describe('URL for create action'),
149
174
  active: z.boolean().optional().describe('Focus the new tab (default: true)'),
150
- tabId: z.number().optional().describe('Tab ID (required for close)'),
175
+ tabId: z.number().int().positive().optional().describe('Tab ID (required for close)'),
151
176
  },
152
177
  },
153
178
  handleTabsTool
@@ -173,7 +198,7 @@ export function createBridgeMcpServer() {
173
198
  'accessibility_tree',
174
199
  ])
175
200
  .describe('DOM operation to perform'),
176
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
201
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
177
202
  budgetPreset: z
178
203
  .enum(['quick', 'normal', 'deep'])
179
204
  .optional()
@@ -189,14 +214,20 @@ export function createBridgeMcpServer() {
189
214
  withinRef: z.string().optional().describe('Scope query to this elementRef subtree'),
190
215
  maxNodes: z
191
216
  .number()
217
+ .int()
218
+ .positive()
192
219
  .optional()
193
220
  .describe(`Maximum nodes to return (default: ${DEFAULT_MAX_NODES})`),
194
221
  maxDepth: z
195
222
  .number()
223
+ .int()
224
+ .positive()
196
225
  .optional()
197
226
  .describe(`Maximum tree depth (default: ${DEFAULT_MAX_DEPTH})`),
198
227
  textBudget: z
199
228
  .number()
229
+ .int()
230
+ .positive()
200
231
  .optional()
201
232
  .describe(`Max chars of text content per node (default: ${DEFAULT_TEXT_BUDGET})`),
202
233
  includeBbox: z
@@ -216,7 +247,12 @@ export function createBridgeMcpServer() {
216
247
  .boolean()
217
248
  .optional()
218
249
  .describe('Require exact text match (default: false, substring match)'),
219
- maxResults: z.number().optional().describe('Maximum search results (default: 10)'),
250
+ maxResults: z
251
+ .number()
252
+ .int()
253
+ .positive()
254
+ .optional()
255
+ .describe('Maximum search results (default: 10)'),
220
256
  role: z.string().optional().describe('ARIA role to search for (for find_role action)'),
221
257
  name: z.string().optional().describe('Accessible name to match with role'),
222
258
  state: z
@@ -225,6 +261,8 @@ export function createBridgeMcpServer() {
225
261
  .describe('Expected element state (for wait action)'),
226
262
  timeoutMs: z
227
263
  .number()
264
+ .int()
265
+ .positive()
228
266
  .optional()
229
267
  .describe(`Timeout for wait operations (default: ${DEFAULT_WAIT_TIMEOUT_MS})`),
230
268
  outer: z
@@ -233,6 +271,8 @@ export function createBridgeMcpServer() {
233
271
  .describe('Return outerHTML instead of innerHTML (default: false)'),
234
272
  maxLength: z
235
273
  .number()
274
+ .int()
275
+ .positive()
236
276
  .optional()
237
277
  .describe(`Max HTML chars to return (default: ${DEFAULT_MAX_HTML_LENGTH})`),
238
278
  },
@@ -250,7 +290,7 @@ export function createBridgeMcpServer() {
250
290
  action: z
251
291
  .enum(['computed', 'matched_rules', 'box_model', 'hit_test'])
252
292
  .describe('Style/layout operation to perform'),
253
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
293
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
254
294
  budgetPreset: z
255
295
  .enum(['quick', 'normal', 'deep'])
256
296
  .optional()
@@ -261,8 +301,16 @@ export function createBridgeMcpServer() {
261
301
  .array(z.string())
262
302
  .optional()
263
303
  .describe('Style properties to fetch (omitting returns all - expensive)'),
264
- x: z.number().optional().describe('X coordinate for hit_test (viewport relative)'),
265
- y: z.number().optional().describe('Y coordinate for hit_test (viewport relative)'),
304
+ x: z
305
+ .number()
306
+ .nonnegative()
307
+ .optional()
308
+ .describe('X coordinate for hit_test (viewport relative)'),
309
+ y: z
310
+ .number()
311
+ .nonnegative()
312
+ .optional()
313
+ .describe('Y coordinate for hit_test (viewport relative)'),
266
314
  },
267
315
  },
268
316
  handleStylesLayoutTool
@@ -287,7 +335,7 @@ export function createBridgeMcpServer() {
287
335
  'performance',
288
336
  ])
289
337
  .describe('Page operation to perform'),
290
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
338
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
291
339
  budgetPreset: z
292
340
  .enum(['quick', 'normal', 'deep'])
293
341
  .optional()
@@ -299,6 +347,8 @@ export function createBridgeMcpServer() {
299
347
  awaitPromise: z.boolean().optional().describe('Await returned promises (default: false)'),
300
348
  timeoutMs: z
301
349
  .number()
350
+ .int()
351
+ .positive()
302
352
  .optional()
303
353
  .describe(`Timeout for evaluate/wait operations (default: ${DEFAULT_WAIT_TIMEOUT_MS})`),
304
354
  returnByValue: z
@@ -312,6 +362,8 @@ export function createBridgeMcpServer() {
312
362
  clear: z.boolean().optional().describe('Clear buffer after reading (default: false)'),
313
363
  limit: z
314
364
  .number()
365
+ .int()
366
+ .positive()
315
367
  .optional()
316
368
  .describe(`Maximum entries to return (default: ${DEFAULT_CONSOLE_LIMIT})`),
317
369
  type: z
@@ -324,6 +376,8 @@ export function createBridgeMcpServer() {
324
376
  .describe('Specific storage keys to fetch (omitting returns all)'),
325
377
  textBudget: z
326
378
  .number()
379
+ .int()
380
+ .positive()
327
381
  .optional()
328
382
  .describe(`Max chars for page text (default: ${DEFAULT_PAGE_TEXT_BUDGET})`),
329
383
  urlPattern: z.string().optional().describe('Filter network entries by URL pattern'),
@@ -342,14 +396,19 @@ export function createBridgeMcpServer() {
342
396
  action: z
343
397
  .enum(['navigate', 'reload', 'go_back', 'go_forward', 'scroll', 'resize'])
344
398
  .describe('Navigation operation to perform'),
345
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
399
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
346
400
  budgetPreset: z
347
401
  .enum(['quick', 'normal', 'deep'])
348
402
  .optional()
349
403
  .describe(BUDGET_PRESET_DESCRIPTION),
350
404
  url: z.string().optional().describe('URL to navigate to (for navigate action)'),
351
405
  waitForLoad: z.boolean().optional().describe('Wait for load event (default: true)'),
352
- timeoutMs: z.number().optional().describe('Timeout for navigation (default: 30000)'),
406
+ timeoutMs: z
407
+ .number()
408
+ .int()
409
+ .positive()
410
+ .optional()
411
+ .describe('Timeout for navigation (default: 30000)'),
353
412
  top: z.number().optional().describe('Scroll target Y position (pixels)'),
354
413
  left: z.number().optional().describe('Scroll target X position (pixels)'),
355
414
  behavior: z.enum(['auto', 'smooth']).optional().describe('Scroll behavior (default: auto)'),
@@ -357,8 +416,13 @@ export function createBridgeMcpServer() {
357
416
  .boolean()
358
417
  .optional()
359
418
  .describe('Scroll relative to current position (default: false)'),
360
- width: z.number().optional().describe('Viewport width in pixels'),
361
- height: z.number().optional().describe('Viewport height in pixels'),
419
+ width: z.number().int().positive().optional().describe('Viewport width in pixels'),
420
+ height: z.number().int().positive().optional().describe('Viewport height in pixels'),
421
+ deviceScaleFactor: z
422
+ .number()
423
+ .nonnegative()
424
+ .optional()
425
+ .describe('Viewport device scale factor (for resize)'),
362
426
  reset: z.boolean().optional().describe('Reset viewport to original size (for resize)'),
363
427
  },
364
428
  },
@@ -386,7 +450,7 @@ export function createBridgeMcpServer() {
386
450
  'scroll_into_view',
387
451
  ])
388
452
  .describe('Input operation to perform'),
389
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
453
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
390
454
  budgetPreset: z
391
455
  .enum(['quick', 'normal', 'deep'])
392
456
  .optional()
@@ -400,7 +464,13 @@ export function createBridgeMcpServer() {
400
464
  .enum(['left', 'middle', 'right'])
401
465
  .optional()
402
466
  .describe('Mouse button for click (default: left)'),
403
- clickCount: z.number().optional().describe('Click count (1=single, 2=double)'),
467
+ clickCount: z
468
+ .number()
469
+ .int()
470
+ .min(1)
471
+ .max(2)
472
+ .optional()
473
+ .describe('Click count (1=single, 2=double)'),
404
474
  text: z.string().max(100000).optional().describe('Text to type (for type action)'),
405
475
  clear: z.boolean().optional().describe('Clear field before typing (default: false)'),
406
476
  submit: z.boolean().optional().describe('Press Enter after typing (default: false)'),
@@ -423,10 +493,15 @@ export function createBridgeMcpServer() {
423
493
  .optional()
424
494
  .describe('Option labels to select (alternative to values)'),
425
495
  indexes: z
426
- .array(z.number())
496
+ .array(z.number().int().nonnegative())
427
497
  .optional()
428
498
  .describe('Option indexes to select (alternative to values/labels)'),
429
- duration: z.number().optional().describe('Hover duration in ms (default: 100)'),
499
+ duration: z
500
+ .number()
501
+ .int()
502
+ .nonnegative()
503
+ .optional()
504
+ .describe('Hover duration in ms (default: 100)'),
430
505
  sourceElementRef: z.string().optional().describe('Drag source element (for drag action)'),
431
506
  sourceSelector: z
432
507
  .string()
@@ -457,7 +532,7 @@ export function createBridgeMcpServer() {
457
532
  action: z
458
533
  .enum(['apply_styles', 'apply_dom', 'list', 'rollback', 'commit_baseline'])
459
534
  .describe('Patch operation to perform'),
460
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
535
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
461
536
  budgetPreset: z
462
537
  .enum(['quick', 'normal', 'deep'])
463
538
  .optional()
@@ -517,7 +592,7 @@ export function createBridgeMcpServer() {
517
592
  .describe(
518
593
  'element (preferred), region (tight crop), full_page (document-level only), or cdp_* for low-level data'
519
594
  ),
520
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
595
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
521
596
  budgetPreset: z
522
597
  .enum(['quick', 'normal', 'deep'])
523
598
  .optional()
@@ -527,13 +602,18 @@ export function createBridgeMcpServer() {
527
602
  .optional()
528
603
  .describe('Element reference (for element action, preferred)'),
529
604
  selector: z.string().optional().describe('CSS selector (used if no elementRef)'),
530
- nodeId: z.number().optional().describe('CDP node id for cdp_box_model/cdp_computed_styles'),
605
+ nodeId: z
606
+ .number()
607
+ .int()
608
+ .positive()
609
+ .optional()
610
+ .describe('CDP node id for cdp_box_model/cdp_computed_styles'),
531
611
  rect: z
532
612
  .object({
533
- x: z.number().describe('Region left edge (viewport pixels)'),
534
- y: z.number().describe('Region top edge (viewport pixels)'),
535
- width: z.number().describe('Region width (pixels)'),
536
- height: z.number().describe('Region height (pixels)'),
613
+ x: z.number().nonnegative().describe('Region left edge (viewport pixels)'),
614
+ y: z.number().nonnegative().describe('Region top edge (viewport pixels)'),
615
+ width: z.number().positive().describe('Region width (pixels)'),
616
+ height: z.number().positive().describe('Region height (pixels)'),
537
617
  })
538
618
  .optional()
539
619
  .describe('Viewport region for region action (keep crop tight)'),
@@ -557,7 +637,7 @@ export function createBridgeMcpServer() {
557
637
  .record(z.string(), z.unknown())
558
638
  .optional()
559
639
  .describe('Method params for this call'),
560
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
640
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
561
641
  budgetPreset: z
562
642
  .enum(['quick', 'normal', 'deep'])
563
643
  .optional()
@@ -583,7 +663,7 @@ export function createBridgeMcpServer() {
583
663
  .record(z.string(), z.unknown())
584
664
  .optional()
585
665
  .describe('Method parameters as object'),
586
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
666
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
587
667
  },
588
668
  },
589
669
  handleRawCallTool
@@ -645,7 +725,7 @@ export function createBridgeMcpServer() {
645
725
  '"normal" (state + DOM + text, default), ' +
646
726
  '"deep" (state + DOM + text + console + network).'
647
727
  ),
648
- tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
728
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
649
729
  selector: z
650
730
  .string()
651
731
  .optional()
@@ -655,6 +735,8 @@ export function createBridgeMcpServer() {
655
735
  handleInvestigateTool
656
736
  );
657
737
 
738
+ registerBridgeMcpGuidance(server);
739
+
658
740
  return server;
659
741
  }
660
742
 
@@ -0,0 +1,92 @@
1
+ // @ts-check
2
+
3
+ import { randomBytes } from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ import { getBridgeDir } from './config.js';
8
+
9
+ const TOKEN_FILENAME = 'daemon.auth';
10
+ const TOKEN_BYTES = 32;
11
+ const TOKEN_PATTERN = /^[A-Za-z0-9_-]{32,256}$/u;
12
+
13
+ /**
14
+ * @param {NodeJS.ProcessEnv} [env=process.env]
15
+ * @returns {string}
16
+ */
17
+ export function getBridgeAuthTokenPath(env = process.env) {
18
+ return path.join(getBridgeDir(env), TOKEN_FILENAME);
19
+ }
20
+
21
+ /**
22
+ * @param {unknown} value
23
+ * @returns {string | null}
24
+ */
25
+ export function normalizeBridgeAuthToken(value) {
26
+ if (typeof value !== 'string') {
27
+ return null;
28
+ }
29
+ const token = value.trim();
30
+ return TOKEN_PATTERN.test(token) ? token : null;
31
+ }
32
+
33
+ /**
34
+ * @param {{ tokenPath?: string, readFile?: typeof fs.promises.readFile }} [options={}]
35
+ * @returns {Promise<string | null>}
36
+ */
37
+ export async function readBridgeAuthToken(options = {}) {
38
+ const tokenPath = options.tokenPath ?? getBridgeAuthTokenPath();
39
+ const readFile = options.readFile ?? fs.promises.readFile.bind(fs.promises);
40
+ try {
41
+ return normalizeBridgeAuthToken(await readFile(tokenPath, 'utf8'));
42
+ } catch (error) {
43
+ if (isMissingFileError(error)) {
44
+ return null;
45
+ }
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @param {{
52
+ * tokenPath?: string,
53
+ * readFile?: typeof fs.promises.readFile,
54
+ * writeFile?: typeof fs.promises.writeFile,
55
+ * mkdir?: typeof fs.promises.mkdir,
56
+ * chmod?: typeof fs.promises.chmod,
57
+ * randomBytesFn?: typeof randomBytes
58
+ * }} [options={}]
59
+ * @returns {Promise<string>}
60
+ */
61
+ export async function ensureBridgeAuthToken(options = {}) {
62
+ const tokenPath = options.tokenPath ?? getBridgeAuthTokenPath();
63
+ const readFile = options.readFile ?? fs.promises.readFile.bind(fs.promises);
64
+ const writeFile = options.writeFile ?? fs.promises.writeFile.bind(fs.promises);
65
+ const mkdir = options.mkdir ?? fs.promises.mkdir.bind(fs.promises);
66
+ const chmod = options.chmod ?? fs.promises.chmod.bind(fs.promises);
67
+ const randomBytesFn = options.randomBytesFn ?? randomBytes;
68
+ const existing = await readBridgeAuthToken({ tokenPath, readFile });
69
+ if (existing) {
70
+ return existing;
71
+ }
72
+
73
+ const token = randomBytesFn(TOKEN_BYTES).toString('base64url');
74
+ await mkdir(path.dirname(tokenPath), { recursive: true });
75
+ await writeFile(tokenPath, `${token}\n`, { encoding: 'utf8', mode: 0o600 });
76
+ if (process.platform !== 'win32') {
77
+ await chmod(tokenPath, 0o600).catch(() => {});
78
+ }
79
+ return token;
80
+ }
81
+
82
+ /**
83
+ * @param {unknown} error
84
+ * @returns {boolean}
85
+ */
86
+ function isMissingFileError(error) {
87
+ return Boolean(
88
+ error &&
89
+ typeof error === 'object' &&
90
+ /** @type {{ code?: unknown }} */ (error).code === 'ENOENT'
91
+ );
92
+ }
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
8
8
 
9
9
  import { pingExistingDaemon } from './daemon.js';
10
10
  import {
11
+ applyWindowsTcpTransportDefaults,
11
12
  createSocketBridgeTransport,
12
13
  formatBridgeTransport,
13
14
  getBridgeTransport,
@@ -124,7 +125,7 @@ export async function clearDaemonPidFile(options = {}) {
124
125
  */
125
126
  export async function stopBridgeDaemon(options = {}) {
126
127
  const {
127
- transport = getBridgeTransport(),
128
+ transport = undefined,
128
129
  socketPath = undefined,
129
130
  pidPath = getDaemonPidPath(),
130
131
  timeoutMs = DEFAULT_DAEMON_RESTART_TIMEOUT_MS,
@@ -136,7 +137,7 @@ export async function stopBridgeDaemon(options = {}) {
136
137
  rmFn = fs.promises.rm,
137
138
  sleepFn = sleep,
138
139
  } = options;
139
- const resolvedTransport = socketPath ? createSocketBridgeTransport(socketPath) : transport;
140
+ const resolvedTransport = resolveDaemonTransport({ transport, socketPath });
140
141
  const resolvedSocketPath =
141
142
  resolvedTransport.type === 'socket' ? resolvedTransport.socketPath : '';
142
143
 
@@ -238,7 +239,7 @@ export async function restartBridgeDaemonIfRunning(options = {}) {
238
239
  */
239
240
  async function restartBridgeDaemonAfterStop(stopResult, options = {}) {
240
241
  const {
241
- transport = getBridgeTransport(),
242
+ transport = undefined,
242
243
  socketPath = undefined,
243
244
  pidPath = getDaemonPidPath(),
244
245
  timeoutMs = DEFAULT_DAEMON_RESTART_TIMEOUT_MS,
@@ -248,7 +249,7 @@ async function restartBridgeDaemonAfterStop(stopResult, options = {}) {
248
249
  sleepFn = sleep,
249
250
  spawnDaemonFn = spawnBridgeDaemonProcess,
250
251
  } = options;
251
- const resolvedTransport = socketPath ? createSocketBridgeTransport(socketPath) : transport;
252
+ const resolvedTransport = resolveDaemonTransport({ transport, socketPath });
252
253
 
253
254
  spawnDaemonFn();
254
255
 
@@ -271,6 +272,27 @@ async function restartBridgeDaemonAfterStop(stopResult, options = {}) {
271
272
  };
272
273
  }
273
274
 
275
+ /**
276
+ * Mirror the daemon entrypoint transport defaults so restart polling targets the
277
+ * same endpoint the spawned process listens on.
278
+ *
279
+ * @param {{ transport?: BridgeTransport, socketPath?: string }} options
280
+ * @returns {BridgeTransport}
281
+ */
282
+ function resolveDaemonTransport(options) {
283
+ const { transport, socketPath } = options;
284
+ if (socketPath) {
285
+ return createSocketBridgeTransport(socketPath);
286
+ }
287
+ if (transport) {
288
+ return transport;
289
+ }
290
+
291
+ const env = { ...process.env };
292
+ applyWindowsTcpTransportDefaults(env);
293
+ return getBridgeTransport(env);
294
+ }
295
+
274
296
  /**
275
297
  * @param {BridgeTransport} transport
276
298
  * @returns {Promise<number | null>}