viyv-browser-mcp 0.5.4 → 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 +412 -153
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 >
|
|
101
|
-
throw new Error(`Message too large: ${body.length} bytes (max ${
|
|
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 >
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
if (chromeProfile &&
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
498
|
-
|
|
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,49 +725,24 @@ function startBridge(options) {
|
|
|
643
725
|
onError
|
|
644
726
|
);
|
|
645
727
|
notifyChromeConnected(false);
|
|
646
|
-
|
|
647
|
-
|
|
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
|
|
731
|
+
process.stderr.write(`${LOG_PREFIX2} Primary Chrome stdin closed
|
|
677
732
|
`);
|
|
678
|
-
|
|
679
|
-
|
|
733
|
+
primaryStdinClosed = true;
|
|
734
|
+
removeChromeConnection(primaryChromeId);
|
|
735
|
+
maybeExitIfEmpty();
|
|
680
736
|
});
|
|
681
737
|
});
|
|
682
738
|
}
|
|
683
739
|
|
|
684
740
|
// src/server.ts
|
|
685
741
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
686
|
-
import { existsSync, statSync } from "fs";
|
|
742
|
+
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
|
687
743
|
import http from "http";
|
|
688
744
|
import { createConnection as createConnection2 } from "net";
|
|
745
|
+
import { dirname } from "path";
|
|
689
746
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
690
747
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
691
748
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -919,10 +976,16 @@ Options:
|
|
|
919
976
|
- maxChars: Limit output size (default: 50000). Use 100000 for full page content.`;
|
|
920
977
|
|
|
921
978
|
// src/tools/core/handle-dialog.ts
|
|
922
|
-
var HANDLE_DIALOG_DESCRIPTION = `Handle JavaScript dialogs
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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.`;
|
|
926
989
|
|
|
927
990
|
// src/tools/core/hover.ts
|
|
928
991
|
var HOVER_DESCRIPTION = `Move mouse to coordinates or element without clicking.
|
|
@@ -1039,38 +1102,6 @@ Returns details of HTTP requests and responses including URL, method,
|
|
|
1039
1102
|
status code, headers, and timing information. Supports filtering
|
|
1040
1103
|
by URL pattern or resource type.`;
|
|
1041
1104
|
|
|
1042
|
-
// src/tools/sheets/descriptions.ts
|
|
1043
|
-
var SHEETS_READ_DESCRIPTION = `Read data from the active Google Sheets spreadsheet.
|
|
1044
|
-
|
|
1045
|
-
Uses the gviz/tq endpoint with session cookies \u2014 works with private sheets without OAuth.
|
|
1046
|
-
Supports range selection, tq query language for filtering/aggregation, and row limits.
|
|
1047
|
-
|
|
1048
|
-
tq query examples:
|
|
1049
|
-
- "SELECT A, B WHERE C > 100" \u2014 filter rows
|
|
1050
|
-
- "SELECT A, SUM(B) GROUP BY A" \u2014 aggregate
|
|
1051
|
-
- "SELECT * ORDER BY C DESC LIMIT 5" \u2014 sort and limit
|
|
1052
|
-
|
|
1053
|
-
Set formulas=true to read raw formulas (e.g., =IMAGE(), =HYPERLINK()) instead of computed values.
|
|
1054
|
-
Formula mode reads cell-by-cell via the formula bar, limited to 200 cells.
|
|
1055
|
-
The tab must be on a Google Sheets page (docs.google.com/spreadsheets/).`;
|
|
1056
|
-
var SHEETS_WRITE_DESCRIPTION = `Write data to the active Google Sheets spreadsheet.
|
|
1057
|
-
|
|
1058
|
-
For single cell: navigates to the cell via Name Box and types the value.
|
|
1059
|
-
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.
|
|
1060
|
-
|
|
1061
|
-
Provide either "value" (single cell string) or "values" (2D array), not both.
|
|
1062
|
-
The tab must be on a Google Sheets page.`;
|
|
1063
|
-
var SHEETS_INFO_DESCRIPTION = `Get metadata about the active Google Sheets spreadsheet.
|
|
1064
|
-
|
|
1065
|
-
Returns spreadsheet ID, document title, sheet list (name, index, row/col counts, active status), active cell reference, and URL.
|
|
1066
|
-
Uses DOM inspection and gviz/tq queries for sheet dimensions.
|
|
1067
|
-
The tab must be on a Google Sheets page.`;
|
|
1068
|
-
var SHEETS_NAVIGATE_DESCRIPTION = `Navigate within the active Google Sheets spreadsheet.
|
|
1069
|
-
|
|
1070
|
-
Jump to a specific cell (e.g., "A1", "Z100") via the Name Box, or switch to a different sheet tab by name.
|
|
1071
|
-
Both cell and sheet can be specified together.
|
|
1072
|
-
The tab must be on a Google Sheets page.`;
|
|
1073
|
-
|
|
1074
1105
|
// src/tools/semantic/sm-add-action.ts
|
|
1075
1106
|
var SM_ADD_ACTION_DESCRIPTION = `Define a semantic action (step sequence) for a registered page.
|
|
1076
1107
|
|
|
@@ -1151,6 +1182,10 @@ var SM_CAPABILITIES_DESCRIPTION = `Query semantic capabilities available for the
|
|
|
1151
1182
|
Returns the matched page definition, available actions, and fetches for the active tab.
|
|
1152
1183
|
Uses page signature matching (URL pattern + DOM markers) to identify the current page state.
|
|
1153
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
|
+
|
|
1154
1189
|
Returns:
|
|
1155
1190
|
- ok: whether a page match was found
|
|
1156
1191
|
- domain: hostname of the current tab
|
|
@@ -1158,7 +1193,7 @@ Returns:
|
|
|
1158
1193
|
- match_score: confidence score of the match
|
|
1159
1194
|
- actions[]: available actions with their parameters
|
|
1160
1195
|
- fetches[]: available data extractions with their parameters
|
|
1161
|
-
- other_pages[]: other registered pages on the same domain
|
|
1196
|
+
- other_pages[]: (only when include_other_pages=true) other registered pages on the same domain
|
|
1162
1197
|
- hint: guidance message when no page matches
|
|
1163
1198
|
|
|
1164
1199
|
Use this before sm_invoke or sm_fetch to discover what operations are available.
|
|
@@ -1245,15 +1280,52 @@ Returns:
|
|
|
1245
1280
|
- cascaded: (page deletion) lists of deleted targets, actions, fetches
|
|
1246
1281
|
- warnings: (target deletion) referencing actions/fetches that may break`;
|
|
1247
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
|
+
|
|
1248
1318
|
// src/tools/semantic/sm-export.ts
|
|
1249
1319
|
var SM_EXPORT_DESCRIPTION = `Export semantic configuration as JSON.
|
|
1250
1320
|
|
|
1251
|
-
Exports pages, targets, actions, and fetches
|
|
1321
|
+
Exports pages, targets, actions, and fetches scoped by the current tab's page.
|
|
1252
1322
|
Secret parameter default values are stripped from the export.
|
|
1253
1323
|
Includes a SHA-256 checksum for integrity verification on import.
|
|
1254
1324
|
|
|
1255
1325
|
Parameters:
|
|
1256
|
-
-
|
|
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)
|
|
1257
1329
|
|
|
1258
1330
|
Returns:
|
|
1259
1331
|
- SemanticExport JSON with schema_version, checksum, pages[], targets[], actions[], fetches[]`;
|
|
@@ -1378,7 +1450,10 @@ Parameters:
|
|
|
1378
1450
|
|
|
1379
1451
|
Returns:
|
|
1380
1452
|
- job_id, job_label
|
|
1381
|
-
- 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`;
|
|
1382
1457
|
var SM_JOB_COMPARE_DESCRIPTION = `Compare two job executions to see data changes between them.
|
|
1383
1458
|
|
|
1384
1459
|
Matches scenarios by scenario_id, data groups by result_key, and rows by loop_params. Computes per-cell diffs with numeric deltas.
|
|
@@ -1398,9 +1473,10 @@ Use this to inspect the full captured data for a single execution, including all
|
|
|
1398
1473
|
|
|
1399
1474
|
Parameters:
|
|
1400
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)
|
|
1401
1477
|
|
|
1402
1478
|
Returns:
|
|
1403
|
-
- 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)`;
|
|
1404
1480
|
|
|
1405
1481
|
// src/tools/semantic/sm-job-list.ts
|
|
1406
1482
|
var SM_JOB_LIST_DESCRIPTION = `List all defined jobs with entry counts and configuration.
|
|
@@ -1429,6 +1505,11 @@ var SM_JOB_REPORT_GET_DESCRIPTION = `Get job execution status and results.
|
|
|
1429
1505
|
Shows overall progress and per-scenario report summaries.
|
|
1430
1506
|
Use sm_report_get with individual report_ids for detailed scenario data.
|
|
1431
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
|
+
|
|
1432
1513
|
Parameters:
|
|
1433
1514
|
- job_report_id: job report ID (JRP_xxxx)
|
|
1434
1515
|
|
|
@@ -1439,8 +1520,7 @@ Returns:
|
|
|
1439
1520
|
// src/tools/semantic/sm-job-run.ts
|
|
1440
1521
|
var SM_JOB_RUN_DESCRIPTION = `Execute a job on a browser tab. Scenarios run sequentially.
|
|
1441
1522
|
|
|
1442
|
-
|
|
1443
|
-
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.
|
|
1444
1524
|
|
|
1445
1525
|
Only one job or scenario can run per tab at a time.
|
|
1446
1526
|
|
|
@@ -1448,12 +1528,16 @@ Parameters:
|
|
|
1448
1528
|
- tabId: target tab for execution
|
|
1449
1529
|
- job_id: the job to execute
|
|
1450
1530
|
- params: optional per-scenario param overrides keyed by scenario_id
|
|
1451
|
-
- wait_for_completion: if true, wait for all scenarios to complete (default: false)
|
|
1452
1531
|
|
|
1453
1532
|
Returns:
|
|
1454
|
-
- 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
|
|
1455
1534
|
- job_id: the executed job
|
|
1456
|
-
- status: 'running'
|
|
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`;
|
|
1457
1541
|
|
|
1458
1542
|
// src/tools/semantic/sm-job-update.ts
|
|
1459
1543
|
var SM_JOB_UPDATE_DESCRIPTION = `Update an existing job definition.
|
|
@@ -1627,12 +1711,17 @@ Parameters:
|
|
|
1627
1711
|
- scenario_id: the scenario to execute
|
|
1628
1712
|
- params: parameter values for template substitution
|
|
1629
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)
|
|
1630
1715
|
|
|
1631
1716
|
Returns:
|
|
1632
1717
|
- report_id: ID of the created report (RPT_xxxx)
|
|
1633
1718
|
- scenario_id: the executed scenario
|
|
1634
1719
|
- status: 'running' (async) or 'completed'/'failed' (when wait_for_completion=true)
|
|
1635
|
-
- 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.`;
|
|
1636
1725
|
|
|
1637
1726
|
// src/tools/semantic/sm-scenario-update.ts
|
|
1638
1727
|
var SM_SCENARIO_UPDATE_DESCRIPTION = `Update an existing scenario.
|
|
@@ -1663,14 +1752,16 @@ Parameters:
|
|
|
1663
1752
|
- page_id (optional): filter results to a specific page
|
|
1664
1753
|
- domain (optional): filter results to pages matching this domain
|
|
1665
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)
|
|
1666
1756
|
|
|
1667
1757
|
Returns:
|
|
1668
1758
|
- summary: {total_pages, total_targets, total_actions, total_fetches}
|
|
1669
1759
|
- pages[]: page definitions with page_type, domains, url_patterns, and counts
|
|
1670
|
-
-
|
|
1671
|
-
-
|
|
1672
|
-
-
|
|
1673
|
-
-
|
|
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`;
|
|
1674
1765
|
|
|
1675
1766
|
// src/tools/semantic/sm-test-target.ts
|
|
1676
1767
|
var SM_TEST_TARGET_DESCRIPTION = `Test a specific target's locators against the current page.
|
|
@@ -1688,6 +1779,38 @@ Returns:
|
|
|
1688
1779
|
- element: resolved element info {tag, text, rect} if found
|
|
1689
1780
|
- test_status: updated test status saved to storage`;
|
|
1690
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
|
+
|
|
1691
1814
|
// src/tools/tabs/select-tab.ts
|
|
1692
1815
|
var SELECT_TAB_DESCRIPTION = `Switch focus to a specific tab by its identifier.
|
|
1693
1816
|
Makes the target tab the active tab in the agent's group,
|
|
@@ -1930,8 +2053,12 @@ var handleDialogTool = {
|
|
|
1930
2053
|
description: HANDLE_DIALOG_DESCRIPTION,
|
|
1931
2054
|
inputSchema: z.object({
|
|
1932
2055
|
tabId: z.coerce.number().describe("Tab ID"),
|
|
1933
|
-
action: z.enum(["accept", "dismiss"]).describe("Dialog action"),
|
|
1934
|
-
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.")
|
|
1935
2062
|
})
|
|
1936
2063
|
};
|
|
1937
2064
|
var tabsContextTool = {
|
|
@@ -2116,7 +2243,8 @@ var smCapabilitiesTool = {
|
|
|
2116
2243
|
name: "sm_capabilities",
|
|
2117
2244
|
description: SM_CAPABILITIES_DESCRIPTION,
|
|
2118
2245
|
inputSchema: z.object({
|
|
2119
|
-
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)")
|
|
2120
2248
|
})
|
|
2121
2249
|
};
|
|
2122
2250
|
var smInvokeTool = {
|
|
@@ -2209,7 +2337,17 @@ var smAddActionTool = {
|
|
|
2209
2337
|
).optional().describe("Parameter definitions"),
|
|
2210
2338
|
steps: z.array(
|
|
2211
2339
|
z.object({
|
|
2212
|
-
type: z.enum([
|
|
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"),
|
|
2213
2351
|
target_id: z.string().optional().describe("Target to act on"),
|
|
2214
2352
|
params: z.object({
|
|
2215
2353
|
text: z.string().optional(),
|
|
@@ -2277,7 +2415,8 @@ var smStatusTool = {
|
|
|
2277
2415
|
tabId: z.coerce.number().optional().describe("Tab ID (required for test_resolve)"),
|
|
2278
2416
|
page_id: z.string().optional().describe("Filter by page ID"),
|
|
2279
2417
|
domain: z.string().optional().describe("Filter results to pages matching this domain"),
|
|
2280
|
-
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)")
|
|
2281
2420
|
})
|
|
2282
2421
|
};
|
|
2283
2422
|
var smTestTargetTool = {
|
|
@@ -2301,7 +2440,9 @@ var smExportTool = {
|
|
|
2301
2440
|
name: "sm_export",
|
|
2302
2441
|
description: SM_EXPORT_DESCRIPTION,
|
|
2303
2442
|
inputSchema: z.object({
|
|
2304
|
-
|
|
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)"),
|
|
2445
|
+
file_path: z.string().optional().describe("Save to this path and return metadata only (keeps data out of context)")
|
|
2305
2446
|
})
|
|
2306
2447
|
};
|
|
2307
2448
|
var smImportTool = {
|
|
@@ -2432,7 +2573,8 @@ var smScenarioRunTool = {
|
|
|
2432
2573
|
tabId: z.coerce.number().describe("Tab ID for execution"),
|
|
2433
2574
|
scenario_id: z.string().describe("Scenario to execute"),
|
|
2434
2575
|
params: z.record(z.unknown()).optional().describe("Parameter values"),
|
|
2435
|
-
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)")
|
|
2436
2578
|
})
|
|
2437
2579
|
};
|
|
2438
2580
|
var smReportListTool = {
|
|
@@ -2462,7 +2604,8 @@ var smReportExportTool = {
|
|
|
2462
2604
|
description: SM_REPORT_EXPORT_DESCRIPTION,
|
|
2463
2605
|
inputSchema: z.object({
|
|
2464
2606
|
report_id: z.string().describe("Report ID to export"),
|
|
2465
|
-
format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)")
|
|
2607
|
+
format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)"),
|
|
2608
|
+
file_path: z.string().optional().describe("Save to this path and return metadata only (keeps data out of context)")
|
|
2466
2609
|
})
|
|
2467
2610
|
};
|
|
2468
2611
|
var jobEntrySchema = z.object({
|
|
@@ -2509,8 +2652,7 @@ var smJobRunTool = {
|
|
|
2509
2652
|
inputSchema: z.object({
|
|
2510
2653
|
tabId: z.coerce.number().describe("Tab ID to execute scenarios on"),
|
|
2511
2654
|
job_id: z.string().describe("Job ID to execute"),
|
|
2512
|
-
params: z.record(z.record(z.unknown())).optional().describe("Per-scenario param overrides keyed by scenario_id")
|
|
2513
|
-
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")
|
|
2514
2656
|
})
|
|
2515
2657
|
};
|
|
2516
2658
|
var smJobCancelTool = {
|
|
@@ -2520,6 +2662,17 @@ var smJobCancelTool = {
|
|
|
2520
2662
|
tabId: z.coerce.number().describe("Tab ID where the job is running")
|
|
2521
2663
|
})
|
|
2522
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
|
+
};
|
|
2523
2676
|
var smJobReportGetTool = {
|
|
2524
2677
|
name: "sm_job_report_get",
|
|
2525
2678
|
description: SM_JOB_REPORT_GET_DESCRIPTION,
|
|
@@ -2532,7 +2685,8 @@ var smJobReportExportTool = {
|
|
|
2532
2685
|
description: SM_JOB_REPORT_EXPORT_DESCRIPTION,
|
|
2533
2686
|
inputSchema: z.object({
|
|
2534
2687
|
job_report_id: z.string().describe("Job report ID to export"),
|
|
2535
|
-
format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)")
|
|
2688
|
+
format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)"),
|
|
2689
|
+
file_path: z.string().optional().describe("Save to this path and return metadata only (keeps data out of context)")
|
|
2536
2690
|
})
|
|
2537
2691
|
};
|
|
2538
2692
|
var smJobHistoryTool = {
|
|
@@ -2557,7 +2711,15 @@ var smJobSnapshotGetTool = {
|
|
|
2557
2711
|
name: "sm_job_snapshot_get",
|
|
2558
2712
|
description: SM_JOB_SNAPSHOT_GET_DESCRIPTION,
|
|
2559
2713
|
inputSchema: z.object({
|
|
2560
|
-
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")
|
|
2561
2723
|
})
|
|
2562
2724
|
};
|
|
2563
2725
|
var dataBindingSchema = z.object({
|
|
@@ -2724,12 +2886,15 @@ var allTools = [
|
|
|
2724
2886
|
smJobListTool,
|
|
2725
2887
|
smJobRunTool,
|
|
2726
2888
|
smJobCancelTool,
|
|
2889
|
+
smExecutionResumeTool,
|
|
2727
2890
|
smJobReportGetTool,
|
|
2728
2891
|
smJobReportExportTool,
|
|
2729
2892
|
// Job History (3)
|
|
2730
2893
|
smJobHistoryTool,
|
|
2731
2894
|
smJobCompareTool,
|
|
2732
2895
|
smJobSnapshotGetTool,
|
|
2896
|
+
// Execution Status (1)
|
|
2897
|
+
smExecutionStatusTool,
|
|
2733
2898
|
// Custom View (5)
|
|
2734
2899
|
smCustomViewCreateTool,
|
|
2735
2900
|
smCustomViewUpdateTool,
|
|
@@ -2743,6 +2908,25 @@ var allTools = [
|
|
|
2743
2908
|
sheetsNavigateTool
|
|
2744
2909
|
];
|
|
2745
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
|
+
|
|
2746
2930
|
// src/server.ts
|
|
2747
2931
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
2748
2932
|
var extensionSocket = null;
|
|
@@ -2766,6 +2950,31 @@ function coerceShape(shape) {
|
|
|
2766
2950
|
}
|
|
2767
2951
|
return result;
|
|
2768
2952
|
}
|
|
2953
|
+
function handleFileExport(filePath, result) {
|
|
2954
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
2955
|
+
let content;
|
|
2956
|
+
let metadata;
|
|
2957
|
+
if (typeof result.data === "string") {
|
|
2958
|
+
const { data, ...rest } = result;
|
|
2959
|
+
content = data;
|
|
2960
|
+
metadata = { ...rest, file_path: filePath };
|
|
2961
|
+
if (result.format === "csv") {
|
|
2962
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
2963
|
+
const headerLine = lines[0]?.replace(/^\uFEFF/, "") ?? "";
|
|
2964
|
+
const columns = headerLine.split(",").map((h) => h.replace(/^"|"$/g, "").trim());
|
|
2965
|
+
metadata.row_count = lines.length - 1;
|
|
2966
|
+
metadata.column_count = columns.length;
|
|
2967
|
+
metadata.columns = columns;
|
|
2968
|
+
}
|
|
2969
|
+
} else {
|
|
2970
|
+
content = JSON.stringify(result, null, 2);
|
|
2971
|
+
const pages = Array.isArray(result.pages) ? result.pages : [];
|
|
2972
|
+
metadata = { ok: true, file_path: filePath, page_count: pages.length };
|
|
2973
|
+
}
|
|
2974
|
+
writeFileSync(filePath, content, "utf-8");
|
|
2975
|
+
metadata.file_size = Buffer.byteLength(content, "utf-8");
|
|
2976
|
+
return metadata;
|
|
2977
|
+
}
|
|
2769
2978
|
function createConfiguredMcpServer() {
|
|
2770
2979
|
const server = new McpServer({
|
|
2771
2980
|
name: MCP_SERVER.NAME,
|
|
@@ -3318,6 +3527,29 @@ function handleExtensionMessage(message) {
|
|
|
3318
3527
|
});
|
|
3319
3528
|
}
|
|
3320
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
|
+
}
|
|
3321
3553
|
async function callExtensionTool(tool, input) {
|
|
3322
3554
|
if (tool === "browser_health") {
|
|
3323
3555
|
return {
|
|
@@ -3383,10 +3615,7 @@ async function callExtensionTool(tool, input) {
|
|
|
3383
3615
|
const agentId = getDefaultAgentId();
|
|
3384
3616
|
touchSession(agentId);
|
|
3385
3617
|
const sock = extensionSocket;
|
|
3386
|
-
|
|
3387
|
-
if (tool === "wait_for" && typeof input.timeout === "number") {
|
|
3388
|
-
toolTimeout = input.timeout + 5e3;
|
|
3389
|
-
}
|
|
3618
|
+
const toolTimeout = computeToolTimeout(tool, input);
|
|
3390
3619
|
return new Promise((resolve2) => {
|
|
3391
3620
|
const onError = () => {
|
|
3392
3621
|
const pending = pendingRequests.get(requestId);
|
|
@@ -3428,22 +3657,53 @@ async function callExtensionTool(tool, input) {
|
|
|
3428
3657
|
]
|
|
3429
3658
|
});
|
|
3430
3659
|
}, toolTimeout);
|
|
3660
|
+
const { chromeProfile, file_path: filePath, ...cleanInput } = input;
|
|
3431
3661
|
pendingRequests.set(requestId, {
|
|
3432
3662
|
resolve: (result) => {
|
|
3433
3663
|
removeErrorListener();
|
|
3664
|
+
if (FILE_EXPORT_TOOLS.has(tool) && typeof filePath === "string" && ("data" in result || "pages" in result)) {
|
|
3665
|
+
try {
|
|
3666
|
+
const metadata = handleFileExport(filePath, result);
|
|
3667
|
+
resolve2({
|
|
3668
|
+
content: [{ type: "text", text: JSON.stringify(metadata) }]
|
|
3669
|
+
});
|
|
3670
|
+
} catch (e) {
|
|
3671
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3672
|
+
resolve2({
|
|
3673
|
+
content: [
|
|
3674
|
+
{
|
|
3675
|
+
type: "text",
|
|
3676
|
+
text: JSON.stringify({
|
|
3677
|
+
error: {
|
|
3678
|
+
code: "FILE_WRITE_ERROR",
|
|
3679
|
+
message: `Failed to write ${filePath}: ${msg}`
|
|
3680
|
+
}
|
|
3681
|
+
})
|
|
3682
|
+
}
|
|
3683
|
+
]
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
const dialogNote = formatAutoHandledDialogs(result);
|
|
3689
|
+
const pauseNote = formatDialogPaused(result);
|
|
3434
3690
|
if (tool === "screenshot" && typeof result.data === "string") {
|
|
3435
3691
|
const { data, ...metadata } = result;
|
|
3436
3692
|
const mimeType = metadata.format === "png" ? "image/png" : "image/jpeg";
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
});
|
|
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 });
|
|
3443
3700
|
} else {
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
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 });
|
|
3447
3707
|
}
|
|
3448
3708
|
},
|
|
3449
3709
|
reject: (error) => {
|
|
@@ -3461,7 +3721,6 @@ async function callExtensionTool(tool, input) {
|
|
|
3461
3721
|
},
|
|
3462
3722
|
timer
|
|
3463
3723
|
});
|
|
3464
|
-
const { chromeProfile, ...cleanInput } = input;
|
|
3465
3724
|
const request = {
|
|
3466
3725
|
id: requestId,
|
|
3467
3726
|
type: "tool_call",
|
|
@@ -3540,9 +3799,9 @@ async function handleSwitchBrowser() {
|
|
|
3540
3799
|
|
|
3541
3800
|
// src/setup.ts
|
|
3542
3801
|
import { execSync } from "child_process";
|
|
3543
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3802
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3544
3803
|
import { homedir, platform } from "os";
|
|
3545
|
-
import { dirname, resolve } from "path";
|
|
3804
|
+
import { dirname as dirname2, resolve } from "path";
|
|
3546
3805
|
function runSetup(options = {}) {
|
|
3547
3806
|
const os = platform();
|
|
3548
3807
|
const binaryPath = getBinaryPath();
|
|
@@ -3569,11 +3828,11 @@ function runSetup(options = {}) {
|
|
|
3569
3828
|
allowed_origins: allowedOrigins
|
|
3570
3829
|
};
|
|
3571
3830
|
const manifestPath = getManifestPath(os);
|
|
3572
|
-
const manifestDir =
|
|
3831
|
+
const manifestDir = dirname2(manifestPath);
|
|
3573
3832
|
console.log(`Wrapper: ${wrapperPath}`);
|
|
3574
3833
|
console.log(`Manifest path: ${manifestPath}`);
|
|
3575
|
-
|
|
3576
|
-
|
|
3834
|
+
mkdirSync2(manifestDir, { recursive: true });
|
|
3835
|
+
writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2));
|
|
3577
3836
|
chmodSync(manifestPath, 420);
|
|
3578
3837
|
console.log("\nNative Messaging Host registered successfully!");
|
|
3579
3838
|
console.log("\nNext steps:");
|
|
@@ -3595,18 +3854,18 @@ function getBinaryPath() {
|
|
|
3595
3854
|
return resolve(currentScript);
|
|
3596
3855
|
}
|
|
3597
3856
|
function createNativeHostWrapper(os, binaryPath) {
|
|
3598
|
-
const manifestDir =
|
|
3599
|
-
|
|
3857
|
+
const manifestDir = dirname2(getManifestPath(os));
|
|
3858
|
+
mkdirSync2(manifestDir, { recursive: true });
|
|
3600
3859
|
const nodePath = getNodePath();
|
|
3601
3860
|
if (os === "win32") {
|
|
3602
3861
|
const wrapperPath2 = resolve(manifestDir, `${NATIVE_HOST_NAME}.bat`);
|
|
3603
|
-
|
|
3862
|
+
writeFileSync2(wrapperPath2, `@echo off\r
|
|
3604
3863
|
"${nodePath}" "${binaryPath}" --native-host\r
|
|
3605
3864
|
`);
|
|
3606
3865
|
return wrapperPath2;
|
|
3607
3866
|
}
|
|
3608
3867
|
const wrapperPath = resolve(manifestDir, `${NATIVE_HOST_NAME}.sh`);
|
|
3609
|
-
|
|
3868
|
+
writeFileSync2(wrapperPath, `#!/bin/bash
|
|
3610
3869
|
exec "${nodePath}" "${binaryPath}" --native-host
|
|
3611
3870
|
`);
|
|
3612
3871
|
chmodSync(wrapperPath, 493);
|
|
@@ -3664,7 +3923,7 @@ function setupClaudeDesktopConfig() {
|
|
|
3664
3923
|
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
3665
3924
|
config.mcpServers = {};
|
|
3666
3925
|
}
|
|
3667
|
-
const nodeBinDir =
|
|
3926
|
+
const nodeBinDir = dirname2(getNodePath());
|
|
3668
3927
|
const defaultPath = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
3669
3928
|
const envPath = nodeBinDir ? `${nodeBinDir}:${defaultPath}` : defaultPath;
|
|
3670
3929
|
config.mcpServers["viyv-browser"] = {
|
|
@@ -3672,9 +3931,9 @@ function setupClaudeDesktopConfig() {
|
|
|
3672
3931
|
args: ["-y", "viyv-browser-mcp@latest"],
|
|
3673
3932
|
env: { PATH: envPath }
|
|
3674
3933
|
};
|
|
3675
|
-
const configDir =
|
|
3676
|
-
|
|
3677
|
-
|
|
3934
|
+
const configDir = dirname2(configPath);
|
|
3935
|
+
mkdirSync2(configDir, { recursive: true });
|
|
3936
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2));
|
|
3678
3937
|
console.log(`npx path: ${npxPath}`);
|
|
3679
3938
|
console.log("\nClaude Desktop config updated successfully!");
|
|
3680
3939
|
console.log("Please restart Claude Desktop for changes to take effect.");
|