viyv-browser-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1823 -383
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -3,14 +3,8 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
7
|
-
import {
|
|
8
|
-
import { existsSync, unlinkSync } from "fs";
|
|
9
|
-
import http from "http";
|
|
10
|
-
import { createServer } from "net";
|
|
11
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
-
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
13
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
// src/native-host/bridge.ts
|
|
7
|
+
import { createConnection } from "net";
|
|
14
8
|
|
|
15
9
|
// ../shared/dist/constants.js
|
|
16
10
|
var PROTOCOL_VERSION = "1.0.0";
|
|
@@ -45,7 +39,7 @@ var LIMITS = {
|
|
|
45
39
|
/** A11y tree max elements */
|
|
46
40
|
A11Y_MAX_ELEMENTS: 5e3,
|
|
47
41
|
/** A11y tree default depth */
|
|
48
|
-
A11Y_DEFAULT_DEPTH:
|
|
42
|
+
A11Y_DEFAULT_DEPTH: 15,
|
|
49
43
|
/** A11y tree default max chars */
|
|
50
44
|
A11Y_DEFAULT_MAX_CHARS: 5e4,
|
|
51
45
|
/** Event buffer max entries */
|
|
@@ -78,6 +72,214 @@ var MCP_SERVER = {
|
|
|
78
72
|
SOCKET_PATH_TEMPLATE: "/tmp/viyv-browser-{pid}.sock"
|
|
79
73
|
};
|
|
80
74
|
|
|
75
|
+
// src/native-host/compression.ts
|
|
76
|
+
import { gunzipSync, gzipSync } from "zlib";
|
|
77
|
+
var CHUNK_SIZE = 768 * 1024;
|
|
78
|
+
function compressPayload(data) {
|
|
79
|
+
const original = Buffer.from(data, "utf-8");
|
|
80
|
+
const gzipped = gzipSync(original);
|
|
81
|
+
if (gzipped.length < original.length) {
|
|
82
|
+
return { compressed: gzipped.toString("base64"), wasCompressed: true };
|
|
83
|
+
}
|
|
84
|
+
return { compressed: data, wasCompressed: false };
|
|
85
|
+
}
|
|
86
|
+
function decompressPayload(data, isCompressed) {
|
|
87
|
+
if (!isCompressed) return data;
|
|
88
|
+
const buf = Buffer.from(data, "base64");
|
|
89
|
+
return gunzipSync(buf).toString("utf-8");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/native-host/transport.ts
|
|
93
|
+
var MAX_MESSAGE_SIZE = 1024 * 1024;
|
|
94
|
+
function encodeMessage(message) {
|
|
95
|
+
const json = JSON.stringify(message);
|
|
96
|
+
const body = Buffer.from(json, "utf-8");
|
|
97
|
+
if (body.length > MAX_MESSAGE_SIZE) {
|
|
98
|
+
throw new Error(`Message too large: ${body.length} bytes (max ${MAX_MESSAGE_SIZE})`);
|
|
99
|
+
}
|
|
100
|
+
const header = Buffer.alloc(4);
|
|
101
|
+
header.writeUInt32LE(body.length, 0);
|
|
102
|
+
return Buffer.concat([header, body]);
|
|
103
|
+
}
|
|
104
|
+
function createMessageReader(stream, onMessage, onError, onClose) {
|
|
105
|
+
let buffer = Buffer.alloc(0);
|
|
106
|
+
let expectedLength = null;
|
|
107
|
+
stream.on("data", (chunk) => {
|
|
108
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
109
|
+
while (true) {
|
|
110
|
+
if (expectedLength === null) {
|
|
111
|
+
if (buffer.length < 4) break;
|
|
112
|
+
expectedLength = buffer.readUInt32LE(0);
|
|
113
|
+
buffer = buffer.subarray(4);
|
|
114
|
+
if (expectedLength > MAX_MESSAGE_SIZE) {
|
|
115
|
+
onError?.(new Error(`Message too large: ${expectedLength} bytes`));
|
|
116
|
+
expectedLength = null;
|
|
117
|
+
buffer = Buffer.alloc(0);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (buffer.length < expectedLength) break;
|
|
122
|
+
const jsonBuffer = buffer.subarray(0, expectedLength);
|
|
123
|
+
buffer = buffer.subarray(expectedLength);
|
|
124
|
+
expectedLength = null;
|
|
125
|
+
try {
|
|
126
|
+
const message = JSON.parse(jsonBuffer.toString("utf-8"));
|
|
127
|
+
onMessage(message);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
onError?.(new Error(`Invalid JSON: ${error.message}`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
stream.on("error", (error) => {
|
|
134
|
+
onError?.(new Error(`Stream error: ${error.message}`));
|
|
135
|
+
});
|
|
136
|
+
stream.on("end", () => {
|
|
137
|
+
onClose?.();
|
|
138
|
+
});
|
|
139
|
+
stream.on("close", () => {
|
|
140
|
+
onClose?.();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function writeMessage(stream, message) {
|
|
144
|
+
const encoded = encodeMessage(message);
|
|
145
|
+
stream.write(encoded);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/native-host/bridge.ts
|
|
149
|
+
var MAX_BUFFER_SIZE = 1e3;
|
|
150
|
+
function startBridge(options) {
|
|
151
|
+
const { socketPath, onError } = options;
|
|
152
|
+
let socket = null;
|
|
153
|
+
let reconnecting = false;
|
|
154
|
+
let retryCount = 0;
|
|
155
|
+
const pendingMessages = [];
|
|
156
|
+
function flushBuffer() {
|
|
157
|
+
if (!socket || socket.destroyed) return;
|
|
158
|
+
while (pendingMessages.length > 0) {
|
|
159
|
+
const msg = pendingMessages[0];
|
|
160
|
+
try {
|
|
161
|
+
const written = socket.write(`${JSON.stringify(msg)}
|
|
162
|
+
`);
|
|
163
|
+
pendingMessages.shift();
|
|
164
|
+
if (!written) {
|
|
165
|
+
socket.once("drain", () => flushBuffer());
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
onError?.(error);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function connectSocket() {
|
|
175
|
+
socket = createConnection(socketPath);
|
|
176
|
+
socket.on("connect", () => {
|
|
177
|
+
process.stderr.write(`[viyv-browser:native-host] Connected to MCP server at ${socketPath}
|
|
178
|
+
`);
|
|
179
|
+
retryCount = 0;
|
|
180
|
+
flushBuffer();
|
|
181
|
+
});
|
|
182
|
+
let lineBuffer = "";
|
|
183
|
+
socket.on("data", (data) => {
|
|
184
|
+
lineBuffer += data.toString("utf-8");
|
|
185
|
+
const lines = lineBuffer.split("\n");
|
|
186
|
+
lineBuffer = lines.pop() ?? "";
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
if (!line) continue;
|
|
189
|
+
try {
|
|
190
|
+
let message = JSON.parse(line);
|
|
191
|
+
if (message.type === "compressed" && typeof message.data === "string") {
|
|
192
|
+
const decompressed = decompressPayload(message.data, true);
|
|
193
|
+
message = JSON.parse(decompressed);
|
|
194
|
+
}
|
|
195
|
+
writeMessage(process.stdout, message);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
onError?.(error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
socket.on("error", (error) => {
|
|
202
|
+
process.stderr.write(`[viyv-browser:native-host] Socket error: ${error.message}
|
|
203
|
+
`);
|
|
204
|
+
onError?.(error);
|
|
205
|
+
});
|
|
206
|
+
socket.on("close", () => {
|
|
207
|
+
process.stderr.write("[viyv-browser:native-host] Socket closed\n");
|
|
208
|
+
socket = null;
|
|
209
|
+
if (!reconnecting) {
|
|
210
|
+
reconnecting = true;
|
|
211
|
+
const delay = Math.min(
|
|
212
|
+
RECONNECT.INITIAL_DELAY * RECONNECT.MULTIPLIER ** retryCount,
|
|
213
|
+
RECONNECT.MAX_DELAY
|
|
214
|
+
);
|
|
215
|
+
retryCount++;
|
|
216
|
+
process.stderr.write(
|
|
217
|
+
`[viyv-browser:native-host] Reconnecting in ${delay}ms (attempt ${retryCount})
|
|
218
|
+
`
|
|
219
|
+
);
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
reconnecting = false;
|
|
222
|
+
connectSocket();
|
|
223
|
+
}, delay);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
createMessageReader(
|
|
228
|
+
process.stdin,
|
|
229
|
+
(message) => {
|
|
230
|
+
if (socket && !socket.destroyed) {
|
|
231
|
+
const json = JSON.stringify(message);
|
|
232
|
+
if (json.length > LIMITS.CHUNK_SIZE) {
|
|
233
|
+
const { compressed, wasCompressed } = compressPayload(json);
|
|
234
|
+
if (wasCompressed) {
|
|
235
|
+
socket.write(`${JSON.stringify({ type: "compressed", data: compressed })}
|
|
236
|
+
`);
|
|
237
|
+
} else {
|
|
238
|
+
socket.write(`${json}
|
|
239
|
+
`);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
socket.write(`${json}
|
|
243
|
+
`);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
if (pendingMessages.length < MAX_BUFFER_SIZE) {
|
|
247
|
+
pendingMessages.push(message);
|
|
248
|
+
} else {
|
|
249
|
+
process.stderr.write("[viyv-browser:native-host] Message buffer full, dropping message\n");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
onError
|
|
254
|
+
);
|
|
255
|
+
connectSocket();
|
|
256
|
+
process.on("SIGINT", () => {
|
|
257
|
+
socket?.destroy();
|
|
258
|
+
process.exit(0);
|
|
259
|
+
});
|
|
260
|
+
process.on("SIGTERM", () => {
|
|
261
|
+
socket?.destroy();
|
|
262
|
+
process.exit(0);
|
|
263
|
+
});
|
|
264
|
+
process.stdin.on("end", () => {
|
|
265
|
+
process.stderr.write("[viyv-browser:native-host] stdin closed, shutting down\n");
|
|
266
|
+
socket?.destroy();
|
|
267
|
+
process.exit(0);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/server.ts
|
|
272
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
273
|
+
import { existsSync, statSync, unlinkSync } from "fs";
|
|
274
|
+
import http from "http";
|
|
275
|
+
import { createServer } from "net";
|
|
276
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
277
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
278
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
279
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
280
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
281
|
+
import { z as z2 } from "zod";
|
|
282
|
+
|
|
81
283
|
// src/agent-session.ts
|
|
82
284
|
import { randomUUID } from "crypto";
|
|
83
285
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -130,10 +332,8 @@ function cleanupStaleSessions() {
|
|
|
130
332
|
}
|
|
131
333
|
}
|
|
132
334
|
if (cleaned > 0) {
|
|
133
|
-
process.stderr.write(
|
|
134
|
-
|
|
135
|
-
`
|
|
136
|
-
);
|
|
335
|
+
process.stderr.write(`[viyv-browser:mcp] Cleaned up ${cleaned} stale session(s)
|
|
336
|
+
`);
|
|
137
337
|
}
|
|
138
338
|
return cleaned;
|
|
139
339
|
}
|
|
@@ -201,26 +401,15 @@ function isExtensionConnected() {
|
|
|
201
401
|
return true;
|
|
202
402
|
}
|
|
203
403
|
|
|
204
|
-
// src/native-host/compression.ts
|
|
205
|
-
import { gzipSync, gunzipSync } from "zlib";
|
|
206
|
-
var CHUNK_SIZE = 768 * 1024;
|
|
207
|
-
function compressPayload(data) {
|
|
208
|
-
const original = Buffer.from(data, "utf-8");
|
|
209
|
-
const gzipped = gzipSync(original);
|
|
210
|
-
if (gzipped.length < original.length) {
|
|
211
|
-
return { compressed: gzipped.toString("base64"), wasCompressed: true };
|
|
212
|
-
}
|
|
213
|
-
return { compressed: data, wasCompressed: false };
|
|
214
|
-
}
|
|
215
|
-
function decompressPayload(data, isCompressed) {
|
|
216
|
-
if (!isCompressed) return data;
|
|
217
|
-
const buf = Buffer.from(data, "base64");
|
|
218
|
-
return gunzipSync(buf).toString("utf-8");
|
|
219
|
-
}
|
|
220
|
-
|
|
221
404
|
// src/tools/index.ts
|
|
222
405
|
import { z } from "zod";
|
|
223
406
|
|
|
407
|
+
// src/tools/advanced/file-upload.ts
|
|
408
|
+
var FILE_UPLOAD_DESCRIPTION = `Upload local files to a file input element on the page using absolute file paths.
|
|
409
|
+
Do not click on file input elements \u2014 clicking opens a native file picker dialog.
|
|
410
|
+
Instead, use read_page or find to locate the file input, then use this tool with its ref.
|
|
411
|
+
The paths must be absolute paths accessible from the machine running Chrome.`;
|
|
412
|
+
|
|
224
413
|
// src/tools/advanced/gif-creator.ts
|
|
225
414
|
var GIF_CREATOR_DESCRIPTION = `Record a GIF of browser activity.
|
|
226
415
|
Captures a sequence of screenshots over a specified duration
|
|
@@ -261,10 +450,10 @@ allowing the user to approve, modify, or reject the plan before
|
|
|
261
450
|
execution begins.`;
|
|
262
451
|
|
|
263
452
|
// src/tools/advanced/upload-image.ts
|
|
264
|
-
var UPLOAD_IMAGE_DESCRIPTION = `Upload
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
common image formats including PNG, JPEG, GIF, and WebP.`;
|
|
453
|
+
var UPLOAD_IMAGE_DESCRIPTION = `Upload a previously captured screenshot or user-uploaded image to a file input or drag & drop target.
|
|
454
|
+
Provide imageId from a prior screenshot action. Supports targeting by
|
|
455
|
+
element ref (for file inputs) or coordinate (for drag & drop).
|
|
456
|
+
Supports common image formats including PNG, JPEG, GIF, and WebP.`;
|
|
268
457
|
|
|
269
458
|
// src/tools/core/click.ts
|
|
270
459
|
var CLICK_DESCRIPTION = `Click at coordinates or on a referenced element.
|
|
@@ -305,13 +494,15 @@ is actively displayed on the page.`;
|
|
|
305
494
|
// src/tools/core/hover.ts
|
|
306
495
|
var HOVER_DESCRIPTION = `Move mouse to coordinates or element without clicking.
|
|
307
496
|
|
|
497
|
+
Provide either coordinate [x, y] or ref (element reference from read_page/find).
|
|
308
498
|
Useful for revealing tooltips, dropdown menus, or triggering hover states.`;
|
|
309
499
|
|
|
310
500
|
// src/tools/core/javascript-exec.ts
|
|
311
501
|
var JAVASCRIPT_EXEC_DESCRIPTION = `Execute arbitrary JavaScript code in the page context.
|
|
312
502
|
The script runs in the main world of the active page and can access
|
|
313
503
|
the DOM, window object, and any page-level APIs. Returns the
|
|
314
|
-
serialized result of the last evaluated expression
|
|
504
|
+
serialized result of the last evaluated expression.
|
|
505
|
+
Supports top-level await (e.g., "await fetch('/api').then(r => r.json())").`;
|
|
315
506
|
|
|
316
507
|
// src/tools/core/key.ts
|
|
317
508
|
var KEY_DESCRIPTION = `Press keyboard key(s).
|
|
@@ -320,62 +511,690 @@ Space-separated key names. Supports keyboard shortcuts.
|
|
|
320
511
|
Examples: "Enter", "Tab", "Backspace", "ctrl+a", "cmd+c".
|
|
321
512
|
Use repeat parameter for repeated presses (e.g., arrow keys).`;
|
|
322
513
|
|
|
323
|
-
// src/tools/core/navigate.ts
|
|
324
|
-
var NAVIGATE_DESCRIPTION = `Navigate to a URL, or go forward/back in browser history.
|
|
514
|
+
// src/tools/core/navigate.ts
|
|
515
|
+
var NAVIGATE_DESCRIPTION = `Navigate to a URL, or go forward/back in browser history.
|
|
516
|
+
|
|
517
|
+
Use "back" to go back in history, "forward" to go forward.
|
|
518
|
+
Provide a full URL (with protocol) to navigate to a new page.
|
|
519
|
+
Waits for page load to complete before returning.`;
|
|
520
|
+
|
|
521
|
+
// src/tools/core/read-page.ts
|
|
522
|
+
var READ_PAGE_DESCRIPTION = `Get accessibility tree representation of page elements.
|
|
523
|
+
|
|
524
|
+
Supports:
|
|
525
|
+
- filter: "interactive" for buttons/links/inputs only, "all" for everything
|
|
526
|
+
- depth: max tree depth (1-20, default 8)
|
|
527
|
+
- refId: focus on a specific element subtree
|
|
528
|
+
- maxChars: limit output size (default 50000)
|
|
529
|
+
|
|
530
|
+
Returns elements with ref IDs that can be used with click, form_input, etc.`;
|
|
531
|
+
|
|
532
|
+
// src/tools/core/screenshot.ts
|
|
533
|
+
var SCREENSHOT_DESCRIPTION = `Take a screenshot of a tab.
|
|
534
|
+
|
|
535
|
+
Returns image as an MCP ImageContent block, plus metadata (imageId) as text.
|
|
536
|
+
Default format is JPEG with quality 80.
|
|
537
|
+
|
|
538
|
+
IMPORTANT - Token-efficient alternatives (prefer these when possible):
|
|
539
|
+
- read_page: Get accessibility tree with element text, values, and states
|
|
540
|
+
- get_page_text: Extract all readable text from the page
|
|
541
|
+
- find: Locate elements by natural language description
|
|
542
|
+
Use screenshot only when visual verification is needed (layout, styling, images, charts).
|
|
543
|
+
|
|
544
|
+
For example: to verify text was typed into an input, use read_page to check the
|
|
545
|
+
input's value \u2014 don't take a screenshot. To confirm a page loaded, check the
|
|
546
|
+
accessibility tree for expected headings or elements.
|
|
547
|
+
|
|
548
|
+
Use ref to capture just a specific element (with padding) instead of the full page.
|
|
549
|
+
This dramatically reduces image size. If both ref and region are provided, region
|
|
550
|
+
takes precedence. The response includes an imageId for use with upload_image.`;
|
|
551
|
+
|
|
552
|
+
// src/tools/core/scroll.ts
|
|
553
|
+
var SCROLL_DESCRIPTION = `Scroll in a direction at given coordinates, or scroll an element into view by ref.
|
|
554
|
+
|
|
555
|
+
Two modes:
|
|
556
|
+
1. Directional scroll: provide coordinate + direction (+ optional amount 1-10, default 3).
|
|
557
|
+
2. Scroll to element: provide ref (element reference ID from read_page/find). Scrolls the element into view using smooth scrolling.`;
|
|
558
|
+
|
|
559
|
+
// src/tools/core/type.ts
|
|
560
|
+
var TYPE_DESCRIPTION = `Type text into the currently focused element.
|
|
561
|
+
|
|
562
|
+
Each character is typed individually with keyDown/keyUp events.
|
|
563
|
+
For special keys (Enter, Tab, etc.), use the 'key' tool instead.`;
|
|
564
|
+
|
|
565
|
+
// src/tools/core/wait-for.ts
|
|
566
|
+
var WAIT_FOR_DESCRIPTION = `Wait for a specified condition before proceeding.
|
|
567
|
+
Supports waiting for a CSS selector to appear in the DOM,
|
|
568
|
+
for a navigation event to complete, or for a fixed timeout duration.
|
|
569
|
+
Useful for synchronizing with asynchronous page updates.
|
|
570
|
+
At least one of selector, navigation, or timeout must be specified.`;
|
|
571
|
+
|
|
572
|
+
// src/tools/debug/read-console-messages.ts
|
|
573
|
+
var READ_CONSOLE_MESSAGES_DESCRIPTION = `Read console messages captured from the page.
|
|
574
|
+
Returns log, warning, error, and info messages that have been
|
|
575
|
+
emitted by page scripts. Supports filtering by log level
|
|
576
|
+
and limiting the number of returned entries.
|
|
577
|
+
Use the pattern parameter to filter messages by regex, and
|
|
578
|
+
the clear parameter to remove messages after reading.`;
|
|
579
|
+
|
|
580
|
+
// src/tools/debug/read-network-requests.ts
|
|
581
|
+
var READ_NETWORK_REQUESTS_DESCRIPTION = `Read network requests captured from the page.
|
|
582
|
+
Returns details of HTTP requests and responses including URL, method,
|
|
583
|
+
status code, headers, and timing information. Supports filtering
|
|
584
|
+
by URL pattern or resource type.`;
|
|
585
|
+
|
|
586
|
+
// src/tools/semantic/sm-add-action.ts
|
|
587
|
+
var SM_ADD_ACTION_DESCRIPTION = `Define a semantic action (step sequence) for a registered page.
|
|
588
|
+
|
|
589
|
+
Creates an executable action with typed parameters and ordered steps.
|
|
590
|
+
Steps reference targets by target_id and support param_ref for dynamic values.
|
|
591
|
+
|
|
592
|
+
Parameters:
|
|
593
|
+
- page_id: the page this action belongs to
|
|
594
|
+
- label: action name (e.g., "Login")
|
|
595
|
+
- verb: action verb (e.g., "login", "submit", "search")
|
|
596
|
+
- description (optional): LLM-facing description for discovery via sm_capabilities
|
|
597
|
+
- params[]: parameter definitions [{name, type, required, secret, description, default_value}]
|
|
598
|
+
- steps[]: ordered step sequence (minimum 1):
|
|
599
|
+
- type: 'click' | 'type' | 'select' | 'wait' | 'scroll' | 'key' | 'navigate'
|
|
600
|
+
- target_id: target to act on
|
|
601
|
+
- params: {text, value, keys, direction, amount, url, param_ref}
|
|
602
|
+
- pre_wait: {strategy, selector, timeout_ms}
|
|
603
|
+
- description: step description
|
|
604
|
+
- retry_policy (optional): {max_retries:0-5, backoff, base_delay_ms, jitter}
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
- action_id, step_count
|
|
608
|
+
- validation: {valid, warnings[]} with target/param reference checks`;
|
|
609
|
+
|
|
610
|
+
// src/tools/semantic/sm-add-fetch.ts
|
|
611
|
+
var SM_ADD_FETCH_DESCRIPTION = `Define a data extraction rule (fetch) for a registered page.
|
|
612
|
+
|
|
613
|
+
Creates a structured data extraction definition that maps DOM elements to typed output fields.
|
|
614
|
+
Supports text, attribute, list, count, and existence extraction types.
|
|
615
|
+
|
|
616
|
+
Parameters:
|
|
617
|
+
- page_id: the page this fetch belongs to
|
|
618
|
+
- label: fetch name (e.g., "Get Posts")
|
|
619
|
+
- type_name: output type name (e.g., "posts", "profile")
|
|
620
|
+
- description (optional): LLM-facing description for discovery via sm_capabilities
|
|
621
|
+
- fields[]: extraction field definitions (minimum 1):
|
|
622
|
+
- name: output field name
|
|
623
|
+
- target_id: target element to extract from
|
|
624
|
+
- extraction_type: 'text' | 'attribute' | 'list' | 'count' | 'exists'
|
|
625
|
+
- attribute: attribute name (for 'attribute' type)
|
|
626
|
+
- item_selector: CSS selector for list items (for 'list' type)
|
|
627
|
+
- item_fields[]: structured extraction for each list item (for 'list' type):
|
|
628
|
+
- name: output field name
|
|
629
|
+
- selector: CSS selector relative to each list item (use ":scope" for item element itself)
|
|
630
|
+
- extraction_type: 'text' | 'attribute' | 'exists'
|
|
631
|
+
- attribute: attribute name (for 'attribute' type)
|
|
632
|
+
- limits (optional): {max_chars:1000-200000, max_items:1-1000}
|
|
633
|
+
|
|
634
|
+
Returns:
|
|
635
|
+
- fetch_id, field_count
|
|
636
|
+
- validation: {valid, warnings[]} with target reference checks`;
|
|
637
|
+
|
|
638
|
+
// src/tools/semantic/sm-add-target.ts
|
|
639
|
+
var SM_ADD_TARGET_DESCRIPTION = `Create or update semantic targets (element locators) for a registered page.
|
|
640
|
+
|
|
641
|
+
Supports batch creation and re-binding of existing targets.
|
|
642
|
+
Use ref from read_page/find to auto-generate locators, or provide locators explicitly.
|
|
643
|
+
|
|
644
|
+
Parameters:
|
|
645
|
+
- tabId: target tab where elements are present
|
|
646
|
+
- page_id: the page these targets belong to
|
|
647
|
+
- targets[]: array of target definitions:
|
|
648
|
+
- target_id (optional): specify to re-bind an existing target's locators
|
|
649
|
+
- label: target name (e.g., "Username Input")
|
|
650
|
+
- role: semantic role (e.g., "textbox", "button")
|
|
651
|
+
- ref (optional): element ref from read_page/find for auto-locator generation
|
|
652
|
+
- locators (optional): explicit locator array [{type, value, stability}]
|
|
653
|
+
|
|
654
|
+
Each target must have either ref or locators.
|
|
655
|
+
When target_id is provided, only locators are updated (label/role preserved).
|
|
656
|
+
|
|
657
|
+
Returns per target:
|
|
658
|
+
- target_id, label, locators, resolve_test (live element check), updated (true = re-bind)`;
|
|
659
|
+
|
|
660
|
+
// src/tools/semantic/sm-capabilities.ts
|
|
661
|
+
var SM_CAPABILITIES_DESCRIPTION = `Query semantic capabilities available for the current page.
|
|
662
|
+
|
|
663
|
+
Returns the matched page definition, available actions, and fetches for the active tab.
|
|
664
|
+
Uses page signature matching (URL pattern + DOM markers) to identify the current page state.
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
- ok: whether a page match was found
|
|
668
|
+
- domain: hostname of the current tab
|
|
669
|
+
- page_id / page_type / variant_id: matched page and variant
|
|
670
|
+
- match_score: confidence score of the match
|
|
671
|
+
- actions[]: available actions with their parameters
|
|
672
|
+
- fetches[]: available data extractions with their parameters
|
|
673
|
+
- other_pages[]: other registered pages on the same domain (for cross-page navigation)
|
|
674
|
+
- hint: guidance message when no page matches
|
|
675
|
+
|
|
676
|
+
Use this before sm_invoke or sm_fetch to discover what operations are available.
|
|
677
|
+
If no page matches (ok: false), use sm_register_page to register the current page,
|
|
678
|
+
then sm_add_target and sm_add_action/sm_add_fetch to configure automation.
|
|
679
|
+
Use sm_status to review all existing configurations.`;
|
|
680
|
+
|
|
681
|
+
// src/tools/semantic/sm-custom-view.ts
|
|
682
|
+
var SM_CUSTOM_VIEW_CREATE_DESCRIPTION = `Create a custom HTML view that can be opened in a new tab.
|
|
683
|
+
|
|
684
|
+
Use this to build dashboards, charts, or reports using HTML/CSS/JS. The HTML can reference external CDN libraries (e.g., Chart.js, D3) and access bound data via window.__VIYV_DATA__.
|
|
685
|
+
|
|
686
|
+
Parameters:
|
|
687
|
+
- label: view name (max 100 chars)
|
|
688
|
+
- description: optional description (max 500 chars)
|
|
689
|
+
- html: full HTML document (max 200KB)
|
|
690
|
+
- data_bindings: optional array of data source references (max 10)
|
|
691
|
+
- binding_id: unique ID within this view (e.g., "b1")
|
|
692
|
+
- source_type: "job_report" | "report" | "job" | "scenario"
|
|
693
|
+
- source_id: entity ID (e.g., "JRP_xxxx")
|
|
694
|
+
- projection: optional {fields, max_entries} to limit injected data
|
|
695
|
+
|
|
696
|
+
The HTML should reference data as window.__VIYV_DATA__[binding_id]. Data is injected when the user opens the view.
|
|
697
|
+
|
|
698
|
+
Returns: {ok, view_id, html_size, binding_count}`;
|
|
699
|
+
var SM_CUSTOM_VIEW_UPDATE_DESCRIPTION = `Update an existing custom view.
|
|
700
|
+
|
|
701
|
+
Use this to refresh HTML after data structure changes (stale bindings) or to modify the view content.
|
|
702
|
+
|
|
703
|
+
Parameters:
|
|
704
|
+
- view_id: view to update
|
|
705
|
+
- label: new label (optional)
|
|
706
|
+
- description: new description (optional)
|
|
707
|
+
- html: new HTML content (optional, max 200KB)
|
|
708
|
+
- data_bindings: new bindings array (optional, replaces all bindings)
|
|
709
|
+
|
|
710
|
+
Returns: {ok, view_id, html_size, binding_count}`;
|
|
711
|
+
var SM_CUSTOM_VIEW_DELETE_DESCRIPTION = `Delete a custom view.
|
|
712
|
+
|
|
713
|
+
Parameters:
|
|
714
|
+
- view_id: view to delete
|
|
715
|
+
|
|
716
|
+
Returns: {ok, view_id}`;
|
|
717
|
+
var SM_CUSTOM_VIEW_LIST_DESCRIPTION = `List all custom views with binding health status.
|
|
718
|
+
|
|
719
|
+
Use this to check which views exist and whether their data bindings are still valid. Each view includes binding_status for every binding:
|
|
720
|
+
- "ok": source exists and structure matches
|
|
721
|
+
- "stale": source exists but structure changed (stale_reason: "schema_changed", includes current vs saved fingerprint)
|
|
722
|
+
- "missing": source has been deleted
|
|
723
|
+
|
|
724
|
+
When bindings are stale, use sm_custom_view_get with include_data=true to fetch current data, then sm_custom_view_update to refresh the HTML.
|
|
725
|
+
|
|
726
|
+
Returns: {ok, views[]: {view_id, label, description, html_size, binding_count, binding_status[], created_at, updated_at}}`;
|
|
727
|
+
var SM_CUSTOM_VIEW_GET_DESCRIPTION = `Get a custom view with optional data injection.
|
|
728
|
+
|
|
729
|
+
Parameters:
|
|
730
|
+
- view_id: view to retrieve
|
|
731
|
+
- include_data: if true, resolve all bindings and return actual data in injected_data (default: false)
|
|
732
|
+
|
|
733
|
+
When include_data=true:
|
|
734
|
+
- injected_data maps binding_id to resolved data from the source
|
|
735
|
+
- Data is filtered by projection (fields, max_entries) if specified
|
|
736
|
+
- Secret parameter values are masked
|
|
737
|
+
|
|
738
|
+
binding_status shows current health of each binding (ok/stale/missing).
|
|
739
|
+
|
|
740
|
+
Returns: {ok, view, binding_status[], injected_data?}`;
|
|
741
|
+
|
|
742
|
+
// src/tools/semantic/sm-delete.ts
|
|
743
|
+
var SM_DELETE_DESCRIPTION = `Delete a semantic entity (page, target, action, or fetch).
|
|
744
|
+
|
|
745
|
+
Supports dry_run mode to preview deletion impact before committing.
|
|
746
|
+
Page deletion cascades to all dependent targets, actions, and fetches.
|
|
747
|
+
Target deletion warns about referencing actions and fetches.
|
|
748
|
+
|
|
749
|
+
Parameters:
|
|
750
|
+
- entity_type: 'page' | 'target' | 'action' | 'fetch'
|
|
751
|
+
- entity_id: the entity ID to delete
|
|
752
|
+
- dry_run (optional): if true, return impact preview without deleting
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
- entity_type, entity_id, label
|
|
756
|
+
- dry_run: whether this was a preview
|
|
757
|
+
- cascaded: (page deletion) lists of deleted targets, actions, fetches
|
|
758
|
+
- warnings: (target deletion) referencing actions/fetches that may break`;
|
|
759
|
+
|
|
760
|
+
// src/tools/semantic/sm-export.ts
|
|
761
|
+
var SM_EXPORT_DESCRIPTION = `Export semantic configuration as JSON.
|
|
762
|
+
|
|
763
|
+
Exports pages, targets, actions, and fetches. Optionally filter by domain.
|
|
764
|
+
Secret parameter default values are stripped from the export.
|
|
765
|
+
Includes a SHA-256 checksum for integrity verification on import.
|
|
766
|
+
|
|
767
|
+
Parameters:
|
|
768
|
+
- domain (optional): export only pages matching this domain (subdomain-aware). If omitted, exports all.
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
- SemanticExport JSON with schema_version, checksum, pages[], targets[], actions[], fetches[]`;
|
|
772
|
+
|
|
773
|
+
// src/tools/semantic/sm-fetch.ts
|
|
774
|
+
var SM_FETCH_DESCRIPTION = `Extract structured data from the current page using predefined extraction rules.
|
|
775
|
+
|
|
776
|
+
Resolves target elements and extracts text, attributes, lists, counts, or existence checks.
|
|
777
|
+
Applies output limits (max_chars, max_items) to prevent oversized responses.
|
|
778
|
+
|
|
779
|
+
Parameters:
|
|
780
|
+
- tabId: target tab
|
|
781
|
+
- fetch_id: the fetch definition to execute (from sm_capabilities)
|
|
782
|
+
- params: optional parameters for the fetch
|
|
783
|
+
|
|
784
|
+
Returns:
|
|
785
|
+
- ok: whether extraction succeeded
|
|
786
|
+
- data: extracted key-value data matching the fetch's return_schema
|
|
787
|
+
- page_id_before / page_id_after: page state tracking
|
|
788
|
+
- needs_rebind: true if any targets could not be resolved
|
|
789
|
+
- missing_targets[]: list of target IDs that failed to resolve
|
|
790
|
+
- hint: recovery guidance when errors occur
|
|
791
|
+
|
|
792
|
+
When needs_rebind is true, use sm_test_target to diagnose each failing target,
|
|
793
|
+
then sm_add_target with target_id to re-bind locators.
|
|
794
|
+
When fetch_id is not found, use sm_capabilities to discover available fetches.`;
|
|
795
|
+
|
|
796
|
+
// src/tools/semantic/sm-import.ts
|
|
797
|
+
var SM_IMPORT_DESCRIPTION = `Import semantic configuration from JSON.
|
|
798
|
+
|
|
799
|
+
Imports pages, targets, actions, and fetches from a SemanticExport JSON object.
|
|
800
|
+
Verifies checksum integrity before importing.
|
|
801
|
+
|
|
802
|
+
Parameters:
|
|
803
|
+
- data (required): SemanticExport JSON object (from sm_export)
|
|
804
|
+
- strategy (optional): "merge" (default) adds new entities, skips existing IDs.
|
|
805
|
+
"replace-domain" deletes all existing pages matching the import's domains
|
|
806
|
+
(with cascade to targets/actions/fetches), then inserts all import entities.
|
|
807
|
+
|
|
808
|
+
Returns:
|
|
809
|
+
- ok: whether import succeeded
|
|
810
|
+
- imported: number of entities imported
|
|
811
|
+
- skipped: number of entities skipped (existing IDs in merge mode)
|
|
812
|
+
- errors[]: any error messages`;
|
|
813
|
+
|
|
814
|
+
// src/tools/semantic/sm-invoke.ts
|
|
815
|
+
var SM_INVOKE_DESCRIPTION = `Execute a semantic action on the current page.
|
|
816
|
+
|
|
817
|
+
Runs a predefined sequence of steps (click, type, select, wait, etc.) using
|
|
818
|
+
stable element locators with automatic fallback. Handles navigation detection,
|
|
819
|
+
retry with jitter, and template parameter substitution.
|
|
820
|
+
|
|
821
|
+
Parameters:
|
|
822
|
+
- tabId: target tab
|
|
823
|
+
- action_id: the action to execute (from sm_capabilities)
|
|
824
|
+
- params: key-value parameters for template substitution (e.g., { username: "..." })
|
|
825
|
+
|
|
826
|
+
Returns:
|
|
827
|
+
- ok: whether all steps completed successfully
|
|
828
|
+
- completed_steps / total_steps: execution progress
|
|
829
|
+
- page_id_before / page_id_after: page state tracking (navigation detection)
|
|
830
|
+
- observations[]: human-readable log of what happened (secrets redacted)
|
|
831
|
+
- needs_rebind: true if any targets could not be resolved
|
|
832
|
+
- missing_targets[]: list of target IDs that failed to resolve
|
|
833
|
+
- hint: recovery guidance when errors occur
|
|
834
|
+
|
|
835
|
+
When needs_rebind is true, use sm_test_target to diagnose each failing target,
|
|
836
|
+
then sm_add_target with target_id to re-bind locators.
|
|
837
|
+
When action_id is not found, use sm_capabilities to discover available actions.`;
|
|
838
|
+
|
|
839
|
+
// src/tools/semantic/sm-job-cancel.ts
|
|
840
|
+
var SM_JOB_CANCEL_DESCRIPTION = `Cancel a running job and its currently executing scenario.
|
|
841
|
+
|
|
842
|
+
Remaining unexecuted entries are marked as skipped.
|
|
843
|
+
|
|
844
|
+
Parameters:
|
|
845
|
+
- tabId: tab where the job is running
|
|
846
|
+
|
|
847
|
+
Returns:
|
|
848
|
+
- ok: success status
|
|
849
|
+
- cancelled: true if a job was found and cancelled`;
|
|
850
|
+
|
|
851
|
+
// src/tools/semantic/sm-job-create.ts
|
|
852
|
+
var SM_JOB_CREATE_DESCRIPTION = `Create a reusable job that groups multiple scenarios for sequential batch execution.
|
|
853
|
+
|
|
854
|
+
Each entry specifies a scenario_id and optional parameter overrides. When executed, scenarios
|
|
855
|
+
run one after another on the same tab, each producing its own Report.
|
|
856
|
+
|
|
857
|
+
Parameters:
|
|
858
|
+
- label: job name (e.g., "Print Price Collection")
|
|
859
|
+
- description: optional description
|
|
860
|
+
- entries: ordered list of scenarios to execute
|
|
861
|
+
- scenario_id: existing scenario ID
|
|
862
|
+
- label: display label override (uses scenario label if omitted)
|
|
863
|
+
- params: parameter overrides for this scenario execution
|
|
864
|
+
- on_error: 'stop' (default) halts on first failure, 'skip_and_continue' skips failed and continues
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
- job_id: the created job ID (JOB_xxxx)
|
|
868
|
+
- entry_count: number of entries`;
|
|
869
|
+
|
|
870
|
+
// src/tools/semantic/sm-job-delete.ts
|
|
871
|
+
var SM_JOB_DELETE_DESCRIPTION = `Delete a job definition.
|
|
872
|
+
|
|
873
|
+
Running executions are not affected \u2014 only the job definition is removed.
|
|
874
|
+
|
|
875
|
+
Parameters:
|
|
876
|
+
- job_id: job to delete
|
|
877
|
+
|
|
878
|
+
Returns:
|
|
879
|
+
- ok: success status
|
|
880
|
+
- job_id: deleted job ID`;
|
|
881
|
+
|
|
882
|
+
// src/tools/semantic/sm-job-history.ts
|
|
883
|
+
var SM_JOB_HISTORY_DESCRIPTION = `Get execution history for a job. Returns condensed snapshots that persist beyond report pruning.
|
|
884
|
+
|
|
885
|
+
Use this to review past executions, track trends over time (e.g. price changes), or select snapshots for comparison.
|
|
886
|
+
|
|
887
|
+
Parameters:
|
|
888
|
+
- job_id: job to get history for
|
|
889
|
+
- limit: max snapshots to return (default: 20, most recent first)
|
|
890
|
+
|
|
891
|
+
Returns:
|
|
892
|
+
- job_id, job_label
|
|
893
|
+
- snapshots[]: list of {snapshot_id, status, started_at, completed_at, duration_ms, scenario_count}`;
|
|
894
|
+
var SM_JOB_COMPARE_DESCRIPTION = `Compare two job executions to see data changes between them.
|
|
895
|
+
|
|
896
|
+
Matches scenarios by scenario_id, data groups by result_key, and rows by loop_params. Computes per-cell diffs with numeric deltas.
|
|
897
|
+
|
|
898
|
+
Parameters:
|
|
899
|
+
- job_id: the job to compare
|
|
900
|
+
- base_snapshot_id: older snapshot (optional, defaults to second-most-recent)
|
|
901
|
+
- compare_snapshot_id: newer snapshot (optional, defaults to most-recent)
|
|
902
|
+
- changed_only: if true, omit unchanged rows (default: false)
|
|
903
|
+
|
|
904
|
+
Returns:
|
|
905
|
+
- diffs[]: per-scenario, per-result_key diff with rows showing added/removed/modified/unchanged
|
|
906
|
+
- summary: total counts of changes`;
|
|
907
|
+
var SM_JOB_SNAPSHOT_GET_DESCRIPTION = `Get detailed data from a specific job snapshot.
|
|
908
|
+
|
|
909
|
+
Use this to inspect the full captured data for a single execution, including all scenario results and data groups.
|
|
910
|
+
|
|
911
|
+
Parameters:
|
|
912
|
+
- snapshot_id: the snapshot to retrieve (SNP_xxxx)
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
- snapshot: full JobSnapshot with scenario_snapshots and data_groups`;
|
|
916
|
+
|
|
917
|
+
// src/tools/semantic/sm-job-list.ts
|
|
918
|
+
var SM_JOB_LIST_DESCRIPTION = `List all defined jobs with entry counts and configuration.
|
|
919
|
+
|
|
920
|
+
Returns:
|
|
921
|
+
- jobs: array of job summaries
|
|
922
|
+
- job_id, label, description, entry_count, on_error, created_at`;
|
|
923
|
+
|
|
924
|
+
// src/tools/semantic/sm-job-report-export.ts
|
|
925
|
+
var SM_JOB_REPORT_EXPORT_DESCRIPTION = `Export all scenario results from a job execution as aggregated JSON or CSV.
|
|
926
|
+
|
|
927
|
+
Combines data from all linked scenario reports into a single file.
|
|
928
|
+
CSV includes a job_entry column to distinguish scenarios.
|
|
929
|
+
|
|
930
|
+
Parameters:
|
|
931
|
+
- job_report_id: job report ID (JRP_xxxx)
|
|
932
|
+
- format: 'json' (default) or 'csv'
|
|
933
|
+
|
|
934
|
+
Returns:
|
|
935
|
+
- data: export content string
|
|
936
|
+
- filename: suggested filename`;
|
|
937
|
+
|
|
938
|
+
// src/tools/semantic/sm-job-report-get.ts
|
|
939
|
+
var SM_JOB_REPORT_GET_DESCRIPTION = `Get job execution status and results.
|
|
940
|
+
|
|
941
|
+
Shows overall progress and per-scenario report summaries.
|
|
942
|
+
Use sm_report_get with individual report_ids for detailed scenario data.
|
|
943
|
+
|
|
944
|
+
Parameters:
|
|
945
|
+
- job_report_id: job report ID (JRP_xxxx)
|
|
946
|
+
|
|
947
|
+
Returns:
|
|
948
|
+
- job_report: full JobReport with entry statuses
|
|
949
|
+
- scenario_summaries: per-scenario report summaries (entry_count, error_count)`;
|
|
950
|
+
|
|
951
|
+
// src/tools/semantic/sm-job-run.ts
|
|
952
|
+
var SM_JOB_RUN_DESCRIPTION = `Execute a job on a browser tab. Scenarios run sequentially.
|
|
953
|
+
|
|
954
|
+
Returns job_report_id immediately by default. Poll with sm_job_report_get to check progress.
|
|
955
|
+
Set wait_for_completion=true to block until all scenarios finish.
|
|
956
|
+
|
|
957
|
+
Only one job or scenario can run per tab at a time.
|
|
958
|
+
|
|
959
|
+
Parameters:
|
|
960
|
+
- tabId: target tab for execution
|
|
961
|
+
- job_id: the job to execute
|
|
962
|
+
- params: optional per-scenario param overrides keyed by scenario_id
|
|
963
|
+
- wait_for_completion: if true, wait for all scenarios to complete (default: false)
|
|
964
|
+
|
|
965
|
+
Returns:
|
|
966
|
+
- job_report_id: ID of the created job report (JRP_xxxx)
|
|
967
|
+
- job_id: the executed job
|
|
968
|
+
- status: 'running' (async) or 'completed'/'failed' (when wait_for_completion=true)`;
|
|
969
|
+
|
|
970
|
+
// src/tools/semantic/sm-job-update.ts
|
|
971
|
+
var SM_JOB_UPDATE_DESCRIPTION = `Update an existing job definition.
|
|
972
|
+
|
|
973
|
+
Supports partial updates \u2014 only specified fields are changed.
|
|
974
|
+
|
|
975
|
+
Parameters:
|
|
976
|
+
- job_id: job to update
|
|
977
|
+
- label: new name (optional)
|
|
978
|
+
- description: new description (optional)
|
|
979
|
+
- entries: new entry list (optional, replaces all entries)
|
|
980
|
+
- on_error: new error handling strategy (optional)
|
|
981
|
+
|
|
982
|
+
Returns:
|
|
983
|
+
- job_id: updated job ID
|
|
984
|
+
- entry_count: number of entries after update`;
|
|
985
|
+
|
|
986
|
+
// src/tools/semantic/sm-register-page.ts
|
|
987
|
+
var SM_REGISTER_PAGE_DESCRIPTION = `Register a page for semantic automation.
|
|
988
|
+
|
|
989
|
+
Creates a page definition with URL pattern matching and DOM markers.
|
|
990
|
+
Idempotent: if a registered page already matches the current tab, returns the existing page_id.
|
|
991
|
+
When page_type is specified on an already-registered page, it updates the page_type.
|
|
992
|
+
|
|
993
|
+
Pages are grouped by domain. Use page_type to distinguish different page types
|
|
994
|
+
within the same domain (e.g., "profile", "compose", "search").
|
|
995
|
+
|
|
996
|
+
Parameters:
|
|
997
|
+
- tabId: target tab (used to auto-derive url_pattern and domains if omitted)
|
|
998
|
+
- label: human-readable page name (e.g., "GitHub Login")
|
|
999
|
+
- page_type: page type within the domain (e.g., "profile", "compose", "search")
|
|
1000
|
+
- url_pattern: URL regex (auto-derived from tab URL if omitted)
|
|
1001
|
+
- domains: domain scope array (auto-derived from tab hostname if omitted)
|
|
1002
|
+
- auth_state: 'logged_in' | 'logged_out' | 'any' (default: 'any')
|
|
1003
|
+
- must_exist: CSS selectors that must be present on the page
|
|
1004
|
+
- must_not_exist: CSS selectors that must not be present
|
|
1005
|
+
- title_regex: regex to match page title
|
|
1006
|
+
|
|
1007
|
+
Returns:
|
|
1008
|
+
- page_id, page_type, variant_id, label
|
|
1009
|
+
- already_registered: true if an existing page matched
|
|
1010
|
+
- match_test: result of matching the current tab against the page definition`;
|
|
1011
|
+
|
|
1012
|
+
// src/tools/semantic/sm-report-delete.ts
|
|
1013
|
+
var SM_REPORT_DELETE_DESCRIPTION = `Delete an execution report.
|
|
1014
|
+
|
|
1015
|
+
Permanently removes a report and its data.
|
|
1016
|
+
|
|
1017
|
+
Parameters:
|
|
1018
|
+
- report_id: the report to delete
|
|
1019
|
+
|
|
1020
|
+
Returns:
|
|
1021
|
+
- ok: whether deletion succeeded
|
|
1022
|
+
- report_id: the deleted report ID`;
|
|
1023
|
+
|
|
1024
|
+
// src/tools/semantic/sm-report-export.ts
|
|
1025
|
+
var SM_REPORT_EXPORT_DESCRIPTION = `Export a report as JSON or CSV.
|
|
1026
|
+
|
|
1027
|
+
JSON format includes the complete report structure.
|
|
1028
|
+
CSV format flattens entries into rows with columns for loop parameters and data fields.
|
|
1029
|
+
CSV uses UTF-8 BOM for Excel compatibility with non-ASCII characters.
|
|
1030
|
+
|
|
1031
|
+
Parameters:
|
|
1032
|
+
- report_id: the report to export
|
|
1033
|
+
- format: 'json' (default) or 'csv'
|
|
1034
|
+
|
|
1035
|
+
Returns:
|
|
1036
|
+
- format: the export format used
|
|
1037
|
+
- data: the exported content as a string
|
|
1038
|
+
- filename: suggested filename for download`;
|
|
1039
|
+
|
|
1040
|
+
// src/tools/semantic/sm-report-get.ts
|
|
1041
|
+
var SM_REPORT_GET_DESCRIPTION = `Get detailed execution report.
|
|
1042
|
+
|
|
1043
|
+
Returns the full report including all data entries, observations, errors,
|
|
1044
|
+
and grouped data (entries organized by result_key).
|
|
1045
|
+
|
|
1046
|
+
Use this to poll running scenarios for progress updates.
|
|
1047
|
+
|
|
1048
|
+
Parameters:
|
|
1049
|
+
- report_id: the report to retrieve
|
|
1050
|
+
|
|
1051
|
+
Returns:
|
|
1052
|
+
- report: full Report object with entries[], grouped_data, observations[], errors[]
|
|
1053
|
+
- report.status: 'running' | 'completed' | 'failed' | 'cancelled'
|
|
1054
|
+
- report.entries[]: collected data with loop_params and extracted data
|
|
1055
|
+
- report.grouped_data: entries grouped by result_key for easy access`;
|
|
1056
|
+
|
|
1057
|
+
// src/tools/semantic/sm-report-list.ts
|
|
1058
|
+
var SM_REPORT_LIST_DESCRIPTION = `List execution reports.
|
|
1059
|
+
|
|
1060
|
+
Returns summaries of scenario execution reports, sorted by most recent first.
|
|
1061
|
+
Optionally filter by scenario_id.
|
|
1062
|
+
|
|
1063
|
+
Parameters:
|
|
1064
|
+
- scenario_id (optional): filter reports for a specific scenario
|
|
1065
|
+
|
|
1066
|
+
Returns:
|
|
1067
|
+
- reports[]: array of report summaries with status, timing, and entry/error counts`;
|
|
1068
|
+
|
|
1069
|
+
// src/tools/semantic/sm-scenario-create.ts
|
|
1070
|
+
var SM_SCENARIO_CREATE_DESCRIPTION = `Create a scenario for multi-page workflow automation.
|
|
1071
|
+
|
|
1072
|
+
Scenarios define sequences of steps (navigate, action, fetch, loop, wait) that execute
|
|
1073
|
+
across multiple pages. Loop steps support cartesian product iteration over variables
|
|
1074
|
+
(static values, select options, or previous fetch results).
|
|
1075
|
+
|
|
1076
|
+
Parameters:
|
|
1077
|
+
- label: scenario name (e.g., "Price Table Collection")
|
|
1078
|
+
- description: optional LLM-facing description
|
|
1079
|
+
- params: global parameter definitions (available as {{param_name}} templates)
|
|
1080
|
+
- steps: ordered step sequence
|
|
1081
|
+
- max_duration_ms: timeout (default: 300000ms = 5min)
|
|
1082
|
+
- on_error: 'stop' (default) or 'skip_and_continue'
|
|
1083
|
+
|
|
1084
|
+
Step types:
|
|
1085
|
+
- navigate: { url, wait_for_load } - navigate to URL (supports {{templates}})
|
|
1086
|
+
- action: { action_id, params } - execute an existing semantic action
|
|
1087
|
+
- fetch: { fetch_id, result_key, params } - extract data, stored in report under result_key
|
|
1088
|
+
- loop: { variables, steps, delay_between_ms, max_iterations } - iterate combinations
|
|
1089
|
+
- wait: { strategy, selector, timeout_ms } - wait for condition
|
|
1090
|
+
|
|
1091
|
+
Loop variables:
|
|
1092
|
+
- static: explicit value list
|
|
1093
|
+
- target_options: auto-extract all <option> values from a <select> target
|
|
1094
|
+
- previous_fetch: extract values from prior fetch results
|
|
1095
|
+
|
|
1096
|
+
Inside loops, use {{loop.variable_name}} to reference loop variables.
|
|
1097
|
+
|
|
1098
|
+
Returns:
|
|
1099
|
+
- scenario_id: the created scenario ID (SCN_xxxx)
|
|
1100
|
+
- step_count: total step count
|
|
1101
|
+
- validation: { valid, warnings[] } referential integrity check`;
|
|
1102
|
+
|
|
1103
|
+
// src/tools/semantic/sm-scenario-delete.ts
|
|
1104
|
+
var SM_SCENARIO_DELETE_DESCRIPTION = `Delete a scenario by ID.
|
|
1105
|
+
|
|
1106
|
+
Associated reports become orphaned but are not deleted.
|
|
1107
|
+
|
|
1108
|
+
Parameters:
|
|
1109
|
+
- scenario_id: the scenario to delete
|
|
1110
|
+
|
|
1111
|
+
Returns:
|
|
1112
|
+
- ok: whether deletion succeeded
|
|
1113
|
+
- scenario_id: the deleted scenario ID`;
|
|
1114
|
+
|
|
1115
|
+
// src/tools/semantic/sm-scenario-list.ts
|
|
1116
|
+
var SM_SCENARIO_LIST_DESCRIPTION = `List all registered scenarios.
|
|
1117
|
+
|
|
1118
|
+
Returns a summary of each scenario including ID, label, step count,
|
|
1119
|
+
parameter names, and creation timestamp.
|
|
1120
|
+
|
|
1121
|
+
No parameters required.
|
|
1122
|
+
|
|
1123
|
+
Returns:
|
|
1124
|
+
- scenarios[]: array of scenario summaries`;
|
|
1125
|
+
|
|
1126
|
+
// src/tools/semantic/sm-scenario-run.ts
|
|
1127
|
+
var SM_SCENARIO_RUN_DESCRIPTION = `Execute a scenario on a tab.
|
|
1128
|
+
|
|
1129
|
+
Starts scenario execution and returns immediately with a report_id.
|
|
1130
|
+
Use sm_report_get to poll for progress and final results.
|
|
1131
|
+
|
|
1132
|
+
Only one scenario can run per tab at a time.
|
|
325
1133
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
1134
|
+
Parameters:
|
|
1135
|
+
- tabId: target tab for execution
|
|
1136
|
+
- scenario_id: the scenario to execute
|
|
1137
|
+
- params: parameter values for template substitution
|
|
1138
|
+
- wait_for_completion: if true, wait for scenario to finish (up to max_duration_ms)
|
|
329
1139
|
|
|
330
|
-
|
|
331
|
-
|
|
1140
|
+
Returns:
|
|
1141
|
+
- report_id: ID of the created report (RPT_xxxx)
|
|
1142
|
+
- scenario_id: the executed scenario
|
|
1143
|
+
- status: 'running' (async) or 'completed'/'failed' (when wait_for_completion=true)
|
|
1144
|
+
- error: error message if startup failed`;
|
|
332
1145
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
- depth: max tree depth (1-20, default 8)
|
|
336
|
-
- refId: focus on a specific element subtree
|
|
337
|
-
- maxChars: limit output size (default 50000)
|
|
1146
|
+
// src/tools/semantic/sm-scenario-update.ts
|
|
1147
|
+
var SM_SCENARIO_UPDATE_DESCRIPTION = `Update an existing scenario.
|
|
338
1148
|
|
|
339
|
-
|
|
1149
|
+
Supports partial updates - only provide fields you want to change.
|
|
340
1150
|
|
|
341
|
-
|
|
342
|
-
|
|
1151
|
+
Parameters:
|
|
1152
|
+
- scenario_id: the scenario to update
|
|
1153
|
+
- label: new name
|
|
1154
|
+
- description: new description
|
|
1155
|
+
- params: new parameter definitions
|
|
1156
|
+
- steps: new step sequence
|
|
1157
|
+
- max_duration_ms: new timeout
|
|
1158
|
+
- on_error: new error handling strategy
|
|
343
1159
|
|
|
344
|
-
Returns
|
|
345
|
-
|
|
346
|
-
Use PNG for lossless screenshots when needed.
|
|
347
|
-
Optionally capture a specific region [x0, y0, x1, y1].`;
|
|
1160
|
+
Returns:
|
|
1161
|
+
- scenario_id, step_count, validation`;
|
|
348
1162
|
|
|
349
|
-
// src/tools/
|
|
350
|
-
var
|
|
1163
|
+
// src/tools/semantic/sm-status.ts
|
|
1164
|
+
var SM_STATUS_DESCRIPTION = `View all semantic configuration (pages, targets, actions, fetches).
|
|
351
1165
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
2. Scroll to element: provide ref (element reference ID from read_page/find). Scrolls the element into view using smooth scrolling.`;
|
|
1166
|
+
Returns a summary and detailed listing of all registered entities.
|
|
1167
|
+
Optionally test-resolve all targets against the current tab.
|
|
355
1168
|
|
|
356
|
-
|
|
357
|
-
|
|
1169
|
+
Parameters:
|
|
1170
|
+
- tabId (optional): required when test_resolve is true
|
|
1171
|
+
- page_id (optional): filter results to a specific page
|
|
1172
|
+
- domain (optional): filter results to pages matching this domain
|
|
1173
|
+
- test_resolve (optional): if true, resolve all targets on the current tab
|
|
358
1174
|
|
|
359
|
-
|
|
360
|
-
|
|
1175
|
+
Returns:
|
|
1176
|
+
- summary: {total_pages, total_targets, total_actions, total_fetches}
|
|
1177
|
+
- pages[]: page definitions with page_type, domains, url_patterns, and counts
|
|
1178
|
+
- targets[]: targets with locator info and optional resolve_result
|
|
1179
|
+
- actions[]: actions with step count and param names
|
|
1180
|
+
- fetches[]: fetches with field count
|
|
1181
|
+
- integrity[]: referential integrity warnings`;
|
|
361
1182
|
|
|
362
|
-
// src/tools/
|
|
363
|
-
var
|
|
364
|
-
Supports waiting for a CSS selector to appear in the DOM,
|
|
365
|
-
for a navigation event to complete, or for a fixed timeout duration.
|
|
366
|
-
Useful for synchronizing with asynchronous page updates.`;
|
|
1183
|
+
// src/tools/semantic/sm-test-target.ts
|
|
1184
|
+
var SM_TEST_TARGET_DESCRIPTION = `Test a specific target's locators against the current page.
|
|
367
1185
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
Returns log, warning, error, and info messages that have been
|
|
371
|
-
emitted by page scripts. Supports filtering by log level
|
|
372
|
-
and limiting the number of returned entries.`;
|
|
1186
|
+
Resolves each locator individually and reports which ones matched.
|
|
1187
|
+
Useful for diagnosing broken targets and deciding which locators to update.
|
|
373
1188
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
1189
|
+
Parameters:
|
|
1190
|
+
- tabId: target tab to test against
|
|
1191
|
+
- target_id: the target to test
|
|
1192
|
+
|
|
1193
|
+
Returns:
|
|
1194
|
+
- found: whether any locator resolved successfully
|
|
1195
|
+
- locator_results[]: per-locator results {type, value, stability, matched}
|
|
1196
|
+
- element: resolved element info {tag, text, rect} if found
|
|
1197
|
+
- test_status: updated test status saved to storage`;
|
|
379
1198
|
|
|
380
1199
|
// src/tools/tabs/select-tab.ts
|
|
381
1200
|
var SELECT_TAB_DESCRIPTION = `Switch focus to a specific tab by its identifier.
|
|
@@ -415,7 +1234,7 @@ in each group.`;
|
|
|
415
1234
|
|
|
416
1235
|
// src/tools/viyv/artifact-from-page.ts
|
|
417
1236
|
var ARTIFACT_FROM_PAGE_DESCRIPTION = `Save the current page as a persistent artifact.
|
|
418
|
-
Captures the page content as HTML,
|
|
1237
|
+
Captures the page content as HTML, text, or screenshot and stores it
|
|
419
1238
|
as a named artifact for later reference. Artifacts can be retrieved
|
|
420
1239
|
by other tools or returned to the user as output.`;
|
|
421
1240
|
|
|
@@ -439,16 +1258,17 @@ diagnostic information useful for troubleshooting connectivity issues.`;
|
|
|
439
1258
|
|
|
440
1259
|
// src/tools/viyv/page-data-extract.ts
|
|
441
1260
|
var PAGE_DATA_EXTRACT_DESCRIPTION = `Extract structured data from the current page.
|
|
442
|
-
Parses the page content according to a provided schema
|
|
443
|
-
|
|
444
|
-
|
|
1261
|
+
Parses the page content according to a provided schema object,
|
|
1262
|
+
returning well-formed JSON. Supports extracting tables, lists,
|
|
1263
|
+
key-value pairs, and other structured patterns.
|
|
1264
|
+
The schema defines CSS selectors for each field to extract.`;
|
|
445
1265
|
|
|
446
1266
|
// src/tools/index.ts
|
|
447
1267
|
var navigateTool = {
|
|
448
1268
|
name: "navigate",
|
|
449
1269
|
description: NAVIGATE_DESCRIPTION,
|
|
450
1270
|
inputSchema: z.object({
|
|
451
|
-
tabId: z.number().describe("Tab ID to navigate"),
|
|
1271
|
+
tabId: z.coerce.number().describe("Tab ID to navigate"),
|
|
452
1272
|
url: z.string().describe('URL to navigate to, or "back"/"forward" for history')
|
|
453
1273
|
})
|
|
454
1274
|
};
|
|
@@ -456,18 +1276,21 @@ var screenshotTool = {
|
|
|
456
1276
|
name: "screenshot",
|
|
457
1277
|
description: SCREENSHOT_DESCRIPTION,
|
|
458
1278
|
inputSchema: z.object({
|
|
459
|
-
tabId: z.number().describe("Tab ID to capture"),
|
|
1279
|
+
tabId: z.coerce.number().describe("Tab ID to capture"),
|
|
460
1280
|
format: z.enum(["jpeg", "png"]).optional().describe("Image format (default: jpeg)"),
|
|
461
|
-
quality: z.number().min(1).max(100).optional().describe("JPEG quality (default: 80)"),
|
|
462
|
-
region: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional().describe("Capture region [x0, y0, x1, y1]")
|
|
1281
|
+
quality: z.coerce.number().min(1).max(100).optional().describe("JPEG quality (default: 80)"),
|
|
1282
|
+
region: z.tuple([z.coerce.number(), z.coerce.number(), z.coerce.number(), z.coerce.number()]).optional().describe("Capture region [x0, y0, x1, y1]"),
|
|
1283
|
+
ref: z.string().optional().describe(
|
|
1284
|
+
"Element reference ID from read_page or find. Captures only that element with padding. Ignored if region is also provided."
|
|
1285
|
+
)
|
|
463
1286
|
})
|
|
464
1287
|
};
|
|
465
1288
|
var clickTool = {
|
|
466
1289
|
name: "click",
|
|
467
1290
|
description: CLICK_DESCRIPTION,
|
|
468
1291
|
inputSchema: z.object({
|
|
469
|
-
tabId: z.number().describe("Tab ID"),
|
|
470
|
-
coordinate: z.tuple([z.number(), z.number()]).optional().describe("Click position [x, y]"),
|
|
1292
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1293
|
+
coordinate: z.tuple([z.coerce.number(), z.coerce.number()]).optional().describe("Click position [x, y]"),
|
|
471
1294
|
ref: z.string().optional().describe("Element reference ID"),
|
|
472
1295
|
action: z.enum(["left_click", "right_click", "double_click", "triple_click"]).optional().describe("Click type (default: left_click)"),
|
|
473
1296
|
modifiers: z.string().optional().describe('Modifier keys (e.g., "ctrl+shift")')
|
|
@@ -477,7 +1300,7 @@ var typeTool = {
|
|
|
477
1300
|
name: "type",
|
|
478
1301
|
description: TYPE_DESCRIPTION,
|
|
479
1302
|
inputSchema: z.object({
|
|
480
|
-
tabId: z.number().describe("Tab ID"),
|
|
1303
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
481
1304
|
text: z.string().describe("Text to type")
|
|
482
1305
|
})
|
|
483
1306
|
};
|
|
@@ -485,19 +1308,19 @@ var keyTool = {
|
|
|
485
1308
|
name: "key",
|
|
486
1309
|
description: KEY_DESCRIPTION,
|
|
487
1310
|
inputSchema: z.object({
|
|
488
|
-
tabId: z.number().describe("Tab ID"),
|
|
1311
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
489
1312
|
keys: z.string().describe('Space-separated keys (e.g., "Enter", "ctrl+a")'),
|
|
490
|
-
repeat: z.number().min(1).max(100).optional().describe("Repeat count")
|
|
1313
|
+
repeat: z.coerce.number().min(1).max(100).optional().describe("Repeat count")
|
|
491
1314
|
})
|
|
492
1315
|
};
|
|
493
1316
|
var scrollTool = {
|
|
494
1317
|
name: "scroll",
|
|
495
1318
|
description: SCROLL_DESCRIPTION,
|
|
496
1319
|
inputSchema: z.object({
|
|
497
|
-
tabId: z.number().describe("Tab ID"),
|
|
498
|
-
coordinate: z.tuple([z.number(), z.number()]).optional().describe("Scroll position [x, y] (required for directional scroll)"),
|
|
1320
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1321
|
+
coordinate: z.tuple([z.coerce.number(), z.coerce.number()]).optional().describe("Scroll position [x, y] (required for directional scroll)"),
|
|
499
1322
|
direction: z.enum(["up", "down", "left", "right"]).optional().describe("Scroll direction (required for directional scroll)"),
|
|
500
|
-
amount: z.number().min(1).max(10).optional().describe("Scroll amount (default: 3)"),
|
|
1323
|
+
amount: z.coerce.number().min(1).max(10).optional().describe("Scroll amount (default: 3)"),
|
|
501
1324
|
ref: z.string().optional().describe("Element reference ID to scroll into view")
|
|
502
1325
|
})
|
|
503
1326
|
};
|
|
@@ -505,8 +1328,8 @@ var hoverTool = {
|
|
|
505
1328
|
name: "hover",
|
|
506
1329
|
description: HOVER_DESCRIPTION,
|
|
507
1330
|
inputSchema: z.object({
|
|
508
|
-
tabId: z.number().describe("Tab ID"),
|
|
509
|
-
coordinate: z.tuple([z.number(), z.number()]).optional().describe("Hover position [x, y]"),
|
|
1331
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1332
|
+
coordinate: z.tuple([z.coerce.number(), z.coerce.number()]).optional().describe("Hover position [x, y]"),
|
|
510
1333
|
ref: z.string().optional().describe("Element reference ID")
|
|
511
1334
|
})
|
|
512
1335
|
};
|
|
@@ -514,27 +1337,27 @@ var dragTool = {
|
|
|
514
1337
|
name: "drag",
|
|
515
1338
|
description: DRAG_DESCRIPTION,
|
|
516
1339
|
inputSchema: z.object({
|
|
517
|
-
tabId: z.number().describe("Tab ID"),
|
|
518
|
-
startCoordinate: z.tuple([z.number(), z.number()]).describe("Start position [x, y]"),
|
|
519
|
-
endCoordinate: z.tuple([z.number(), z.number()]).describe("End position [x, y]")
|
|
1340
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1341
|
+
startCoordinate: z.tuple([z.coerce.number(), z.coerce.number()]).describe("Start position [x, y]"),
|
|
1342
|
+
endCoordinate: z.tuple([z.coerce.number(), z.coerce.number()]).describe("End position [x, y]")
|
|
520
1343
|
})
|
|
521
1344
|
};
|
|
522
1345
|
var readPageTool = {
|
|
523
1346
|
name: "read_page",
|
|
524
1347
|
description: READ_PAGE_DESCRIPTION,
|
|
525
1348
|
inputSchema: z.object({
|
|
526
|
-
tabId: z.number().describe("Tab ID"),
|
|
1349
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
527
1350
|
filter: z.enum(["interactive", "all"]).optional().describe('Filter: "interactive" for buttons/links/inputs, "all" for everything'),
|
|
528
|
-
depth: z.number().min(1).max(20).optional().describe("Max tree depth (default:
|
|
1351
|
+
depth: z.coerce.number().min(1).max(20).optional().describe("Max tree depth (default: 15)"),
|
|
529
1352
|
refId: z.string().optional().describe("Focus on a specific element by ref"),
|
|
530
|
-
maxChars: z.number().optional().describe("Max output characters (default: 50000)")
|
|
1353
|
+
maxChars: z.coerce.number().optional().describe("Max output characters (default: 50000)")
|
|
531
1354
|
})
|
|
532
1355
|
};
|
|
533
1356
|
var findTool = {
|
|
534
1357
|
name: "find",
|
|
535
1358
|
description: FIND_DESCRIPTION,
|
|
536
1359
|
inputSchema: z.object({
|
|
537
|
-
tabId: z.number().describe("Tab ID"),
|
|
1360
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
538
1361
|
query: z.string().describe("Natural language description of what to find")
|
|
539
1362
|
})
|
|
540
1363
|
};
|
|
@@ -542,16 +1365,16 @@ var formInputTool = {
|
|
|
542
1365
|
name: "form_input",
|
|
543
1366
|
description: FORM_INPUT_DESCRIPTION,
|
|
544
1367
|
inputSchema: z.object({
|
|
545
|
-
tabId: z.number().describe("Tab ID"),
|
|
1368
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
546
1369
|
ref: z.string().describe("Element reference ID"),
|
|
547
|
-
value: z.union([z.string(), z.boolean(), z.number()]).describe("Value to set")
|
|
1370
|
+
value: z.union([z.string(), z.boolean(), z.coerce.number()]).describe("Value to set")
|
|
548
1371
|
})
|
|
549
1372
|
};
|
|
550
1373
|
var javascriptExecTool = {
|
|
551
1374
|
name: "javascript_exec",
|
|
552
1375
|
description: JAVASCRIPT_EXEC_DESCRIPTION,
|
|
553
1376
|
inputSchema: z.object({
|
|
554
|
-
tabId: z.number().describe("Tab ID"),
|
|
1377
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
555
1378
|
code: z.string().describe("JavaScript code to execute")
|
|
556
1379
|
})
|
|
557
1380
|
};
|
|
@@ -559,24 +1382,24 @@ var waitForTool = {
|
|
|
559
1382
|
name: "wait_for",
|
|
560
1383
|
description: WAIT_FOR_DESCRIPTION,
|
|
561
1384
|
inputSchema: z.object({
|
|
562
|
-
tabId: z.number().describe("Tab ID"),
|
|
1385
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
563
1386
|
selector: z.string().optional().describe("CSS selector to wait for"),
|
|
564
1387
|
navigation: z.boolean().optional().describe("Wait for navigation to complete"),
|
|
565
|
-
timeout: z.number().optional().describe("Timeout in ms (default: 30000)")
|
|
1388
|
+
timeout: z.coerce.number().optional().describe("Timeout in ms (default: 30000)")
|
|
566
1389
|
})
|
|
567
1390
|
};
|
|
568
1391
|
var getPageTextTool = {
|
|
569
1392
|
name: "get_page_text",
|
|
570
1393
|
description: GET_PAGE_TEXT_DESCRIPTION,
|
|
571
1394
|
inputSchema: z.object({
|
|
572
|
-
tabId: z.number().describe("Tab ID")
|
|
1395
|
+
tabId: z.coerce.number().describe("Tab ID")
|
|
573
1396
|
})
|
|
574
1397
|
};
|
|
575
1398
|
var handleDialogTool = {
|
|
576
1399
|
name: "handle_dialog",
|
|
577
1400
|
description: HANDLE_DIALOG_DESCRIPTION,
|
|
578
1401
|
inputSchema: z.object({
|
|
579
|
-
tabId: z.number().describe("Tab ID"),
|
|
1402
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
580
1403
|
action: z.enum(["accept", "dismiss"]).describe("Dialog action"),
|
|
581
1404
|
text: z.string().optional().describe("Text for prompt dialog")
|
|
582
1405
|
})
|
|
@@ -599,24 +1422,24 @@ var tabCloseTool = {
|
|
|
599
1422
|
name: "tab_close",
|
|
600
1423
|
description: TAB_CLOSE_DESCRIPTION,
|
|
601
1424
|
inputSchema: z.object({
|
|
602
|
-
tabId: z.number().describe("Tab ID to close")
|
|
1425
|
+
tabId: z.coerce.number().describe("Tab ID to close")
|
|
603
1426
|
})
|
|
604
1427
|
};
|
|
605
1428
|
var selectTabTool = {
|
|
606
1429
|
name: "select_tab",
|
|
607
1430
|
description: SELECT_TAB_DESCRIPTION,
|
|
608
1431
|
inputSchema: z.object({
|
|
609
|
-
tabId: z.number().describe("Tab ID to focus")
|
|
1432
|
+
tabId: z.coerce.number().describe("Tab ID to focus")
|
|
610
1433
|
})
|
|
611
1434
|
};
|
|
612
1435
|
var readConsoleMessagesTool = {
|
|
613
1436
|
name: "read_console_messages",
|
|
614
1437
|
description: READ_CONSOLE_MESSAGES_DESCRIPTION,
|
|
615
1438
|
inputSchema: z.object({
|
|
616
|
-
tabId: z.number().describe("Tab ID"),
|
|
1439
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
617
1440
|
pattern: z.string().optional().describe("Regex pattern to filter messages"),
|
|
618
1441
|
onlyErrors: z.boolean().optional().describe("Only return errors"),
|
|
619
|
-
limit: z.number().optional().describe("Max messages to return (default: 100)"),
|
|
1442
|
+
limit: z.coerce.number().optional().describe("Max messages to return (default: 100)"),
|
|
620
1443
|
clear: z.boolean().optional().describe("Clear messages after reading")
|
|
621
1444
|
})
|
|
622
1445
|
};
|
|
@@ -624,9 +1447,9 @@ var readNetworkRequestsTool = {
|
|
|
624
1447
|
name: "read_network_requests",
|
|
625
1448
|
description: READ_NETWORK_REQUESTS_DESCRIPTION,
|
|
626
1449
|
inputSchema: z.object({
|
|
627
|
-
tabId: z.number().describe("Tab ID"),
|
|
1450
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
628
1451
|
urlPattern: z.string().optional().describe("URL pattern to filter requests"),
|
|
629
|
-
limit: z.number().optional().describe("Max requests to return (default: 100)"),
|
|
1452
|
+
limit: z.coerce.number().optional().describe("Max requests to return (default: 100)"),
|
|
630
1453
|
clear: z.boolean().optional().describe("Clear requests after reading")
|
|
631
1454
|
})
|
|
632
1455
|
};
|
|
@@ -634,7 +1457,7 @@ var gifCreatorTool = {
|
|
|
634
1457
|
name: "gif_creator",
|
|
635
1458
|
description: GIF_CREATOR_DESCRIPTION,
|
|
636
1459
|
inputSchema: z.object({
|
|
637
|
-
tabId: z.number().describe("Tab ID"),
|
|
1460
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
638
1461
|
action: z.enum(["start_recording", "stop_recording", "export", "clear"]).describe("GIF action"),
|
|
639
1462
|
filename: z.string().optional().describe("Filename for export"),
|
|
640
1463
|
options: z.object({
|
|
@@ -643,7 +1466,7 @@ var gifCreatorTool = {
|
|
|
643
1466
|
showActionLabels: z.boolean().optional(),
|
|
644
1467
|
showProgressBar: z.boolean().optional(),
|
|
645
1468
|
showWatermark: z.boolean().optional(),
|
|
646
|
-
quality: z.number().min(1).max(30).optional()
|
|
1469
|
+
quality: z.coerce.number().min(1).max(30).optional()
|
|
647
1470
|
}).optional().describe("GIF rendering options"),
|
|
648
1471
|
download: z.boolean().optional().describe("Download the GIF")
|
|
649
1472
|
})
|
|
@@ -652,10 +1475,11 @@ var uploadImageTool = {
|
|
|
652
1475
|
name: "upload_image",
|
|
653
1476
|
description: UPLOAD_IMAGE_DESCRIPTION,
|
|
654
1477
|
inputSchema: z.object({
|
|
655
|
-
tabId: z.number().describe("Tab ID"),
|
|
1478
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
656
1479
|
imageId: z.string().describe("Image ID from a previous screenshot"),
|
|
657
1480
|
ref: z.string().optional().describe("Element reference for file input"),
|
|
658
|
-
coordinate: z.tuple([z.number(), z.number()]).optional().describe("Coordinates for drag & drop")
|
|
1481
|
+
coordinate: z.tuple([z.coerce.number(), z.coerce.number()]).optional().describe("Coordinates for drag & drop"),
|
|
1482
|
+
filename: z.string().optional().describe('Filename for the uploaded file (default: "image.png")')
|
|
659
1483
|
})
|
|
660
1484
|
};
|
|
661
1485
|
var updatePlanTool = {
|
|
@@ -670,23 +1494,23 @@ var resizeWindowTool = {
|
|
|
670
1494
|
name: "resize_window",
|
|
671
1495
|
description: RESIZE_WINDOW_DESCRIPTION,
|
|
672
1496
|
inputSchema: z.object({
|
|
673
|
-
tabId: z.number().describe("Tab ID"),
|
|
674
|
-
width: z.number().describe("Window width in pixels"),
|
|
675
|
-
height: z.number().describe("Window height in pixels")
|
|
1497
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1498
|
+
width: z.coerce.number().describe("Window width in pixels"),
|
|
1499
|
+
height: z.coerce.number().describe("Window height in pixels")
|
|
676
1500
|
})
|
|
677
1501
|
};
|
|
678
1502
|
var shortcutsListTool = {
|
|
679
1503
|
name: "shortcuts_list",
|
|
680
1504
|
description: SHORTCUTS_LIST_DESCRIPTION,
|
|
681
1505
|
inputSchema: z.object({
|
|
682
|
-
tabId: z.number().optional().describe("Tab ID (used to identify the tab group context)")
|
|
1506
|
+
tabId: z.coerce.number().optional().describe("Tab ID (used to identify the tab group context)")
|
|
683
1507
|
})
|
|
684
1508
|
};
|
|
685
1509
|
var shortcutsExecuteTool = {
|
|
686
1510
|
name: "shortcuts_execute",
|
|
687
1511
|
description: SHORTCUTS_EXECUTE_DESCRIPTION,
|
|
688
1512
|
inputSchema: z.object({
|
|
689
|
-
tabId: z.number().describe("Tab ID to execute the shortcut on"),
|
|
1513
|
+
tabId: z.coerce.number().describe("Tab ID to execute the shortcut on"),
|
|
690
1514
|
command: z.string().optional().describe("Command name of the shortcut"),
|
|
691
1515
|
shortcutId: z.string().optional().describe("ID of the shortcut")
|
|
692
1516
|
})
|
|
@@ -696,6 +1520,15 @@ var switchBrowserTool = {
|
|
|
696
1520
|
description: SWITCH_BROWSER_DESCRIPTION,
|
|
697
1521
|
inputSchema: z.object({})
|
|
698
1522
|
};
|
|
1523
|
+
var fileUploadTool = {
|
|
1524
|
+
name: "file_upload",
|
|
1525
|
+
description: FILE_UPLOAD_DESCRIPTION,
|
|
1526
|
+
inputSchema: z.object({
|
|
1527
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1528
|
+
ref: z.string().describe("Element reference ID of the file input"),
|
|
1529
|
+
paths: z.array(z.string()).min(1).describe("Absolute file paths to upload")
|
|
1530
|
+
})
|
|
1531
|
+
};
|
|
699
1532
|
var agentTabAssignTool = {
|
|
700
1533
|
name: "agent_tab_assign",
|
|
701
1534
|
description: AGENT_TAB_ASSIGN_DESCRIPTION,
|
|
@@ -730,7 +1563,7 @@ var artifactFromPageTool = {
|
|
|
730
1563
|
name: "artifact_from_page",
|
|
731
1564
|
description: ARTIFACT_FROM_PAGE_DESCRIPTION,
|
|
732
1565
|
inputSchema: z.object({
|
|
733
|
-
tabId: z.number().describe("Tab ID"),
|
|
1566
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
734
1567
|
type: z.string().describe("Artifact type (text, html, screenshot)"),
|
|
735
1568
|
title: z.string().optional().describe("Artifact title")
|
|
736
1569
|
})
|
|
@@ -739,7 +1572,7 @@ var pageDataExtractTool = {
|
|
|
739
1572
|
name: "page_data_extract",
|
|
740
1573
|
description: PAGE_DATA_EXTRACT_DESCRIPTION,
|
|
741
1574
|
inputSchema: z.object({
|
|
742
|
-
tabId: z.number().describe("Tab ID"),
|
|
1575
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
743
1576
|
schema: z.record(z.unknown()).describe("Data extraction schema"),
|
|
744
1577
|
selector: z.string().optional().describe("CSS selector to scope extraction")
|
|
745
1578
|
})
|
|
@@ -749,6 +1582,489 @@ var browserHealthTool = {
|
|
|
749
1582
|
description: BROWSER_HEALTH_DESCRIPTION,
|
|
750
1583
|
inputSchema: z.object({})
|
|
751
1584
|
};
|
|
1585
|
+
var smCapabilitiesTool = {
|
|
1586
|
+
name: "sm_capabilities",
|
|
1587
|
+
description: SM_CAPABILITIES_DESCRIPTION,
|
|
1588
|
+
inputSchema: z.object({
|
|
1589
|
+
tabId: z.coerce.number().describe("Tab ID")
|
|
1590
|
+
})
|
|
1591
|
+
};
|
|
1592
|
+
var smInvokeTool = {
|
|
1593
|
+
name: "sm_invoke",
|
|
1594
|
+
description: SM_INVOKE_DESCRIPTION,
|
|
1595
|
+
inputSchema: z.object({
|
|
1596
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1597
|
+
action_id: z.string().describe("Action ID to execute"),
|
|
1598
|
+
params: z.record(z.unknown()).optional().describe("Parameters for template substitution")
|
|
1599
|
+
})
|
|
1600
|
+
};
|
|
1601
|
+
var smFetchTool = {
|
|
1602
|
+
name: "sm_fetch",
|
|
1603
|
+
description: SM_FETCH_DESCRIPTION,
|
|
1604
|
+
inputSchema: z.object({
|
|
1605
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1606
|
+
fetch_id: z.string().describe("Fetch definition ID to execute"),
|
|
1607
|
+
params: z.record(z.unknown()).optional().describe("Optional parameters")
|
|
1608
|
+
})
|
|
1609
|
+
};
|
|
1610
|
+
var smRegisterPageTool = {
|
|
1611
|
+
name: "sm_register_page",
|
|
1612
|
+
description: SM_REGISTER_PAGE_DESCRIPTION,
|
|
1613
|
+
inputSchema: z.object({
|
|
1614
|
+
tabId: z.coerce.number().describe("Target tab"),
|
|
1615
|
+
label: z.string().describe('Page name (e.g., "GitHub Login")'),
|
|
1616
|
+
page_type: z.string().optional().describe(
|
|
1617
|
+
'Page type within the domain (e.g., "profile", "compose", "search"). Used for grouping pages.'
|
|
1618
|
+
),
|
|
1619
|
+
url_pattern: z.string().optional().describe("URL regex (auto-derived from tab URL if omitted)"),
|
|
1620
|
+
domains: z.array(z.string()).optional().describe("Domain scope (auto-derived if omitted)"),
|
|
1621
|
+
auth_state: z.enum(["logged_in", "logged_out", "any"]).optional().describe("Auth state (default: any)"),
|
|
1622
|
+
must_exist: z.array(z.string()).optional().describe("Required CSS selectors"),
|
|
1623
|
+
must_not_exist: z.array(z.string()).optional().describe("Forbidden CSS selectors"),
|
|
1624
|
+
title_regex: z.string().optional().describe("Title regex")
|
|
1625
|
+
})
|
|
1626
|
+
};
|
|
1627
|
+
var smAddTargetTool = {
|
|
1628
|
+
name: "sm_add_target",
|
|
1629
|
+
description: SM_ADD_TARGET_DESCRIPTION,
|
|
1630
|
+
inputSchema: z.object({
|
|
1631
|
+
tabId: z.coerce.number().describe("Target tab where elements are present"),
|
|
1632
|
+
page_id: z.string().describe("Page ID these targets belong to"),
|
|
1633
|
+
targets: z.array(
|
|
1634
|
+
z.object({
|
|
1635
|
+
target_id: z.string().optional().describe("Existing target ID for re-bind"),
|
|
1636
|
+
label: z.string().describe("Target name"),
|
|
1637
|
+
role: z.string().describe('Semantic role (e.g., "textbox", "button")'),
|
|
1638
|
+
ref: z.string().optional().describe("Element ref from read_page/find"),
|
|
1639
|
+
locators: z.array(
|
|
1640
|
+
z.object({
|
|
1641
|
+
type: z.string().describe("Locator type"),
|
|
1642
|
+
value: z.string().describe("Locator value"),
|
|
1643
|
+
stability: z.enum(["high", "medium", "low"]).describe("Stability level")
|
|
1644
|
+
})
|
|
1645
|
+
).optional().describe("Explicit locators")
|
|
1646
|
+
})
|
|
1647
|
+
).min(1).describe("Target definitions")
|
|
1648
|
+
})
|
|
1649
|
+
};
|
|
1650
|
+
var smAddActionTool = {
|
|
1651
|
+
name: "sm_add_action",
|
|
1652
|
+
description: SM_ADD_ACTION_DESCRIPTION,
|
|
1653
|
+
inputSchema: z.object({
|
|
1654
|
+
page_id: z.string().describe("Page ID"),
|
|
1655
|
+
label: z.string().describe("Action name"),
|
|
1656
|
+
verb: z.string().describe('Action verb (e.g., "login", "submit")'),
|
|
1657
|
+
description: z.string().optional().describe("LLM-facing description"),
|
|
1658
|
+
params: z.array(
|
|
1659
|
+
z.object({
|
|
1660
|
+
name: z.string(),
|
|
1661
|
+
type: z.enum(["string", "number", "boolean"]).default("string"),
|
|
1662
|
+
required: z.boolean().default(true),
|
|
1663
|
+
secret: z.boolean().default(false),
|
|
1664
|
+
description: z.string().optional(),
|
|
1665
|
+
default_value: z.union([z.string(), z.coerce.number(), z.boolean()]).optional()
|
|
1666
|
+
})
|
|
1667
|
+
).optional().describe("Parameter definitions"),
|
|
1668
|
+
steps: z.array(
|
|
1669
|
+
z.object({
|
|
1670
|
+
type: z.enum(["click", "type", "select", "wait", "scroll", "key", "navigate", "file_upload"]).describe("Step type"),
|
|
1671
|
+
target_id: z.string().optional().describe("Target to act on"),
|
|
1672
|
+
params: z.object({
|
|
1673
|
+
text: z.string().optional(),
|
|
1674
|
+
value: z.string().optional(),
|
|
1675
|
+
keys: z.string().optional(),
|
|
1676
|
+
direction: z.enum(["up", "down", "left", "right"]).optional(),
|
|
1677
|
+
amount: z.coerce.number().optional(),
|
|
1678
|
+
url: z.string().optional(),
|
|
1679
|
+
param_ref: z.string().optional(),
|
|
1680
|
+
file_paths: z.array(z.string()).optional().describe("File paths for file_upload step")
|
|
1681
|
+
}).optional().describe("Step parameters"),
|
|
1682
|
+
pre_wait: z.object({
|
|
1683
|
+
strategy: z.enum(["selector", "navigation", "time"]),
|
|
1684
|
+
selector: z.string().optional(),
|
|
1685
|
+
timeout_ms: z.coerce.number().default(5e3)
|
|
1686
|
+
}).optional().describe("Pre-step wait rule"),
|
|
1687
|
+
description: z.string().optional()
|
|
1688
|
+
})
|
|
1689
|
+
).min(1).describe("Step sequence"),
|
|
1690
|
+
retry_policy: z.object({
|
|
1691
|
+
max_retries: z.coerce.number().min(0).max(5).optional(),
|
|
1692
|
+
backoff: z.enum(["fixed", "exponential"]).optional(),
|
|
1693
|
+
base_delay_ms: z.coerce.number().min(100).max(1e4).optional(),
|
|
1694
|
+
jitter: z.boolean().optional()
|
|
1695
|
+
}).optional().describe("Retry policy")
|
|
1696
|
+
})
|
|
1697
|
+
};
|
|
1698
|
+
var smAddFetchTool = {
|
|
1699
|
+
name: "sm_add_fetch",
|
|
1700
|
+
description: SM_ADD_FETCH_DESCRIPTION,
|
|
1701
|
+
inputSchema: z.object({
|
|
1702
|
+
page_id: z.string().describe("Page ID"),
|
|
1703
|
+
label: z.string().describe("Fetch name"),
|
|
1704
|
+
type_name: z.string().describe('Output type name (e.g., "posts")'),
|
|
1705
|
+
description: z.string().optional().describe("LLM-facing description for discovery via sm_capabilities"),
|
|
1706
|
+
fields: z.array(
|
|
1707
|
+
z.object({
|
|
1708
|
+
name: z.string().describe("Output field name"),
|
|
1709
|
+
target_id: z.string().describe("Target element"),
|
|
1710
|
+
extraction_type: z.enum(["text", "attribute", "list", "count", "exists"]).describe("Extraction type"),
|
|
1711
|
+
attribute: z.string().optional().describe("Attribute name (for attribute type)"),
|
|
1712
|
+
item_selector: z.string().optional().describe("List item CSS selector"),
|
|
1713
|
+
item_fields: z.array(
|
|
1714
|
+
z.object({
|
|
1715
|
+
name: z.string(),
|
|
1716
|
+
selector: z.string().describe(
|
|
1717
|
+
'CSS selector relative to each list item (use ":scope" for item itself)'
|
|
1718
|
+
),
|
|
1719
|
+
extraction_type: z.enum(["text", "attribute", "exists"]),
|
|
1720
|
+
attribute: z.string().optional()
|
|
1721
|
+
})
|
|
1722
|
+
).optional().describe("Nested fields for structured list item extraction")
|
|
1723
|
+
})
|
|
1724
|
+
).min(1).describe("Extraction fields"),
|
|
1725
|
+
limits: z.object({
|
|
1726
|
+
max_chars: z.coerce.number().min(1e3).max(2e5).optional(),
|
|
1727
|
+
max_items: z.coerce.number().min(1).max(1e3).optional()
|
|
1728
|
+
}).optional().describe("Output limits")
|
|
1729
|
+
})
|
|
1730
|
+
};
|
|
1731
|
+
var smStatusTool = {
|
|
1732
|
+
name: "sm_status",
|
|
1733
|
+
description: SM_STATUS_DESCRIPTION,
|
|
1734
|
+
inputSchema: z.object({
|
|
1735
|
+
tabId: z.coerce.number().optional().describe("Tab ID (required for test_resolve)"),
|
|
1736
|
+
page_id: z.string().optional().describe("Filter by page ID"),
|
|
1737
|
+
domain: z.string().optional().describe("Filter results to pages matching this domain"),
|
|
1738
|
+
test_resolve: z.boolean().optional().describe("Test-resolve all targets")
|
|
1739
|
+
})
|
|
1740
|
+
};
|
|
1741
|
+
var smTestTargetTool = {
|
|
1742
|
+
name: "sm_test_target",
|
|
1743
|
+
description: SM_TEST_TARGET_DESCRIPTION,
|
|
1744
|
+
inputSchema: z.object({
|
|
1745
|
+
tabId: z.coerce.number().describe("Tab ID"),
|
|
1746
|
+
target_id: z.string().describe("Target ID to test")
|
|
1747
|
+
})
|
|
1748
|
+
};
|
|
1749
|
+
var smDeleteTool = {
|
|
1750
|
+
name: "sm_delete",
|
|
1751
|
+
description: SM_DELETE_DESCRIPTION,
|
|
1752
|
+
inputSchema: z.object({
|
|
1753
|
+
entity_type: z.enum(["page", "target", "action", "fetch"]).describe("Entity type"),
|
|
1754
|
+
entity_id: z.string().describe("Entity ID to delete"),
|
|
1755
|
+
dry_run: z.boolean().optional().describe("Preview impact without deleting")
|
|
1756
|
+
})
|
|
1757
|
+
};
|
|
1758
|
+
var smExportTool = {
|
|
1759
|
+
name: "sm_export",
|
|
1760
|
+
description: SM_EXPORT_DESCRIPTION,
|
|
1761
|
+
inputSchema: z.object({
|
|
1762
|
+
domain: z.string().optional().describe("Export only pages matching this domain (subdomain-aware). Omit for all.")
|
|
1763
|
+
})
|
|
1764
|
+
};
|
|
1765
|
+
var smImportTool = {
|
|
1766
|
+
name: "sm_import",
|
|
1767
|
+
description: SM_IMPORT_DESCRIPTION,
|
|
1768
|
+
inputSchema: z.object({
|
|
1769
|
+
data: z.object({
|
|
1770
|
+
schema_version: z.string(),
|
|
1771
|
+
checksum: z.string(),
|
|
1772
|
+
exported_at: z.coerce.number().optional(),
|
|
1773
|
+
source: z.record(z.unknown()).optional(),
|
|
1774
|
+
pages: z.array(z.record(z.unknown())),
|
|
1775
|
+
targets: z.array(z.record(z.unknown())).optional(),
|
|
1776
|
+
actions: z.array(z.record(z.unknown())).optional(),
|
|
1777
|
+
fetches: z.array(z.record(z.unknown())).optional()
|
|
1778
|
+
}).passthrough().describe("SemanticExport JSON object from sm_export"),
|
|
1779
|
+
strategy: z.enum(["merge", "replace-domain"]).optional().describe('"merge" (default) or "replace-domain"')
|
|
1780
|
+
})
|
|
1781
|
+
};
|
|
1782
|
+
var scenarioStepSchema = z.lazy(
|
|
1783
|
+
() => z.discriminatedUnion("type", [
|
|
1784
|
+
z.object({
|
|
1785
|
+
type: z.literal("navigate"),
|
|
1786
|
+
label: z.string().optional(),
|
|
1787
|
+
url: z.string(),
|
|
1788
|
+
wait_for_load: z.boolean().optional()
|
|
1789
|
+
}),
|
|
1790
|
+
z.object({
|
|
1791
|
+
type: z.literal("action"),
|
|
1792
|
+
label: z.string().optional(),
|
|
1793
|
+
action_id: z.string(),
|
|
1794
|
+
params: z.record(z.string()).optional()
|
|
1795
|
+
}),
|
|
1796
|
+
z.object({
|
|
1797
|
+
type: z.literal("fetch"),
|
|
1798
|
+
label: z.string().optional(),
|
|
1799
|
+
fetch_id: z.string(),
|
|
1800
|
+
result_key: z.string(),
|
|
1801
|
+
params: z.record(z.string()).optional()
|
|
1802
|
+
}),
|
|
1803
|
+
z.object({
|
|
1804
|
+
type: z.literal("loop"),
|
|
1805
|
+
label: z.string().optional(),
|
|
1806
|
+
variables: z.array(
|
|
1807
|
+
z.object({
|
|
1808
|
+
name: z.string(),
|
|
1809
|
+
source: z.enum(["static", "target_options", "previous_fetch"]),
|
|
1810
|
+
values: z.array(z.string()).optional(),
|
|
1811
|
+
target_id: z.string().optional(),
|
|
1812
|
+
fetch_result_key: z.string().optional(),
|
|
1813
|
+
field_name: z.string().optional()
|
|
1814
|
+
})
|
|
1815
|
+
),
|
|
1816
|
+
steps: z.array(scenarioStepSchema),
|
|
1817
|
+
delay_between_ms: z.coerce.number().optional(),
|
|
1818
|
+
max_iterations: z.coerce.number().min(1).max(500).optional()
|
|
1819
|
+
}),
|
|
1820
|
+
z.object({
|
|
1821
|
+
type: z.literal("wait"),
|
|
1822
|
+
label: z.string().optional(),
|
|
1823
|
+
strategy: z.enum(["time", "selector", "navigation"]),
|
|
1824
|
+
selector: z.string().optional(),
|
|
1825
|
+
timeout_ms: z.coerce.number()
|
|
1826
|
+
})
|
|
1827
|
+
])
|
|
1828
|
+
);
|
|
1829
|
+
var smScenarioCreateTool = {
|
|
1830
|
+
name: "sm_scenario_create",
|
|
1831
|
+
description: SM_SCENARIO_CREATE_DESCRIPTION,
|
|
1832
|
+
inputSchema: z.object({
|
|
1833
|
+
label: z.string().describe("Scenario name"),
|
|
1834
|
+
description: z.string().optional().describe("LLM-facing description"),
|
|
1835
|
+
params: z.array(
|
|
1836
|
+
z.object({
|
|
1837
|
+
name: z.string(),
|
|
1838
|
+
type: z.enum(["string", "number", "boolean"]).default("string"),
|
|
1839
|
+
required: z.boolean().default(true),
|
|
1840
|
+
secret: z.boolean().default(false),
|
|
1841
|
+
description: z.string().optional(),
|
|
1842
|
+
default_value: z.union([z.string(), z.coerce.number(), z.boolean()]).optional()
|
|
1843
|
+
})
|
|
1844
|
+
).optional().describe("Global parameter definitions"),
|
|
1845
|
+
steps: z.array(scenarioStepSchema).min(1).describe("Step sequence"),
|
|
1846
|
+
max_duration_ms: z.coerce.number().optional().describe("Timeout in ms (default: 300000)"),
|
|
1847
|
+
on_error: z.enum(["stop", "skip_and_continue"]).optional().describe("Error handling (default: stop)")
|
|
1848
|
+
})
|
|
1849
|
+
};
|
|
1850
|
+
var smScenarioUpdateTool = {
|
|
1851
|
+
name: "sm_scenario_update",
|
|
1852
|
+
description: SM_SCENARIO_UPDATE_DESCRIPTION,
|
|
1853
|
+
inputSchema: z.object({
|
|
1854
|
+
scenario_id: z.string().describe("Scenario ID to update"),
|
|
1855
|
+
label: z.string().optional(),
|
|
1856
|
+
description: z.string().optional(),
|
|
1857
|
+
params: z.array(
|
|
1858
|
+
z.object({
|
|
1859
|
+
name: z.string(),
|
|
1860
|
+
type: z.enum(["string", "number", "boolean"]).default("string"),
|
|
1861
|
+
required: z.boolean().default(true),
|
|
1862
|
+
secret: z.boolean().default(false),
|
|
1863
|
+
description: z.string().optional(),
|
|
1864
|
+
default_value: z.union([z.string(), z.coerce.number(), z.boolean()]).optional()
|
|
1865
|
+
})
|
|
1866
|
+
).optional(),
|
|
1867
|
+
steps: z.array(scenarioStepSchema).optional(),
|
|
1868
|
+
max_duration_ms: z.coerce.number().optional(),
|
|
1869
|
+
on_error: z.enum(["stop", "skip_and_continue"]).optional()
|
|
1870
|
+
})
|
|
1871
|
+
};
|
|
1872
|
+
var smScenarioDeleteTool = {
|
|
1873
|
+
name: "sm_scenario_delete",
|
|
1874
|
+
description: SM_SCENARIO_DELETE_DESCRIPTION,
|
|
1875
|
+
inputSchema: z.object({
|
|
1876
|
+
scenario_id: z.string().describe("Scenario ID to delete")
|
|
1877
|
+
})
|
|
1878
|
+
};
|
|
1879
|
+
var smScenarioListTool = {
|
|
1880
|
+
name: "sm_scenario_list",
|
|
1881
|
+
description: SM_SCENARIO_LIST_DESCRIPTION,
|
|
1882
|
+
inputSchema: z.object({})
|
|
1883
|
+
};
|
|
1884
|
+
var smScenarioRunTool = {
|
|
1885
|
+
name: "sm_scenario_run",
|
|
1886
|
+
description: SM_SCENARIO_RUN_DESCRIPTION,
|
|
1887
|
+
inputSchema: z.object({
|
|
1888
|
+
tabId: z.coerce.number().describe("Tab ID for execution"),
|
|
1889
|
+
scenario_id: z.string().describe("Scenario to execute"),
|
|
1890
|
+
params: z.record(z.unknown()).optional().describe("Parameter values"),
|
|
1891
|
+
wait_for_completion: z.boolean().optional().describe("Wait for completion (default: false, async)")
|
|
1892
|
+
})
|
|
1893
|
+
};
|
|
1894
|
+
var smReportListTool = {
|
|
1895
|
+
name: "sm_report_list",
|
|
1896
|
+
description: SM_REPORT_LIST_DESCRIPTION,
|
|
1897
|
+
inputSchema: z.object({
|
|
1898
|
+
scenario_id: z.string().optional().describe("Filter by scenario ID")
|
|
1899
|
+
})
|
|
1900
|
+
};
|
|
1901
|
+
var smReportGetTool = {
|
|
1902
|
+
name: "sm_report_get",
|
|
1903
|
+
description: SM_REPORT_GET_DESCRIPTION,
|
|
1904
|
+
inputSchema: z.object({
|
|
1905
|
+
report_id: z.string().describe("Report ID to retrieve")
|
|
1906
|
+
})
|
|
1907
|
+
};
|
|
1908
|
+
var smReportDeleteTool = {
|
|
1909
|
+
name: "sm_report_delete",
|
|
1910
|
+
description: SM_REPORT_DELETE_DESCRIPTION,
|
|
1911
|
+
inputSchema: z.object({
|
|
1912
|
+
report_id: z.string().describe("Report ID to delete")
|
|
1913
|
+
})
|
|
1914
|
+
};
|
|
1915
|
+
var smReportExportTool = {
|
|
1916
|
+
name: "sm_report_export",
|
|
1917
|
+
description: SM_REPORT_EXPORT_DESCRIPTION,
|
|
1918
|
+
inputSchema: z.object({
|
|
1919
|
+
report_id: z.string().describe("Report ID to export"),
|
|
1920
|
+
format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)")
|
|
1921
|
+
})
|
|
1922
|
+
};
|
|
1923
|
+
var jobEntrySchema = z.object({
|
|
1924
|
+
scenario_id: z.string().describe("Scenario ID to execute"),
|
|
1925
|
+
label: z.string().optional().describe("Display label override"),
|
|
1926
|
+
params: z.record(z.unknown()).optional().default({}).describe("Scenario parameter overrides")
|
|
1927
|
+
});
|
|
1928
|
+
var smJobCreateTool = {
|
|
1929
|
+
name: "sm_job_create",
|
|
1930
|
+
description: SM_JOB_CREATE_DESCRIPTION,
|
|
1931
|
+
inputSchema: z.object({
|
|
1932
|
+
label: z.string().min(1).describe("Job name"),
|
|
1933
|
+
description: z.string().optional().describe("Job description"),
|
|
1934
|
+
entries: z.array(jobEntrySchema).min(1).describe("Ordered list of scenarios to execute"),
|
|
1935
|
+
on_error: z.enum(["stop", "skip_and_continue"]).optional().describe("How to handle scenario failures (default: stop)")
|
|
1936
|
+
})
|
|
1937
|
+
};
|
|
1938
|
+
var smJobUpdateTool = {
|
|
1939
|
+
name: "sm_job_update",
|
|
1940
|
+
description: SM_JOB_UPDATE_DESCRIPTION,
|
|
1941
|
+
inputSchema: z.object({
|
|
1942
|
+
job_id: z.string().describe("Job ID to update"),
|
|
1943
|
+
label: z.string().optional(),
|
|
1944
|
+
description: z.string().optional(),
|
|
1945
|
+
entries: z.array(jobEntrySchema).min(1).optional(),
|
|
1946
|
+
on_error: z.enum(["stop", "skip_and_continue"]).optional()
|
|
1947
|
+
})
|
|
1948
|
+
};
|
|
1949
|
+
var smJobDeleteTool = {
|
|
1950
|
+
name: "sm_job_delete",
|
|
1951
|
+
description: SM_JOB_DELETE_DESCRIPTION,
|
|
1952
|
+
inputSchema: z.object({
|
|
1953
|
+
job_id: z.string().describe("Job ID to delete")
|
|
1954
|
+
})
|
|
1955
|
+
};
|
|
1956
|
+
var smJobListTool = {
|
|
1957
|
+
name: "sm_job_list",
|
|
1958
|
+
description: SM_JOB_LIST_DESCRIPTION,
|
|
1959
|
+
inputSchema: z.object({})
|
|
1960
|
+
};
|
|
1961
|
+
var smJobRunTool = {
|
|
1962
|
+
name: "sm_job_run",
|
|
1963
|
+
description: SM_JOB_RUN_DESCRIPTION,
|
|
1964
|
+
inputSchema: z.object({
|
|
1965
|
+
tabId: z.coerce.number().describe("Tab ID to execute scenarios on"),
|
|
1966
|
+
job_id: z.string().describe("Job ID to execute"),
|
|
1967
|
+
params: z.record(z.record(z.unknown())).optional().describe("Per-scenario param overrides keyed by scenario_id"),
|
|
1968
|
+
wait_for_completion: z.boolean().optional().describe("Wait for all scenarios to complete (default: false)")
|
|
1969
|
+
})
|
|
1970
|
+
};
|
|
1971
|
+
var smJobCancelTool = {
|
|
1972
|
+
name: "sm_job_cancel",
|
|
1973
|
+
description: SM_JOB_CANCEL_DESCRIPTION,
|
|
1974
|
+
inputSchema: z.object({
|
|
1975
|
+
tabId: z.coerce.number().describe("Tab ID where the job is running")
|
|
1976
|
+
})
|
|
1977
|
+
};
|
|
1978
|
+
var smJobReportGetTool = {
|
|
1979
|
+
name: "sm_job_report_get",
|
|
1980
|
+
description: SM_JOB_REPORT_GET_DESCRIPTION,
|
|
1981
|
+
inputSchema: z.object({
|
|
1982
|
+
job_report_id: z.string().describe("Job report ID to retrieve")
|
|
1983
|
+
})
|
|
1984
|
+
};
|
|
1985
|
+
var smJobReportExportTool = {
|
|
1986
|
+
name: "sm_job_report_export",
|
|
1987
|
+
description: SM_JOB_REPORT_EXPORT_DESCRIPTION,
|
|
1988
|
+
inputSchema: z.object({
|
|
1989
|
+
job_report_id: z.string().describe("Job report ID to export"),
|
|
1990
|
+
format: z.enum(["json", "csv"]).optional().describe("Export format (default: json)")
|
|
1991
|
+
})
|
|
1992
|
+
};
|
|
1993
|
+
var smJobHistoryTool = {
|
|
1994
|
+
name: "sm_job_history",
|
|
1995
|
+
description: SM_JOB_HISTORY_DESCRIPTION,
|
|
1996
|
+
inputSchema: z.object({
|
|
1997
|
+
job_id: z.string().describe("Job ID to get history for"),
|
|
1998
|
+
limit: z.coerce.number().min(1).max(100).optional().describe("Max snapshots (default: 20)")
|
|
1999
|
+
})
|
|
2000
|
+
};
|
|
2001
|
+
var smJobCompareTool = {
|
|
2002
|
+
name: "sm_job_compare",
|
|
2003
|
+
description: SM_JOB_COMPARE_DESCRIPTION,
|
|
2004
|
+
inputSchema: z.object({
|
|
2005
|
+
job_id: z.string().describe("Job ID to compare"),
|
|
2006
|
+
base_snapshot_id: z.string().optional().describe("Older snapshot ID (default: second-most-recent)"),
|
|
2007
|
+
compare_snapshot_id: z.string().optional().describe("Newer snapshot ID (default: most-recent)"),
|
|
2008
|
+
changed_only: z.boolean().optional().describe("Omit unchanged rows (default: false)")
|
|
2009
|
+
})
|
|
2010
|
+
};
|
|
2011
|
+
var smJobSnapshotGetTool = {
|
|
2012
|
+
name: "sm_job_snapshot_get",
|
|
2013
|
+
description: SM_JOB_SNAPSHOT_GET_DESCRIPTION,
|
|
2014
|
+
inputSchema: z.object({
|
|
2015
|
+
snapshot_id: z.string().describe("Snapshot ID to retrieve (SNP_xxxx)")
|
|
2016
|
+
})
|
|
2017
|
+
};
|
|
2018
|
+
var dataBindingSchema = z.object({
|
|
2019
|
+
binding_id: z.string().min(1).max(20).describe('Unique binding ID within this view (e.g., "b1")'),
|
|
2020
|
+
source_type: z.enum(["job_report", "report", "job", "scenario"]).describe("Data source type"),
|
|
2021
|
+
source_id: z.string().describe('Source entity ID (e.g., "JRP_xxxx")'),
|
|
2022
|
+
projection: z.object({
|
|
2023
|
+
fields: z.array(z.string()).optional().describe("Field whitelist"),
|
|
2024
|
+
max_entries: z.number().min(1).max(1e3).optional().describe("Max array entries")
|
|
2025
|
+
}).optional().describe("Data projection to limit injected data")
|
|
2026
|
+
});
|
|
2027
|
+
var smCustomViewCreateTool = {
|
|
2028
|
+
name: "sm_custom_view_create",
|
|
2029
|
+
description: SM_CUSTOM_VIEW_CREATE_DESCRIPTION,
|
|
2030
|
+
inputSchema: z.object({
|
|
2031
|
+
label: z.string().min(1).max(100).describe("View name"),
|
|
2032
|
+
description: z.string().max(500).optional().describe("View description"),
|
|
2033
|
+
html: z.string().max(2e5).describe("HTML content (max 200KB)"),
|
|
2034
|
+
data_bindings: z.array(dataBindingSchema).max(10).optional().describe("Data source bindings")
|
|
2035
|
+
})
|
|
2036
|
+
};
|
|
2037
|
+
var smCustomViewUpdateTool = {
|
|
2038
|
+
name: "sm_custom_view_update",
|
|
2039
|
+
description: SM_CUSTOM_VIEW_UPDATE_DESCRIPTION,
|
|
2040
|
+
inputSchema: z.object({
|
|
2041
|
+
view_id: z.string().describe("View ID to update"),
|
|
2042
|
+
label: z.string().min(1).max(100).optional().describe("New label"),
|
|
2043
|
+
description: z.string().max(500).optional().describe("New description"),
|
|
2044
|
+
html: z.string().max(2e5).optional().describe("New HTML content"),
|
|
2045
|
+
data_bindings: z.array(dataBindingSchema).max(10).optional().describe("New data bindings (replaces all)")
|
|
2046
|
+
})
|
|
2047
|
+
};
|
|
2048
|
+
var smCustomViewDeleteTool = {
|
|
2049
|
+
name: "sm_custom_view_delete",
|
|
2050
|
+
description: SM_CUSTOM_VIEW_DELETE_DESCRIPTION,
|
|
2051
|
+
inputSchema: z.object({
|
|
2052
|
+
view_id: z.string().describe("View ID to delete")
|
|
2053
|
+
})
|
|
2054
|
+
};
|
|
2055
|
+
var smCustomViewListTool = {
|
|
2056
|
+
name: "sm_custom_view_list",
|
|
2057
|
+
description: SM_CUSTOM_VIEW_LIST_DESCRIPTION,
|
|
2058
|
+
inputSchema: z.object({})
|
|
2059
|
+
};
|
|
2060
|
+
var smCustomViewGetTool = {
|
|
2061
|
+
name: "sm_custom_view_get",
|
|
2062
|
+
description: SM_CUSTOM_VIEW_GET_DESCRIPTION,
|
|
2063
|
+
inputSchema: z.object({
|
|
2064
|
+
view_id: z.string().describe("View ID to retrieve"),
|
|
2065
|
+
include_data: z.boolean().optional().describe("Include resolved data in response (default: false)")
|
|
2066
|
+
})
|
|
2067
|
+
};
|
|
752
2068
|
var allTools = [
|
|
753
2069
|
// Core (15)
|
|
754
2070
|
navigateTool,
|
|
@@ -774,9 +2090,10 @@ var allTools = [
|
|
|
774
2090
|
// Debug (2)
|
|
775
2091
|
readConsoleMessagesTool,
|
|
776
2092
|
readNetworkRequestsTool,
|
|
777
|
-
// Advanced (
|
|
2093
|
+
// Advanced (8)
|
|
778
2094
|
gifCreatorTool,
|
|
779
2095
|
uploadImageTool,
|
|
2096
|
+
fileUploadTool,
|
|
780
2097
|
updatePlanTool,
|
|
781
2098
|
resizeWindowTool,
|
|
782
2099
|
shortcutsListTool,
|
|
@@ -789,47 +2106,115 @@ var allTools = [
|
|
|
789
2106
|
browserEventUnsubscribeTool,
|
|
790
2107
|
artifactFromPageTool,
|
|
791
2108
|
pageDataExtractTool,
|
|
792
|
-
browserHealthTool
|
|
2109
|
+
browserHealthTool,
|
|
2110
|
+
// Semantic (12)
|
|
2111
|
+
smCapabilitiesTool,
|
|
2112
|
+
smInvokeTool,
|
|
2113
|
+
smFetchTool,
|
|
2114
|
+
smRegisterPageTool,
|
|
2115
|
+
smAddTargetTool,
|
|
2116
|
+
smAddActionTool,
|
|
2117
|
+
smAddFetchTool,
|
|
2118
|
+
smStatusTool,
|
|
2119
|
+
smTestTargetTool,
|
|
2120
|
+
smDeleteTool,
|
|
2121
|
+
smExportTool,
|
|
2122
|
+
smImportTool,
|
|
2123
|
+
// Scenario & Report (9)
|
|
2124
|
+
smScenarioCreateTool,
|
|
2125
|
+
smScenarioUpdateTool,
|
|
2126
|
+
smScenarioDeleteTool,
|
|
2127
|
+
smScenarioListTool,
|
|
2128
|
+
smScenarioRunTool,
|
|
2129
|
+
smReportListTool,
|
|
2130
|
+
smReportGetTool,
|
|
2131
|
+
smReportDeleteTool,
|
|
2132
|
+
smReportExportTool,
|
|
2133
|
+
// Job (8)
|
|
2134
|
+
smJobCreateTool,
|
|
2135
|
+
smJobUpdateTool,
|
|
2136
|
+
smJobDeleteTool,
|
|
2137
|
+
smJobListTool,
|
|
2138
|
+
smJobRunTool,
|
|
2139
|
+
smJobCancelTool,
|
|
2140
|
+
smJobReportGetTool,
|
|
2141
|
+
smJobReportExportTool,
|
|
2142
|
+
// Job History (3)
|
|
2143
|
+
smJobHistoryTool,
|
|
2144
|
+
smJobCompareTool,
|
|
2145
|
+
smJobSnapshotGetTool,
|
|
2146
|
+
// Custom View (5)
|
|
2147
|
+
smCustomViewCreateTool,
|
|
2148
|
+
smCustomViewUpdateTool,
|
|
2149
|
+
smCustomViewDeleteTool,
|
|
2150
|
+
smCustomViewListTool,
|
|
2151
|
+
smCustomViewGetTool
|
|
793
2152
|
];
|
|
794
2153
|
|
|
795
2154
|
// src/server.ts
|
|
796
2155
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
797
2156
|
var extensionSocket = null;
|
|
2157
|
+
function coerceValue(v) {
|
|
2158
|
+
if (typeof v !== "string") return v;
|
|
2159
|
+
if (v.startsWith("[") && v.endsWith("]") || v.startsWith("{") && v.endsWith("}")) {
|
|
2160
|
+
try {
|
|
2161
|
+
return JSON.parse(v);
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
if (v === "true") return true;
|
|
2166
|
+
if (v === "false") return false;
|
|
2167
|
+
return v;
|
|
2168
|
+
}
|
|
2169
|
+
function coerceShape(shape) {
|
|
2170
|
+
const result = {};
|
|
2171
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
2172
|
+
result[key] = z2.preprocess(coerceValue, schema);
|
|
2173
|
+
}
|
|
2174
|
+
return result;
|
|
2175
|
+
}
|
|
798
2176
|
function createConfiguredMcpServer() {
|
|
799
2177
|
const server = new McpServer({
|
|
800
2178
|
name: MCP_SERVER.NAME,
|
|
801
2179
|
version: MCP_SERVER.VERSION
|
|
802
2180
|
});
|
|
803
2181
|
for (const tool of allTools) {
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
2182
|
+
const def = tool.inputSchema._def;
|
|
2183
|
+
const shape = def.shape?.() ?? {};
|
|
2184
|
+
server.tool(
|
|
2185
|
+
tool.name,
|
|
2186
|
+
tool.description,
|
|
2187
|
+
coerceShape(shape),
|
|
2188
|
+
async (params) => {
|
|
2189
|
+
const result = await callExtensionTool(tool.name, params);
|
|
2190
|
+
const first = result.content[0];
|
|
2191
|
+
if (tool.name === "browser_event_subscribe" && first?.type === "text") {
|
|
2192
|
+
try {
|
|
2193
|
+
const parsed = JSON.parse(first.text);
|
|
2194
|
+
if (parsed.subscriptionId) {
|
|
2195
|
+
const p = params;
|
|
2196
|
+
addSubscription({
|
|
2197
|
+
id: parsed.subscriptionId,
|
|
2198
|
+
agentId: getDefaultAgentId(),
|
|
2199
|
+
eventTypes: p.eventTypes ?? [],
|
|
2200
|
+
urlPattern: p.urlPattern,
|
|
2201
|
+
createdAt: Date.now()
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
} catch {
|
|
819
2205
|
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
2206
|
+
} else if (tool.name === "browser_event_unsubscribe" && first?.type === "text") {
|
|
2207
|
+
try {
|
|
2208
|
+
const parsed = JSON.parse(first.text);
|
|
2209
|
+
if (parsed.subscriptionId) {
|
|
2210
|
+
removeSubscription(parsed.subscriptionId);
|
|
2211
|
+
}
|
|
2212
|
+
} catch {
|
|
827
2213
|
}
|
|
828
|
-
} catch {
|
|
829
2214
|
}
|
|
2215
|
+
return result;
|
|
830
2216
|
}
|
|
831
|
-
|
|
832
|
-
});
|
|
2217
|
+
);
|
|
833
2218
|
}
|
|
834
2219
|
const listener = (event) => {
|
|
835
2220
|
server.sendLoggingMessage({
|
|
@@ -896,6 +2281,75 @@ async function startMcpServer(socketPath, agentName, options) {
|
|
|
896
2281
|
process.on("exit", () => {
|
|
897
2282
|
cleanupSocket(socketPath);
|
|
898
2283
|
});
|
|
2284
|
+
} else if (options?.transport === "streamable-http") {
|
|
2285
|
+
const sessions2 = /* @__PURE__ */ new Map();
|
|
2286
|
+
const SESSION_TTL = 30 * 60 * 1e3;
|
|
2287
|
+
const SWEEP_INTERVAL = 5 * 60 * 1e3;
|
|
2288
|
+
const sweepTimer = setInterval(() => {
|
|
2289
|
+
const now = Date.now();
|
|
2290
|
+
for (const [id, session] of sessions2) {
|
|
2291
|
+
if (now - session.lastActivity > SESSION_TTL) {
|
|
2292
|
+
process.stderr.write(`[viyv-browser:mcp] Streamable HTTP session TTL expired: ${id}
|
|
2293
|
+
`);
|
|
2294
|
+
sessions2.delete(id);
|
|
2295
|
+
session.server.close().catch(() => {
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}, SWEEP_INTERVAL);
|
|
2300
|
+
sweepTimer.unref();
|
|
2301
|
+
const httpServer = http.createServer();
|
|
2302
|
+
httpServer.on("request", (req, res) => {
|
|
2303
|
+
handleStreamableHttpRequest(req, res, sessions2).catch((error) => {
|
|
2304
|
+
process.stderr.write(
|
|
2305
|
+
`[viyv-browser:mcp] Streamable HTTP request error: ${error.message}
|
|
2306
|
+
`
|
|
2307
|
+
);
|
|
2308
|
+
if (!res.headersSent) {
|
|
2309
|
+
res.writeHead(500).end("Internal server error");
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
});
|
|
2313
|
+
const listenPort = options.port ?? 0;
|
|
2314
|
+
httpServer.listen(listenPort, "127.0.0.1", () => {
|
|
2315
|
+
const addr = httpServer.address();
|
|
2316
|
+
const port = typeof addr === "object" ? addr?.port : listenPort;
|
|
2317
|
+
process.stdout.write(`${JSON.stringify({ port })}
|
|
2318
|
+
`);
|
|
2319
|
+
process.stderr.write(
|
|
2320
|
+
`[viyv-browser:mcp] Streamable HTTP server listening on 127.0.0.1:${port}
|
|
2321
|
+
`
|
|
2322
|
+
);
|
|
2323
|
+
});
|
|
2324
|
+
process.stderr.write(
|
|
2325
|
+
`[viyv-browser:mcp] MCP Server started (streamable-http), socket: ${socketPath}
|
|
2326
|
+
`
|
|
2327
|
+
);
|
|
2328
|
+
let shuttingDown = false;
|
|
2329
|
+
const shutdown = async () => {
|
|
2330
|
+
if (shuttingDown) return;
|
|
2331
|
+
shuttingDown = true;
|
|
2332
|
+
clearInterval(sweepTimer);
|
|
2333
|
+
for (const { server: s } of sessions2.values()) {
|
|
2334
|
+
await s.close().catch(() => {
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
httpServer.close(() => {
|
|
2338
|
+
});
|
|
2339
|
+
socketServer.close(() => {
|
|
2340
|
+
});
|
|
2341
|
+
cleanupSocket(socketPath);
|
|
2342
|
+
process.exit(0);
|
|
2343
|
+
};
|
|
2344
|
+
process.on("SIGINT", () => {
|
|
2345
|
+
shutdown();
|
|
2346
|
+
});
|
|
2347
|
+
process.on("SIGTERM", () => {
|
|
2348
|
+
shutdown();
|
|
2349
|
+
});
|
|
2350
|
+
process.on("exit", () => {
|
|
2351
|
+
cleanupSocket(socketPath);
|
|
2352
|
+
});
|
|
899
2353
|
} else {
|
|
900
2354
|
const server = createConfiguredMcpServer();
|
|
901
2355
|
const transport = new StdioServerTransport();
|
|
@@ -915,28 +2369,168 @@ async function handleSseRequest(req, res, sessions2) {
|
|
|
915
2369
|
const transport = new SSEServerTransport("/message", res);
|
|
916
2370
|
const mcpServer = createConfiguredMcpServer();
|
|
917
2371
|
transport.onclose = () => {
|
|
918
|
-
sessions2.delete(transport.sessionId);
|
|
2372
|
+
sessions2.delete(transport.sessionId);
|
|
2373
|
+
};
|
|
2374
|
+
res.on("close", () => {
|
|
2375
|
+
if (sessions2.has(transport.sessionId)) {
|
|
2376
|
+
sessions2.delete(transport.sessionId);
|
|
2377
|
+
transport.close();
|
|
2378
|
+
}
|
|
2379
|
+
});
|
|
2380
|
+
await mcpServer.connect(transport);
|
|
2381
|
+
sessions2.set(transport.sessionId, { transport, server: mcpServer });
|
|
2382
|
+
} else if (req.method === "POST" && req.url?.startsWith("/message")) {
|
|
2383
|
+
const url = new URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
2384
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
2385
|
+
const session = sessionId ? sessions2.get(sessionId) : null;
|
|
2386
|
+
if (session) {
|
|
2387
|
+
await session.transport.handlePostMessage(req, res);
|
|
2388
|
+
} else {
|
|
2389
|
+
res.writeHead(404).end("Session not found");
|
|
2390
|
+
}
|
|
2391
|
+
} else {
|
|
2392
|
+
res.writeHead(404).end();
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
async function handleStreamableHttpRequest(req, res, sessions2) {
|
|
2396
|
+
let url;
|
|
2397
|
+
try {
|
|
2398
|
+
url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
2399
|
+
} catch {
|
|
2400
|
+
res.writeHead(400).end("Bad request");
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
if (url.pathname !== "/mcp") {
|
|
2404
|
+
res.writeHead(404).end("Not found");
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const rawSessionId = req.headers["mcp-session-id"];
|
|
2408
|
+
const sessionId = typeof rawSessionId === "string" ? rawSessionId : void 0;
|
|
2409
|
+
const existingSession = sessionId ? sessions2.get(sessionId) : void 0;
|
|
2410
|
+
if (existingSession) {
|
|
2411
|
+
existingSession.lastActivity = Date.now();
|
|
2412
|
+
if (req.method === "POST") {
|
|
2413
|
+
let body;
|
|
2414
|
+
try {
|
|
2415
|
+
body = await parseJsonBody(req);
|
|
2416
|
+
} catch (parseError) {
|
|
2417
|
+
process.stderr.write(
|
|
2418
|
+
`[viyv-browser:mcp] Streamable HTTP body parse failed: ${parseError.message}
|
|
2419
|
+
`
|
|
2420
|
+
);
|
|
2421
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
2422
|
+
JSON.stringify({
|
|
2423
|
+
jsonrpc: "2.0",
|
|
2424
|
+
error: { code: -32700, message: "Parse error" },
|
|
2425
|
+
id: null
|
|
2426
|
+
})
|
|
2427
|
+
);
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
await existingSession.transport.handleRequest(req, res, body);
|
|
2431
|
+
} else {
|
|
2432
|
+
await existingSession.transport.handleRequest(req, res);
|
|
2433
|
+
}
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
if (!sessionId && req.method === "POST") {
|
|
2437
|
+
let body;
|
|
2438
|
+
try {
|
|
2439
|
+
body = await parseJsonBody(req);
|
|
2440
|
+
} catch (parseError) {
|
|
2441
|
+
process.stderr.write(
|
|
2442
|
+
`[viyv-browser:mcp] Streamable HTTP body parse failed: ${parseError.message}
|
|
2443
|
+
`
|
|
2444
|
+
);
|
|
2445
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
2446
|
+
JSON.stringify({
|
|
2447
|
+
jsonrpc: "2.0",
|
|
2448
|
+
error: { code: -32700, message: "Parse error" },
|
|
2449
|
+
id: null
|
|
2450
|
+
})
|
|
2451
|
+
);
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
if (!isInitializeRequest(body)) {
|
|
2455
|
+
const requestId = body && typeof body === "object" && "id" in body ? body.id : null;
|
|
2456
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
2457
|
+
JSON.stringify({
|
|
2458
|
+
jsonrpc: "2.0",
|
|
2459
|
+
error: { code: -32600, message: "First request must be an initialize request" },
|
|
2460
|
+
id: requestId ?? null
|
|
2461
|
+
})
|
|
2462
|
+
);
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
const mcpServer = createConfiguredMcpServer();
|
|
2466
|
+
const transport = new StreamableHTTPServerTransport({
|
|
2467
|
+
sessionIdGenerator: () => randomUUID2(),
|
|
2468
|
+
onsessioninitialized: (id) => {
|
|
2469
|
+
sessions2.set(id, { transport, server: mcpServer, lastActivity: Date.now() });
|
|
2470
|
+
}
|
|
2471
|
+
});
|
|
2472
|
+
transport.onclose = () => {
|
|
2473
|
+
const id = transport.sessionId;
|
|
2474
|
+
if (id) sessions2.delete(id);
|
|
919
2475
|
};
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
2476
|
+
let connected = false;
|
|
2477
|
+
try {
|
|
2478
|
+
await mcpServer.connect(transport);
|
|
2479
|
+
connected = true;
|
|
2480
|
+
await transport.handleRequest(req, res, body);
|
|
2481
|
+
} catch (error) {
|
|
2482
|
+
const stage = connected ? "handleRequest" : "connect";
|
|
2483
|
+
process.stderr.write(
|
|
2484
|
+
`[viyv-browser:mcp] Streamable HTTP session ${stage} failed: ${error.message}
|
|
2485
|
+
`
|
|
2486
|
+
);
|
|
2487
|
+
try {
|
|
2488
|
+
mcpServer.server.onclose?.();
|
|
2489
|
+
} catch {
|
|
2490
|
+
}
|
|
2491
|
+
await mcpServer.close().catch(() => {
|
|
2492
|
+
});
|
|
2493
|
+
if (!res.headersSent) {
|
|
2494
|
+
res.writeHead(500).end("Internal server error");
|
|
924
2495
|
}
|
|
925
|
-
});
|
|
926
|
-
await mcpServer.connect(transport);
|
|
927
|
-
sessions2.set(transport.sessionId, { transport, server: mcpServer });
|
|
928
|
-
} else if (req.method === "POST" && req.url?.startsWith("/message")) {
|
|
929
|
-
const url = new URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
930
|
-
const sessionId = url.searchParams.get("sessionId");
|
|
931
|
-
const session = sessionId ? sessions2.get(sessionId) : null;
|
|
932
|
-
if (session) {
|
|
933
|
-
await session.transport.handlePostMessage(req, res);
|
|
934
|
-
} else {
|
|
935
|
-
res.writeHead(404).end("Session not found");
|
|
936
2496
|
}
|
|
937
|
-
|
|
938
|
-
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
if (sessionId && !existingSession) {
|
|
2500
|
+
res.writeHead(404).end("Session not found");
|
|
2501
|
+
return;
|
|
939
2502
|
}
|
|
2503
|
+
res.writeHead(400).end("Bad request");
|
|
2504
|
+
}
|
|
2505
|
+
var MAX_BODY_SIZE = 4 * 1024 * 1024;
|
|
2506
|
+
function parseJsonBody(req) {
|
|
2507
|
+
return new Promise((resolve2, reject) => {
|
|
2508
|
+
const chunks = [];
|
|
2509
|
+
let size = 0;
|
|
2510
|
+
let destroyed = false;
|
|
2511
|
+
req.on("data", (chunk) => {
|
|
2512
|
+
if (destroyed) return;
|
|
2513
|
+
size += chunk.length;
|
|
2514
|
+
if (size > MAX_BODY_SIZE) {
|
|
2515
|
+
destroyed = true;
|
|
2516
|
+
req.destroy();
|
|
2517
|
+
reject(new Error("Request body too large"));
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
chunks.push(chunk);
|
|
2521
|
+
});
|
|
2522
|
+
req.on("end", () => {
|
|
2523
|
+
if (destroyed) return;
|
|
2524
|
+
try {
|
|
2525
|
+
resolve2(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
|
|
2526
|
+
} catch (error) {
|
|
2527
|
+
reject(new Error(`Invalid JSON: ${error.message}`));
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2530
|
+
req.on("error", (err) => {
|
|
2531
|
+
if (!destroyed) reject(err);
|
|
2532
|
+
});
|
|
2533
|
+
});
|
|
940
2534
|
}
|
|
941
2535
|
function createSocketServer(socketPath) {
|
|
942
2536
|
cleanupSocket(socketPath);
|
|
@@ -1071,7 +2665,39 @@ async function callExtensionTool(tool, input) {
|
|
|
1071
2665
|
if (tool === "switch_browser") {
|
|
1072
2666
|
return handleSwitchBrowser();
|
|
1073
2667
|
}
|
|
1074
|
-
if (
|
|
2668
|
+
if (tool === "file_upload") {
|
|
2669
|
+
const paths = input.paths;
|
|
2670
|
+
if (paths) {
|
|
2671
|
+
for (const p of paths) {
|
|
2672
|
+
try {
|
|
2673
|
+
if (!existsSync(p) || !statSync(p).isFile()) {
|
|
2674
|
+
return {
|
|
2675
|
+
content: [
|
|
2676
|
+
{
|
|
2677
|
+
type: "text",
|
|
2678
|
+
text: JSON.stringify({
|
|
2679
|
+
error: { code: "FILE_NOT_FOUND", message: `File not found: ${p}` }
|
|
2680
|
+
})
|
|
2681
|
+
}
|
|
2682
|
+
]
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
} catch {
|
|
2686
|
+
return {
|
|
2687
|
+
content: [
|
|
2688
|
+
{
|
|
2689
|
+
type: "text",
|
|
2690
|
+
text: JSON.stringify({
|
|
2691
|
+
error: { code: "FILE_NOT_FOUND", message: `Cannot access file: ${p}` }
|
|
2692
|
+
})
|
|
2693
|
+
}
|
|
2694
|
+
]
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
if (!extensionSocket || extensionSocket.destroyed) {
|
|
1075
2701
|
return {
|
|
1076
2702
|
content: [
|
|
1077
2703
|
{
|
|
@@ -1138,9 +2764,20 @@ async function callExtensionTool(tool, input) {
|
|
|
1138
2764
|
pendingRequests.set(requestId, {
|
|
1139
2765
|
resolve: (result) => {
|
|
1140
2766
|
removeErrorListener();
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
2767
|
+
if (tool === "screenshot" && typeof result.data === "string") {
|
|
2768
|
+
const { data, ...metadata } = result;
|
|
2769
|
+
const mimeType = metadata.format === "png" ? "image/png" : "image/jpeg";
|
|
2770
|
+
resolve2({
|
|
2771
|
+
content: [
|
|
2772
|
+
{ type: "image", data, mimeType },
|
|
2773
|
+
{ type: "text", text: JSON.stringify(metadata) }
|
|
2774
|
+
]
|
|
2775
|
+
});
|
|
2776
|
+
} else {
|
|
2777
|
+
resolve2({
|
|
2778
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
2779
|
+
});
|
|
2780
|
+
}
|
|
1144
2781
|
},
|
|
1145
2782
|
reject: (error) => {
|
|
1146
2783
|
removeErrorListener();
|
|
@@ -1228,199 +2865,11 @@ function cleanupSocket(socketPath) {
|
|
|
1228
2865
|
}
|
|
1229
2866
|
}
|
|
1230
2867
|
|
|
1231
|
-
// src/native-host/bridge.ts
|
|
1232
|
-
import { createConnection } from "net";
|
|
1233
|
-
|
|
1234
|
-
// src/native-host/transport.ts
|
|
1235
|
-
var MAX_MESSAGE_SIZE = 1024 * 1024;
|
|
1236
|
-
function encodeMessage(message) {
|
|
1237
|
-
const json = JSON.stringify(message);
|
|
1238
|
-
const body = Buffer.from(json, "utf-8");
|
|
1239
|
-
if (body.length > MAX_MESSAGE_SIZE) {
|
|
1240
|
-
throw new Error(`Message too large: ${body.length} bytes (max ${MAX_MESSAGE_SIZE})`);
|
|
1241
|
-
}
|
|
1242
|
-
const header = Buffer.alloc(4);
|
|
1243
|
-
header.writeUInt32LE(body.length, 0);
|
|
1244
|
-
return Buffer.concat([header, body]);
|
|
1245
|
-
}
|
|
1246
|
-
function createMessageReader(stream, onMessage, onError, onClose) {
|
|
1247
|
-
let buffer = Buffer.alloc(0);
|
|
1248
|
-
let expectedLength = null;
|
|
1249
|
-
stream.on("data", (chunk) => {
|
|
1250
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
1251
|
-
while (true) {
|
|
1252
|
-
if (expectedLength === null) {
|
|
1253
|
-
if (buffer.length < 4) break;
|
|
1254
|
-
expectedLength = buffer.readUInt32LE(0);
|
|
1255
|
-
buffer = buffer.subarray(4);
|
|
1256
|
-
if (expectedLength > MAX_MESSAGE_SIZE) {
|
|
1257
|
-
onError?.(new Error(`Message too large: ${expectedLength} bytes`));
|
|
1258
|
-
expectedLength = null;
|
|
1259
|
-
buffer = Buffer.alloc(0);
|
|
1260
|
-
break;
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
if (buffer.length < expectedLength) break;
|
|
1264
|
-
const jsonBuffer = buffer.subarray(0, expectedLength);
|
|
1265
|
-
buffer = buffer.subarray(expectedLength);
|
|
1266
|
-
expectedLength = null;
|
|
1267
|
-
try {
|
|
1268
|
-
const message = JSON.parse(jsonBuffer.toString("utf-8"));
|
|
1269
|
-
onMessage(message);
|
|
1270
|
-
} catch (error) {
|
|
1271
|
-
onError?.(new Error(`Invalid JSON: ${error.message}`));
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
});
|
|
1275
|
-
stream.on("error", (error) => {
|
|
1276
|
-
onError?.(new Error(`Stream error: ${error.message}`));
|
|
1277
|
-
});
|
|
1278
|
-
stream.on("end", () => {
|
|
1279
|
-
onClose?.();
|
|
1280
|
-
});
|
|
1281
|
-
stream.on("close", () => {
|
|
1282
|
-
onClose?.();
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
function writeMessage(stream, message) {
|
|
1286
|
-
const encoded = encodeMessage(message);
|
|
1287
|
-
stream.write(encoded);
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
// src/native-host/bridge.ts
|
|
1291
|
-
var MAX_BUFFER_SIZE = 1e3;
|
|
1292
|
-
function startBridge(options) {
|
|
1293
|
-
const { socketPath, onError } = options;
|
|
1294
|
-
let socket = null;
|
|
1295
|
-
let reconnecting = false;
|
|
1296
|
-
let retryCount = 0;
|
|
1297
|
-
const pendingMessages = [];
|
|
1298
|
-
function flushBuffer() {
|
|
1299
|
-
if (!socket || socket.destroyed) return;
|
|
1300
|
-
while (pendingMessages.length > 0) {
|
|
1301
|
-
const msg = pendingMessages[0];
|
|
1302
|
-
try {
|
|
1303
|
-
const written = socket.write(`${JSON.stringify(msg)}
|
|
1304
|
-
`);
|
|
1305
|
-
pendingMessages.shift();
|
|
1306
|
-
if (!written) {
|
|
1307
|
-
socket.once("drain", () => flushBuffer());
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1310
|
-
} catch (error) {
|
|
1311
|
-
onError?.(error);
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
function connectSocket() {
|
|
1317
|
-
socket = createConnection(socketPath);
|
|
1318
|
-
socket.on("connect", () => {
|
|
1319
|
-
process.stderr.write(
|
|
1320
|
-
`[viyv-browser:native-host] Connected to MCP server at ${socketPath}
|
|
1321
|
-
`
|
|
1322
|
-
);
|
|
1323
|
-
retryCount = 0;
|
|
1324
|
-
flushBuffer();
|
|
1325
|
-
});
|
|
1326
|
-
let lineBuffer = "";
|
|
1327
|
-
socket.on("data", (data) => {
|
|
1328
|
-
lineBuffer += data.toString("utf-8");
|
|
1329
|
-
const lines = lineBuffer.split("\n");
|
|
1330
|
-
lineBuffer = lines.pop() ?? "";
|
|
1331
|
-
for (const line of lines) {
|
|
1332
|
-
if (!line) continue;
|
|
1333
|
-
try {
|
|
1334
|
-
let message = JSON.parse(line);
|
|
1335
|
-
if (message.type === "compressed" && typeof message.data === "string") {
|
|
1336
|
-
const decompressed = decompressPayload(message.data, true);
|
|
1337
|
-
message = JSON.parse(decompressed);
|
|
1338
|
-
}
|
|
1339
|
-
writeMessage(process.stdout, message);
|
|
1340
|
-
} catch (error) {
|
|
1341
|
-
onError?.(error);
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
});
|
|
1345
|
-
socket.on("error", (error) => {
|
|
1346
|
-
process.stderr.write(
|
|
1347
|
-
`[viyv-browser:native-host] Socket error: ${error.message}
|
|
1348
|
-
`
|
|
1349
|
-
);
|
|
1350
|
-
onError?.(error);
|
|
1351
|
-
});
|
|
1352
|
-
socket.on("close", () => {
|
|
1353
|
-
process.stderr.write("[viyv-browser:native-host] Socket closed\n");
|
|
1354
|
-
socket = null;
|
|
1355
|
-
if (!reconnecting) {
|
|
1356
|
-
reconnecting = true;
|
|
1357
|
-
const delay = Math.min(
|
|
1358
|
-
RECONNECT.INITIAL_DELAY * Math.pow(RECONNECT.MULTIPLIER, retryCount),
|
|
1359
|
-
RECONNECT.MAX_DELAY
|
|
1360
|
-
);
|
|
1361
|
-
retryCount++;
|
|
1362
|
-
process.stderr.write(
|
|
1363
|
-
`[viyv-browser:native-host] Reconnecting in ${delay}ms (attempt ${retryCount})
|
|
1364
|
-
`
|
|
1365
|
-
);
|
|
1366
|
-
setTimeout(() => {
|
|
1367
|
-
reconnecting = false;
|
|
1368
|
-
connectSocket();
|
|
1369
|
-
}, delay);
|
|
1370
|
-
}
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
createMessageReader(
|
|
1374
|
-
process.stdin,
|
|
1375
|
-
(message) => {
|
|
1376
|
-
if (socket && !socket.destroyed) {
|
|
1377
|
-
const json = JSON.stringify(message);
|
|
1378
|
-
if (json.length > LIMITS.CHUNK_SIZE) {
|
|
1379
|
-
const { compressed, wasCompressed } = compressPayload(json);
|
|
1380
|
-
if (wasCompressed) {
|
|
1381
|
-
socket.write(`${JSON.stringify({ type: "compressed", data: compressed })}
|
|
1382
|
-
`);
|
|
1383
|
-
} else {
|
|
1384
|
-
socket.write(`${json}
|
|
1385
|
-
`);
|
|
1386
|
-
}
|
|
1387
|
-
} else {
|
|
1388
|
-
socket.write(`${json}
|
|
1389
|
-
`);
|
|
1390
|
-
}
|
|
1391
|
-
} else {
|
|
1392
|
-
if (pendingMessages.length < MAX_BUFFER_SIZE) {
|
|
1393
|
-
pendingMessages.push(message);
|
|
1394
|
-
} else {
|
|
1395
|
-
process.stderr.write(
|
|
1396
|
-
"[viyv-browser:native-host] Message buffer full, dropping message\n"
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
},
|
|
1401
|
-
onError
|
|
1402
|
-
);
|
|
1403
|
-
connectSocket();
|
|
1404
|
-
process.on("SIGINT", () => {
|
|
1405
|
-
socket?.destroy();
|
|
1406
|
-
process.exit(0);
|
|
1407
|
-
});
|
|
1408
|
-
process.on("SIGTERM", () => {
|
|
1409
|
-
socket?.destroy();
|
|
1410
|
-
process.exit(0);
|
|
1411
|
-
});
|
|
1412
|
-
process.stdin.on("end", () => {
|
|
1413
|
-
process.stderr.write("[viyv-browser:native-host] stdin closed, shutting down\n");
|
|
1414
|
-
socket?.destroy();
|
|
1415
|
-
process.exit(0);
|
|
1416
|
-
});
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
2868
|
// src/setup.ts
|
|
1420
|
-
import { writeFileSync, mkdirSync, chmodSync, existsSync as existsSync2 } from "fs";
|
|
1421
|
-
import { resolve, dirname } from "path";
|
|
1422
|
-
import { homedir, platform } from "os";
|
|
1423
2869
|
import { execSync } from "child_process";
|
|
2870
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
2871
|
+
import { homedir, platform } from "os";
|
|
2872
|
+
import { dirname, resolve } from "path";
|
|
1424
2873
|
function runSetup(options = {}) {
|
|
1425
2874
|
const os = platform();
|
|
1426
2875
|
const binaryPath = getBinaryPath();
|
|
@@ -1481,12 +2930,9 @@ function createNativeHostWrapper(os, binaryPath) {
|
|
|
1481
2930
|
return wrapperPath2;
|
|
1482
2931
|
}
|
|
1483
2932
|
const wrapperPath = resolve(manifestDir, `${NATIVE_HOST_NAME}.sh`);
|
|
1484
|
-
writeFileSync(
|
|
1485
|
-
wrapperPath,
|
|
1486
|
-
`#!/bin/bash
|
|
2933
|
+
writeFileSync(wrapperPath, `#!/bin/bash
|
|
1487
2934
|
exec "${nodePath}" "${binaryPath}" --native-host
|
|
1488
|
-
`
|
|
1489
|
-
);
|
|
2935
|
+
`);
|
|
1490
2936
|
chmodSync(wrapperPath, 493);
|
|
1491
2937
|
return wrapperPath;
|
|
1492
2938
|
}
|
|
@@ -1507,11 +2953,7 @@ function getManifestPath(os) {
|
|
|
1507
2953
|
`${NATIVE_HOST_NAME}.json`
|
|
1508
2954
|
);
|
|
1509
2955
|
case "linux":
|
|
1510
|
-
return resolve(
|
|
1511
|
-
home,
|
|
1512
|
-
".config/google-chrome/NativeMessagingHosts",
|
|
1513
|
-
`${NATIVE_HOST_NAME}.json`
|
|
1514
|
-
);
|
|
2956
|
+
return resolve(home, ".config/google-chrome/NativeMessagingHosts", `${NATIVE_HOST_NAME}.json`);
|
|
1515
2957
|
case "win32":
|
|
1516
2958
|
return resolve(
|
|
1517
2959
|
home,
|
|
@@ -1535,10 +2977,8 @@ if (args.includes("setup")) {
|
|
|
1535
2977
|
function poll() {
|
|
1536
2978
|
const socketPath = findSocketPath();
|
|
1537
2979
|
if (socketPath) {
|
|
1538
|
-
process.stderr.write(
|
|
1539
|
-
|
|
1540
|
-
`
|
|
1541
|
-
);
|
|
2980
|
+
process.stderr.write(`[viyv-browser:native-host] Found socket at ${socketPath}
|
|
2981
|
+
`);
|
|
1542
2982
|
startBridge({
|
|
1543
2983
|
socketPath,
|
|
1544
2984
|
onError: (error) => {
|
|
@@ -1574,9 +3014,9 @@ if (args.includes("setup")) {
|
|
|
1574
3014
|
const agentName = agentNameIdx >= 0 ? args[agentNameIdx + 1] : void 0;
|
|
1575
3015
|
const transportIdx = args.indexOf("--transport");
|
|
1576
3016
|
const transportMode = transportIdx >= 0 ? args[transportIdx + 1] : "stdio";
|
|
1577
|
-
if (transportMode !== "stdio" && transportMode !== "sse") {
|
|
3017
|
+
if (transportMode !== "stdio" && transportMode !== "sse" && transportMode !== "streamable-http") {
|
|
1578
3018
|
process.stderr.write(
|
|
1579
|
-
`[viyv-browser:mcp] Invalid transport: "${transportMode}". Must be "stdio" or "
|
|
3019
|
+
`[viyv-browser:mcp] Invalid transport: "${transportMode}". Must be "stdio", "sse", or "streamable-http".
|
|
1580
3020
|
`
|
|
1581
3021
|
);
|
|
1582
3022
|
process.exit(1);
|