viyv-browser-mcp 0.5.5 → 0.5.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.
package/dist/index.js CHANGED
@@ -27,7 +27,15 @@ var TIMEOUTS = {
27
27
  /** CDP idle detach delay */
28
28
  CDP_IDLE_DETACH: 5e3,
29
29
  /** Tab lock TTL (deadlock prevention) */
30
- TAB_LOCK_TTL: 6e4
30
+ TAB_LOCK_TTL: 6e4,
31
+ /** Default scenario max_duration_ms (30 min) */
32
+ SCENARIO_MAX_DURATION: 18e5,
33
+ /** Buffer added to scenario max_duration for MCP timeout */
34
+ MCP_SCENARIO_BUFFER: 6e4,
35
+ /** MCP timeout for sm_job_run with wait_for_completion (4 hours) */
36
+ MCP_JOB_WAIT: 4 * 60 * 60 * 1e3,
37
+ /** Dialog pause timeout (2 min) — auto-fail if no resume */
38
+ DIALOG_PAUSE_TIMEOUT: 12e4
31
39
  };
32
40
  var LIMITS = {
33
41
  /** Native Messaging max message size (Chrome limit) */
@@ -93,12 +101,13 @@ function decompressPayload(data, isCompressed) {
93
101
  }
94
102
 
95
103
  // src/native-host/transport.ts
96
- var MAX_MESSAGE_SIZE = 1024 * 1024;
104
+ var MAX_OUTBOUND_SIZE = 1024 * 1024;
105
+ var MAX_INBOUND_SIZE = 100 * 1024 * 1024;
97
106
  function encodeMessage(message) {
98
107
  const json = JSON.stringify(message);
99
108
  const body = Buffer.from(json, "utf-8");
100
- if (body.length > MAX_MESSAGE_SIZE) {
101
- throw new Error(`Message too large: ${body.length} bytes (max ${MAX_MESSAGE_SIZE})`);
109
+ if (body.length > MAX_OUTBOUND_SIZE) {
110
+ throw new Error(`Message too large: ${body.length} bytes (max ${MAX_OUTBOUND_SIZE})`);
102
111
  }
103
112
  const header = Buffer.alloc(4);
104
113
  header.writeUInt32LE(body.length, 0);
@@ -114,8 +123,8 @@ function createMessageReader(stream, onMessage, onError, onClose) {
114
123
  if (buffer.length < 4) break;
115
124
  expectedLength = buffer.readUInt32LE(0);
116
125
  buffer = buffer.subarray(4);
117
- if (expectedLength > MAX_MESSAGE_SIZE) {
118
- onError?.(new Error(`Message too large: ${expectedLength} bytes`));
126
+ if (expectedLength > MAX_INBOUND_SIZE) {
127
+ onError?.(new Error(`Message too large: ${expectedLength} bytes (max ${MAX_INBOUND_SIZE})`));
119
128
  expectedLength = null;
120
129
  buffer = Buffer.alloc(0);
121
130
  break;
@@ -297,16 +306,27 @@ function startBridge(options) {
297
306
  const chrome = chromeConnections.get(chromeId);
298
307
  if (chrome) {
299
308
  chrome.send(message);
309
+ return;
310
+ }
311
+ const fallback = chromeConnections.values().next().value;
312
+ if (fallback) {
313
+ process.stderr.write(
314
+ `${LOG_PREFIX2} WARNING: '${chromeId}' not found, using ${fallback.chromeId}
315
+ `
316
+ );
317
+ fallback.send(message);
300
318
  } else {
301
319
  process.stderr.write(
302
- `${LOG_PREFIX2} WARNING: chromeId '${chromeId}' not found, falling back to primary
320
+ `${LOG_PREFIX2} WARNING: No Chrome connections available, dropping message
303
321
  `
304
322
  );
305
- chromeConnections.get(primaryChromeId)?.send(message);
306
323
  }
307
324
  }
308
325
  function getChromeForAgent(agentId) {
309
- return agentToChrome.get(agentId) ?? primaryChromeId;
326
+ const mapped = agentToChrome.get(agentId);
327
+ if (mapped && chromeConnections.has(mapped)) return mapped;
328
+ const first = chromeConnections.keys().next().value;
329
+ return first ?? primaryChromeId;
310
330
  }
311
331
  function connIdForAgent(agentId) {
312
332
  return agentToConn.get(agentId);
@@ -373,12 +393,17 @@ function startBridge(options) {
373
393
  agentToConn.set(agentId, connId);
374
394
  mcpConnections.get(connId)?.agentIds.add(agentId);
375
395
  const chromeProfile = message.chromeProfile;
376
- const resolvedChrome = chromeProfile && chromeConnections.has(chromeProfile) ? chromeProfile : primaryChromeId;
377
- if (chromeProfile && !chromeConnections.has(chromeProfile)) {
378
- process.stderr.write(
379
- `${LOG_PREFIX2} WARNING: chromeProfile '${chromeProfile}' not found, using primary
396
+ let resolvedChrome;
397
+ if (chromeProfile && chromeConnections.has(chromeProfile)) {
398
+ resolvedChrome = chromeProfile;
399
+ } else {
400
+ if (chromeProfile) {
401
+ process.stderr.write(
402
+ `${LOG_PREFIX2} WARNING: chromeProfile '${chromeProfile}' not found, using default
380
403
  `
381
- );
404
+ );
405
+ }
406
+ resolvedChrome = chromeConnections.keys().next().value ?? primaryChromeId;
382
407
  }
383
408
  agentToChrome.set(agentId, resolvedChrome);
384
409
  chromeConnections.get(resolvedChrome)?.agentIds.add(agentId);
@@ -455,6 +480,75 @@ function startBridge(options) {
455
480
  `);
456
481
  }
457
482
  }
483
+ function removeChromeConnection(chromeId) {
484
+ const chrome = chromeConnections.get(chromeId);
485
+ if (!chrome) return;
486
+ for (const agentId of [...chrome.agentIds]) {
487
+ const connId = connIdForAgent(agentId);
488
+ if (connId) {
489
+ sendToMcp(connId, {
490
+ id: randomUUID(),
491
+ type: "session_close",
492
+ agentId,
493
+ timestamp: Date.now()
494
+ });
495
+ cleanupAgent(agentId, connId);
496
+ } else {
497
+ agentToChrome.delete(agentId);
498
+ }
499
+ }
500
+ chromeConnections.delete(chromeId);
501
+ broadcastChromeProfiles();
502
+ }
503
+ let primaryStdinClosed = false;
504
+ const ORPHAN_TIMEOUT = 6e4;
505
+ let orphanTimer = null;
506
+ function gracefulExit(reason) {
507
+ process.stderr.write(`${LOG_PREFIX2} ${reason}, exiting
508
+ `);
509
+ if (orphanTimer) {
510
+ clearTimeout(orphanTimer);
511
+ orphanTimer = null;
512
+ }
513
+ const closingMsg = JSON.stringify({ type: "bridge_closing", timestamp: Date.now() });
514
+ for (const [, conn] of mcpConnections) {
515
+ try {
516
+ conn.socket.write(`${closingMsg}
517
+ `);
518
+ } catch {
519
+ }
520
+ }
521
+ for (const [, conn] of mcpConnections) {
522
+ conn.socket.destroy();
523
+ }
524
+ mcpConnections.clear();
525
+ agentToConn.clear();
526
+ agentToChrome.clear();
527
+ chromeConnections.clear();
528
+ requestOrigin.clear();
529
+ clearInterval(cleanupTimer2);
530
+ server?.close();
531
+ process.exit(0);
532
+ }
533
+ function maybeExitIfEmpty() {
534
+ if (!primaryStdinClosed) return;
535
+ if (chromeConnections.size > 0 && orphanTimer) {
536
+ clearTimeout(orphanTimer);
537
+ orphanTimer = null;
538
+ return;
539
+ }
540
+ if (chromeConnections.size === 0 && mcpConnections.size === 0) {
541
+ gracefulExit("No connections remaining");
542
+ }
543
+ if (chromeConnections.size === 0 && !orphanTimer) {
544
+ orphanTimer = setTimeout(() => {
545
+ orphanTimer = null;
546
+ if (chromeConnections.size === 0) {
547
+ gracefulExit(`No Chrome reconnected within ${ORPHAN_TIMEOUT}ms`);
548
+ }
549
+ }, ORPHAN_TIMEOUT);
550
+ }
551
+ }
458
552
  function setupRelayConnection(socket) {
459
553
  const chromeId = `chrome-${nextChromeIndex++}`;
460
554
  const chrome = {
@@ -483,6 +577,7 @@ function startBridge(options) {
483
577
  chrome.send({ type: "bridge_status", connected: true, timestamp: Date.now() });
484
578
  }
485
579
  broadcastChromeProfiles();
580
+ maybeExitIfEmpty();
486
581
  const processData = createTcpLineReader((message) => handleChromeMessage(message), onError);
487
582
  socket.on("data", processData);
488
583
  socket.on("error", (error) => {
@@ -494,22 +589,8 @@ function startBridge(options) {
494
589
  `${LOG_PREFIX2} Chrome relay disconnected: ${chromeId} (remaining chromes: ${chromeConnections.size - 1})
495
590
  `
496
591
  );
497
- for (const agentId of [...chrome.agentIds]) {
498
- const connId = connIdForAgent(agentId);
499
- if (connId) {
500
- sendToMcp(connId, {
501
- id: randomUUID(),
502
- type: "session_close",
503
- agentId,
504
- timestamp: Date.now()
505
- });
506
- cleanupAgent(agentId, connId);
507
- } else {
508
- agentToChrome.delete(agentId);
509
- }
510
- }
511
- chromeConnections.delete(chromeId);
512
- broadcastChromeProfiles();
592
+ removeChromeConnection(chromeId);
593
+ maybeExitIfEmpty();
513
594
  });
514
595
  }
515
596
  function handleTcpConnection(socket) {
@@ -616,6 +697,7 @@ function startBridge(options) {
616
697
  if (mcpConnections.size === 0) {
617
698
  notifyChromeConnected(false);
618
699
  }
700
+ maybeExitIfEmpty();
619
701
  });
620
702
  }
621
703
  server = createServer((socket) => handleTcpConnection(socket));
@@ -643,40 +725,14 @@ function startBridge(options) {
643
725
  onError
644
726
  );
645
727
  notifyChromeConnected(false);
646
- function shutdown() {
647
- const closingMsg = JSON.stringify({ type: "bridge_closing", timestamp: Date.now() });
648
- for (const [, conn] of mcpConnections) {
649
- try {
650
- conn.socket.write(`${closingMsg}
651
- `);
652
- } catch {
653
- }
654
- }
655
- for (const [, conn] of mcpConnections) {
656
- conn.socket.destroy();
657
- }
658
- mcpConnections.clear();
659
- agentToConn.clear();
660
- agentToChrome.clear();
661
- chromeConnections.clear();
662
- requestOrigin.clear();
663
- clearInterval(cleanupTimer2);
664
- server?.close();
665
- server = null;
666
- }
667
- process.on("SIGINT", () => {
668
- shutdown();
669
- process.exit(0);
670
- });
671
- process.on("SIGTERM", () => {
672
- shutdown();
673
- process.exit(0);
674
- });
728
+ process.on("SIGINT", () => gracefulExit("SIGINT received"));
729
+ process.on("SIGTERM", () => gracefulExit("SIGTERM received"));
675
730
  process.stdin.on("end", () => {
676
- process.stderr.write(`${LOG_PREFIX2} stdin closed, shutting down
731
+ process.stderr.write(`${LOG_PREFIX2} Primary Chrome stdin closed
677
732
  `);
678
- shutdown();
679
- process.exit(0);
733
+ primaryStdinClosed = true;
734
+ removeChromeConnection(primaryChromeId);
735
+ maybeExitIfEmpty();
680
736
  });
681
737
  });
682
738
  }
@@ -920,10 +976,16 @@ Options:
920
976
  - maxChars: Limit output size (default: 50000). Use 100000 for full page content.`;
921
977
 
922
978
  // src/tools/core/handle-dialog.ts
923
- var HANDLE_DIALOG_DESCRIPTION = `Handle JavaScript dialogs such as alert, confirm, and prompt.
924
- Allows accepting or dismissing the dialog, and optionally providing
925
- input text for prompt dialogs. Must be called while a dialog
926
- is actively displayed on the page.`;
979
+ var HANDLE_DIALOG_DESCRIPTION = `Handle JavaScript dialogs (alert, confirm, prompt).
980
+
981
+ Two modes:
982
+ 1. Handle current dialog: provide action ('accept'/'dismiss') and optional text
983
+ 2. Set next policy: provide nextPolicy to pre-configure how the next
984
+ auto-handled dialog will be processed. The policy is one-shot \u2014
985
+ consumed after a single dialog, then reverts to default behavior.
986
+
987
+ Use mode 2 after seeing _auto_handled_dialogs in a tool result to change
988
+ how the next dialog will be handled when you re-trigger the action.`;
927
989
 
928
990
  // src/tools/core/hover.ts
929
991
  var HOVER_DESCRIPTION = `Move mouse to coordinates or element without clicking.
@@ -1040,38 +1102,6 @@ Returns details of HTTP requests and responses including URL, method,
1040
1102
  status code, headers, and timing information. Supports filtering
1041
1103
  by URL pattern or resource type.`;
1042
1104
 
1043
- // src/tools/sheets/descriptions.ts
1044
- var SHEETS_READ_DESCRIPTION = `Read data from the active Google Sheets spreadsheet.
1045
-
1046
- Uses the gviz/tq endpoint with session cookies \u2014 works with private sheets without OAuth.
1047
- Supports range selection, tq query language for filtering/aggregation, and row limits.
1048
-
1049
- tq query examples:
1050
- - "SELECT A, B WHERE C > 100" \u2014 filter rows
1051
- - "SELECT A, SUM(B) GROUP BY A" \u2014 aggregate
1052
- - "SELECT * ORDER BY C DESC LIMIT 5" \u2014 sort and limit
1053
-
1054
- Set formulas=true to read raw formulas (e.g., =IMAGE(), =HYPERLINK()) instead of computed values.
1055
- Formula mode reads cell-by-cell via the formula bar, limited to 200 cells.
1056
- The tab must be on a Google Sheets page (docs.google.com/spreadsheets/).`;
1057
- var SHEETS_WRITE_DESCRIPTION = `Write data to the active Google Sheets spreadsheet.
1058
-
1059
- For single cell: navigates to the cell via Name Box and types the value.
1060
- For multiple cells: converts 2D array to TSV, copies to clipboard via Offscreen Document, then pastes with Ctrl+V. Sheets auto-expands TSV into multiple cells.
1061
-
1062
- Provide either "value" (single cell string) or "values" (2D array), not both.
1063
- The tab must be on a Google Sheets page.`;
1064
- var SHEETS_INFO_DESCRIPTION = `Get metadata about the active Google Sheets spreadsheet.
1065
-
1066
- Returns spreadsheet ID, document title, sheet list (name, index, row/col counts, active status), active cell reference, and URL.
1067
- Uses DOM inspection and gviz/tq queries for sheet dimensions.
1068
- The tab must be on a Google Sheets page.`;
1069
- var SHEETS_NAVIGATE_DESCRIPTION = `Navigate within the active Google Sheets spreadsheet.
1070
-
1071
- Jump to a specific cell (e.g., "A1", "Z100") via the Name Box, or switch to a different sheet tab by name.
1072
- Both cell and sheet can be specified together.
1073
- The tab must be on a Google Sheets page.`;
1074
-
1075
1105
  // src/tools/semantic/sm-add-action.ts
1076
1106
  var SM_ADD_ACTION_DESCRIPTION = `Define a semantic action (step sequence) for a registered page.
1077
1107
 
@@ -1152,6 +1182,10 @@ var SM_CAPABILITIES_DESCRIPTION = `Query semantic capabilities available for the
1152
1182
  Returns the matched page definition, available actions, and fetches for the active tab.
1153
1183
  Uses page signature matching (URL pattern + DOM markers) to identify the current page state.
1154
1184
 
1185
+ Parameters:
1186
+ - tabId: target tab
1187
+ - include_other_pages (optional): include list of other registered pages on the same domain (default: false). Enable when looking for related pages to navigate to.
1188
+
1155
1189
  Returns:
1156
1190
  - ok: whether a page match was found
1157
1191
  - domain: hostname of the current tab
@@ -1159,7 +1193,7 @@ Returns:
1159
1193
  - match_score: confidence score of the match
1160
1194
  - actions[]: available actions with their parameters
1161
1195
  - fetches[]: available data extractions with their parameters
1162
- - other_pages[]: other registered pages on the same domain (for cross-page navigation)
1196
+ - other_pages[]: (only when include_other_pages=true) other registered pages on the same domain
1163
1197
  - hint: guidance message when no page matches
1164
1198
 
1165
1199
  Use this before sm_invoke or sm_fetch to discover what operations are available.
@@ -1246,15 +1280,52 @@ Returns:
1246
1280
  - cascaded: (page deletion) lists of deleted targets, actions, fetches
1247
1281
  - warnings: (target deletion) referencing actions/fetches that may break`;
1248
1282
 
1283
+ // src/tools/semantic/sm-execution-resume.ts
1284
+ var SM_EXECUTION_RESUME_DESCRIPTION = `Resume a paused scenario/job execution.
1285
+
1286
+ When execution is paused (e.g. a browser dialog appeared), use this tool to decide how to proceed.
1287
+ Check sm_execution_status or sm_job_report_get (status 'dialog_paused') to detect pauses.
1288
+
1289
+ Actions:
1290
+ - continue: Dialog already handled, continue to next step
1291
+ - retry: Set dialog policy and re-execute the step that triggered the dialog
1292
+ - skip_iteration: Skip to next loop iteration (only inside a loop)
1293
+ - skip_scenario: Skip entire scenario (marks as cancelled)
1294
+ - fail: Fail the current step (scenario continues or stops based on on_error)
1295
+
1296
+ Returns execution result or another dialog_paused if a new dialog appears.`;
1297
+
1298
+ // src/tools/semantic/sm-execution-status.ts
1299
+ var SM_EXECUTION_STATUS_DESCRIPTION = `Check what is currently executing on a tab.
1300
+
1301
+ Returns the execution type (job/scenario/none), IDs, progress, and pause state.
1302
+ Use this to see if a tab is busy before starting new work, or to get IDs for polling.
1303
+
1304
+ Parameters:
1305
+ - tabId: tab to check
1306
+
1307
+ Returns:
1308
+ - type: 'job' | 'scenario' | 'none'
1309
+ - label: execution label
1310
+ - started_at: execution start timestamp
1311
+ - job_id, job_report_id: (job only) IDs for polling with sm_job_report_get
1312
+ - scenario_id, report_id: (standalone scenario only) IDs for polling with sm_report_get
1313
+ - current_scenario: (job only) currently running scenario {scenario_id, scenario_label, report_id}
1314
+ - progress: (job only) {current_entry_index, total_entries, entries_completed}
1315
+ - is_paused: whether execution is paused due to dialog
1316
+ - pause_info: dialog details if paused`;
1317
+
1249
1318
  // src/tools/semantic/sm-export.ts
1250
1319
  var SM_EXPORT_DESCRIPTION = `Export semantic configuration as JSON.
1251
1320
 
1252
- Exports pages, targets, actions, and fetches. Optionally filter by domain.
1321
+ Exports pages, targets, actions, and fetches scoped by the current tab's page.
1253
1322
  Secret parameter default values are stripped from the export.
1254
1323
  Includes a SHA-256 checksum for integrity verification on import.
1255
1324
 
1256
1325
  Parameters:
1257
- - domain (optional): export only pages matching this domain (subdomain-aware). If omitted, exports all.
1326
+ - tabId (required): Tab ID to identify the current page
1327
+ - scope (optional): "page" (default) exports only the current page's config, "domain" exports all pages for the tab's domain, "all" exports everything
1328
+ - file_path (optional): save to this path and return metadata only (keeps data out of context)
1258
1329
 
1259
1330
  Returns:
1260
1331
  - SemanticExport JSON with schema_version, checksum, pages[], targets[], actions[], fetches[]`;
@@ -1379,7 +1450,10 @@ Parameters:
1379
1450
 
1380
1451
  Returns:
1381
1452
  - job_id, job_label
1382
- - snapshots[]: list of {snapshot_id, status, started_at, completed_at, duration_ms, scenario_count}`;
1453
+ - snapshots[]: list of {snapshot_id, job_report_id, status, started_at, completed_at, duration_ms, scenario_count, scenarios[]}
1454
+ - job_report_id: use with sm_job_report_get for detailed results
1455
+ - scenarios[]: per-scenario {scenario_id, scenario_label, report_id, status}
1456
+ - report_id: use with sm_report_get or sm_report_export for individual scenario data`;
1383
1457
  var SM_JOB_COMPARE_DESCRIPTION = `Compare two job executions to see data changes between them.
1384
1458
 
1385
1459
  Matches scenarios by scenario_id, data groups by result_key, and rows by loop_params. Computes per-cell diffs with numeric deltas.
@@ -1399,9 +1473,10 @@ Use this to inspect the full captured data for a single execution, including all
1399
1473
 
1400
1474
  Parameters:
1401
1475
  - snapshot_id: the snapshot to retrieve (SNP_xxxx)
1476
+ - summary_only: if true, omit data_groups from scenario snapshots to reduce payload size (default: false)
1402
1477
 
1403
1478
  Returns:
1404
- - snapshot: full JobSnapshot with scenario_snapshots and data_groups`;
1479
+ - snapshot: full JobSnapshot with scenario_snapshots and data_groups (or empty data_groups if summary_only)`;
1405
1480
 
1406
1481
  // src/tools/semantic/sm-job-list.ts
1407
1482
  var SM_JOB_LIST_DESCRIPTION = `List all defined jobs with entry counts and configuration.
@@ -1430,6 +1505,11 @@ var SM_JOB_REPORT_GET_DESCRIPTION = `Get job execution status and results.
1430
1505
  Shows overall progress and per-scenario report summaries.
1431
1506
  Use sm_report_get with individual report_ids for detailed scenario data.
1432
1507
 
1508
+ Poll this after sm_job_run to track progress:
1509
+ - status 'running': job is still executing
1510
+ - status 'dialog_paused': a browser dialog appeared, use sm_execution_resume to continue
1511
+ - status 'completed'/'failed': job finished
1512
+
1433
1513
  Parameters:
1434
1514
  - job_report_id: job report ID (JRP_xxxx)
1435
1515
 
@@ -1440,8 +1520,7 @@ Returns:
1440
1520
  // src/tools/semantic/sm-job-run.ts
1441
1521
  var SM_JOB_RUN_DESCRIPTION = `Execute a job on a browser tab. Scenarios run sequentially.
1442
1522
 
1443
- Returns job_report_id immediately by default. Poll with sm_job_report_get to check progress.
1444
- Set wait_for_completion=true to block until all scenarios finish.
1523
+ Always returns immediately with job_report_id. Poll sm_job_report_get to check progress.
1445
1524
 
1446
1525
  Only one job or scenario can run per tab at a time.
1447
1526
 
@@ -1449,12 +1528,16 @@ Parameters:
1449
1528
  - tabId: target tab for execution
1450
1529
  - job_id: the job to execute
1451
1530
  - params: optional per-scenario param overrides keyed by scenario_id
1452
- - wait_for_completion: if true, wait for all scenarios to complete (default: false)
1453
1531
 
1454
1532
  Returns:
1455
- - job_report_id: ID of the created job report (JRP_xxxx)
1533
+ - job_report_id: ID of the created job report (JRP_xxxx) \u2014 use with sm_job_report_get to poll
1456
1534
  - job_id: the executed job
1457
- - status: 'running' (async) or 'completed'/'failed' (when wait_for_completion=true)`;
1535
+ - status: 'running' | 'failed' (failed = could not start)
1536
+
1537
+ Workflow:
1538
+ 1. sm_job_run \u2192 get job_report_id
1539
+ 2. sm_job_report_get(job_report_id) \u2192 poll until status is 'completed'/'failed'
1540
+ 3. If status is 'dialog_paused', use sm_execution_resume to continue`;
1458
1541
 
1459
1542
  // src/tools/semantic/sm-job-update.ts
1460
1543
  var SM_JOB_UPDATE_DESCRIPTION = `Update an existing job definition.
@@ -1628,12 +1711,17 @@ Parameters:
1628
1711
  - scenario_id: the scenario to execute
1629
1712
  - params: parameter values for template substitution
1630
1713
  - wait_for_completion: if true, wait for scenario to finish (up to max_duration_ms)
1714
+ - max_duration_ms (optional): override scenario timeout in milliseconds (default: scenario's own value, typically 1800000ms / 30min)
1631
1715
 
1632
1716
  Returns:
1633
1717
  - report_id: ID of the created report (RPT_xxxx)
1634
1718
  - scenario_id: the executed scenario
1635
1719
  - status: 'running' (async) or 'completed'/'failed' (when wait_for_completion=true)
1636
- - error: error message if startup failed`;
1720
+ - error: error message if startup failed
1721
+
1722
+ When wait_for_completion=true, may return status 'dialog_paused' if a browser dialog
1723
+ appeared during execution. The dialog was auto-handled to unblock the page.
1724
+ Use sm_execution_resume to decide how to proceed.`;
1637
1725
 
1638
1726
  // src/tools/semantic/sm-scenario-update.ts
1639
1727
  var SM_SCENARIO_UPDATE_DESCRIPTION = `Update an existing scenario.
@@ -1664,14 +1752,16 @@ Parameters:
1664
1752
  - page_id (optional): filter results to a specific page
1665
1753
  - domain (optional): filter results to pages matching this domain
1666
1754
  - test_resolve (optional): if true, resolve all targets on the current tab
1755
+ - summary_only (optional): return only summary counts and page list, omitting targets/actions/fetches/integrity arrays (default: true)
1667
1756
 
1668
1757
  Returns:
1669
1758
  - summary: {total_pages, total_targets, total_actions, total_fetches}
1670
1759
  - pages[]: page definitions with page_type, domains, url_patterns, and counts
1671
- - targets[]: targets with locator info and optional resolve_result
1672
- - actions[]: actions with step count and param names
1673
- - fetches[]: fetches with field count
1674
- - integrity[]: referential integrity warnings`;
1760
+ - When summary_only is false, also includes:
1761
+ - targets[]: targets with locator info and optional resolve_result
1762
+ - actions[]: actions with step count and param names
1763
+ - fetches[]: fetches with field count
1764
+ - integrity[]: referential integrity warnings`;
1675
1765
 
1676
1766
  // src/tools/semantic/sm-test-target.ts
1677
1767
  var SM_TEST_TARGET_DESCRIPTION = `Test a specific target's locators against the current page.
@@ -1689,6 +1779,38 @@ Returns:
1689
1779
  - element: resolved element info {tag, text, rect} if found
1690
1780
  - test_status: updated test status saved to storage`;
1691
1781
 
1782
+ // src/tools/sheets/descriptions.ts
1783
+ var SHEETS_READ_DESCRIPTION = `Read data from the active Google Sheets spreadsheet.
1784
+
1785
+ Uses the gviz/tq endpoint with session cookies \u2014 works with private sheets without OAuth.
1786
+ Supports range selection, tq query language for filtering/aggregation, and row limits.
1787
+
1788
+ tq query examples:
1789
+ - "SELECT A, B WHERE C > 100" \u2014 filter rows
1790
+ - "SELECT A, SUM(B) GROUP BY A" \u2014 aggregate
1791
+ - "SELECT * ORDER BY C DESC LIMIT 5" \u2014 sort and limit
1792
+
1793
+ Set formulas=true to read raw formulas (e.g., =IMAGE(), =HYPERLINK()) instead of computed values.
1794
+ Formula mode reads cell-by-cell via the formula bar, limited to 200 cells.
1795
+ The tab must be on a Google Sheets page (docs.google.com/spreadsheets/).`;
1796
+ var SHEETS_WRITE_DESCRIPTION = `Write data to the active Google Sheets spreadsheet.
1797
+
1798
+ For single cell: navigates to the cell via Name Box and types the value.
1799
+ For multiple cells: converts 2D array to TSV, copies to clipboard via Offscreen Document, then pastes with Ctrl+V. Sheets auto-expands TSV into multiple cells.
1800
+
1801
+ Provide either "value" (single cell string) or "values" (2D array), not both.
1802
+ The tab must be on a Google Sheets page.`;
1803
+ var SHEETS_INFO_DESCRIPTION = `Get metadata about the active Google Sheets spreadsheet.
1804
+
1805
+ Returns spreadsheet ID, document title, sheet list (name, index, row/col counts, active status), active cell reference, and URL.
1806
+ Uses DOM inspection and gviz/tq queries for sheet dimensions.
1807
+ The tab must be on a Google Sheets page.`;
1808
+ var SHEETS_NAVIGATE_DESCRIPTION = `Navigate within the active Google Sheets spreadsheet.
1809
+
1810
+ Jump to a specific cell (e.g., "A1", "Z100") via the Name Box, or switch to a different sheet tab by name.
1811
+ Both cell and sheet can be specified together.
1812
+ The tab must be on a Google Sheets page.`;
1813
+
1692
1814
  // src/tools/tabs/select-tab.ts
1693
1815
  var SELECT_TAB_DESCRIPTION = `Switch focus to a specific tab by its identifier.
1694
1816
  Makes the target tab the active tab in the agent's group,
@@ -1931,8 +2053,12 @@ var handleDialogTool = {
1931
2053
  description: HANDLE_DIALOG_DESCRIPTION,
1932
2054
  inputSchema: z.object({
1933
2055
  tabId: z.coerce.number().describe("Tab ID"),
1934
- action: z.enum(["accept", "dismiss"]).describe("Dialog action"),
1935
- text: z.string().optional().describe("Text for prompt dialog")
2056
+ action: z.enum(["accept", "dismiss"]).optional().describe("Dialog action (required when handling current dialog)"),
2057
+ text: z.string().optional().describe("Text for prompt dialog"),
2058
+ nextPolicy: z.object({
2059
+ action: z.enum(["accept", "dismiss"]).describe("Action for next dialog"),
2060
+ text: z.string().optional().describe("Text for prompt dialog")
2061
+ }).optional().describe("Set one-shot policy for the next auto-handled dialog. Consumed after one use.")
1936
2062
  })
1937
2063
  };
1938
2064
  var tabsContextTool = {
@@ -2117,7 +2243,8 @@ var smCapabilitiesTool = {
2117
2243
  name: "sm_capabilities",
2118
2244
  description: SM_CAPABILITIES_DESCRIPTION,
2119
2245
  inputSchema: z.object({
2120
- tabId: z.coerce.number().describe("Tab ID")
2246
+ tabId: z.coerce.number().describe("Tab ID"),
2247
+ include_other_pages: z.boolean().optional().describe("Include list of other registered pages on the same domain (default: false)")
2121
2248
  })
2122
2249
  };
2123
2250
  var smInvokeTool = {
@@ -2210,7 +2337,17 @@ var smAddActionTool = {
2210
2337
  ).optional().describe("Parameter definitions"),
2211
2338
  steps: z.array(
2212
2339
  z.object({
2213
- type: z.enum(["click", "type", "select", "wait", "scroll", "key", "navigate", "file_upload", "script"]).describe("Step type"),
2340
+ type: z.enum([
2341
+ "click",
2342
+ "type",
2343
+ "select",
2344
+ "wait",
2345
+ "scroll",
2346
+ "key",
2347
+ "navigate",
2348
+ "file_upload",
2349
+ "script"
2350
+ ]).describe("Step type"),
2214
2351
  target_id: z.string().optional().describe("Target to act on"),
2215
2352
  params: z.object({
2216
2353
  text: z.string().optional(),
@@ -2278,7 +2415,8 @@ var smStatusTool = {
2278
2415
  tabId: z.coerce.number().optional().describe("Tab ID (required for test_resolve)"),
2279
2416
  page_id: z.string().optional().describe("Filter by page ID"),
2280
2417
  domain: z.string().optional().describe("Filter results to pages matching this domain"),
2281
- test_resolve: z.boolean().optional().describe("Test-resolve all targets")
2418
+ test_resolve: z.boolean().optional().describe("Test-resolve all targets"),
2419
+ summary_only: z.boolean().optional().describe("Return only summary counts and page list, omitting detail arrays (default: true)")
2282
2420
  })
2283
2421
  };
2284
2422
  var smTestTargetTool = {
@@ -2302,7 +2440,8 @@ var smExportTool = {
2302
2440
  name: "sm_export",
2303
2441
  description: SM_EXPORT_DESCRIPTION,
2304
2442
  inputSchema: z.object({
2305
- domain: z.string().optional().describe("Export only pages matching this domain (subdomain-aware). Omit for all."),
2443
+ tabId: z.coerce.number().describe("Tab ID to identify the current page"),
2444
+ scope: z.enum(["page", "domain", "all"]).optional().describe("Export scope (default: page)"),
2306
2445
  file_path: z.string().optional().describe("Save to this path and return metadata only (keeps data out of context)")
2307
2446
  })
2308
2447
  };
@@ -2434,7 +2573,8 @@ var smScenarioRunTool = {
2434
2573
  tabId: z.coerce.number().describe("Tab ID for execution"),
2435
2574
  scenario_id: z.string().describe("Scenario to execute"),
2436
2575
  params: z.record(z.unknown()).optional().describe("Parameter values"),
2437
- wait_for_completion: z.boolean().optional().describe("Wait for completion (default: false, async)")
2576
+ wait_for_completion: z.boolean().optional().describe("Wait for completion (default: false, async)"),
2577
+ max_duration_ms: z.coerce.number().optional().describe("Override scenario timeout in ms (default: scenario value, typically 1800000)")
2438
2578
  })
2439
2579
  };
2440
2580
  var smReportListTool = {
@@ -2512,8 +2652,7 @@ var smJobRunTool = {
2512
2652
  inputSchema: z.object({
2513
2653
  tabId: z.coerce.number().describe("Tab ID to execute scenarios on"),
2514
2654
  job_id: z.string().describe("Job ID to execute"),
2515
- params: z.record(z.record(z.unknown())).optional().describe("Per-scenario param overrides keyed by scenario_id"),
2516
- wait_for_completion: z.boolean().optional().describe("Wait for all scenarios to complete (default: false)")
2655
+ params: z.record(z.record(z.unknown())).optional().describe("Per-scenario param overrides keyed by scenario_id")
2517
2656
  })
2518
2657
  };
2519
2658
  var smJobCancelTool = {
@@ -2523,6 +2662,17 @@ var smJobCancelTool = {
2523
2662
  tabId: z.coerce.number().describe("Tab ID where the job is running")
2524
2663
  })
2525
2664
  };
2665
+ var smExecutionResumeTool = {
2666
+ name: "sm_execution_resume",
2667
+ description: SM_EXECUTION_RESUME_DESCRIPTION,
2668
+ inputSchema: z.object({
2669
+ tabId: z.coerce.number().describe("Tab ID where execution is paused"),
2670
+ action: z.enum(["continue", "retry", "skip_iteration", "skip_scenario", "fail"]).describe("How to proceed after the dialog"),
2671
+ accept: z.boolean().optional().describe("For retry: accept (true) or dismiss (false) the dialog"),
2672
+ prompt_text: z.string().optional().describe("For retry on prompt dialogs: text to enter"),
2673
+ wait_for_completion: z.boolean().optional().default(true).describe("Wait for execution to finish or hit another pause")
2674
+ })
2675
+ };
2526
2676
  var smJobReportGetTool = {
2527
2677
  name: "sm_job_report_get",
2528
2678
  description: SM_JOB_REPORT_GET_DESCRIPTION,
@@ -2561,7 +2711,15 @@ var smJobSnapshotGetTool = {
2561
2711
  name: "sm_job_snapshot_get",
2562
2712
  description: SM_JOB_SNAPSHOT_GET_DESCRIPTION,
2563
2713
  inputSchema: z.object({
2564
- snapshot_id: z.string().describe("Snapshot ID to retrieve (SNP_xxxx)")
2714
+ snapshot_id: z.string().describe("Snapshot ID to retrieve (SNP_xxxx)"),
2715
+ summary_only: z.boolean().optional().describe("If true, omit data_groups from scenario snapshots to reduce payload")
2716
+ })
2717
+ };
2718
+ var smExecutionStatusTool = {
2719
+ name: "sm_execution_status",
2720
+ description: SM_EXECUTION_STATUS_DESCRIPTION,
2721
+ inputSchema: z.object({
2722
+ tabId: z.coerce.number().describe("Tab ID to check execution status")
2565
2723
  })
2566
2724
  };
2567
2725
  var dataBindingSchema = z.object({
@@ -2728,12 +2886,15 @@ var allTools = [
2728
2886
  smJobListTool,
2729
2887
  smJobRunTool,
2730
2888
  smJobCancelTool,
2889
+ smExecutionResumeTool,
2731
2890
  smJobReportGetTool,
2732
2891
  smJobReportExportTool,
2733
2892
  // Job History (3)
2734
2893
  smJobHistoryTool,
2735
2894
  smJobCompareTool,
2736
2895
  smJobSnapshotGetTool,
2896
+ // Execution Status (1)
2897
+ smExecutionStatusTool,
2737
2898
  // Custom View (5)
2738
2899
  smCustomViewCreateTool,
2739
2900
  smCustomViewUpdateTool,
@@ -2747,6 +2908,25 @@ var allTools = [
2747
2908
  sheetsNavigateTool
2748
2909
  ];
2749
2910
 
2911
+ // src/tools/timeout.ts
2912
+ var FILE_EXPORT_TOOLS = /* @__PURE__ */ new Set(["sm_report_export", "sm_job_report_export", "sm_export"]);
2913
+ function computeToolTimeout(tool, input) {
2914
+ if (tool === "wait_for" && typeof input.timeout === "number") {
2915
+ return input.timeout + 5e3;
2916
+ }
2917
+ if (FILE_EXPORT_TOOLS.has(tool)) {
2918
+ return 3e5;
2919
+ }
2920
+ if (tool === "sm_scenario_run" && input.wait_for_completion === true) {
2921
+ const maxDuration = typeof input.max_duration_ms === "number" ? input.max_duration_ms : TIMEOUTS.SCENARIO_MAX_DURATION;
2922
+ return maxDuration + TIMEOUTS.MCP_SCENARIO_BUFFER;
2923
+ }
2924
+ if (tool === "sm_execution_resume" && input.wait_for_completion !== false) {
2925
+ return TIMEOUTS.MCP_JOB_WAIT;
2926
+ }
2927
+ return TIMEOUTS.MCP_TOOL;
2928
+ }
2929
+
2750
2930
  // src/server.ts
2751
2931
  var pendingRequests = /* @__PURE__ */ new Map();
2752
2932
  var extensionSocket = null;
@@ -2770,7 +2950,6 @@ function coerceShape(shape) {
2770
2950
  }
2771
2951
  return result;
2772
2952
  }
2773
- var FILE_EXPORT_TOOLS = /* @__PURE__ */ new Set(["sm_report_export", "sm_job_report_export", "sm_export"]);
2774
2953
  function handleFileExport(filePath, result) {
2775
2954
  mkdirSync(dirname(filePath), { recursive: true });
2776
2955
  let content;
@@ -3348,6 +3527,29 @@ function handleExtensionMessage(message) {
3348
3527
  });
3349
3528
  }
3350
3529
  }
3530
+ function formatAutoHandledDialogs(result) {
3531
+ const dialogs = result._auto_handled_dialogs;
3532
+ if (!dialogs?.length) return null;
3533
+ const lines = dialogs.map((d) => {
3534
+ const action = d.action === "accept" ? "accepted" : "dismissed";
3535
+ return `- [${d.type}] ${action}: "${d.message}"`;
3536
+ });
3537
+ return [
3538
+ "[Auto-handled dialog] A browser dialog appeared during this operation and was automatically handled:",
3539
+ ...lines,
3540
+ "Use handle_dialog with nextPolicy to change how future dialogs are handled."
3541
+ ].join("\n");
3542
+ }
3543
+ function formatDialogPaused(result) {
3544
+ if (result.status !== "dialog_paused") return null;
3545
+ const dp = result.dialog_pause;
3546
+ if (!dp) return null;
3547
+ return [
3548
+ `[Execution Paused] A ${dp.dialog_type} dialog appeared during "${dp.step_context}":`,
3549
+ `"${dp.dialog_message}" (auto-${dp.auto_action}ed)`,
3550
+ "Use sm_execution_resume to decide: continue, retry, skip_iteration, skip_scenario, or fail."
3551
+ ].join("\n");
3552
+ }
3351
3553
  async function callExtensionTool(tool, input) {
3352
3554
  if (tool === "browser_health") {
3353
3555
  return {
@@ -3413,10 +3615,7 @@ async function callExtensionTool(tool, input) {
3413
3615
  const agentId = getDefaultAgentId();
3414
3616
  touchSession(agentId);
3415
3617
  const sock = extensionSocket;
3416
- let toolTimeout = TIMEOUTS.MCP_TOOL;
3417
- if (tool === "wait_for" && typeof input.timeout === "number") {
3418
- toolTimeout = input.timeout + 5e3;
3419
- }
3618
+ const toolTimeout = computeToolTimeout(tool, input);
3420
3619
  return new Promise((resolve2) => {
3421
3620
  const onError = () => {
3422
3621
  const pending = pendingRequests.get(requestId);
@@ -3486,19 +3685,25 @@ async function callExtensionTool(tool, input) {
3486
3685
  }
3487
3686
  return;
3488
3687
  }
3688
+ const dialogNote = formatAutoHandledDialogs(result);
3689
+ const pauseNote = formatDialogPaused(result);
3489
3690
  if (tool === "screenshot" && typeof result.data === "string") {
3490
3691
  const { data, ...metadata } = result;
3491
3692
  const mimeType = metadata.format === "png" ? "image/png" : "image/jpeg";
3492
- resolve2({
3493
- content: [
3494
- { type: "image", data, mimeType },
3495
- { type: "text", text: JSON.stringify(metadata) }
3496
- ]
3497
- });
3693
+ const content = [
3694
+ { type: "image", data, mimeType },
3695
+ { type: "text", text: JSON.stringify(metadata) }
3696
+ ];
3697
+ if (dialogNote) content.push({ type: "text", text: dialogNote });
3698
+ if (pauseNote) content.push({ type: "text", text: pauseNote });
3699
+ resolve2({ content });
3498
3700
  } else {
3499
- resolve2({
3500
- content: [{ type: "text", text: JSON.stringify(result) }]
3501
- });
3701
+ const content = [
3702
+ { type: "text", text: JSON.stringify(result) }
3703
+ ];
3704
+ if (dialogNote) content.push({ type: "text", text: dialogNote });
3705
+ if (pauseNote) content.push({ type: "text", text: pauseNote });
3706
+ resolve2({ content });
3502
3707
  }
3503
3708
  },
3504
3709
  reject: (error) => {