@vibebrowser/mcp 0.2.5 → 0.2.6

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.
@@ -4,6 +4,8 @@ import { setTimeout as delay } from 'node:timers/promises';
4
4
  import { ExtensionConnection } from './connection.js';
5
5
  import { DEFAULT_WS_PORT } from './types.js';
6
6
  const DEFAULT_TIMEOUT_MS = 30_000;
7
+ /** Short timeout for the `status` command — it should never block for 30 s. */
8
+ const STATUS_TOOLS_TIMEOUT_MS = 2_000;
7
9
  const DEFAULT_BROWSER_PROFILE = process.env.VIBE_BROWSER_PROFILE || 'user';
8
10
  const DEFAULT_REMOTE_UUID = process.env.VIBE_EXTENSION_UUID || process.env.VIBE_RELAY_UUID;
9
11
  const DEFAULT_REMOTE_RELAY_URL = process.env.VIBE_REMOTE_RELAY_URL || process.env.VIBE_RELAY_URL;
@@ -23,9 +25,12 @@ function buildBrowserCommand(command) {
23
25
  .option('-p, --port <number>', 'WebSocket port for local relay (agent) connection', String(DEFAULT_WS_PORT))
24
26
  .option('-d, --debug', 'Enable debug logging', false)
25
27
  .option('-r, --remote <uuid>', 'Connect to a remote extension via public relay (provide the extension UUID)', DEFAULT_REMOTE_UUID)
28
+ .option('-s, --session <id>', 'Target a specific local browser session ID; defaults to the first connected session')
26
29
  .option('--relay-url <url>', 'Custom relay server URL', DEFAULT_REMOTE_RELAY_URL)
27
30
  .option('--json', 'Emit machine-readable JSON output', false)
28
- .option('--timeout <ms>', 'Command timeout in milliseconds', String(DEFAULT_TIMEOUT_MS));
31
+ .option('--timeout <ms>', 'Command timeout in milliseconds', String(DEFAULT_TIMEOUT_MS))
32
+ .option('--page-id <id>', 'Target a specific page/tab by its numeric ID (avoids switching the user\'s active tab)')
33
+ .option('--pageId <id>', 'Alias for --page-id');
29
34
  }
30
35
  function registerBrowserSubcommands(browser) {
31
36
  browser
@@ -61,6 +66,12 @@ function registerBrowserSubcommands(browser) {
61
66
  note: 'The Vibe browser bridge does not own the browser process, so stop only disconnects this CLI session',
62
67
  }));
63
68
  });
69
+ browser
70
+ .command('sessions')
71
+ .description('List connected browser sessions')
72
+ .action(async function () {
73
+ await runBrowserCommand(this, 'sessions', false, async (ctx) => ctx.listSessions());
74
+ });
64
75
  browser
65
76
  .command('tabs')
66
77
  .description('List browser tabs/pages')
@@ -80,7 +91,7 @@ function registerBrowserSubcommands(browser) {
80
91
  });
81
92
  tab
82
93
  .command('select <id>')
83
- .description('Focus a tab/page')
94
+ .description('Switch to a tab/page by its ID (rarely needed — most commands accept a tab id argument instead, prefer that to avoid disrupting the user\'s browser)')
84
95
  .action(async function (id) {
85
96
  await runBrowserCommand(this, 'tab select', true, async (ctx) => ctx.focus(id));
86
97
  });
@@ -104,7 +115,7 @@ function registerBrowserSubcommands(browser) {
104
115
  });
105
116
  browser
106
117
  .command('focus <id>')
107
- .description('Focus a tab/page')
118
+ .description('Switch focus to a tab/page by its ID (rarely needed — most commands accept a tab id argument instead, prefer that to avoid disrupting the user\'s browser)')
108
119
  .action(async function (id) {
109
120
  await runBrowserCommand(this, 'focus', true, async (ctx) => ctx.focus(id));
110
121
  });
@@ -156,31 +167,7 @@ function registerBrowserSubcommands(browser) {
156
167
  grayscale: Boolean(options.grayscale),
157
168
  }));
158
169
  });
159
- browser
160
- .command('pdf')
161
- .description('Render the current page as PDF when supported')
162
- .option('--output <path>', 'Write PDF bytes to a file')
163
- .action(async function () {
164
- await runBrowserCommand(this, 'pdf', true, async (ctx, options) => ctx.pdf(options.output ? String(options.output) : undefined));
165
- });
166
- browser
167
- .command('console')
168
- .description('List console messages')
169
- .option('--level <level>', 'Console level filter')
170
- .option('--preserve', 'Include preserved messages when supported', false)
171
- .action(async function () {
172
- await runBrowserCommand(this, 'console', true, async (ctx, options) => ctx.consoleMessages({
173
- level: options.level ? String(options.level) : undefined,
174
- preserve: Boolean(options.preserve),
175
- }));
176
- });
177
- browser
178
- .command('errors')
179
- .description('List console errors')
180
- .option('--clear', 'Compatibility flag', false)
181
- .action(async function () {
182
- await runBrowserCommand(this, 'errors', true, async (ctx) => ctx.consoleMessages({ level: 'error' }));
183
- });
170
+ // NOTE: pdf, console, errors commands removed — no matching browser tools.
184
171
  browser
185
172
  .command('requests')
186
173
  .description('List network requests')
@@ -205,12 +192,7 @@ function registerBrowserSubcommands(browser) {
205
192
  : undefined,
206
193
  }));
207
194
  });
208
- browser
209
- .command('resize <width> <height>')
210
- .description('Resize the browser viewport')
211
- .action(async function (width, height) {
212
- await runBrowserCommand(this, 'resize', true, async (ctx) => ctx.resize(parsePositiveInteger(width, 'width'), parsePositiveInteger(height, 'height')));
213
- });
195
+ // NOTE: resize command removed — no resize_page tool in the extension.
214
196
  browser
215
197
  .command('click <ref>')
216
198
  .description('Click an element by ref/index')
@@ -237,12 +219,7 @@ function registerBrowserSubcommands(browser) {
237
219
  .action(async function (ref) {
238
220
  await runBrowserCommand(this, 'hover', true, async (ctx) => ctx.hover(ref));
239
221
  });
240
- browser
241
- .command('scrollintoview <ref>')
242
- .description('Scroll an element into view when supported')
243
- .action(async function (ref) {
244
- await runBrowserCommand(this, 'scrollintoview', true, async (ctx) => ctx.scrollIntoView(ref));
245
- });
222
+ // NOTE: scrollintoview, download, waitfordownload, upload commands removed — no matching tools.
246
223
  browser
247
224
  .command('drag <source> <target>')
248
225
  .description('Drag one element to another')
@@ -255,25 +232,6 @@ function registerBrowserSubcommands(browser) {
255
232
  .action(async function (ref, values) {
256
233
  await runBrowserCommand(this, 'select', true, async (ctx) => ctx.select(ref, values));
257
234
  });
258
- browser
259
- .command('download <ref> [filename]')
260
- .description('Trigger/download an element when supported')
261
- .action(async function (ref, filename) {
262
- await runBrowserCommand(this, 'download', true, async (ctx) => ctx.download(ref, filename));
263
- });
264
- browser
265
- .command('waitfordownload [filename]')
266
- .description('Wait for a download when supported')
267
- .action(async function (filename) {
268
- await runBrowserCommand(this, 'waitfordownload', true, async (ctx) => ctx.waitForDownload(filename));
269
- });
270
- browser
271
- .command('upload <path>')
272
- .description('Upload a file when supported')
273
- .option('--ref <ref>', 'Element ref/index')
274
- .action(async function (path) {
275
- await runBrowserCommand(this, 'upload', true, async (ctx, options) => ctx.upload(path, options.ref ? String(options.ref) : undefined));
276
- });
277
235
  browser
278
236
  .command('fill')
279
237
  .description('Fill a form using JSON field descriptors')
@@ -281,19 +239,7 @@ function registerBrowserSubcommands(browser) {
281
239
  .action(async function () {
282
240
  await runBrowserCommand(this, 'fill', true, async (ctx, options) => ctx.fillForm(String(options.fields)));
283
241
  });
284
- browser
285
- .command('dialog')
286
- .description('Accept or dismiss a browser dialog')
287
- .option('--accept', 'Accept the dialog', false)
288
- .option('--dismiss', 'Dismiss the dialog', false)
289
- .option('--prompt <text>', 'Prompt text to enter')
290
- .action(async function () {
291
- await runBrowserCommand(this, 'dialog', true, async (ctx, options) => ctx.dialog({
292
- accept: Boolean(options.accept),
293
- dismiss: Boolean(options.dismiss),
294
- prompt: options.prompt ? String(options.prompt) : undefined,
295
- }));
296
- });
242
+ // NOTE: dialog command removed — no handle_dialog tool in the extension.
297
243
  browser
298
244
  .command('wait')
299
245
  .description('Wait for text or a short delay')
@@ -320,31 +266,8 @@ function registerBrowserSubcommands(browser) {
320
266
  argsJson: options.args ? String(options.args) : undefined,
321
267
  }));
322
268
  });
323
- browser
324
- .command('highlight <ref>')
325
- .description('Highlight an element when supported')
326
- .action(async function (ref) {
327
- await runBrowserCommand(this, 'highlight', true, async (ctx) => ctx.highlight(ref));
328
- });
329
- const trace = browser.command('trace').description('Start/stop browser tracing');
330
- trace
331
- .command('start')
332
- .description('Start a trace')
333
- .option('--reload', 'Reload the page during trace start when supported', false)
334
- .option('--output <path>', 'Trace output path')
335
- .action(async function () {
336
- await runBrowserCommand(this, 'trace start', true, async (ctx, options) => ctx.traceStart({
337
- reload: Boolean(options.reload),
338
- outputPath: options.output ? String(options.output) : undefined,
339
- }));
340
- });
341
- trace
342
- .command('stop')
343
- .description('Stop an active trace')
344
- .option('--output <path>', 'Trace output path')
345
- .action(async function () {
346
- await runBrowserCommand(this, 'trace stop', true, async (ctx, options) => ctx.traceStop(options.output ? String(options.output) : undefined));
347
- });
269
+ // NOTE: highlight command removed — no highlight tool; users can use 'hover' directly.
270
+ // NOTE: trace commands removed — no performance_start_trace/performance_stop_trace tools.
348
271
  }
349
272
  async function runBrowserCommand(command, commandName, requireExtension, handler) {
350
273
  const globalOptions = command.optsWithGlobals();
@@ -353,11 +276,13 @@ async function runBrowserCommand(command, commandName, requireExtension, handler
353
276
  port: parsePositiveInteger(globalOptions.port, '--port'),
354
277
  debug: Boolean(globalOptions.debug),
355
278
  remoteUuid: globalOptions.remote,
279
+ sessionId: globalOptions.session,
356
280
  relayUrl: globalOptions.relayUrl,
357
281
  profile: globalOptions.browserProfile || DEFAULT_BROWSER_PROFILE,
358
282
  json: Boolean(globalOptions.json),
359
283
  timeoutMs: parsePositiveInteger(globalOptions.timeout, '--timeout'),
360
284
  target: globalOptions.target,
285
+ pageId: globalOptions.pageId ? parsePositiveInteger(globalOptions.pageId, '--page-id') : undefined,
361
286
  });
362
287
  try {
363
288
  await ctx.connect();
@@ -381,17 +306,22 @@ class BrowserCliContext {
381
306
  json;
382
307
  timeoutMs;
383
308
  remoteUuid;
309
+ requestedSessionId;
384
310
  target;
311
+ pageId;
385
312
  toolsLoaded = false;
386
313
  tools = [];
314
+ sessions = [];
387
315
  ignoredCompatibilityOptions;
388
316
  constructor(init) {
389
- this.connection = new ExtensionConnection(init.port, init.debug, init.remoteUuid ? { uuid: init.remoteUuid, relayUrl: init.relayUrl } : undefined);
317
+ this.connection = new ExtensionConnection(init.port, init.debug, init.remoteUuid ? { uuid: init.remoteUuid, relayUrl: init.relayUrl } : undefined, init.remoteUuid ? undefined : { sessionId: init.sessionId });
390
318
  this.profile = init.profile;
391
319
  this.json = init.json;
392
320
  this.timeoutMs = init.timeoutMs;
393
321
  this.remoteUuid = init.remoteUuid;
322
+ this.requestedSessionId = init.sessionId;
394
323
  this.target = init.target;
324
+ this.pageId = init.pageId;
395
325
  this.ignoredCompatibilityOptions = [];
396
326
  if (this.target) {
397
327
  this.ignoredCompatibilityOptions.push(`target=${this.target}`);
@@ -400,6 +330,7 @@ class BrowserCliContext {
400
330
  async connect() {
401
331
  await this.connection.start();
402
332
  await delay(100);
333
+ this.sessions = await this.connection.listSessions(1_500).catch(() => this.connection.getSessions());
403
334
  await this.connection.waitForToolsUpdate(500);
404
335
  }
405
336
  async shutdown() {
@@ -409,20 +340,41 @@ class BrowserCliContext {
409
340
  if (this.connection.isExtensionConnected()) {
410
341
  return;
411
342
  }
343
+ this.sessions = await this.connection.listSessions(1_500).catch(() => this.connection.getSessions());
412
344
  await this.ensureToolsLoaded();
413
345
  if (!this.connection.isExtensionConnected()) {
414
- throw new Error('No browser extension is connected to the Vibe relay');
346
+ throw new Error(this.connection.getConnectionErrorMessage());
415
347
  }
416
348
  }
349
+ async listSessions() {
350
+ this.sessions = await this.connection.listSessions(this.timeoutMs);
351
+ return {
352
+ ok: true,
353
+ command: 'sessions',
354
+ profile: this.profile,
355
+ mode: this.mode(),
356
+ ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
357
+ sessionId: this.currentSessionId(),
358
+ requestedSessionId: this.requestedSessionId,
359
+ sessions: this.sessions,
360
+ };
361
+ }
417
362
  async status() {
363
+ this.sessions = await this.connection.listSessions(STATUS_TOOLS_TIMEOUT_MS).catch(() => this.connection.getSessions());
418
364
  if (this.connection.isExtensionConnected()) {
419
- await this.ensureToolsLoaded();
365
+ // Use a short timeout for status — this is a diagnostic command that
366
+ // should return quickly. Fall back to cached tools if the extension
367
+ // is slow to respond.
368
+ await this.ensureToolsLoaded(STATUS_TOOLS_TIMEOUT_MS);
420
369
  }
421
370
  return {
422
371
  ok: true,
423
372
  command: 'status',
424
373
  profile: this.profile,
425
374
  mode: this.remoteUuid ? 'remote' : 'local',
375
+ sessionId: this.currentSessionId(),
376
+ requestedSessionId: this.requestedSessionId,
377
+ sessions: this.sessions,
426
378
  ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
427
379
  relayConnected: this.connection.getStatus() === 'connected',
428
380
  extensionConnected: this.connection.isExtensionConnected(),
@@ -442,6 +394,8 @@ class BrowserCliContext {
442
394
  command: 'tabs',
443
395
  profile: this.profile,
444
396
  mode: this.mode(),
397
+ sessionId: this.currentSessionId(),
398
+ requestedSessionId: this.requestedSessionId,
445
399
  ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
446
400
  tool: invocation.tool,
447
401
  pages,
@@ -452,72 +406,74 @@ class BrowserCliContext {
452
406
  if (!url) {
453
407
  return this.callGenericCommand('open', [{ names: ['new_page', 'create_new_tab'] }], {});
454
408
  }
455
- const invocation = await this.callTool('open', [
456
- {
457
- names: ['new_page', 'create_new_tab'],
458
- buildArgs: (tool) => withUrlArgs(tool, url),
459
- },
460
- {
461
- names: ['navigate_page', 'navigate_to_url'],
462
- buildArgs: (tool) => withNavigateArgs(tool, url),
463
- },
464
- ], {});
465
- return this.outputFromInvocation('open', invocation);
409
+ try {
410
+ const invocation = await this.callTool('open', [
411
+ {
412
+ names: ['new_page', 'create_new_tab'],
413
+ buildArgs: (tool) => withOpenArgs(tool, url),
414
+ },
415
+ {
416
+ names: ['navigate_page', 'navigate_to_url'],
417
+ buildArgs: (tool) => withNavigateArgs(tool, url, this.timeoutMs),
418
+ },
419
+ ], {});
420
+ const output = this.outputFromInvocation('open', invocation);
421
+ return this.ensurePageContentInOutput('open', invocation, output, url);
422
+ }
423
+ catch (error) {
424
+ const recovered = await this.recoverPageContentAfterTimeout('open', url, error);
425
+ if (recovered) {
426
+ return recovered;
427
+ }
428
+ throw error;
429
+ }
466
430
  }
467
431
  async navigate(url) {
468
- const invocation = await this.callTool('navigate', [
469
- {
470
- names: ['navigate_page', 'navigate_to_url'],
471
- buildArgs: (tool) => withNavigateArgs(tool, url),
472
- },
432
+ try {
433
+ const invocation = await this.callTool('navigate', [
434
+ {
435
+ names: ['navigate_page', 'navigate_to_url'],
436
+ buildArgs: (tool) => withNavigateArgs(tool, url, this.timeoutMs),
437
+ },
438
+ {
439
+ names: ['new_page', 'create_new_tab'],
440
+ buildArgs: (tool) => withOpenArgs(tool, url),
441
+ },
442
+ ], {});
443
+ const output = this.outputFromInvocation('navigate', invocation);
444
+ return this.ensurePageContentInOutput('navigate', invocation, output, url);
445
+ }
446
+ catch (error) {
447
+ const recovered = await this.recoverPageContentAfterTimeout('navigate', url, error);
448
+ if (recovered) {
449
+ return recovered;
450
+ }
451
+ throw error;
452
+ }
453
+ }
454
+ async close(id) {
455
+ const invocation = await this.callTool('close', [
473
456
  {
474
- names: ['new_page', 'create_new_tab'],
475
- buildArgs: (tool) => withUrlArgs(tool, url),
457
+ names: ['close_page', 'close_tab'],
458
+ buildArgs: (tool) => withPageArgs(tool, id),
476
459
  },
477
460
  ], {});
478
- return this.outputFromInvocation('navigate', invocation);
461
+ return this.outputFromInvocation('close', invocation);
479
462
  }
480
463
  async focus(id) {
481
464
  const invocation = await this.callTool('focus', [
482
465
  {
483
- names: ['select_page', 'switch_to_tab', 'focus_tab'],
466
+ names: ['switch_to_page', 'switch_to_tab', 'select_page', 'focus_tab'],
484
467
  buildArgs: (tool) => withPageArgs(tool, id),
485
468
  },
486
469
  ], {});
487
470
  return this.outputFromInvocation('focus', invocation);
488
471
  }
489
- async close(id) {
490
- const invocation = await this.callTool('close', [
491
- {
492
- names: ['close_page', 'close_tab'],
493
- buildArgs: (tool) => withPageArgs(tool, id),
494
- },
495
- ], {});
496
- return this.outputFromInvocation('close', invocation);
497
- }
498
472
  async snapshot(options) {
499
473
  const wantsAria = options.format === 'aria' || options.interactive || Boolean(options.selector) || Boolean(options.frame);
500
- if (!wantsAria) {
501
- const result = await this.connection.getSnapshot();
502
- return {
503
- ok: true,
504
- command: 'snapshot',
505
- profile: this.profile,
506
- mode: this.mode(),
507
- ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
508
- format: options.format,
509
- url: result.url,
510
- title: result.title,
511
- snapshot: limitText(result.snapshot, options.limit),
512
- };
513
- }
514
474
  const invocation = await this.callTool('snapshot', [
515
475
  {
516
- names: ['take_a11y_snapshot'],
517
- buildArgs: (tool) => withSnapshotArgs(tool, options),
518
- },
519
- {
520
- names: ['take_snapshot', 'get_page_content'],
476
+ names: [wantsAria ? 'take_a11y_snapshot' : 'take_md_snapshot'],
521
477
  buildArgs: (tool) => withSnapshotArgs(tool, options),
522
478
  },
523
479
  ], {});
@@ -553,38 +509,6 @@ class BrowserCliContext {
553
509
  raw: normalizeToolResult(invocation.result),
554
510
  };
555
511
  }
556
- async pdf(outputPath) {
557
- const invocation = await this.callTool('pdf', [{ names: ['pdf', 'generate_pdf'] }], {});
558
- const savedPath = maybeWriteBinaryOutput(invocation.result, outputPath);
559
- return {
560
- ok: true,
561
- command: 'pdf',
562
- profile: this.profile,
563
- mode: this.mode(),
564
- ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
565
- tool: invocation.tool,
566
- outputPath: savedPath || outputPath,
567
- raw: normalizeToolResult(invocation.result),
568
- };
569
- }
570
- async consoleMessages(options) {
571
- const invocation = await this.callTool('console', [
572
- {
573
- names: ['list_console_messages'],
574
- buildArgs: (tool) => withConsoleArgs(tool, options),
575
- },
576
- ], {});
577
- return {
578
- ok: true,
579
- command: options.level === 'error' ? 'errors' : 'console',
580
- profile: this.profile,
581
- mode: this.mode(),
582
- ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
583
- tool: invocation.tool,
584
- messages: extractMessages(invocation.result),
585
- raw: normalizeToolResult(invocation.result),
586
- };
587
- }
588
512
  async requests(options) {
589
513
  const invocation = await this.callTool('requests', [
590
514
  {
@@ -638,15 +562,6 @@ class BrowserCliContext {
638
562
  raw: normalizeToolResult(invocation.result),
639
563
  };
640
564
  }
641
- async resize(width, height) {
642
- const invocation = await this.callTool('resize', [
643
- {
644
- names: ['resize_page'],
645
- buildArgs: (tool) => withResizeArgs(tool, width, height),
646
- },
647
- ], {});
648
- return this.outputFromInvocation('resize', invocation);
649
- }
650
565
  async click(ref, options) {
651
566
  const invocation = await this.callTool('click', [
652
567
  {
@@ -687,15 +602,6 @@ class BrowserCliContext {
687
602
  ], {});
688
603
  return this.outputFromInvocation('hover', invocation);
689
604
  }
690
- async scrollIntoView(ref) {
691
- const invocation = await this.callTool('scrollintoview', [
692
- {
693
- names: ['scroll_into_view', 'scrollintoview'],
694
- buildArgs: (tool) => withRefArgs(tool, ref),
695
- },
696
- ], {});
697
- return this.outputFromInvocation('scrollintoview', invocation);
698
- }
699
605
  async drag(source, target) {
700
606
  const invocation = await this.callTool('drag', [
701
607
  {
@@ -718,37 +624,6 @@ class BrowserCliContext {
718
624
  ], {});
719
625
  return this.outputFromInvocation('select', invocation);
720
626
  }
721
- async download(ref, filename) {
722
- const invocation = await this.callTool('download', [
723
- {
724
- names: ['download'],
725
- buildArgs: (tool) => withDownloadArgs(tool, ref, filename),
726
- },
727
- {
728
- names: ['click'],
729
- buildArgs: (tool) => withRefArgs(tool, ref),
730
- },
731
- ], {});
732
- return this.outputFromInvocation('download', invocation);
733
- }
734
- async waitForDownload(filename) {
735
- const invocation = await this.callTool('waitfordownload', [
736
- {
737
- names: ['wait_for_download', 'waitfordownload'],
738
- buildArgs: (tool) => withFilenameArgs(tool, filename),
739
- },
740
- ], {});
741
- return this.outputFromInvocation('waitfordownload', invocation);
742
- }
743
- async upload(path, ref) {
744
- const invocation = await this.callTool('upload', [
745
- {
746
- names: ['upload_file'],
747
- buildArgs: (tool) => withUploadArgs(tool, path, ref),
748
- },
749
- ], {});
750
- return this.outputFromInvocation('upload', invocation);
751
- }
752
627
  async fillForm(fieldsJson) {
753
628
  const fields = parseJsonValue(fieldsJson, '--fields');
754
629
  if (!Array.isArray(fields)) {
@@ -762,16 +637,6 @@ class BrowserCliContext {
762
637
  ], {});
763
638
  return this.outputFromInvocation('fill', invocation);
764
639
  }
765
- async dialog(options) {
766
- const action = options.accept ? 'accept' : options.dismiss ? 'dismiss' : 'accept';
767
- const invocation = await this.callTool('dialog', [
768
- {
769
- names: ['handle_dialog'],
770
- buildArgs: (tool) => withDialogArgs(tool, action, options.prompt),
771
- },
772
- ], {});
773
- return this.outputFromInvocation('dialog', invocation);
774
- }
775
640
  async wait(options) {
776
641
  if (options.text && options.text.length > 0) {
777
642
  const invocation = await this.callTool('wait', [
@@ -804,37 +669,6 @@ class BrowserCliContext {
804
669
  ], {});
805
670
  return this.outputFromInvocation('evaluate', invocation);
806
671
  }
807
- async highlight(ref) {
808
- const invocation = await this.callTool('highlight', [
809
- {
810
- names: ['highlight'],
811
- buildArgs: (tool) => withRefArgs(tool, ref),
812
- },
813
- {
814
- names: ['hover'],
815
- buildArgs: (tool) => withRefArgs(tool, ref, { duration: 1500 }),
816
- },
817
- ], {});
818
- return this.outputFromInvocation('highlight', invocation);
819
- }
820
- async traceStart(options) {
821
- const invocation = await this.callTool('trace start', [
822
- {
823
- names: ['performance_start_trace'],
824
- buildArgs: (tool) => withTraceStartArgs(tool, options),
825
- },
826
- ], {});
827
- return this.outputFromInvocation('trace start', invocation);
828
- }
829
- async traceStop(outputPath) {
830
- const invocation = await this.callTool('trace stop', [
831
- {
832
- names: ['performance_stop_trace'],
833
- buildArgs: (tool) => withTraceStopArgs(tool, outputPath),
834
- },
835
- ], {});
836
- return this.outputFromInvocation('trace stop', invocation);
837
- }
838
672
  async callGenericCommand(commandName, candidates, canonicalArgs) {
839
673
  const invocation = await this.callTool(commandName, candidates, canonicalArgs);
840
674
  return this.outputFromInvocation(commandName, invocation);
@@ -851,23 +685,44 @@ class BrowserCliContext {
851
685
  const args = candidate.buildArgs
852
686
  ? candidate.buildArgs(tool)
853
687
  : withCanonicalArgs(tool, canonicalArgs);
854
- const result = await this.connection.callTool(tool.name, args);
688
+ // Inject --page-id into every tool call that accepts pageId/tabId,
689
+ // so the agent doesn't need to use focus/tab select (which disrupts
690
+ // the user's active browser tab).
691
+ if (this.pageId !== undefined) {
692
+ const pageKey = hasProperty(tool, 'pageId', 'tabId');
693
+ if (pageKey && !(pageKey in args)) {
694
+ args[pageKey] = this.pageId;
695
+ }
696
+ }
697
+ if (shouldRequestPageStateForCommand(commandName)) {
698
+ const pageStateKey = hasProperty(tool, 'pageStateFormat', 'page_state_format');
699
+ if (pageStateKey && !('pageStateFormat' in args) && !('page_state_format' in args)) {
700
+ args[pageStateKey] = 'markdown';
701
+ }
702
+ }
703
+ const result = await this.connection.callTool(tool.name, args, this.timeoutMs);
855
704
  return { tool: tool.name, args, result: result };
856
705
  }
857
706
  }
858
707
  const requested = candidates.flatMap((candidate) => candidate.names);
859
708
  throw new Error(`No compatible browser tool found for "${commandName}". Tried ${requested.join(', ')}. Available tools: ${this.tools.map((tool) => tool.name).join(', ')}`);
860
709
  }
861
- async ensureToolsLoaded() {
710
+ async ensureToolsLoaded(timeoutMs) {
862
711
  if (this.toolsLoaded && this.tools.length > 0) {
863
712
  return;
864
713
  }
714
+ const effectiveTimeout = timeoutMs ?? this.timeoutMs;
865
715
  if (this.connection.isExtensionConnected()) {
866
716
  try {
867
- this.tools = await this.connection.refreshTools(this.timeoutMs);
717
+ this.tools = await this.connection.refreshTools(effectiveTimeout);
868
718
  }
869
719
  catch {
870
- this.tools = await this.connection.waitForToolsUpdate(1_000);
720
+ // Refresh timed out or failed — fall back to cached tools or a short
721
+ // passive wait so the status command is not blocked.
722
+ this.tools = this.connection.getTools();
723
+ if (this.tools.length === 0) {
724
+ this.tools = await this.connection.waitForToolsUpdate(1_000);
725
+ }
871
726
  }
872
727
  }
873
728
  else {
@@ -876,19 +731,308 @@ class BrowserCliContext {
876
731
  this.toolsLoaded = true;
877
732
  }
878
733
  outputFromInvocation(commandName, invocation) {
734
+ const pageContent = firstText(invocation.result);
879
735
  return {
880
736
  ok: !invocation.result.isError,
881
737
  command: commandName,
882
738
  profile: this.profile,
883
739
  mode: this.mode(),
740
+ sessionId: this.currentSessionId(),
741
+ requestedSessionId: this.requestedSessionId,
884
742
  ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
885
743
  tool: invocation.tool,
744
+ ...(looksLikePageContentText(pageContent) ? { pageContent } : {}),
886
745
  raw: normalizeToolResult(invocation.result),
887
746
  };
888
747
  }
748
+ async ensurePageContentInOutput(commandName, invocation, output, targetUrl) {
749
+ if (!output.ok) {
750
+ return output;
751
+ }
752
+ const currentText = firstText(invocation.result);
753
+ if (looksLikePageContentText(currentText) && !isLikelyIncompletePageContent(currentText, targetUrl)) {
754
+ return output;
755
+ }
756
+ const targetPageId = extractPageIdFromResult(invocation.result) ?? this.pageId;
757
+ if (targetPageId === undefined) {
758
+ return output;
759
+ }
760
+ const fallback = await this.takeMarkdownSnapshotForTarget(targetPageId, targetUrl);
761
+ if (!fallback.content) {
762
+ return output;
763
+ }
764
+ return {
765
+ ...output,
766
+ pageContent: fallback.content,
767
+ pageContentSource: 'take_md_snapshot',
768
+ ...(fallback.pageId !== targetPageId ? { pageId: fallback.pageId } : {}),
769
+ };
770
+ }
771
+ async recoverPageContentAfterTimeout(commandName, url, error) {
772
+ if (!isTimeoutError(error)) {
773
+ return undefined;
774
+ }
775
+ const recoveryTimeoutMs = Math.max(this.timeoutMs, 10_000);
776
+ let targetPageId = this.pageId;
777
+ if (targetPageId === undefined) {
778
+ for (let attempt = 0; attempt < 3; attempt += 1) {
779
+ const pages = await this.listPagesForRecovery(recoveryTimeoutMs);
780
+ const matched = findBestPageMatchByUrl(pages, url);
781
+ if (matched) {
782
+ targetPageId = matched.id;
783
+ break;
784
+ }
785
+ await delay(750);
786
+ }
787
+ }
788
+ if (targetPageId === undefined) {
789
+ return undefined;
790
+ }
791
+ const fallback = await this.takeMarkdownSnapshotForTarget(targetPageId, url, recoveryTimeoutMs);
792
+ if (!fallback.content) {
793
+ return undefined;
794
+ }
795
+ return {
796
+ ok: true,
797
+ command: commandName,
798
+ profile: this.profile,
799
+ mode: this.mode(),
800
+ sessionId: this.currentSessionId(),
801
+ requestedSessionId: this.requestedSessionId,
802
+ ignoredCompatibilityOptions: this.ignoredCompatibilityOptions,
803
+ tool: 'take_md_snapshot',
804
+ recoveredFromTimeout: true,
805
+ pageId: fallback.pageId,
806
+ url,
807
+ pageContent: fallback.content,
808
+ raw: {
809
+ recovery: 'timeout_fallback',
810
+ },
811
+ };
812
+ }
813
+ async takeMarkdownSnapshotForTarget(initialPageId, targetUrl, timeoutMs = this.timeoutMs) {
814
+ let latest;
815
+ let pageId = initialPageId;
816
+ const recoveryTimeoutMs = Math.max(timeoutMs, 10_000);
817
+ const attempts = 8;
818
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
819
+ latest = await this.takeMarkdownSnapshotForPage(pageId, recoveryTimeoutMs);
820
+ if (latest && !isLikelyIncompletePageContent(latest, targetUrl)) {
821
+ return { content: latest, pageId };
822
+ }
823
+ if (targetUrl) {
824
+ const pages = await this.listPagesForRecovery(recoveryTimeoutMs);
825
+ const matched = findBestPageMatchByUrl(pages, targetUrl);
826
+ if (matched) {
827
+ pageId = matched.id;
828
+ }
829
+ }
830
+ if (attempt < attempts - 1) {
831
+ await delay(750);
832
+ }
833
+ }
834
+ return { content: latest, pageId };
835
+ }
836
+ async listPagesForRecovery(timeoutMs) {
837
+ try {
838
+ await this.ensureToolsLoaded(timeoutMs);
839
+ const listTool = this.tools.find((tool) => {
840
+ const normalized = normalizeName(tool.name);
841
+ return normalized === 'list_pages' || normalized === 'get_tabs';
842
+ });
843
+ if (!listTool) {
844
+ return [];
845
+ }
846
+ const result = await this.connection.callTool(listTool.name, {}, timeoutMs);
847
+ return extractPages(result);
848
+ }
849
+ catch {
850
+ return [];
851
+ }
852
+ }
853
+ async takeMarkdownSnapshotForPage(pageId, timeoutMs = this.timeoutMs) {
854
+ await this.ensureToolsLoaded();
855
+ const snapshotTool = this.tools.find((tool) => normalizeName(tool.name) === 'take_md_snapshot');
856
+ if (!snapshotTool) {
857
+ return undefined;
858
+ }
859
+ const args = {};
860
+ const pageKey = hasProperty(snapshotTool, 'pageId', 'tabId');
861
+ if (pageKey) {
862
+ args[pageKey] = pageId;
863
+ }
864
+ const pageStateKey = hasProperty(snapshotTool, 'pageStateFormat', 'page_state_format');
865
+ if (pageStateKey) {
866
+ args[pageStateKey] = 'markdown';
867
+ }
868
+ try {
869
+ const result = await this.connection.callTool(snapshotTool.name, args, timeoutMs);
870
+ const text = firstText(result);
871
+ if (!text) {
872
+ return undefined;
873
+ }
874
+ const trimmed = text.trim();
875
+ if (/^Error:/i.test(trimmed) || /Failed to get valid tab/i.test(trimmed)) {
876
+ return undefined;
877
+ }
878
+ return text;
879
+ }
880
+ catch {
881
+ return undefined;
882
+ }
883
+ }
889
884
  mode() {
890
885
  return this.remoteUuid ? 'remote' : 'local';
891
886
  }
887
+ currentSessionId() {
888
+ if (this.remoteUuid) {
889
+ return this.remoteUuid;
890
+ }
891
+ const connectedSessionIds = new Set(this.sessions
892
+ .filter((session) => session.connected)
893
+ .map((session) => session.sessionId));
894
+ if (this.requestedSessionId && connectedSessionIds.has(this.requestedSessionId)) {
895
+ return this.requestedSessionId;
896
+ }
897
+ return this.sessions.find((session) => session.connected)?.sessionId;
898
+ }
899
+ }
900
+ function shouldRequestPageStateForCommand(commandName) {
901
+ return new Set([
902
+ 'open',
903
+ 'navigate',
904
+ 'click',
905
+ 'type',
906
+ 'press',
907
+ 'hover',
908
+ 'drag',
909
+ 'select',
910
+ 'fill',
911
+ ]).has(commandName);
912
+ }
913
+ function looksLikePageContentText(text) {
914
+ const trimmed = text.trim();
915
+ if (!trimmed) {
916
+ return false;
917
+ }
918
+ if (/Error retrieving page content/i.test(trimmed) || /page content extraction failed/i.test(trimmed)) {
919
+ return false;
920
+ }
921
+ return /```(?:markdown|text|html)/i.test(trimmed) || /Page State Format:/i.test(trimmed);
922
+ }
923
+ function isLikelyIncompletePageContent(text, targetUrl) {
924
+ const trimmed = text.trim();
925
+ if (!trimmed) {
926
+ return true;
927
+ }
928
+ const interactiveRefCount = (trimmed.match(/\[M\d+:/g) || []).length;
929
+ // Common in early snapshots right after new_page(waitForReady=false):
930
+ // tab exists but title/url are still empty.
931
+ if (/Tab ID:[^\n]*\nTitle:\s*\nURL:\s*(?:\n|$)/i.test(trimmed)) {
932
+ return true;
933
+ }
934
+ if (/#\s*Markdown Snapshot:\s*Untitled/i.test(trimmed) && interactiveRefCount < 5) {
935
+ return true;
936
+ }
937
+ if (targetUrl) {
938
+ const normalizedTarget = normalizeUrlForMatch(targetUrl);
939
+ const hasAnyUrl = /\bURL:\s*\S+/i.test(trimmed);
940
+ const textNormalized = normalizeUrlForMatch(trimmed);
941
+ if (!hasAnyUrl) {
942
+ return true;
943
+ }
944
+ if (normalizedTarget && !textNormalized.includes(normalizedTarget)) {
945
+ return true;
946
+ }
947
+ }
948
+ return false;
949
+ }
950
+ function extractPageIdFromResult(result) {
951
+ const direct = firstDefined(result, ['pageId', 'tabId', 'id']);
952
+ if (typeof direct === 'number' && Number.isFinite(direct)) {
953
+ return direct;
954
+ }
955
+ const parsed = parseResultText(result);
956
+ if (parsed && typeof parsed === 'object') {
957
+ const parsedId = firstDefined(parsed, ['pageId', 'tabId', 'id']);
958
+ if (typeof parsedId === 'number' && Number.isFinite(parsedId)) {
959
+ return parsedId;
960
+ }
961
+ }
962
+ const text = firstText(result);
963
+ const createdMatch = /new background page \(ID:\s*(\d+)\)/i.exec(text);
964
+ if (createdMatch) {
965
+ return Number.parseInt(createdMatch[1], 10);
966
+ }
967
+ const tabMatch = /\bTab ID:\s*(\d+)\b/i.exec(text);
968
+ if (tabMatch) {
969
+ return Number.parseInt(tabMatch[1], 10);
970
+ }
971
+ const pageMatch = /\bPage ID:\s*(\d+)\b/i.exec(text);
972
+ if (pageMatch) {
973
+ return Number.parseInt(pageMatch[1], 10);
974
+ }
975
+ return undefined;
976
+ }
977
+ function isTimeoutError(error) {
978
+ const message = error instanceof Error ? error.message : String(error);
979
+ return /timed out|timeout/i.test(message);
980
+ }
981
+ function findBestPageMatchByUrl(pages, targetUrl) {
982
+ const normalizedTarget = normalizeUrlForMatch(targetUrl);
983
+ let bestExact;
984
+ let bestPartial;
985
+ for (const page of pages) {
986
+ const id = toFinitePageId(page.id);
987
+ if (id === undefined || typeof page.url !== 'string') {
988
+ continue;
989
+ }
990
+ const normalizedPage = normalizeUrlForMatch(page.url);
991
+ if (normalizedPage === normalizedTarget) {
992
+ if (!bestExact || id > bestExact.id) {
993
+ bestExact = { id, url: page.url };
994
+ }
995
+ }
996
+ if (normalizedPage.includes(normalizedTarget) || normalizedTarget.includes(normalizedPage)) {
997
+ if (!bestPartial || id > bestPartial.id) {
998
+ bestPartial = { id, url: page.url };
999
+ }
1000
+ }
1001
+ }
1002
+ return bestExact ?? bestPartial;
1003
+ }
1004
+ function toFinitePageId(value) {
1005
+ if (typeof value === 'number' && Number.isFinite(value)) {
1006
+ return value;
1007
+ }
1008
+ if (typeof value === 'string') {
1009
+ const parsed = Number.parseInt(value, 10);
1010
+ if (Number.isFinite(parsed)) {
1011
+ return parsed;
1012
+ }
1013
+ }
1014
+ return undefined;
1015
+ }
1016
+ function normalizeUrlForMatch(value) {
1017
+ const trimmed = value.trim().replace(/\/$/, '');
1018
+ if (!trimmed) {
1019
+ return trimmed;
1020
+ }
1021
+ try {
1022
+ const parsed = new URL(trimmed);
1023
+ return safeDecode(`${parsed.origin}${parsed.pathname}${parsed.search}`.replace(/\/$/, ''));
1024
+ }
1025
+ catch {
1026
+ return safeDecode(trimmed);
1027
+ }
1028
+ }
1029
+ function safeDecode(value) {
1030
+ try {
1031
+ return decodeURIComponent(value);
1032
+ }
1033
+ catch {
1034
+ return value;
1035
+ }
892
1036
  }
893
1037
  function emitOutput(asJson, data, humanOutput) {
894
1038
  if (asJson) {
@@ -898,7 +1042,11 @@ function emitOutput(asJson, data, humanOutput) {
898
1042
  console.log(humanOutput);
899
1043
  }
900
1044
  function emitError(asJson, commandName, ctx, error) {
901
- const message = error instanceof Error ? error.message : String(error);
1045
+ let message = error instanceof Error ? error.message : String(error);
1046
+ // Improve guidance when the extension requires a pageId that wasn't provided
1047
+ if (/\bpageId\b/i.test(message) && /\bmissing\b|\brequired\b/i.test(message)) {
1048
+ message += '\nHint: use `tabs` to list pages, then pass --page-id <id> to target a specific tab.';
1049
+ }
902
1050
  if (asJson) {
903
1051
  console.log(JSON.stringify({
904
1052
  ok: false,
@@ -919,19 +1067,36 @@ function formatHumanOutput(commandName, output) {
919
1067
  return [
920
1068
  `Profile: ${String(output.profile)}`,
921
1069
  `Mode: ${String(output.mode)}`,
1070
+ output.sessionId ? `Session: ${String(output.sessionId)}` : null,
1071
+ output.requestedSessionId && output.requestedSessionId !== output.sessionId ? `Requested session: ${String(output.requestedSessionId)}` : null,
922
1072
  `Relay connected: ${boolText(output.relayConnected)}`,
923
1073
  `Extension connected: ${boolText(output.extensionConnected)}`,
924
1074
  `Managed lifecycle: ${boolText(output.managedLifecycle)}`,
925
1075
  output.toolCount !== undefined ? `Tools: ${String(output.toolCount)}` : null,
926
1076
  output.note ? String(output.note) : null,
927
1077
  ].filter(Boolean).join('\n');
1078
+ case 'sessions': {
1079
+ const sessions = Array.isArray(output.sessions) ? output.sessions : [];
1080
+ if (sessions.length === 0) {
1081
+ return 'No browser sessions connected';
1082
+ }
1083
+ return sessions.map((session, index) => {
1084
+ const selected = output.sessionId === session.sessionId ? ' [selected]' : '';
1085
+ const connected = session.connected ? 'connected' : 'disconnected';
1086
+ const tools = session.toolCount !== undefined ? ` tools=${session.toolCount}` : '';
1087
+ return `${index + 1}. ${session.sessionId}${selected} - ${connected}${tools}`;
1088
+ }).join('\n');
1089
+ }
928
1090
  case 'tabs':
929
1091
  case 'tab': {
930
1092
  const pages = Array.isArray(output.pages) ? output.pages : [];
931
1093
  if (pages.length === 0) {
932
1094
  return firstDefinedText(output.raw, 'No tabs reported by browser');
933
1095
  }
934
- return pages.map((page, index) => `${index + 1}. ${page.active ? '[active] ' : ''}${page.title || '(untitled)'}${page.url ? ` — ${page.url}` : ''}${page.id !== undefined ? ` [id=${page.id}]` : ''}`).join('\n');
1096
+ return [
1097
+ output.sessionId ? `Session: ${String(output.sessionId)}` : null,
1098
+ ...pages.map((page, index) => `${index + 1}. ${page.active ? '[active] ' : ''}${page.title || '(untitled)'}${page.url ? ` — ${page.url}` : ''}${page.id !== undefined ? ` [id=${page.id}]` : ''}`),
1099
+ ].filter(Boolean).join('\n');
935
1100
  }
936
1101
  case 'snapshot':
937
1102
  return [
@@ -946,14 +1111,6 @@ function formatHumanOutput(commandName, output) {
946
1111
  }
947
1112
  return requests.map((request, index) => `${index + 1}. ${request.method || 'GET'} ${request.url || '(unknown)'}${request.status !== undefined ? ` [${request.status}]` : ''}${request.requestId ? ` [id=${request.requestId}]` : ''}`).join('\n');
948
1113
  }
949
- case 'console':
950
- case 'errors': {
951
- const messages = Array.isArray(output.messages) ? output.messages : [];
952
- if (messages.length > 0) {
953
- return messages.map((message) => stringifyJson(message)).join('\n');
954
- }
955
- return firstDefinedText(output.raw, 'No console messages reported by browser');
956
- }
957
1114
  case 'responsebody':
958
1115
  return String(output.responseBody ?? firstDefinedText(output.raw, ''));
959
1116
  default:
@@ -970,8 +1127,19 @@ function firstDefinedText(value, fallback) {
970
1127
  if (typeof value === 'string' && value.length > 0) {
971
1128
  return value;
972
1129
  }
973
- if (value && typeof value === 'object' && 'text' in value && typeof value.text === 'string') {
974
- return value.text;
1130
+ if (value && typeof value === 'object') {
1131
+ // Direct { text: "..." } wrapper
1132
+ if ('text' in value && typeof value.text === 'string') {
1133
+ return value.text;
1134
+ }
1135
+ // MCP tool result: { content: [{ type: 'text', text: '...' }] }
1136
+ const rec = value;
1137
+ if (Array.isArray(rec.content)) {
1138
+ const textItem = rec.content.find((entry) => entry && typeof entry === 'object' && entry.type === 'text');
1139
+ if (textItem && typeof textItem.text === 'string' && textItem.text.length > 0) {
1140
+ return textItem.text;
1141
+ }
1142
+ }
975
1143
  }
976
1144
  return fallback;
977
1145
  }
@@ -1026,8 +1194,34 @@ function extractPages(result) {
1026
1194
  }
1027
1195
  }
1028
1196
  }
1197
+ // Fallback: parse the plain-text format produced by ListPagesTool
1198
+ // e.g. 'Page 123 [ACTIVE]: "Google" - https://www.google.com'
1199
+ const text = firstText(result);
1200
+ if (text) {
1201
+ const textPages = parsePlainTextPages(text);
1202
+ if (textPages.length > 0) {
1203
+ return textPages;
1204
+ }
1205
+ }
1029
1206
  return [];
1030
1207
  }
1208
+ // Matches ListPagesTool output: 'Page <id>[ [ACTIVE]]: "title" - url'
1209
+ const PAGE_LINE_RE = /^Page\s+(\d+)\s*(\[ACTIVE\])?\s*:\s*"(.*)"\s*-\s*(.+)$/i;
1210
+ function parsePlainTextPages(text) {
1211
+ const pages = [];
1212
+ for (const line of text.split('\n')) {
1213
+ const match = PAGE_LINE_RE.exec(line.trim());
1214
+ if (match) {
1215
+ pages.push({
1216
+ id: Number(match[1]),
1217
+ active: match[2] !== undefined,
1218
+ title: match[3],
1219
+ url: match[4].trim(),
1220
+ });
1221
+ }
1222
+ }
1223
+ return pages;
1224
+ }
1031
1225
  function normalizePage(value) {
1032
1226
  if (!value || typeof value !== 'object') {
1033
1227
  return {};
@@ -1071,26 +1265,6 @@ function normalizeRequest(value) {
1071
1265
  status: firstNumber(record, ['status']),
1072
1266
  };
1073
1267
  }
1074
- function extractMessages(result) {
1075
- const direct = extractStructured(result, ['messages', 'consoleMessages']);
1076
- if (Array.isArray(direct)) {
1077
- return direct.map((entry) => sanitizeUnknown(entry) ?? null);
1078
- }
1079
- const parsed = parseResultText(result);
1080
- if (Array.isArray(parsed)) {
1081
- return parsed.map((entry) => sanitizeUnknown(entry) ?? null);
1082
- }
1083
- if (parsed && typeof parsed === 'object') {
1084
- for (const key of ['messages', 'consoleMessages']) {
1085
- const candidate = parsed[key];
1086
- if (Array.isArray(candidate)) {
1087
- return candidate.map((entry) => sanitizeUnknown(entry) ?? null);
1088
- }
1089
- }
1090
- }
1091
- const text = firstText(result);
1092
- return text ? [text] : [];
1093
- }
1094
1268
  function extractResponseBody(result) {
1095
1269
  const direct = extractStructured(result, ['responseBody', 'body']);
1096
1270
  if (typeof direct === 'string') {
@@ -1218,12 +1392,24 @@ function withUrlArgs(tool, url) {
1218
1392
  }
1219
1393
  return args;
1220
1394
  }
1221
- function withNavigateArgs(tool, url) {
1395
+ function withOpenArgs(tool, url) {
1396
+ const args = withUrlArgs(tool, url);
1397
+ const waitForReadyKey = hasProperty(tool, 'waitForReady');
1398
+ if (waitForReadyKey) {
1399
+ args[waitForReadyKey] = false;
1400
+ }
1401
+ return stripUndefined(args);
1402
+ }
1403
+ function withNavigateArgs(tool, url, timeoutMs) {
1222
1404
  const args = withUrlArgs(tool, url);
1223
1405
  const pageIdKey = hasProperty(tool, 'pageId', 'tabId');
1224
1406
  if (pageIdKey && !(pageIdKey in args)) {
1225
1407
  args[pageIdKey] = undefined;
1226
1408
  }
1409
+ const timeoutKey = hasProperty(tool, 'timeoutMs', 'timeout');
1410
+ if (timeoutKey && typeof timeoutMs === 'number' && Number.isFinite(timeoutMs)) {
1411
+ args[timeoutKey] = timeoutMs;
1412
+ }
1227
1413
  return stripUndefined(args);
1228
1414
  }
1229
1415
  function withPageArgs(tool, id) {
@@ -1258,21 +1444,6 @@ function withScreenshotArgs(tool, options) {
1258
1444
  maybeAssign(args, tool, 'grayscale', options.grayscale || undefined);
1259
1445
  return args;
1260
1446
  }
1261
- function withConsoleArgs(tool, options) {
1262
- const args = {};
1263
- if (options.level) {
1264
- if (hasProperty(tool, 'types')) {
1265
- args.types = [options.level];
1266
- }
1267
- else {
1268
- maybeAssign(args, tool, 'level', options.level);
1269
- }
1270
- }
1271
- if (options.preserve) {
1272
- maybeAssign(args, tool, 'includePreservedMessages', true);
1273
- }
1274
- return args;
1275
- }
1276
1447
  function withRequestListArgs(tool, options) {
1277
1448
  const args = {};
1278
1449
  if (options.limit) {
@@ -1287,12 +1458,6 @@ function withRequestDetailsArgs(tool, requestId) {
1287
1458
  maybeAssign(args, tool, 'reqid', requestId);
1288
1459
  return args;
1289
1460
  }
1290
- function withResizeArgs(tool, width, height) {
1291
- const args = {};
1292
- maybeAssign(args, tool, 'width', width);
1293
- maybeAssign(args, tool, 'height', height);
1294
- return args;
1295
- }
1296
1461
  function withRefArgs(tool, ref, extra = {}) {
1297
1462
  const args = { ...extra };
1298
1463
  const parsedRef = parseRef(ref);
@@ -1358,38 +1523,12 @@ function withSelectArgs(tool, ref, values) {
1358
1523
  maybeAssign(args, tool, 'values', values);
1359
1524
  return args;
1360
1525
  }
1361
- function withDownloadArgs(tool, ref, filename) {
1362
- const args = withRefArgs(tool, ref);
1363
- maybeAssign(args, tool, 'filename', filename);
1364
- maybeAssign(args, tool, 'path', filename);
1365
- return args;
1366
- }
1367
- function withFilenameArgs(tool, filename) {
1368
- const args = {};
1369
- maybeAssign(args, tool, 'filename', filename);
1370
- maybeAssign(args, tool, 'path', filename);
1371
- return args;
1372
- }
1373
- function withUploadArgs(tool, path, ref) {
1374
- const args = {};
1375
- maybeAssign(args, tool, 'filePath', resolve(path));
1376
- if (ref) {
1377
- Object.assign(args, withRefArgs(tool, ref));
1378
- }
1379
- return args;
1380
- }
1381
1526
  function withFillFormArgs(tool, fields) {
1382
1527
  const args = {};
1383
1528
  maybeAssign(args, tool, 'fields', fields);
1384
1529
  maybeAssign(args, tool, 'elements', fields);
1385
1530
  return args;
1386
1531
  }
1387
- function withDialogArgs(tool, action, prompt) {
1388
- const args = {};
1389
- maybeAssign(args, tool, 'action', action);
1390
- maybeAssign(args, tool, 'promptText', prompt);
1391
- return args;
1392
- }
1393
1532
  function withWaitArgs(tool, text, timeoutMs) {
1394
1533
  const args = {};
1395
1534
  maybeAssign(args, tool, 'text', text);
@@ -1417,21 +1556,6 @@ function withEvaluateArgs(tool, fn, ref, argsJson) {
1417
1556
  }
1418
1557
  return args;
1419
1558
  }
1420
- function withTraceStartArgs(tool, options) {
1421
- const args = {};
1422
- maybeAssign(args, tool, 'reload', options.reload || undefined);
1423
- if (options.outputPath) {
1424
- maybeAssign(args, tool, 'filePath', resolve(options.outputPath));
1425
- }
1426
- return args;
1427
- }
1428
- function withTraceStopArgs(tool, outputPath) {
1429
- const args = {};
1430
- if (outputPath) {
1431
- maybeAssign(args, tool, 'filePath', resolve(outputPath));
1432
- }
1433
- return args;
1434
- }
1435
1559
  function maybeAssign(target, tool, property, value) {
1436
1560
  if (value === undefined) {
1437
1561
  return;