@quanta-intellect/vessel-browser 0.1.14 → 0.1.15
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/README.md +1 -1
- package/out/main/index.js +1195 -505
- package/out/preload/index.js +32 -1
- package/out/renderer/assets/{index-CvRVBELV.js → index-DSaws_sH.js} +313 -307
- package/out/renderer/index.html +2 -1
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -8,11 +8,125 @@ const Anthropic = require("@anthropic-ai/sdk");
|
|
|
8
8
|
const OpenAI = require("openai");
|
|
9
9
|
const zod = require("zod");
|
|
10
10
|
const path$1 = require("node:path");
|
|
11
|
-
const
|
|
11
|
+
const crypto$1 = require("node:crypto");
|
|
12
12
|
const http = require("node:http");
|
|
13
13
|
const os = require("node:os");
|
|
14
14
|
const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
15
15
|
const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
16
|
+
const defaults = {
|
|
17
|
+
defaultUrl: "https://start.duckduckgo.com",
|
|
18
|
+
theme: "dark",
|
|
19
|
+
sidebarWidth: 340,
|
|
20
|
+
mcpPort: 3100,
|
|
21
|
+
autoRestoreSession: true,
|
|
22
|
+
clearBookmarksOnLaunch: false,
|
|
23
|
+
obsidianVaultPath: "",
|
|
24
|
+
approvalMode: "confirm-dangerous",
|
|
25
|
+
agentTranscriptMode: "summary",
|
|
26
|
+
chatProvider: null,
|
|
27
|
+
maxToolIterations: 200,
|
|
28
|
+
domainPolicy: { allowedDomains: [], blockedDomains: [] },
|
|
29
|
+
downloadPath: ""
|
|
30
|
+
};
|
|
31
|
+
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
32
|
+
let settings = null;
|
|
33
|
+
let settingsIssues = [];
|
|
34
|
+
function getSettingsPath() {
|
|
35
|
+
return path.join(electron.app.getPath("userData"), "vessel-settings.json");
|
|
36
|
+
}
|
|
37
|
+
function getSettingsLoadIssues() {
|
|
38
|
+
return settingsIssues.map((issue) => ({ ...issue }));
|
|
39
|
+
}
|
|
40
|
+
function sanitizePort(value) {
|
|
41
|
+
const parsed = Number(value);
|
|
42
|
+
if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535) {
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
settingsIssues.push({
|
|
46
|
+
code: "settings-invalid-mcp-port",
|
|
47
|
+
severity: "warning",
|
|
48
|
+
title: "Invalid MCP port in settings",
|
|
49
|
+
detail: `Expected an integer between 1 and 65535 but found ${JSON.stringify(value)}.`,
|
|
50
|
+
action: `Using default port ${defaults.mcpPort} instead.`
|
|
51
|
+
});
|
|
52
|
+
return defaults.mcpPort;
|
|
53
|
+
}
|
|
54
|
+
function loadSettings() {
|
|
55
|
+
if (settings) return settings;
|
|
56
|
+
settingsIssues = [];
|
|
57
|
+
try {
|
|
58
|
+
const raw = fs.readFileSync(getSettingsPath(), "utf-8");
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
delete parsed.apiKey;
|
|
61
|
+
delete parsed.provider;
|
|
62
|
+
settings = {
|
|
63
|
+
...defaults,
|
|
64
|
+
...parsed,
|
|
65
|
+
mcpPort: sanitizePort(parsed.mcpPort ?? defaults.mcpPort),
|
|
66
|
+
agentTranscriptMode: parsed.agentTranscriptMode === "off" || parsed.agentTranscriptMode === "summary" || parsed.agentTranscriptMode === "full" ? parsed.agentTranscriptMode : parsed.showAgentTranscript === false ? "off" : defaults.agentTranscriptMode
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (fs.existsSync(getSettingsPath())) {
|
|
70
|
+
settingsIssues.push({
|
|
71
|
+
code: "settings-read-failed",
|
|
72
|
+
severity: "warning",
|
|
73
|
+
title: "Could not read Vessel settings",
|
|
74
|
+
detail: error instanceof Error ? error.message : "Unknown settings error.",
|
|
75
|
+
action: "Falling back to built-in defaults for this launch."
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
settings = { ...defaults };
|
|
79
|
+
}
|
|
80
|
+
return settings;
|
|
81
|
+
}
|
|
82
|
+
function saveSettings() {
|
|
83
|
+
try {
|
|
84
|
+
fs.mkdirSync(path.dirname(getSettingsPath()), { recursive: true });
|
|
85
|
+
fs.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("[Vessel] Failed to save settings:", err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function setSetting(key, value) {
|
|
91
|
+
loadSettings();
|
|
92
|
+
if (key === "mcpPort") {
|
|
93
|
+
settings.mcpPort = sanitizePort(value);
|
|
94
|
+
} else {
|
|
95
|
+
settings[key] = value;
|
|
96
|
+
}
|
|
97
|
+
saveSettings();
|
|
98
|
+
return { ...settings };
|
|
99
|
+
}
|
|
100
|
+
function checkDomainPolicy(url) {
|
|
101
|
+
if (!url || url.startsWith("about:")) return null;
|
|
102
|
+
const settings2 = loadSettings();
|
|
103
|
+
const policy = settings2.domainPolicy;
|
|
104
|
+
if (policy.allowedDomains.length === 0 && policy.blockedDomains.length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
let hostname;
|
|
108
|
+
try {
|
|
109
|
+
hostname = new URL(url).hostname.toLowerCase();
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (policy.allowedDomains.length > 0) {
|
|
114
|
+
const allowed = policy.allowedDomains.some(
|
|
115
|
+
(d) => matchesDomain(hostname, d.toLowerCase())
|
|
116
|
+
);
|
|
117
|
+
return allowed ? null : `Navigation blocked by domain policy: ${hostname} is not in the allowed domains list.`;
|
|
118
|
+
}
|
|
119
|
+
if (policy.blockedDomains.length > 0) {
|
|
120
|
+
const blocked = policy.blockedDomains.some(
|
|
121
|
+
(d) => matchesDomain(hostname, d.toLowerCase())
|
|
122
|
+
);
|
|
123
|
+
return blocked ? `Navigation blocked by domain policy: ${hostname} is in the blocked domains list.` : null;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function matchesDomain(hostname, policyDomain) {
|
|
128
|
+
return hostname === policyDomain || hostname.endsWith("." + policyDomain);
|
|
129
|
+
}
|
|
16
130
|
const MAX_CUSTOM_HISTORY = 50;
|
|
17
131
|
class Tab {
|
|
18
132
|
id;
|
|
@@ -60,7 +174,8 @@ class Tab {
|
|
|
60
174
|
canGoBack: false,
|
|
61
175
|
canGoForward: false,
|
|
62
176
|
isReaderMode: false,
|
|
63
|
-
adBlockingEnabled: options?.adBlockingEnabled ?? true
|
|
177
|
+
adBlockingEnabled: options?.adBlockingEnabled ?? true,
|
|
178
|
+
role: options?.role
|
|
64
179
|
};
|
|
65
180
|
this.view.webContents.on("before-input-event", (_event, input) => {
|
|
66
181
|
if (!input.control && !input.meta) return;
|
|
@@ -250,7 +365,13 @@ class Tab {
|
|
|
250
365
|
url = `https://duckduckgo.com/?q=${encodeURIComponent(url)}`;
|
|
251
366
|
}
|
|
252
367
|
}
|
|
368
|
+
if (!/^https?:\/\//i.test(url) && !url.startsWith("about:")) {
|
|
369
|
+
return `Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`;
|
|
370
|
+
}
|
|
371
|
+
const policyError = checkDomainPolicy(url);
|
|
372
|
+
if (policyError) return policyError;
|
|
253
373
|
this.view.webContents.loadURL(url);
|
|
374
|
+
return null;
|
|
254
375
|
}
|
|
255
376
|
goBack() {
|
|
256
377
|
const previousUrl = this.urlHistory.pop();
|
|
@@ -388,31 +509,35 @@ class Tab {
|
|
|
388
509
|
this.view.webContents.close();
|
|
389
510
|
}
|
|
390
511
|
}
|
|
391
|
-
let state$
|
|
392
|
-
const listeners$
|
|
512
|
+
let state$3 = null;
|
|
513
|
+
const listeners$2 = /* @__PURE__ */ new Set();
|
|
393
514
|
function getHighlightsPath() {
|
|
394
515
|
return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
|
|
395
516
|
}
|
|
396
|
-
function load$
|
|
397
|
-
if (state$
|
|
517
|
+
function load$2() {
|
|
518
|
+
if (state$3) return state$3;
|
|
398
519
|
try {
|
|
399
520
|
const raw = fs.readFileSync(getHighlightsPath(), "utf-8");
|
|
400
521
|
const parsed = JSON.parse(raw);
|
|
401
|
-
state$
|
|
522
|
+
state$3 = {
|
|
402
523
|
highlights: Array.isArray(parsed.highlights) ? parsed.highlights : []
|
|
403
524
|
};
|
|
404
525
|
} catch {
|
|
405
|
-
state$
|
|
526
|
+
state$3 = { highlights: [] };
|
|
406
527
|
}
|
|
407
|
-
return state$
|
|
528
|
+
return state$3;
|
|
408
529
|
}
|
|
409
|
-
function save$
|
|
410
|
-
|
|
530
|
+
function save$2() {
|
|
531
|
+
try {
|
|
532
|
+
fs.writeFileSync(getHighlightsPath(), JSON.stringify(state$3, null, 2), "utf-8");
|
|
533
|
+
} catch (err) {
|
|
534
|
+
console.error("[Vessel] Failed to save highlights:", err);
|
|
535
|
+
}
|
|
411
536
|
}
|
|
412
|
-
function emit$
|
|
413
|
-
if (!state$
|
|
414
|
-
const snapshot = { highlights: [...state$
|
|
415
|
-
for (const listener of listeners$
|
|
537
|
+
function emit$2() {
|
|
538
|
+
if (!state$3) return;
|
|
539
|
+
const snapshot = { highlights: [...state$3.highlights] };
|
|
540
|
+
for (const listener of listeners$2) {
|
|
416
541
|
listener(snapshot);
|
|
417
542
|
}
|
|
418
543
|
}
|
|
@@ -425,17 +550,17 @@ function normalizeUrl(rawUrl) {
|
|
|
425
550
|
return rawUrl;
|
|
426
551
|
}
|
|
427
552
|
}
|
|
428
|
-
function getState$
|
|
429
|
-
load$
|
|
430
|
-
return { highlights: [...state$
|
|
553
|
+
function getState$2() {
|
|
554
|
+
load$2();
|
|
555
|
+
return { highlights: [...state$3.highlights] };
|
|
431
556
|
}
|
|
432
557
|
function getHighlightsForUrl(url) {
|
|
433
|
-
load$
|
|
558
|
+
load$2();
|
|
434
559
|
const normalized = normalizeUrl(url);
|
|
435
|
-
return state$
|
|
560
|
+
return state$3.highlights.filter((h) => h.url === normalized);
|
|
436
561
|
}
|
|
437
562
|
function addHighlight(url, selector, text, label, color, source) {
|
|
438
|
-
load$
|
|
563
|
+
load$2();
|
|
439
564
|
const highlight = {
|
|
440
565
|
id: crypto.randomUUID(),
|
|
441
566
|
url: normalizeUrl(url),
|
|
@@ -446,45 +571,45 @@ function addHighlight(url, selector, text, label, color, source) {
|
|
|
446
571
|
source: source || void 0,
|
|
447
572
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
448
573
|
};
|
|
449
|
-
state$
|
|
450
|
-
save$
|
|
451
|
-
emit$
|
|
574
|
+
state$3.highlights.push(highlight);
|
|
575
|
+
save$2();
|
|
576
|
+
emit$2();
|
|
452
577
|
return highlight;
|
|
453
578
|
}
|
|
454
579
|
function removeHighlight(id) {
|
|
455
|
-
load$
|
|
456
|
-
const index = state$
|
|
580
|
+
load$2();
|
|
581
|
+
const index = state$3.highlights.findIndex((h) => h.id === id);
|
|
457
582
|
if (index === -1) return null;
|
|
458
|
-
const [removed] = state$
|
|
459
|
-
save$
|
|
460
|
-
emit$
|
|
583
|
+
const [removed] = state$3.highlights.splice(index, 1);
|
|
584
|
+
save$2();
|
|
585
|
+
emit$2();
|
|
461
586
|
return removed;
|
|
462
587
|
}
|
|
463
588
|
function findHighlightByText(url, text) {
|
|
464
|
-
load$
|
|
589
|
+
load$2();
|
|
465
590
|
const normalized = normalizeUrl(url);
|
|
466
|
-
return state$
|
|
591
|
+
return state$3.highlights.find(
|
|
467
592
|
(h) => h.url === normalized && h.text && h.text === text
|
|
468
593
|
) ?? null;
|
|
469
594
|
}
|
|
470
595
|
function updateHighlightColor(id, color) {
|
|
471
|
-
load$
|
|
472
|
-
const highlight = state$
|
|
596
|
+
load$2();
|
|
597
|
+
const highlight = state$3.highlights.find((h) => h.id === id);
|
|
473
598
|
if (!highlight) return null;
|
|
474
599
|
highlight.color = color;
|
|
475
|
-
save$
|
|
476
|
-
emit$
|
|
600
|
+
save$2();
|
|
601
|
+
emit$2();
|
|
477
602
|
return highlight;
|
|
478
603
|
}
|
|
479
604
|
function clearHighlightsForUrl(url) {
|
|
480
|
-
load$
|
|
605
|
+
load$2();
|
|
481
606
|
const normalized = normalizeUrl(url);
|
|
482
|
-
const before = state$
|
|
483
|
-
state$
|
|
484
|
-
const removed = before - state$
|
|
607
|
+
const before = state$3.highlights.length;
|
|
608
|
+
state$3.highlights = state$3.highlights.filter((h) => h.url !== normalized);
|
|
609
|
+
const removed = before - state$3.highlights.length;
|
|
485
610
|
if (removed > 0) {
|
|
486
|
-
save$
|
|
487
|
-
emit$
|
|
611
|
+
save$2();
|
|
612
|
+
emit$2();
|
|
488
613
|
}
|
|
489
614
|
return removed;
|
|
490
615
|
}
|
|
@@ -824,6 +949,172 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
824
949
|
}
|
|
825
950
|
return "Error: No element or text to highlight";
|
|
826
951
|
}
|
|
952
|
+
async function highlightBatchOnPage(wc, entries) {
|
|
953
|
+
if (entries.length === 0) return;
|
|
954
|
+
const serialized = entries.filter((e) => e.selector || e.text).map((e) => ({
|
|
955
|
+
selector: e.selector ?? null,
|
|
956
|
+
text: e.text ?? null,
|
|
957
|
+
label: e.label ?? null,
|
|
958
|
+
color: resolveColor(e.color)
|
|
959
|
+
}));
|
|
960
|
+
if (serialized.length === 0) return;
|
|
961
|
+
await wc.executeJavaScript(`
|
|
962
|
+
(function() {
|
|
963
|
+
if (!document.getElementById('__vessel-highlight-styles')) {
|
|
964
|
+
var s = document.createElement('style');
|
|
965
|
+
s.id = '__vessel-highlight-styles';
|
|
966
|
+
s.textContent = ${JSON.stringify(VESSEL_HIGHLIGHT_CSS)};
|
|
967
|
+
document.head.appendChild(s);
|
|
968
|
+
}
|
|
969
|
+
var entries = ${JSON.stringify(serialized)};
|
|
970
|
+
var SKIP_TAGS = {SCRIPT:1,STYLE:1,NOSCRIPT:1,TEMPLATE:1,IFRAME:1,SVG:1};
|
|
971
|
+
var contentRoots = ['main', 'article', '[role="main"]', '#mw-content-text', '.mw-parser-output', '#content', '.post-content', '.entry-content', '.article-body'];
|
|
972
|
+
var NAV_ANCESTORS = 'nav, aside, footer, header, [role="navigation"], [role="complementary"], .sidebar, .navbox, .infobox, figcaption, .thumbcaption, .mw-jump-link';
|
|
973
|
+
var contentRoot = null;
|
|
974
|
+
for (var cr = 0; cr < contentRoots.length; cr++) {
|
|
975
|
+
contentRoot = document.querySelector(contentRoots[cr]);
|
|
976
|
+
if (contentRoot) break;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function collectMatches(root, searchText, foldedSearchText, limit) {
|
|
980
|
+
var matches = [];
|
|
981
|
+
var w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
982
|
+
acceptNode: function(n) {
|
|
983
|
+
var p = n.parentElement;
|
|
984
|
+
if (!p) return NodeFilter.FILTER_REJECT;
|
|
985
|
+
if (SKIP_TAGS[p.tagName]) return NodeFilter.FILTER_REJECT;
|
|
986
|
+
if (p.closest('[data-vessel-highlight]')) return NodeFilter.FILTER_REJECT;
|
|
987
|
+
if (p.closest(NAV_ANCESTORS)) return NodeFilter.FILTER_REJECT;
|
|
988
|
+
var style = window.getComputedStyle(p);
|
|
989
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return NodeFilter.FILTER_REJECT;
|
|
990
|
+
if (p.offsetWidth === 0 && p.offsetHeight === 0) return NodeFilter.FILTER_REJECT;
|
|
991
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
var n;
|
|
995
|
+
while ((n = w.nextNode())) {
|
|
996
|
+
var haystack = n.textContent || '';
|
|
997
|
+
var idx = haystack.indexOf(searchText);
|
|
998
|
+
if (idx === -1 && foldedSearchText) {
|
|
999
|
+
idx = haystack.toLowerCase().indexOf(foldedSearchText);
|
|
1000
|
+
}
|
|
1001
|
+
if (idx !== -1) {
|
|
1002
|
+
matches.push({ node: n, idx: idx });
|
|
1003
|
+
if (matches.length >= limit) break;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return matches;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
for (var e = 0; e < entries.length; e++) {
|
|
1010
|
+
var entry = entries[e];
|
|
1011
|
+
var c = entry.color;
|
|
1012
|
+
if (entry.text) {
|
|
1013
|
+
var searchText = (entry.text || '').trim();
|
|
1014
|
+
var foldedSearchText = searchText.toLowerCase();
|
|
1015
|
+
var textNodes = contentRoot ? collectMatches(contentRoot, searchText, foldedSearchText, 20) : [];
|
|
1016
|
+
if (textNodes.length === 0) {
|
|
1017
|
+
textNodes = collectMatches(document.body, searchText, foldedSearchText, 20);
|
|
1018
|
+
}
|
|
1019
|
+
for (var i = 0; i < textNodes.length; i++) {
|
|
1020
|
+
try {
|
|
1021
|
+
var match = textNodes[i];
|
|
1022
|
+
var range = document.createRange();
|
|
1023
|
+
range.setStart(match.node, match.idx);
|
|
1024
|
+
range.setEnd(match.node, match.idx + searchText.length);
|
|
1025
|
+
var mark = document.createElement('mark');
|
|
1026
|
+
mark.className = '__vessel-highlight-text';
|
|
1027
|
+
mark.style.setProperty('background', c.bg, 'important');
|
|
1028
|
+
mark.style.setProperty('border-bottom-color', c.solid, 'important');
|
|
1029
|
+
mark.setAttribute('data-vessel-highlight', 'true');
|
|
1030
|
+
range.surroundContents(mark);
|
|
1031
|
+
} catch (_e) {}
|
|
1032
|
+
}
|
|
1033
|
+
} else if (entry.selector) {
|
|
1034
|
+
try {
|
|
1035
|
+
var el = document.querySelector(entry.selector);
|
|
1036
|
+
if (el) {
|
|
1037
|
+
el.classList.add('__vessel-highlight');
|
|
1038
|
+
el.style.setProperty('background', c.bg, 'important');
|
|
1039
|
+
el.style.setProperty('outline-color', c.solid, 'important');
|
|
1040
|
+
el.style.setProperty('box-shadow', '0 0 8px ' + c.glow, 'important');
|
|
1041
|
+
}
|
|
1042
|
+
} catch (_e) {}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
})()
|
|
1046
|
+
`);
|
|
1047
|
+
}
|
|
1048
|
+
const HIGHLIGHT_SELECTOR = "'.__vessel-highlight, .__vessel-highlight-text'";
|
|
1049
|
+
async function getHighlightCount(wc) {
|
|
1050
|
+
return wc.executeJavaScript(
|
|
1051
|
+
`document.querySelectorAll(${HIGHLIGHT_SELECTOR}).length`
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
async function scrollToHighlight(wc, index) {
|
|
1055
|
+
const safeIndex = Math.floor(Number(index));
|
|
1056
|
+
return wc.executeJavaScript(`
|
|
1057
|
+
(function() {
|
|
1058
|
+
var highlights = document.querySelectorAll(${HIGHLIGHT_SELECTOR});
|
|
1059
|
+
if (${safeIndex} < 0 || ${safeIndex} >= highlights.length) return false;
|
|
1060
|
+
highlights.forEach(function(h) { h.style.removeProperty('outline'); h.style.removeProperty('outline-offset'); });
|
|
1061
|
+
var target = highlights[${safeIndex}];
|
|
1062
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1063
|
+
target.style.setProperty('outline', '2px solid rgba(255, 255, 255, 0.9)', 'important');
|
|
1064
|
+
target.style.setProperty('outline-offset', '2px', 'important');
|
|
1065
|
+
return true;
|
|
1066
|
+
})()
|
|
1067
|
+
`);
|
|
1068
|
+
}
|
|
1069
|
+
async function removeHighlightAtIndex(wc, index) {
|
|
1070
|
+
const safeIndex = Math.floor(Number(index));
|
|
1071
|
+
return wc.executeJavaScript(`
|
|
1072
|
+
(function() {
|
|
1073
|
+
var highlights = document.querySelectorAll(${HIGHLIGHT_SELECTOR});
|
|
1074
|
+
if (${safeIndex} < 0 || ${safeIndex} >= highlights.length) return false;
|
|
1075
|
+
var el = highlights[${safeIndex}];
|
|
1076
|
+
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) {
|
|
1077
|
+
if (b.__vesselAnchor === el) b.remove();
|
|
1078
|
+
});
|
|
1079
|
+
if (el.tagName === 'MARK' && el.classList.contains('__vessel-highlight-text')) {
|
|
1080
|
+
var parent = el.parentNode;
|
|
1081
|
+
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
1082
|
+
parent.removeChild(el);
|
|
1083
|
+
parent.normalize();
|
|
1084
|
+
} else {
|
|
1085
|
+
el.classList.remove('__vessel-highlight');
|
|
1086
|
+
el.style.removeProperty('background');
|
|
1087
|
+
el.style.removeProperty('outline-color');
|
|
1088
|
+
el.style.removeProperty('box-shadow');
|
|
1089
|
+
el.style.removeProperty('outline');
|
|
1090
|
+
el.style.removeProperty('outline-offset');
|
|
1091
|
+
}
|
|
1092
|
+
return true;
|
|
1093
|
+
})()
|
|
1094
|
+
`);
|
|
1095
|
+
}
|
|
1096
|
+
async function clearAllHighlightElements(wc) {
|
|
1097
|
+
return wc.executeJavaScript(`
|
|
1098
|
+
(function() {
|
|
1099
|
+
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) { b.remove(); });
|
|
1100
|
+
document.querySelectorAll('.__vessel-highlight-text').forEach(function(mark) {
|
|
1101
|
+
var parent = mark.parentNode;
|
|
1102
|
+
while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
|
|
1103
|
+
parent.removeChild(mark);
|
|
1104
|
+
parent.normalize();
|
|
1105
|
+
});
|
|
1106
|
+
document.querySelectorAll('.__vessel-highlight').forEach(function(el) {
|
|
1107
|
+
el.classList.remove('__vessel-highlight');
|
|
1108
|
+
el.style.removeProperty('background');
|
|
1109
|
+
el.style.removeProperty('outline-color');
|
|
1110
|
+
el.style.removeProperty('box-shadow');
|
|
1111
|
+
el.style.removeProperty('outline');
|
|
1112
|
+
el.style.removeProperty('outline-offset');
|
|
1113
|
+
});
|
|
1114
|
+
return true;
|
|
1115
|
+
})()
|
|
1116
|
+
`);
|
|
1117
|
+
}
|
|
827
1118
|
async function clearHighlights(wc) {
|
|
828
1119
|
return wc.executeJavaScript(`
|
|
829
1120
|
(function() {
|
|
@@ -848,6 +1139,134 @@ async function clearHighlights(wc) {
|
|
|
848
1139
|
})()
|
|
849
1140
|
`);
|
|
850
1141
|
}
|
|
1142
|
+
const MAX_HIGHLIGHT_TEXT = 5e3;
|
|
1143
|
+
async function captureSelectionHighlight(wc) {
|
|
1144
|
+
if (wc.isDestroyed()) {
|
|
1145
|
+
return { success: false, message: "Tab is not available" };
|
|
1146
|
+
}
|
|
1147
|
+
const url = wc.getURL();
|
|
1148
|
+
if (!url || url === "about:blank") {
|
|
1149
|
+
return { success: false, message: "No page loaded" };
|
|
1150
|
+
}
|
|
1151
|
+
const selectedText = await wc.executeJavaScript(`
|
|
1152
|
+
(function() {
|
|
1153
|
+
var sel = window.getSelection();
|
|
1154
|
+
return sel ? sel.toString().trim() : '';
|
|
1155
|
+
})()
|
|
1156
|
+
`);
|
|
1157
|
+
if (!selectedText) {
|
|
1158
|
+
return { success: false, message: "No text selected" };
|
|
1159
|
+
}
|
|
1160
|
+
return persistHighlight(url, selectedText);
|
|
1161
|
+
}
|
|
1162
|
+
async function persistAndMarkHighlight(wc, text) {
|
|
1163
|
+
if (wc.isDestroyed()) {
|
|
1164
|
+
return { success: false, message: "Tab is not available" };
|
|
1165
|
+
}
|
|
1166
|
+
const url = wc.getURL();
|
|
1167
|
+
if (!url || url === "about:blank") {
|
|
1168
|
+
return { success: false, message: "No page loaded" };
|
|
1169
|
+
}
|
|
1170
|
+
const result = persistHighlight(url, text);
|
|
1171
|
+
return result;
|
|
1172
|
+
}
|
|
1173
|
+
function persistHighlight(url, text) {
|
|
1174
|
+
const capped = text.length > MAX_HIGHLIGHT_TEXT ? text.slice(0, MAX_HIGHLIGHT_TEXT) : text;
|
|
1175
|
+
const highlight = addHighlight(
|
|
1176
|
+
url,
|
|
1177
|
+
void 0,
|
|
1178
|
+
capped,
|
|
1179
|
+
void 0,
|
|
1180
|
+
"yellow",
|
|
1181
|
+
"user"
|
|
1182
|
+
);
|
|
1183
|
+
return { success: true, text: capped, id: highlight.id };
|
|
1184
|
+
}
|
|
1185
|
+
const MAX_HISTORY_ENTRIES = 5e3;
|
|
1186
|
+
let state$2 = null;
|
|
1187
|
+
const listeners$1 = /* @__PURE__ */ new Set();
|
|
1188
|
+
function getHistoryPath() {
|
|
1189
|
+
return path.join(electron.app.getPath("userData"), "vessel-history.json");
|
|
1190
|
+
}
|
|
1191
|
+
function load$1() {
|
|
1192
|
+
if (state$2) return state$2;
|
|
1193
|
+
try {
|
|
1194
|
+
const raw = fs.readFileSync(getHistoryPath(), "utf-8");
|
|
1195
|
+
const parsed = JSON.parse(raw);
|
|
1196
|
+
state$2 = {
|
|
1197
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : []
|
|
1198
|
+
};
|
|
1199
|
+
} catch {
|
|
1200
|
+
state$2 = { entries: [] };
|
|
1201
|
+
}
|
|
1202
|
+
return state$2;
|
|
1203
|
+
}
|
|
1204
|
+
function save$1() {
|
|
1205
|
+
try {
|
|
1206
|
+
fs.mkdirSync(path.dirname(getHistoryPath()), { recursive: true });
|
|
1207
|
+
fs.writeFileSync(
|
|
1208
|
+
getHistoryPath(),
|
|
1209
|
+
JSON.stringify(state$2, null, 2),
|
|
1210
|
+
"utf-8"
|
|
1211
|
+
);
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
console.error("[Vessel] Failed to save history:", err);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function emit$1() {
|
|
1217
|
+
if (!state$2) return;
|
|
1218
|
+
const snapshot = { entries: [...state$2.entries] };
|
|
1219
|
+
for (const listener of listeners$1) {
|
|
1220
|
+
listener(snapshot);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function getState$1() {
|
|
1224
|
+
load$1();
|
|
1225
|
+
return { entries: [...state$2.entries] };
|
|
1226
|
+
}
|
|
1227
|
+
function subscribe$1(listener) {
|
|
1228
|
+
listeners$1.add(listener);
|
|
1229
|
+
return () => {
|
|
1230
|
+
listeners$1.delete(listener);
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function addEntry(url, title) {
|
|
1234
|
+
if (!url || url === "about:blank") return;
|
|
1235
|
+
load$1();
|
|
1236
|
+
const last = state$2.entries[0];
|
|
1237
|
+
if (last && last.url === url) {
|
|
1238
|
+
if (title && title !== last.title) {
|
|
1239
|
+
last.title = title;
|
|
1240
|
+
save$1();
|
|
1241
|
+
emit$1();
|
|
1242
|
+
}
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const entry = {
|
|
1246
|
+
url,
|
|
1247
|
+
title: title || url,
|
|
1248
|
+
visitedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1249
|
+
};
|
|
1250
|
+
state$2.entries.unshift(entry);
|
|
1251
|
+
if (state$2.entries.length > MAX_HISTORY_ENTRIES) {
|
|
1252
|
+
state$2.entries = state$2.entries.slice(0, MAX_HISTORY_ENTRIES);
|
|
1253
|
+
}
|
|
1254
|
+
save$1();
|
|
1255
|
+
emit$1();
|
|
1256
|
+
}
|
|
1257
|
+
function search(query, limit = 50) {
|
|
1258
|
+
load$1();
|
|
1259
|
+
if (!query.trim()) return state$2.entries.slice(0, limit);
|
|
1260
|
+
const normalized = query.toLowerCase();
|
|
1261
|
+
return state$2.entries.filter(
|
|
1262
|
+
(e) => e.url.toLowerCase().includes(normalized) || e.title.toLowerCase().includes(normalized)
|
|
1263
|
+
).slice(0, limit);
|
|
1264
|
+
}
|
|
1265
|
+
function clearAll$1() {
|
|
1266
|
+
state$2 = { entries: [] };
|
|
1267
|
+
save$1();
|
|
1268
|
+
emit$1();
|
|
1269
|
+
}
|
|
851
1270
|
const MAX_CONSOLE_ENTRIES = 500;
|
|
852
1271
|
const MAX_NETWORK_ENTRIES = 200;
|
|
853
1272
|
const MAX_ERROR_ENTRIES = 200;
|
|
@@ -1572,7 +1991,10 @@ class TabManager {
|
|
|
1572
1991
|
onOpenUrl: ({ url: requestedUrl, background: background2, adBlockingEnabled }) => {
|
|
1573
1992
|
this.createTab(requestedUrl, { background: background2, adBlockingEnabled });
|
|
1574
1993
|
},
|
|
1575
|
-
onPageLoad: (pageUrl, wc) =>
|
|
1994
|
+
onPageLoad: (pageUrl, wc) => {
|
|
1995
|
+
this.reapplyHighlights(pageUrl, wc);
|
|
1996
|
+
addEntry(pageUrl, wc.getTitle());
|
|
1997
|
+
},
|
|
1576
1998
|
onHighlightSelection: (wc) => this.captureHighlightFromPage(wc),
|
|
1577
1999
|
onHighlightRemove: (url2, text) => this.removeHighlightByText(url2, text),
|
|
1578
2000
|
onHighlightRecolor: (url2, text, color) => this.recolorHighlightByText(url2, text, color)
|
|
@@ -1724,16 +2146,14 @@ class TabManager {
|
|
|
1724
2146
|
if (last && last.url === normalized && now - last.at < 500) return;
|
|
1725
2147
|
this.lastReapply.set(wcId, { url: normalized, at: now });
|
|
1726
2148
|
const highlights = getHighlightsForUrl(url);
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
h.color
|
|
1736
|
-
).catch(() => {
|
|
2149
|
+
const entries = highlights.filter((h) => h.selector || h.text).map((h) => ({
|
|
2150
|
+
selector: h.selector ?? null,
|
|
2151
|
+
text: h.text,
|
|
2152
|
+
label: h.label,
|
|
2153
|
+
color: h.color
|
|
2154
|
+
}));
|
|
2155
|
+
if (entries.length > 0) {
|
|
2156
|
+
void highlightBatchOnPage(wc, entries).catch(() => {
|
|
1737
2157
|
});
|
|
1738
2158
|
}
|
|
1739
2159
|
}
|
|
@@ -1741,62 +2161,23 @@ class TabManager {
|
|
|
1741
2161
|
this.highlightCaptureCallback = callback;
|
|
1742
2162
|
}
|
|
1743
2163
|
captureHighlightFromActiveTab() {
|
|
1744
|
-
console.log("[Vessel] captureHighlightFromActiveTab called");
|
|
1745
2164
|
const activeTab = this.getActiveTab();
|
|
1746
2165
|
if (!activeTab) {
|
|
1747
|
-
console.log("[Vessel] No active tab in captureHighlightFromActiveTab");
|
|
1748
2166
|
return { success: false, message: "No active tab" };
|
|
1749
2167
|
}
|
|
1750
2168
|
const wc = activeTab.view.webContents;
|
|
1751
|
-
console.log("[Vessel] Calling captureHighlightFromPage for:", wc.getURL());
|
|
1752
2169
|
this.captureHighlightFromPage(wc);
|
|
1753
2170
|
return null;
|
|
1754
2171
|
}
|
|
1755
2172
|
captureHighlightFromPage(wc) {
|
|
1756
|
-
console.log("[Vessel] captureHighlightFromPage called");
|
|
1757
2173
|
void (async () => {
|
|
1758
2174
|
try {
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
const url = wc.getURL();
|
|
1764
|
-
console.log("[Vessel] URL:", url);
|
|
1765
|
-
if (!url || url === "about:blank") {
|
|
1766
|
-
console.log("[Vessel] No URL or about:blank");
|
|
1767
|
-
return;
|
|
2175
|
+
const result = await captureSelectionHighlight(wc);
|
|
2176
|
+
if (result.success && result.text) {
|
|
2177
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
|
|
2178
|
+
});
|
|
1768
2179
|
}
|
|
1769
|
-
|
|
1770
|
-
(function() {
|
|
1771
|
-
var sel = window.getSelection();
|
|
1772
|
-
return sel ? sel.toString().trim() : '';
|
|
1773
|
-
})()
|
|
1774
|
-
`);
|
|
1775
|
-
console.log("[Vessel] Selected text:", selectedText?.slice(0, 50));
|
|
1776
|
-
if (!selectedText) return;
|
|
1777
|
-
const capped = selectedText.length > 5e3 ? selectedText.slice(0, 5e3) : selectedText;
|
|
1778
|
-
const highlight = addHighlight(
|
|
1779
|
-
url,
|
|
1780
|
-
void 0,
|
|
1781
|
-
capped,
|
|
1782
|
-
void 0,
|
|
1783
|
-
"yellow",
|
|
1784
|
-
"user"
|
|
1785
|
-
);
|
|
1786
|
-
await highlightOnPage(
|
|
1787
|
-
wc,
|
|
1788
|
-
null,
|
|
1789
|
-
capped,
|
|
1790
|
-
void 0,
|
|
1791
|
-
void 0,
|
|
1792
|
-
"yellow"
|
|
1793
|
-
).catch(() => {
|
|
1794
|
-
});
|
|
1795
|
-
this.highlightCaptureCallback?.({
|
|
1796
|
-
success: true,
|
|
1797
|
-
text: capped,
|
|
1798
|
-
id: highlight.id
|
|
1799
|
-
});
|
|
2180
|
+
this.highlightCaptureCallback?.(result);
|
|
1800
2181
|
} catch {
|
|
1801
2182
|
this.highlightCaptureCallback?.({
|
|
1802
2183
|
success: false,
|
|
@@ -1836,130 +2217,53 @@ class TabManager {
|
|
|
1836
2217
|
}
|
|
1837
2218
|
const normalized = normalizeUrl(url);
|
|
1838
2219
|
for (const id of this.order) {
|
|
1839
|
-
const tab = this.tabs.get(id);
|
|
1840
|
-
if (!tab) continue;
|
|
1841
|
-
const wc = tab.view.webContents;
|
|
1842
|
-
if (wc.isDestroyed()) continue;
|
|
1843
|
-
try {
|
|
1844
|
-
const tabUrl = normalizeUrl(wc.getURL());
|
|
1845
|
-
if (tabUrl === normalized) {
|
|
1846
|
-
void this.removeHighlightMarksForText(wc, text).then(() => {
|
|
1847
|
-
void highlightOnPage(
|
|
1848
|
-
wc,
|
|
1849
|
-
null,
|
|
1850
|
-
text,
|
|
1851
|
-
void 0,
|
|
1852
|
-
void 0,
|
|
1853
|
-
color
|
|
1854
|
-
).catch(() => {
|
|
1855
|
-
});
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
} catch {
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
this.highlightCaptureCallback?.({
|
|
1862
|
-
success: true,
|
|
1863
|
-
message: `Color changed to ${color}`
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
async removeHighlightMarksForText(wc, text) {
|
|
1867
|
-
await wc.executeJavaScript(
|
|
1868
|
-
`(function() {
|
|
1869
|
-
var marks = document.querySelectorAll('mark.__vessel-highlight-text[data-vessel-highlight]');
|
|
1870
|
-
marks.forEach(function(m) {
|
|
1871
|
-
if (m.textContent === ${JSON.stringify(text)}) {
|
|
1872
|
-
var parent = m.parentNode;
|
|
1873
|
-
while (m.firstChild) parent.insertBefore(m.firstChild, m);
|
|
1874
|
-
m.remove();
|
|
1875
|
-
if (parent) parent.normalize();
|
|
1876
|
-
}
|
|
1877
|
-
});
|
|
1878
|
-
})()`
|
|
1879
|
-
).catch(() => {
|
|
1880
|
-
});
|
|
1881
|
-
}
|
|
1882
|
-
broadcastState() {
|
|
1883
|
-
const states = this.getAllStates();
|
|
1884
|
-
this.onStateChange(states, this.activeTabId || "");
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
const defaults = {
|
|
1888
|
-
defaultUrl: "https://start.duckduckgo.com",
|
|
1889
|
-
theme: "dark",
|
|
1890
|
-
sidebarWidth: 340,
|
|
1891
|
-
mcpPort: 3100,
|
|
1892
|
-
autoRestoreSession: true,
|
|
1893
|
-
clearBookmarksOnLaunch: false,
|
|
1894
|
-
obsidianVaultPath: "",
|
|
1895
|
-
approvalMode: "confirm-dangerous",
|
|
1896
|
-
agentTranscriptMode: "summary",
|
|
1897
|
-
chatProvider: null,
|
|
1898
|
-
maxToolIterations: 200
|
|
1899
|
-
};
|
|
1900
|
-
let settings = null;
|
|
1901
|
-
let settingsIssues = [];
|
|
1902
|
-
function getSettingsPath() {
|
|
1903
|
-
return path.join(electron.app.getPath("userData"), "vessel-settings.json");
|
|
1904
|
-
}
|
|
1905
|
-
function getSettingsLoadIssues() {
|
|
1906
|
-
return settingsIssues.map((issue) => ({ ...issue }));
|
|
1907
|
-
}
|
|
1908
|
-
function sanitizePort(value) {
|
|
1909
|
-
const parsed = Number(value);
|
|
1910
|
-
if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535) {
|
|
1911
|
-
return parsed;
|
|
1912
|
-
}
|
|
1913
|
-
settingsIssues.push({
|
|
1914
|
-
code: "settings-invalid-mcp-port",
|
|
1915
|
-
severity: "warning",
|
|
1916
|
-
title: "Invalid MCP port in settings",
|
|
1917
|
-
detail: `Expected an integer between 1 and 65535 but found ${JSON.stringify(value)}.`,
|
|
1918
|
-
action: `Using default port ${defaults.mcpPort} instead.`
|
|
1919
|
-
});
|
|
1920
|
-
return defaults.mcpPort;
|
|
1921
|
-
}
|
|
1922
|
-
function loadSettings() {
|
|
1923
|
-
if (settings) return settings;
|
|
1924
|
-
settingsIssues = [];
|
|
1925
|
-
try {
|
|
1926
|
-
const raw = fs.readFileSync(getSettingsPath(), "utf-8");
|
|
1927
|
-
const parsed = JSON.parse(raw);
|
|
1928
|
-
delete parsed.apiKey;
|
|
1929
|
-
delete parsed.provider;
|
|
1930
|
-
settings = {
|
|
1931
|
-
...defaults,
|
|
1932
|
-
...parsed,
|
|
1933
|
-
mcpPort: sanitizePort(parsed.mcpPort ?? defaults.mcpPort),
|
|
1934
|
-
agentTranscriptMode: parsed.agentTranscriptMode === "off" || parsed.agentTranscriptMode === "summary" || parsed.agentTranscriptMode === "full" ? parsed.agentTranscriptMode : parsed.showAgentTranscript === false ? "off" : defaults.agentTranscriptMode
|
|
1935
|
-
};
|
|
1936
|
-
} catch (error) {
|
|
1937
|
-
if (fs.existsSync(getSettingsPath())) {
|
|
1938
|
-
settingsIssues.push({
|
|
1939
|
-
code: "settings-read-failed",
|
|
1940
|
-
severity: "warning",
|
|
1941
|
-
title: "Could not read Vessel settings",
|
|
1942
|
-
detail: error instanceof Error ? error.message : "Unknown settings error.",
|
|
1943
|
-
action: "Falling back to built-in defaults for this launch."
|
|
1944
|
-
});
|
|
2220
|
+
const tab = this.tabs.get(id);
|
|
2221
|
+
if (!tab) continue;
|
|
2222
|
+
const wc = tab.view.webContents;
|
|
2223
|
+
if (wc.isDestroyed()) continue;
|
|
2224
|
+
try {
|
|
2225
|
+
const tabUrl = normalizeUrl(wc.getURL());
|
|
2226
|
+
if (tabUrl === normalized) {
|
|
2227
|
+
void this.removeHighlightMarksForText(wc, text).then(() => {
|
|
2228
|
+
void highlightOnPage(
|
|
2229
|
+
wc,
|
|
2230
|
+
null,
|
|
2231
|
+
text,
|
|
2232
|
+
void 0,
|
|
2233
|
+
void 0,
|
|
2234
|
+
color
|
|
2235
|
+
).catch(() => {
|
|
2236
|
+
});
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
} catch {
|
|
2240
|
+
}
|
|
1945
2241
|
}
|
|
1946
|
-
|
|
2242
|
+
this.highlightCaptureCallback?.({
|
|
2243
|
+
success: true,
|
|
2244
|
+
message: `Color changed to ${color}`
|
|
2245
|
+
});
|
|
1947
2246
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
function
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2247
|
+
async removeHighlightMarksForText(wc, text) {
|
|
2248
|
+
await wc.executeJavaScript(
|
|
2249
|
+
`(function() {
|
|
2250
|
+
var marks = document.querySelectorAll('mark.__vessel-highlight-text[data-vessel-highlight]');
|
|
2251
|
+
marks.forEach(function(m) {
|
|
2252
|
+
if (m.textContent === ${JSON.stringify(text)}) {
|
|
2253
|
+
var parent = m.parentNode;
|
|
2254
|
+
while (m.firstChild) parent.insertBefore(m.firstChild, m);
|
|
2255
|
+
m.remove();
|
|
2256
|
+
if (parent) parent.normalize();
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
})()`
|
|
2260
|
+
).catch(() => {
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
broadcastState() {
|
|
2264
|
+
const states = this.getAllStates();
|
|
2265
|
+
this.onStateChange(states, this.activeTabId || "");
|
|
1960
2266
|
}
|
|
1961
|
-
saveSettings();
|
|
1962
|
-
return { ...settings };
|
|
1963
2267
|
}
|
|
1964
2268
|
const Channels = {
|
|
1965
2269
|
// Tab management
|
|
@@ -2025,6 +2329,21 @@ const Channels = {
|
|
|
2025
2329
|
// DevTools panel
|
|
2026
2330
|
DEVTOOLS_PANEL_TOGGLE: "devtools-panel:toggle",
|
|
2027
2331
|
DEVTOOLS_PANEL_STATE: "devtools-panel:state",
|
|
2332
|
+
DEVTOOLS_PANEL_RESIZE: "devtools-panel:resize",
|
|
2333
|
+
// Find in page
|
|
2334
|
+
FIND_IN_PAGE_START: "find:start",
|
|
2335
|
+
FIND_IN_PAGE_NEXT: "find:next",
|
|
2336
|
+
FIND_IN_PAGE_STOP: "find:stop",
|
|
2337
|
+
FIND_IN_PAGE_RESULT: "find:result",
|
|
2338
|
+
// Browsing history
|
|
2339
|
+
HISTORY_GET: "history:get",
|
|
2340
|
+
HISTORY_SEARCH: "history:search",
|
|
2341
|
+
HISTORY_CLEAR: "history:clear",
|
|
2342
|
+
HISTORY_UPDATE: "history:update",
|
|
2343
|
+
// Downloads
|
|
2344
|
+
DOWNLOAD_STARTED: "download:started",
|
|
2345
|
+
DOWNLOAD_PROGRESS: "download:progress",
|
|
2346
|
+
DOWNLOAD_DONE: "download:done",
|
|
2028
2347
|
// Window controls
|
|
2029
2348
|
WINDOW_MINIMIZE: "window:minimize",
|
|
2030
2349
|
WINDOW_MAXIMIZE: "window:maximize",
|
|
@@ -3146,6 +3465,16 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3146
3465
|
"dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']"
|
|
3147
3466
|
).forEach(function(el) { candidates.add(el); });
|
|
3148
3467
|
|
|
3468
|
+
// Known consent manager containers — these are often missed by generic
|
|
3469
|
+
// heuristics because they use custom stacking or non-standard z-indices
|
|
3470
|
+
document.body.querySelectorAll(
|
|
3471
|
+
'#onetrust-consent-sdk, #CybotCookiebotDialog, [class*="consent-banner"], ' +
|
|
3472
|
+
'[class*="cookie-banner"], [class*="privacy-banner"], [id*="consent-wall"], ' +
|
|
3473
|
+
'.fc-consent-root, #sp_message_container_, [id*="trustarc"], ' +
|
|
3474
|
+
'[class*="cmp-"], [id*="cmp-container"], [class*="gdpr"], ' +
|
|
3475
|
+
'[data-testid*="consent"], [data-testid*="cookie"], [data-testid*="privacy"]'
|
|
3476
|
+
).forEach(function(el) { candidates.add(el); });
|
|
3477
|
+
|
|
3149
3478
|
// Fixed/sticky elements are the other overlay category — walk only
|
|
3150
3479
|
// direct children of body and high-level containers (depth ≤ 3)
|
|
3151
3480
|
// since real overlays are almost always near the top of the DOM tree.
|
|
@@ -3173,13 +3502,23 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3173
3502
|
var cartConfirm = !dialogLike && !drawerLike && isPositioned(style) &&
|
|
3174
3503
|
rect.width >= 160 && rect.height >= 100 &&
|
|
3175
3504
|
looksLikeCartConfirmation(node);
|
|
3505
|
+
// Body scroll-lock + large fixed element is a strong overlay signal
|
|
3506
|
+
// even without high z-index or exact center coverage
|
|
3507
|
+
var bodyLocked = (function() {
|
|
3508
|
+
var bs = window.getComputedStyle(document.body);
|
|
3509
|
+
var hs = window.getComputedStyle(document.documentElement);
|
|
3510
|
+
return bs.overflow === "hidden" || hs.overflow === "hidden";
|
|
3511
|
+
})();
|
|
3176
3512
|
var blocksInteraction = dialogLike ||
|
|
3177
3513
|
drawerLike ||
|
|
3178
3514
|
cartConfirm ||
|
|
3179
3515
|
((style.position === "fixed" || style.position === "sticky") &&
|
|
3180
3516
|
parseZIndex(style) >= 10 &&
|
|
3181
3517
|
areaRatio >= 0.3 &&
|
|
3182
|
-
coversViewportCenter(rect))
|
|
3518
|
+
coversViewportCenter(rect)) ||
|
|
3519
|
+
(bodyLocked &&
|
|
3520
|
+
(style.position === "fixed" || style.position === "sticky") &&
|
|
3521
|
+
areaRatio >= 0.2);
|
|
3183
3522
|
|
|
3184
3523
|
if (!blocksInteraction && type !== "dialog" && type !== "modal") return;
|
|
3185
3524
|
|
|
@@ -4006,9 +4345,12 @@ function generateReaderHTML(page) {
|
|
|
4006
4345
|
function escapeHtml(str) {
|
|
4007
4346
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4008
4347
|
}
|
|
4009
|
-
|
|
4348
|
+
const mcpStatusChangeListeners = /* @__PURE__ */ new Set();
|
|
4010
4349
|
function onMcpStatusChange(listener) {
|
|
4011
|
-
|
|
4350
|
+
mcpStatusChangeListeners.add(listener);
|
|
4351
|
+
return () => {
|
|
4352
|
+
mcpStatusChangeListeners.delete(listener);
|
|
4353
|
+
};
|
|
4012
4354
|
}
|
|
4013
4355
|
function getMcpStatus() {
|
|
4014
4356
|
return state$1.mcp.status;
|
|
@@ -4059,7 +4401,9 @@ function setMcpHealth(update) {
|
|
|
4059
4401
|
state$1.mcp.status = update.status;
|
|
4060
4402
|
state$1.mcp.message = update.message;
|
|
4061
4403
|
if (prevStatus !== state$1.mcp.status) {
|
|
4062
|
-
|
|
4404
|
+
for (const listener of mcpStatusChangeListeners) {
|
|
4405
|
+
listener(state$1.mcp.status);
|
|
4406
|
+
}
|
|
4063
4407
|
}
|
|
4064
4408
|
}
|
|
4065
4409
|
const DEFAULT_MAX_ITERATIONS$1 = 200;
|
|
@@ -6356,6 +6700,7 @@ const TOOL_DEFINITIONS = [
|
|
|
6356
6700
|
description: "Read the current page using a scoped mode. Defaults to a minimal navigation-focused brief; use mode='debug' only when narrower modes are insufficient.",
|
|
6357
6701
|
inputSchema: {
|
|
6358
6702
|
mode: zod.z.enum([
|
|
6703
|
+
"glance",
|
|
6359
6704
|
"summary",
|
|
6360
6705
|
"interactives_only",
|
|
6361
6706
|
"forms_only",
|
|
@@ -6365,7 +6710,7 @@ const TOOL_DEFINITIONS = [
|
|
|
6365
6710
|
"full",
|
|
6366
6711
|
"debug"
|
|
6367
6712
|
]).optional().describe(
|
|
6368
|
-
"Read mode: visible_only/results_only/forms_only/summary/text_only for narrow reads, full/debug for the complete page dump"
|
|
6713
|
+
"Read mode: glance (fastest — viewport snapshot, no JS extraction, ideal for heavy pages), visible_only/results_only/forms_only/summary/text_only for narrow reads, full/debug for the complete page dump"
|
|
6369
6714
|
)
|
|
6370
6715
|
},
|
|
6371
6716
|
tier: 0
|
|
@@ -7069,8 +7414,12 @@ function load() {
|
|
|
7069
7414
|
return state;
|
|
7070
7415
|
}
|
|
7071
7416
|
function save() {
|
|
7072
|
-
|
|
7073
|
-
|
|
7417
|
+
try {
|
|
7418
|
+
fs.mkdirSync(path.dirname(getBookmarksPath()), { recursive: true });
|
|
7419
|
+
fs.writeFileSync(getBookmarksPath(), JSON.stringify(state, null, 2), "utf-8");
|
|
7420
|
+
} catch (err) {
|
|
7421
|
+
console.error("[Vessel] Failed to save bookmarks:", err);
|
|
7422
|
+
}
|
|
7074
7423
|
}
|
|
7075
7424
|
function emit() {
|
|
7076
7425
|
if (!state) return;
|
|
@@ -7496,6 +7845,22 @@ function formatDeadLinkMessage(label, result) {
|
|
|
7496
7845
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
7497
7846
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
7498
7847
|
}
|
|
7848
|
+
const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
7849
|
+
function isSafeNavigationURL(url) {
|
|
7850
|
+
try {
|
|
7851
|
+
const parsed = new URL(url);
|
|
7852
|
+
return ALLOWED_SCHEMES.has(parsed.protocol);
|
|
7853
|
+
} catch {
|
|
7854
|
+
return false;
|
|
7855
|
+
}
|
|
7856
|
+
}
|
|
7857
|
+
function assertSafeURL(url) {
|
|
7858
|
+
if (!isSafeNavigationURL(url)) {
|
|
7859
|
+
throw new Error(
|
|
7860
|
+
`Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`
|
|
7861
|
+
);
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7499
7864
|
const SESSION_VERSION = 1;
|
|
7500
7865
|
function getSessionsDir() {
|
|
7501
7866
|
return path$1.join(electron.app.getPath("userData"), "named-sessions");
|
|
@@ -7515,7 +7880,7 @@ function normalizeSessionName(name) {
|
|
|
7515
7880
|
function sessionFileName(name) {
|
|
7516
7881
|
const normalized = normalizeSessionName(name).toLowerCase();
|
|
7517
7882
|
const slug = normalized.replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "session";
|
|
7518
|
-
const hash =
|
|
7883
|
+
const hash = crypto$1.createHash("sha256").update(normalized).digest("hex").slice(0, 8);
|
|
7519
7884
|
return `${slug}-${hash}.json`;
|
|
7520
7885
|
}
|
|
7521
7886
|
function getSessionPath(name) {
|
|
@@ -7780,10 +8145,148 @@ const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
|
7780
8145
|
function pageBusyError(action) {
|
|
7781
8146
|
return `Error: Page is still busy; ${action} timed out waiting for page scripts. Retry in a moment.`;
|
|
7782
8147
|
}
|
|
8148
|
+
async function glanceExtract(wc) {
|
|
8149
|
+
const startMs = Date.now();
|
|
8150
|
+
const result = await executePageScript(
|
|
8151
|
+
wc,
|
|
8152
|
+
`(function() {
|
|
8153
|
+
var vw = window.innerWidth || document.documentElement.clientWidth || 0;
|
|
8154
|
+
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
8155
|
+
var sy = window.scrollY || window.pageYOffset || 0;
|
|
8156
|
+
|
|
8157
|
+
function inViewport(el) {
|
|
8158
|
+
var r = el.getBoundingClientRect();
|
|
8159
|
+
return r.bottom > 0 && r.top < vh && r.right > 0 && r.left < vw && r.width > 0 && r.height > 0;
|
|
8160
|
+
}
|
|
8161
|
+
|
|
8162
|
+
function label(el) {
|
|
8163
|
+
return (el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 120);
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
// Headings visible on screen
|
|
8167
|
+
var headings = [];
|
|
8168
|
+
document.querySelectorAll('h1, h2, h3, h4').forEach(function(h) {
|
|
8169
|
+
if (!inViewport(h)) return;
|
|
8170
|
+
var t = (h.textContent || '').trim();
|
|
8171
|
+
if (t && t.length < 200) headings.push(h.tagName.toLowerCase() + ': ' + t);
|
|
8172
|
+
});
|
|
8173
|
+
|
|
8174
|
+
// Links visible on screen (deduplicated by text)
|
|
8175
|
+
var links = [];
|
|
8176
|
+
var seenLinks = {};
|
|
8177
|
+
var idx = 1;
|
|
8178
|
+
document.querySelectorAll('a[href]').forEach(function(a) {
|
|
8179
|
+
if (!inViewport(a)) return;
|
|
8180
|
+
var t = (a.textContent || '').trim().slice(0, 100);
|
|
8181
|
+
if (!t || t.length < 2 || seenLinks[t]) return;
|
|
8182
|
+
seenLinks[t] = true;
|
|
8183
|
+
links.push({ text: t, href: (a.href || '').slice(0, 200), index: idx++ });
|
|
8184
|
+
});
|
|
8185
|
+
|
|
8186
|
+
// Buttons visible on screen
|
|
8187
|
+
var buttons = [];
|
|
8188
|
+
document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]').forEach(function(b) {
|
|
8189
|
+
if (!inViewport(b)) return;
|
|
8190
|
+
var t = label(b);
|
|
8191
|
+
if (!t || t.length < 1) return;
|
|
8192
|
+
buttons.push({ text: t, index: idx++ });
|
|
8193
|
+
});
|
|
8194
|
+
|
|
8195
|
+
// Input fields visible on screen
|
|
8196
|
+
var inputs = [];
|
|
8197
|
+
document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea').forEach(function(inp) {
|
|
8198
|
+
if (!inViewport(inp)) return;
|
|
8199
|
+
var type = (inp.type || inp.tagName.toLowerCase() || '').toLowerCase();
|
|
8200
|
+
var lbl = (inp.getAttribute('aria-label') || inp.getAttribute('placeholder') || inp.name || '').trim();
|
|
8201
|
+
inputs.push({ type: type, label: lbl.slice(0, 80), placeholder: (inp.getAttribute('placeholder') || '').slice(0, 80), index: idx++ });
|
|
8202
|
+
});
|
|
8203
|
+
|
|
8204
|
+
// Content snapshot from main content area using textContent (instant, no reflow)
|
|
8205
|
+
var roots = ['main', 'article', '[role="main"]', '#content', '.content', '.story-body'];
|
|
8206
|
+
var contentRoot = null;
|
|
8207
|
+
for (var i = 0; i < roots.length; i++) {
|
|
8208
|
+
contentRoot = document.querySelector(roots[i]);
|
|
8209
|
+
if (contentRoot && contentRoot.textContent.trim().length > 50) break;
|
|
8210
|
+
contentRoot = null;
|
|
8211
|
+
}
|
|
8212
|
+
var snippet = '';
|
|
8213
|
+
if (contentRoot) {
|
|
8214
|
+
snippet = contentRoot.textContent.replace(/[ \\t]+/g, ' ').replace(/(\\n\\s*){3,}/g, '\\n\\n').trim().slice(0, 8000);
|
|
8215
|
+
} else {
|
|
8216
|
+
// Fallback: grab text from visible elements only
|
|
8217
|
+
var parts = [];
|
|
8218
|
+
document.querySelectorAll('h1, h2, h3, p, li, td, span, div').forEach(function(el) {
|
|
8219
|
+
if (parts.length > 100 || !inViewport(el)) return;
|
|
8220
|
+
var t = (el.textContent || '').trim();
|
|
8221
|
+
if (t.length > 10 && t.length < 500) parts.push(t);
|
|
8222
|
+
});
|
|
8223
|
+
snippet = parts.join('\\n').slice(0, 8000);
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8226
|
+
return {
|
|
8227
|
+
title: document.title || '',
|
|
8228
|
+
url: location.href,
|
|
8229
|
+
headings: headings.slice(0, 20),
|
|
8230
|
+
links: links.slice(0, 40),
|
|
8231
|
+
buttons: buttons.slice(0, 20),
|
|
8232
|
+
inputs: inputs.slice(0, 15),
|
|
8233
|
+
contentSnippet: snippet,
|
|
8234
|
+
viewportHeight: vh,
|
|
8235
|
+
viewportWidth: vw,
|
|
8236
|
+
scrollY: Math.round(sy),
|
|
8237
|
+
};
|
|
8238
|
+
})()`,
|
|
8239
|
+
{ timeoutMs: 2500, label: "glance-extract" }
|
|
8240
|
+
);
|
|
8241
|
+
const elapsed = Date.now() - startMs;
|
|
8242
|
+
if (!result || result === PAGE_SCRIPT_TIMEOUT) {
|
|
8243
|
+
return [
|
|
8244
|
+
`# ${wc.getTitle() || "(untitled)"}`,
|
|
8245
|
+
`URL: ${wc.getURL()}`,
|
|
8246
|
+
"",
|
|
8247
|
+
"[read_page mode=glance — page JS thread is completely blocked, no content available]",
|
|
8248
|
+
"[Try: click or type_text to interact directly, or wait a few seconds and retry]"
|
|
8249
|
+
].join("\n");
|
|
8250
|
+
}
|
|
8251
|
+
const sections = [
|
|
8252
|
+
`# ${result.title}`,
|
|
8253
|
+
`URL: ${result.url}`,
|
|
8254
|
+
`Viewport: ${result.viewportWidth}×${result.viewportHeight} scrollY=${result.scrollY}`,
|
|
8255
|
+
`[read_page mode=glance — ${elapsed}ms, showing what's visible on screen]`
|
|
8256
|
+
];
|
|
8257
|
+
if (result.headings.length > 0) {
|
|
8258
|
+
sections.push("", "## Headings", ...result.headings);
|
|
8259
|
+
}
|
|
8260
|
+
if (result.inputs.length > 0) {
|
|
8261
|
+
sections.push("", "## Input Fields");
|
|
8262
|
+
for (const inp of result.inputs) {
|
|
8263
|
+
const desc = inp.label || inp.placeholder || inp.type;
|
|
8264
|
+
sections.push(` [#${inp.index}] ${inp.type}: ${desc}`);
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
if (result.buttons.length > 0) {
|
|
8268
|
+
sections.push("", "## Buttons");
|
|
8269
|
+
for (const btn of result.buttons) {
|
|
8270
|
+
sections.push(` [#${btn.index}] ${btn.text}`);
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
if (result.links.length > 0) {
|
|
8274
|
+
sections.push("", "## Visible Links");
|
|
8275
|
+
for (const link of result.links) {
|
|
8276
|
+
sections.push(` [#${link.index}] ${link.text}`);
|
|
8277
|
+
}
|
|
8278
|
+
}
|
|
8279
|
+
if (result.contentSnippet) {
|
|
8280
|
+
const truncated = result.contentSnippet.length > 6e3 ? result.contentSnippet.slice(0, 6e3) + "\n[truncated]" : result.contentSnippet;
|
|
8281
|
+
sections.push("", "## Page Content (viewport)", "", truncated);
|
|
8282
|
+
}
|
|
8283
|
+
return sections.join("\n");
|
|
8284
|
+
}
|
|
7783
8285
|
function normalizeReadPageMode(mode, pageContent) {
|
|
7784
8286
|
if (typeof mode === "string") {
|
|
7785
8287
|
const normalized = mode.trim().toLowerCase();
|
|
7786
8288
|
if (normalized === "debug") return "debug";
|
|
8289
|
+
if (normalized === "glance") return "glance";
|
|
7787
8290
|
if (normalized === "full" || normalized === "summary" || normalized === "interactives_only" || normalized === "forms_only" || normalized === "text_only" || normalized === "visible_only" || normalized === "results_only") {
|
|
7788
8291
|
return normalized;
|
|
7789
8292
|
}
|
|
@@ -7911,10 +8414,62 @@ function waitForPotentialNavigation$1(wc, beforeUrl, timeout = 2500) {
|
|
|
7911
8414
|
wc.on("page-title-updated", onNativeChange);
|
|
7912
8415
|
});
|
|
7913
8416
|
}
|
|
7914
|
-
function getPostNavSummary(wc) {
|
|
8417
|
+
async function getPostNavSummary(wc) {
|
|
7915
8418
|
const title = wc.getTitle();
|
|
7916
|
-
|
|
8419
|
+
const titleLine = title ? `
|
|
7917
8420
|
Page title: ${title}` : "";
|
|
8421
|
+
const overlaySignal = await executePageScript(
|
|
8422
|
+
wc,
|
|
8423
|
+
`(function() {
|
|
8424
|
+
var signals = [];
|
|
8425
|
+
// Body scroll lock is a strong overlay signal
|
|
8426
|
+
var bodyStyle = window.getComputedStyle(document.body);
|
|
8427
|
+
var htmlStyle = window.getComputedStyle(document.documentElement);
|
|
8428
|
+
if (bodyStyle.overflow === 'hidden' || htmlStyle.overflow === 'hidden') {
|
|
8429
|
+
signals.push('body-scroll-locked');
|
|
8430
|
+
}
|
|
8431
|
+
// Check for known consent manager containers
|
|
8432
|
+
var consentSelectors = [
|
|
8433
|
+
'#onetrust-consent-sdk', '#CybotCookiebotDialog', '[class*="consent-banner"]',
|
|
8434
|
+
'[class*="cookie-banner"]', '[class*="privacy-banner"]', '[id*="consent"]',
|
|
8435
|
+
'[class*="gdpr"]', '[data-testid*="consent"]', '[data-testid*="cookie"]',
|
|
8436
|
+
'.fc-consent-root', '#sp_message_container_', '[id*="trustarc"]',
|
|
8437
|
+
'[class*="cmp-"]', '[id*="cmp-"]'
|
|
8438
|
+
];
|
|
8439
|
+
for (var i = 0; i < consentSelectors.length; i++) {
|
|
8440
|
+
try {
|
|
8441
|
+
var el = document.querySelector(consentSelectors[i]);
|
|
8442
|
+
if (el && el.offsetHeight > 50) {
|
|
8443
|
+
signals.push('consent-banner:' + consentSelectors[i]);
|
|
8444
|
+
break;
|
|
8445
|
+
}
|
|
8446
|
+
} catch(e) {}
|
|
8447
|
+
}
|
|
8448
|
+
// Check for large fixed/sticky elements covering viewport
|
|
8449
|
+
var vw = window.innerWidth || 0;
|
|
8450
|
+
var vh = window.innerHeight || 0;
|
|
8451
|
+
var vpArea = Math.max(1, vw * vh);
|
|
8452
|
+
var els = document.querySelectorAll('dialog[open], [role="dialog"], [aria-modal="true"]');
|
|
8453
|
+
if (els.length > 0) signals.push('dialog-open');
|
|
8454
|
+
if (signals.length === 0) {
|
|
8455
|
+
var fixed = document.querySelectorAll('div[style*="position: fixed"], div[style*="position:fixed"]');
|
|
8456
|
+
for (var j = 0; j < fixed.length && j < 20; j++) {
|
|
8457
|
+
var r = fixed[j].getBoundingClientRect();
|
|
8458
|
+
if ((r.width * r.height) / vpArea > 0.3) {
|
|
8459
|
+
signals.push('large-fixed-overlay');
|
|
8460
|
+
break;
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8464
|
+
return signals.length > 0 ? signals.join(', ') : null;
|
|
8465
|
+
})()`,
|
|
8466
|
+
{ timeoutMs: 1500, label: "overlay-probe" }
|
|
8467
|
+
);
|
|
8468
|
+
if (overlaySignal && overlaySignal !== PAGE_SCRIPT_TIMEOUT) {
|
|
8469
|
+
return `${titleLine}
|
|
8470
|
+
WARNING: Blocking overlay detected (${overlaySignal}). Call clear_overlays or accept_cookies before reading the page.`;
|
|
8471
|
+
}
|
|
8472
|
+
return titleLine;
|
|
7918
8473
|
}
|
|
7919
8474
|
async function scrollPage$1(wc, deltaY) {
|
|
7920
8475
|
const getScrollY = async () => {
|
|
@@ -8363,6 +8918,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
8363
8918
|
}
|
|
8364
8919
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
8365
8920
|
try {
|
|
8921
|
+
assertSafeURL(snapshot.url);
|
|
8366
8922
|
await wc.loadURL(snapshot.url);
|
|
8367
8923
|
await waitForLoad$1(wc, 3e3);
|
|
8368
8924
|
return;
|
|
@@ -9007,6 +9563,71 @@ async function clickOverlayCandidate(wc, action) {
|
|
|
9007
9563
|
const result = await clickResolvedSelector$1(wc, action.selector);
|
|
9008
9564
|
return `${action.label || action.selector}: ${result}`;
|
|
9009
9565
|
}
|
|
9566
|
+
async function tryDismissConsentIframe(wc) {
|
|
9567
|
+
try {
|
|
9568
|
+
const hasSignal = await executePageScript(
|
|
9569
|
+
wc,
|
|
9570
|
+
`(function() {
|
|
9571
|
+
var bs = window.getComputedStyle(document.body);
|
|
9572
|
+
var hs = window.getComputedStyle(document.documentElement);
|
|
9573
|
+
if (bs.overflow === 'hidden' || hs.overflow === 'hidden') return true;
|
|
9574
|
+
var sels = '#onetrust-consent-sdk, [class*="consent"], [class*="cookie-banner"], [id*="consent"], [id*="sp_message"], .fc-consent-root, [class*="cmp-"]';
|
|
9575
|
+
var el = document.querySelector(sels);
|
|
9576
|
+
return !!(el && el.offsetHeight > 20);
|
|
9577
|
+
})()`,
|
|
9578
|
+
{ timeoutMs: 1e3, label: "iframe-consent-signal" }
|
|
9579
|
+
);
|
|
9580
|
+
if (!hasSignal || hasSignal === PAGE_SCRIPT_TIMEOUT) return null;
|
|
9581
|
+
const frames = wc.mainFrame.framesInSubtree;
|
|
9582
|
+
for (const frame of frames) {
|
|
9583
|
+
if (frame === wc.mainFrame) continue;
|
|
9584
|
+
try {
|
|
9585
|
+
const result = await frame.executeJavaScript(`
|
|
9586
|
+
(function() {
|
|
9587
|
+
var selectors = [
|
|
9588
|
+
'button[title*="Accept"], button[title*="Agree"], button[title*="OK"]',
|
|
9589
|
+
'[class*="accept"], [class*="agree"], [class*="consent-accept"]',
|
|
9590
|
+
'button[aria-label*="accept" i], button[aria-label*="agree" i]',
|
|
9591
|
+
'.sp_choice_type_11', '.message-component.message-button',
|
|
9592
|
+
];
|
|
9593
|
+
// Try selectors first
|
|
9594
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
9595
|
+
try {
|
|
9596
|
+
var els = document.querySelectorAll(selectors[i]);
|
|
9597
|
+
for (var j = 0; j < els.length; j++) {
|
|
9598
|
+
var el = els[j];
|
|
9599
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
9600
|
+
var text = (el.textContent || '').trim().toLowerCase();
|
|
9601
|
+
if (/accept|agree|consent|got it|ok|continue|i understand/i.test(text) || el.offsetHeight > 0) {
|
|
9602
|
+
el.click();
|
|
9603
|
+
return 'Clicked iframe consent button: ' + text.slice(0, 60);
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9606
|
+
} catch(e) {}
|
|
9607
|
+
}
|
|
9608
|
+
// Text-match fallback on all buttons
|
|
9609
|
+
var buttons = document.querySelectorAll('button, [role="button"], a.message-component');
|
|
9610
|
+
for (var k = 0; k < buttons.length; k++) {
|
|
9611
|
+
var btn = buttons[k];
|
|
9612
|
+
var label = (btn.textContent || '').trim().toLowerCase();
|
|
9613
|
+
if (/^(accept|agree|accept all|i agree|i accept|ok|got it|allow|continue|yes)$/i.test(label) ||
|
|
9614
|
+
/accept all|agree and|accept & continue|accept and continue/i.test(label)) {
|
|
9615
|
+
btn.click();
|
|
9616
|
+
return 'Clicked iframe consent button: ' + label.slice(0, 60);
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
return null;
|
|
9620
|
+
})()
|
|
9621
|
+
`);
|
|
9622
|
+
if (result) return result;
|
|
9623
|
+
} catch {
|
|
9624
|
+
continue;
|
|
9625
|
+
}
|
|
9626
|
+
}
|
|
9627
|
+
} catch {
|
|
9628
|
+
}
|
|
9629
|
+
return null;
|
|
9630
|
+
}
|
|
9010
9631
|
async function clearOverlays(wc, strategy = "auto") {
|
|
9011
9632
|
const steps = [];
|
|
9012
9633
|
let cleared = 0;
|
|
@@ -9018,7 +9639,15 @@ async function clearOverlays(wc, strategy = "auto") {
|
|
|
9018
9639
|
(overlay2) => overlay2.blocksInteraction
|
|
9019
9640
|
);
|
|
9020
9641
|
if (blockingOverlays.length === 0) {
|
|
9021
|
-
if (cleared === 0)
|
|
9642
|
+
if (cleared === 0) {
|
|
9643
|
+
const iframeResult = await tryDismissConsentIframe(wc);
|
|
9644
|
+
if (iframeResult) {
|
|
9645
|
+
steps.push(`Iframe consent: ${iframeResult}`);
|
|
9646
|
+
await sleep$1(500);
|
|
9647
|
+
return steps.join("\n");
|
|
9648
|
+
}
|
|
9649
|
+
return "No blocking overlays detected";
|
|
9650
|
+
}
|
|
9022
9651
|
steps.push(`Overlays remaining: ${beforeState.total}`);
|
|
9023
9652
|
steps.push("Page still blocked: false");
|
|
9024
9653
|
return steps.join("\n");
|
|
@@ -9870,6 +10499,7 @@ async function submitForm$1(wc, args) {
|
|
|
9870
10499
|
if (formInfo.params) {
|
|
9871
10500
|
url.search = formInfo.params;
|
|
9872
10501
|
}
|
|
10502
|
+
assertSafeURL(url.toString());
|
|
9873
10503
|
wc.loadURL(url.toString());
|
|
9874
10504
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
9875
10505
|
const afterUrl = wc.getURL();
|
|
@@ -10164,7 +10794,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10164
10794
|
const created = ctx.tabManager.getActiveTab();
|
|
10165
10795
|
if (created) {
|
|
10166
10796
|
await waitForLoad$1(created.view.webContents);
|
|
10167
|
-
return `Created tab ${createdId}${getPostNavSummary(created.view.webContents)}`;
|
|
10797
|
+
return `Created tab ${createdId}${await getPostNavSummary(created.view.webContents)}`;
|
|
10168
10798
|
}
|
|
10169
10799
|
return `Created tab ${createdId}`;
|
|
10170
10800
|
}
|
|
@@ -10176,7 +10806,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10176
10806
|
}
|
|
10177
10807
|
ctx.tabManager.navigateTab(tabId, args.url);
|
|
10178
10808
|
await waitForLoad$1(wc);
|
|
10179
|
-
return `Navigated to ${wc.getURL()}${getPostNavSummary(wc)}`;
|
|
10809
|
+
return `Navigated to ${wc.getURL()}${await getPostNavSummary(wc)}`;
|
|
10180
10810
|
}
|
|
10181
10811
|
case "go_back": {
|
|
10182
10812
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -10187,7 +10817,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10187
10817
|
ctx.tabManager.goBack(tabId);
|
|
10188
10818
|
await waitForLoad$1(wc);
|
|
10189
10819
|
const afterUrl = wc.getURL();
|
|
10190
|
-
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
10820
|
+
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${await getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
10191
10821
|
}
|
|
10192
10822
|
case "go_forward": {
|
|
10193
10823
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -10198,7 +10828,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10198
10828
|
ctx.tabManager.goForward(tabId);
|
|
10199
10829
|
await waitForLoad$1(wc);
|
|
10200
10830
|
const afterUrl = wc.getURL();
|
|
10201
|
-
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
10831
|
+
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${await getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
10202
10832
|
}
|
|
10203
10833
|
case "reload": {
|
|
10204
10834
|
if (!wc || !tabId) return "Error: No active tab";
|
|
@@ -10317,6 +10947,10 @@ async function executeAction(name, args, ctx) {
|
|
|
10317
10947
|
}
|
|
10318
10948
|
case "read_page": {
|
|
10319
10949
|
if (!wc) return "Error: No active tab";
|
|
10950
|
+
const requestedGlance = typeof args.mode === "string" && args.mode.trim().toLowerCase() === "glance";
|
|
10951
|
+
if (requestedGlance) {
|
|
10952
|
+
return glanceExtract(wc);
|
|
10953
|
+
}
|
|
10320
10954
|
console.log("[Vessel read_page] starting extraction with 6s timeout");
|
|
10321
10955
|
let content = null;
|
|
10322
10956
|
try {
|
|
@@ -10335,7 +10969,29 @@ async function executeAction(name, args, ctx) {
|
|
|
10335
10969
|
console.log(
|
|
10336
10970
|
`[Vessel read_page] extraction result: ${content ? `content=${content.content.length}` : "null (timeout)"}`
|
|
10337
10971
|
);
|
|
10338
|
-
if (content) {
|
|
10972
|
+
if (!content || content.content.length === 0) {
|
|
10973
|
+
console.log("[Vessel read_page] content empty/null, trying quick iframe dismiss");
|
|
10974
|
+
try {
|
|
10975
|
+
const iframeResult = await Promise.race([
|
|
10976
|
+
tryDismissConsentIframe(wc),
|
|
10977
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 2e3))
|
|
10978
|
+
]);
|
|
10979
|
+
if (iframeResult) {
|
|
10980
|
+
console.log(`[Vessel read_page] iframe dismiss: ${iframeResult}`);
|
|
10981
|
+
await sleep$1(500);
|
|
10982
|
+
try {
|
|
10983
|
+
content = await Promise.race([
|
|
10984
|
+
extractContent(wc),
|
|
10985
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
10986
|
+
]);
|
|
10987
|
+
} catch {
|
|
10988
|
+
content = null;
|
|
10989
|
+
}
|
|
10990
|
+
}
|
|
10991
|
+
} catch {
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
if (content && content.content.length > 0) {
|
|
10339
10995
|
const liveSelectionSection = formatLiveSelectionSection(
|
|
10340
10996
|
await captureLiveHighlightSnapshot(
|
|
10341
10997
|
wc,
|
|
@@ -10367,16 +11023,8 @@ ${truncated}`;
|
|
|
10367
11023
|
`Need more detail? Escalate with read_page(mode="debug") only if the narrow modes are insufficient.`
|
|
10368
11024
|
].filter(Boolean).join("\n\n");
|
|
10369
11025
|
}
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
return [
|
|
10373
|
-
`# ${title}`,
|
|
10374
|
-
`URL: ${url}`,
|
|
10375
|
-
"",
|
|
10376
|
-
"[Page content extraction timed out — the page JS thread is busy.]",
|
|
10377
|
-
"[Use the search tool to search the site, or type_text/click to interact directly.]",
|
|
10378
|
-
"[You can retry read_page in a few seconds once the page finishes loading.]"
|
|
10379
|
-
].join("\n");
|
|
11026
|
+
console.log("[Vessel read_page] falling back to glance mode");
|
|
11027
|
+
return glanceExtract(wc);
|
|
10380
11028
|
}
|
|
10381
11029
|
case "wait_for": {
|
|
10382
11030
|
if (!wc) return "Error: No active tab";
|
|
@@ -11078,6 +11726,7 @@ ${steps.join("\n")}`;
|
|
|
11078
11726
|
try {
|
|
11079
11727
|
const url = new URL(searchInfo.formAction);
|
|
11080
11728
|
url.searchParams.set(searchInfo.inputName || "q", query);
|
|
11729
|
+
assertSafeURL(url.toString());
|
|
11081
11730
|
wc.loadURL(url.toString());
|
|
11082
11731
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
11083
11732
|
afterUrl = wc.getURL();
|
|
@@ -11147,9 +11796,20 @@ ${steps.join("\n")}`;
|
|
|
11147
11796
|
'[aria-label="Accept cookies"]',
|
|
11148
11797
|
'[aria-label="Accept all cookies"]',
|
|
11149
11798
|
'[data-testid="cookie-accept"]',
|
|
11799
|
+
// CNN / WarnerMedia / common consent SDKs
|
|
11800
|
+
'[data-testid="consent-accept"]',
|
|
11801
|
+
'[data-testid="accept-all"]',
|
|
11802
|
+
'button[class*="consent"][class*="accept"]',
|
|
11803
|
+
'button[class*="privacy"][class*="accept"]',
|
|
11804
|
+
'.fc-cta-consent',
|
|
11805
|
+
'#sp_choice_button_accept',
|
|
11806
|
+
'.message-component.message-button.no-children.focusable.sp_choice_type_11',
|
|
11807
|
+
'[class*="truste"] [class*="accept"]',
|
|
11808
|
+
'[id*="consent-accept"]',
|
|
11809
|
+
'[class*="cmp-accept"]',
|
|
11150
11810
|
];
|
|
11151
11811
|
// Also try text-matching on buttons
|
|
11152
|
-
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'consent'];
|
|
11812
|
+
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'i accept', 'consent', 'continue', 'accept and continue', 'accept & continue'];
|
|
11153
11813
|
for (var i = 0; i < selectors.length; i++) {
|
|
11154
11814
|
var el = document.querySelector(selectors[i]);
|
|
11155
11815
|
if (el && el instanceof HTMLElement) { el.click(); return "Dismissed cookie banner via: " + selectors[i]; }
|
|
@@ -11175,7 +11835,10 @@ ${steps.join("\n")}`;
|
|
|
11175
11835
|
if (dismissed === PAGE_SCRIPT_TIMEOUT) {
|
|
11176
11836
|
return pageBusyError("accept_cookies");
|
|
11177
11837
|
}
|
|
11178
|
-
|
|
11838
|
+
if (dismissed) return dismissed;
|
|
11839
|
+
const iframeResult = await tryDismissConsentIframe(wc);
|
|
11840
|
+
if (iframeResult) return iframeResult;
|
|
11841
|
+
return "No cookie consent banner detected. Try dismiss_popup for other overlays.";
|
|
11179
11842
|
}
|
|
11180
11843
|
case "extract_table": {
|
|
11181
11844
|
if (!wc) return "Error: No active tab";
|
|
@@ -11273,10 +11936,10 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
11273
11936
|
const flowCtx = ctx.runtime.getFlowContext();
|
|
11274
11937
|
return result + await getPostActionState$1(ctx, name) + flowCtx;
|
|
11275
11938
|
}
|
|
11276
|
-
async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd, tabManager,
|
|
11939
|
+
async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd, tabManager, runtime2, history) {
|
|
11277
11940
|
const lowerQuery = query.toLowerCase().trim();
|
|
11278
11941
|
const isSummarize = lowerQuery.startsWith("summarize") || lowerQuery.startsWith("tldr") || lowerQuery === "summary";
|
|
11279
|
-
if (provider.streamAgentQuery && tabManager && activeWebContents &&
|
|
11942
|
+
if (provider.streamAgentQuery && tabManager && activeWebContents && runtime2) {
|
|
11280
11943
|
try {
|
|
11281
11944
|
const extractStart = Date.now();
|
|
11282
11945
|
const pageContent = await extractContent(activeWebContents);
|
|
@@ -11289,7 +11952,7 @@ async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd,
|
|
|
11289
11952
|
pageContent,
|
|
11290
11953
|
defaultReadMode
|
|
11291
11954
|
);
|
|
11292
|
-
const runtimeState =
|
|
11955
|
+
const runtimeState = runtime2.getState();
|
|
11293
11956
|
const recentCheckpoints = runtimeState.checkpoints.slice(-3).map((item) => `- ${item.name} (${item.id})`).join("\n");
|
|
11294
11957
|
const activeTabTitle = pageContent.title || "(untitled)";
|
|
11295
11958
|
const activeTabUrl = pageContent.url || activeWebContents.getURL();
|
|
@@ -11332,8 +11995,10 @@ Instructions:
|
|
|
11332
11995
|
- After navigating to a new site, DO NOT call read_page immediately. Instead, act on what you already know: use the search tool to search the site, type_text to enter queries in search bars, or click on known navigation patterns. You know what major sites look like — use that knowledge. Only call read_page if you're genuinely stuck and need to discover unfamiliar page structure.
|
|
11333
11996
|
- The page brief you start with is intentionally sparse. It is optimized for navigation speed, not completeness.
|
|
11334
11997
|
- When you only need detail on one product/result/card/form section, use inspect_element instead of reading the page.
|
|
11335
|
-
- Escalate page reads progressively: read_page(mode="visible_only"), read_page(mode="results_only"), read_page(mode="forms_only"), read_page(mode="summary"), or read_page(mode="text_only") depending on what you need.
|
|
11998
|
+
- Escalate page reads progressively: read_page(mode="glance") for a fast viewport snapshot on heavy/slow pages, then read_page(mode="visible_only"), read_page(mode="results_only"), read_page(mode="forms_only"), read_page(mode="summary"), or read_page(mode="text_only") depending on what you need.
|
|
11999
|
+
- Use read_page(mode="glance") when a page is slow to load or extraction times out — it shows what's on screen (headings, links, buttons, inputs) without waiting for heavy JS. It's what a human would see by just looking at the page.
|
|
11336
12000
|
- Use read_page(mode="debug") only as a last resort when the narrower modes are insufficient.
|
|
12001
|
+
- If read_page returns empty or times out, do NOT retry with the same mode. Switch to read_page(mode="glance") or interact directly with click/type_text.
|
|
11337
12002
|
- VIEWPORT SYNC: Treat scrolling as a real, user-visible browser action. If you say you are going to scroll, call scroll or scroll_to_element so the human sees the page move too.
|
|
11338
12003
|
- read_page inspects the page without moving the human-visible viewport. Do not describe read_page as scrolling. If you want more context without changing the user's view, say you're reading the page; if you want the user to follow along lower on the page, actually scroll first.
|
|
11339
12004
|
- After clicking or submitting a form, prefer wait_for on a specific result signal or a narrow read_page mode. Do not jump straight to read_page(mode="debug").
|
|
@@ -11351,7 +12016,7 @@ Instructions:
|
|
|
11351
12016
|
- ACT, DON'T HEDGE: You have a full browser — you can navigate to any website, see live content, search, click, add to cart, fill forms, and interact with real pages in real time. Never claim you "don't have access" to a website's inventory, pricing, or content. If the user asks you to go somewhere and do something, start doing it immediately. Don't ask for permission to do what the user just asked you to do — that's redundant and frustrating. Jump straight into action.
|
|
11352
12017
|
- USE YOUR KNOWLEDGE: You have broad, practical knowledge about technology, products, cooking, travel, finance, and countless other domains. When the user asks for recommendations, GIVE them — don't deflect to Reddit, YouTubers, or other sources. You know enough to recommend PC parts, suggest restaurants, pick a good laptop, or advise on most consumer decisions. Make a clear recommendation, explain your reasoning briefly, and then execute. If there's genuine ambiguity (e.g. AMD vs Intel is preference-dependent), state your pick and why, then ask only the questions that would actually change your recommendation. Never refuse a recommendation by claiming you're "not an expert" — the user chose to ask you, so help them.
|
|
11353
12018
|
- NEVER USE EMOJIS unless the user uses them first.`;
|
|
11354
|
-
const actionCtx = { tabManager, runtime };
|
|
12019
|
+
const actionCtx = { tabManager, runtime: runtime2 };
|
|
11355
12020
|
const contextualTools = pruneToolsForContext(
|
|
11356
12021
|
AGENT_TOOLS,
|
|
11357
12022
|
pageType,
|
|
@@ -11779,7 +12444,7 @@ function broadcastState(tabManager) {
|
|
|
11779
12444
|
const tabId = tabManager.getActiveTabId();
|
|
11780
12445
|
stateListener(getDevToolsPanelState(tabId));
|
|
11781
12446
|
}
|
|
11782
|
-
async function withDevToolsAction(
|
|
12447
|
+
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
11783
12448
|
const activityEntry = {
|
|
11784
12449
|
id: ++activityCounter,
|
|
11785
12450
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11796,7 +12461,7 @@ async function withDevToolsAction(runtime, tabManager, name, args, executor) {
|
|
|
11796
12461
|
broadcastState(tabManager);
|
|
11797
12462
|
const startTime = Date.now();
|
|
11798
12463
|
try {
|
|
11799
|
-
const result = await
|
|
12464
|
+
const result = await runtime2.runControlledAction({
|
|
11800
12465
|
source: "mcp",
|
|
11801
12466
|
name,
|
|
11802
12467
|
args,
|
|
@@ -11818,7 +12483,7 @@ async function withDevToolsAction(runtime, tabManager, name, args, executor) {
|
|
|
11818
12483
|
return asTextResponse$1(`Error: ${message}`);
|
|
11819
12484
|
}
|
|
11820
12485
|
}
|
|
11821
|
-
function registerDevTools(server, tabManager,
|
|
12486
|
+
function registerDevTools(server, tabManager, runtime2) {
|
|
11822
12487
|
server.registerTool(
|
|
11823
12488
|
"vessel_devtools_console_logs",
|
|
11824
12489
|
{
|
|
@@ -11830,16 +12495,16 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11830
12495
|
search: zod.z.string().optional().describe("Filter entries containing this text (case-insensitive)")
|
|
11831
12496
|
}
|
|
11832
12497
|
},
|
|
11833
|
-
async ({ level, limit, search }) => {
|
|
12498
|
+
async ({ level, limit, search: search2 }) => {
|
|
11834
12499
|
return withDevToolsAction(
|
|
11835
|
-
|
|
12500
|
+
runtime2,
|
|
11836
12501
|
tabManager,
|
|
11837
12502
|
"devtools_console_logs",
|
|
11838
|
-
{ level, limit, search },
|
|
12503
|
+
{ level, limit, search: search2 },
|
|
11839
12504
|
async () => {
|
|
11840
12505
|
const session = getOrCreateSession(tabManager);
|
|
11841
12506
|
await session.ensureConsoleDomain();
|
|
11842
|
-
const entries = session.getConsoleLogs({ level, limit, search });
|
|
12507
|
+
const entries = session.getConsoleLogs({ level, limit, search: search2 });
|
|
11843
12508
|
if (entries.length === 0) {
|
|
11844
12509
|
return "No console entries captured yet. Console monitoring is now active — new entries will be captured as they occur.";
|
|
11845
12510
|
}
|
|
@@ -11856,7 +12521,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11856
12521
|
},
|
|
11857
12522
|
async () => {
|
|
11858
12523
|
return withDevToolsAction(
|
|
11859
|
-
|
|
12524
|
+
runtime2,
|
|
11860
12525
|
tabManager,
|
|
11861
12526
|
"devtools_console_clear",
|
|
11862
12527
|
{},
|
|
@@ -11883,7 +12548,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11883
12548
|
},
|
|
11884
12549
|
async ({ url_pattern, method, status_min, status_max, limit }) => {
|
|
11885
12550
|
return withDevToolsAction(
|
|
11886
|
-
|
|
12551
|
+
runtime2,
|
|
11887
12552
|
tabManager,
|
|
11888
12553
|
"devtools_network_log",
|
|
11889
12554
|
{ url_pattern, method, status_min, status_max, limit },
|
|
@@ -11915,7 +12580,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11915
12580
|
},
|
|
11916
12581
|
async ({ request_id }) => {
|
|
11917
12582
|
return withDevToolsAction(
|
|
11918
|
-
|
|
12583
|
+
runtime2,
|
|
11919
12584
|
tabManager,
|
|
11920
12585
|
"devtools_network_response_body",
|
|
11921
12586
|
{ request_id },
|
|
@@ -11941,7 +12606,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11941
12606
|
},
|
|
11942
12607
|
async () => {
|
|
11943
12608
|
return withDevToolsAction(
|
|
11944
|
-
|
|
12609
|
+
runtime2,
|
|
11945
12610
|
tabManager,
|
|
11946
12611
|
"devtools_network_clear",
|
|
11947
12612
|
{},
|
|
@@ -11965,7 +12630,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11965
12630
|
},
|
|
11966
12631
|
async ({ selector, include_html }) => {
|
|
11967
12632
|
return withDevToolsAction(
|
|
11968
|
-
|
|
12633
|
+
runtime2,
|
|
11969
12634
|
tabManager,
|
|
11970
12635
|
"devtools_query_dom",
|
|
11971
12636
|
{ selector, include_html },
|
|
@@ -11996,7 +12661,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11996
12661
|
},
|
|
11997
12662
|
async ({ selector, properties }) => {
|
|
11998
12663
|
return withDevToolsAction(
|
|
11999
|
-
|
|
12664
|
+
runtime2,
|
|
12000
12665
|
tabManager,
|
|
12001
12666
|
"devtools_get_styles",
|
|
12002
12667
|
{ selector, properties },
|
|
@@ -12024,7 +12689,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
12024
12689
|
},
|
|
12025
12690
|
async ({ selector, attribute, value }) => {
|
|
12026
12691
|
return withDevToolsAction(
|
|
12027
|
-
|
|
12692
|
+
runtime2,
|
|
12028
12693
|
tabManager,
|
|
12029
12694
|
"devtools_modify_dom",
|
|
12030
12695
|
{ selector, attribute, value },
|
|
@@ -12046,7 +12711,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
12046
12711
|
},
|
|
12047
12712
|
async ({ expression }) => {
|
|
12048
12713
|
return withDevToolsAction(
|
|
12049
|
-
|
|
12714
|
+
runtime2,
|
|
12050
12715
|
tabManager,
|
|
12051
12716
|
"devtools_execute_js",
|
|
12052
12717
|
{ expression: expression.slice(0, 200) },
|
|
@@ -12074,7 +12739,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12074
12739
|
},
|
|
12075
12740
|
async ({ type }) => {
|
|
12076
12741
|
return withDevToolsAction(
|
|
12077
|
-
|
|
12742
|
+
runtime2,
|
|
12078
12743
|
tabManager,
|
|
12079
12744
|
"devtools_get_storage",
|
|
12080
12745
|
{ type },
|
|
@@ -12103,7 +12768,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12103
12768
|
},
|
|
12104
12769
|
async ({ type, key, value }) => {
|
|
12105
12770
|
return withDevToolsAction(
|
|
12106
|
-
|
|
12771
|
+
runtime2,
|
|
12107
12772
|
tabManager,
|
|
12108
12773
|
"devtools_set_storage",
|
|
12109
12774
|
{ type, key, value: value ? value.slice(0, 100) : null },
|
|
@@ -12122,7 +12787,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12122
12787
|
},
|
|
12123
12788
|
async () => {
|
|
12124
12789
|
return withDevToolsAction(
|
|
12125
|
-
|
|
12790
|
+
runtime2,
|
|
12126
12791
|
tabManager,
|
|
12127
12792
|
"devtools_performance",
|
|
12128
12793
|
{},
|
|
@@ -12146,7 +12811,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12146
12811
|
},
|
|
12147
12812
|
async ({ type, limit }) => {
|
|
12148
12813
|
return withDevToolsAction(
|
|
12149
|
-
|
|
12814
|
+
runtime2,
|
|
12150
12815
|
tabManager,
|
|
12151
12816
|
"devtools_get_errors",
|
|
12152
12817
|
{ type, limit },
|
|
@@ -12170,7 +12835,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12170
12835
|
},
|
|
12171
12836
|
async () => {
|
|
12172
12837
|
return withDevToolsAction(
|
|
12173
|
-
|
|
12838
|
+
runtime2,
|
|
12174
12839
|
tabManager,
|
|
12175
12840
|
"devtools_clear_errors",
|
|
12176
12841
|
{},
|
|
@@ -12184,6 +12849,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12184
12849
|
);
|
|
12185
12850
|
}
|
|
12186
12851
|
let httpServer = null;
|
|
12852
|
+
let mcpAuthToken = null;
|
|
12187
12853
|
function asTextResponse(text) {
|
|
12188
12854
|
return { content: [{ type: "text", text }] };
|
|
12189
12855
|
}
|
|
@@ -13121,9 +13787,9 @@ async function getPostActionState(tabManager, name) {
|
|
|
13121
13787
|
}
|
|
13122
13788
|
return "";
|
|
13123
13789
|
}
|
|
13124
|
-
async function withAction(
|
|
13790
|
+
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
13125
13791
|
try {
|
|
13126
|
-
const result = await
|
|
13792
|
+
const result = await runtime2.runControlledAction({
|
|
13127
13793
|
source: "mcp",
|
|
13128
13794
|
name,
|
|
13129
13795
|
args,
|
|
@@ -13132,7 +13798,7 @@ async function withAction(runtime, tabManager, name, args, executor) {
|
|
|
13132
13798
|
executor
|
|
13133
13799
|
});
|
|
13134
13800
|
const stateInfo = await getPostActionState(tabManager, name);
|
|
13135
|
-
const flowCtx =
|
|
13801
|
+
const flowCtx = runtime2.getFlowContext();
|
|
13136
13802
|
return asTextResponse(result + stateInfo + flowCtx);
|
|
13137
13803
|
} catch (error) {
|
|
13138
13804
|
return asTextResponse(
|
|
@@ -13426,6 +14092,7 @@ async function submitForm(wc, index, selector) {
|
|
|
13426
14092
|
if (formInfo.params) {
|
|
13427
14093
|
url.search = formInfo.params;
|
|
13428
14094
|
}
|
|
14095
|
+
assertSafeURL(url.toString());
|
|
13429
14096
|
wc.loadURL(url.toString());
|
|
13430
14097
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13431
14098
|
const afterUrl = wc.getURL();
|
|
@@ -13567,7 +14234,7 @@ async function captureScreenshotPayload(wc) {
|
|
|
13567
14234
|
}
|
|
13568
14235
|
return { ok: false, error: "page image was empty after 3 attempts" };
|
|
13569
14236
|
}
|
|
13570
|
-
function registerTools(server, tabManager,
|
|
14237
|
+
function registerTools(server, tabManager, runtime2) {
|
|
13571
14238
|
server.registerPrompt(
|
|
13572
14239
|
"vessel-supervisor-brief",
|
|
13573
14240
|
{
|
|
@@ -13575,7 +14242,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13575
14242
|
description: "A reusable prompt for reviewing the current Vessel runtime state."
|
|
13576
14243
|
},
|
|
13577
14244
|
async () => {
|
|
13578
|
-
const state2 =
|
|
14245
|
+
const state2 = runtime2.getState();
|
|
13579
14246
|
const activeTab = getActiveTabSummary(tabManager);
|
|
13580
14247
|
return asPromptResponse(
|
|
13581
14248
|
[
|
|
@@ -13602,7 +14269,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13602
14269
|
contents: [
|
|
13603
14270
|
{
|
|
13604
14271
|
uri: "vessel://runtime/state",
|
|
13605
|
-
text: JSON.stringify(
|
|
14272
|
+
text: JSON.stringify(runtime2.getState(), null, 2)
|
|
13606
14273
|
}
|
|
13607
14274
|
]
|
|
13608
14275
|
})
|
|
@@ -13718,7 +14385,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13718
14385
|
}
|
|
13719
14386
|
},
|
|
13720
14387
|
async ({ text, stream_id, mode, kind, title }) => {
|
|
13721
|
-
const entry =
|
|
14388
|
+
const entry = runtime2.publishTranscript({
|
|
13722
14389
|
source: "mcp",
|
|
13723
14390
|
text,
|
|
13724
14391
|
streamId: stream_id,
|
|
@@ -13748,7 +14415,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13748
14415
|
description: "Clear the in-browser transcript monitor state."
|
|
13749
14416
|
},
|
|
13750
14417
|
async () => {
|
|
13751
|
-
|
|
14418
|
+
runtime2.clearTranscript();
|
|
13752
14419
|
return asTextResponse("Cleared browser transcript monitor.");
|
|
13753
14420
|
}
|
|
13754
14421
|
);
|
|
@@ -13888,7 +14555,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13888
14555
|
`Navigation blocked: ${url} returned ${preCheck.detail || "dead link"}. Try a different URL or go back and choose another link.`
|
|
13889
14556
|
);
|
|
13890
14557
|
}
|
|
13891
|
-
return withAction(
|
|
14558
|
+
return withAction(runtime2, tabManager, "navigate", { url }, async () => {
|
|
13892
14559
|
const id = tabManager.getActiveTabId();
|
|
13893
14560
|
tabManager.navigateTab(id, url);
|
|
13894
14561
|
const { httpStatus } = await waitForLoadWithStatus(
|
|
@@ -13918,7 +14585,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13918
14585
|
return asTextResponse("Error: No active tab");
|
|
13919
14586
|
}
|
|
13920
14587
|
return withAction(
|
|
13921
|
-
|
|
14588
|
+
runtime2,
|
|
13922
14589
|
tabManager,
|
|
13923
14590
|
"set_ad_blocking",
|
|
13924
14591
|
{ enabled, tabId, match, reload },
|
|
@@ -14007,7 +14674,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14007
14674
|
async () => {
|
|
14008
14675
|
const tab = tabManager.getActiveTab();
|
|
14009
14676
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14010
|
-
return withAction(
|
|
14677
|
+
return withAction(runtime2, tabManager, "go_back", {}, async () => {
|
|
14011
14678
|
if (!tab.canGoBack()) {
|
|
14012
14679
|
return "No previous page in history";
|
|
14013
14680
|
}
|
|
@@ -14028,7 +14695,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14028
14695
|
async () => {
|
|
14029
14696
|
const tab = tabManager.getActiveTab();
|
|
14030
14697
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14031
|
-
return withAction(
|
|
14698
|
+
return withAction(runtime2, tabManager, "go_forward", {}, async () => {
|
|
14032
14699
|
if (!tab.canGoForward()) {
|
|
14033
14700
|
return "No forward page in history";
|
|
14034
14701
|
}
|
|
@@ -14049,7 +14716,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14049
14716
|
async () => {
|
|
14050
14717
|
const tab = tabManager.getActiveTab();
|
|
14051
14718
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14052
|
-
return withAction(
|
|
14719
|
+
return withAction(runtime2, tabManager, "reload", {}, async () => {
|
|
14053
14720
|
tabManager.reloadTab(tabManager.getActiveTabId());
|
|
14054
14721
|
await waitForLoad(tab.view.webContents);
|
|
14055
14722
|
return `Reloaded ${tab.view.webContents.getURL()}`;
|
|
@@ -14070,7 +14737,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14070
14737
|
const tab = tabManager.getActiveTab();
|
|
14071
14738
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14072
14739
|
return withAction(
|
|
14073
|
-
|
|
14740
|
+
runtime2,
|
|
14074
14741
|
tabManager,
|
|
14075
14742
|
"click",
|
|
14076
14743
|
{ index, selector },
|
|
@@ -14099,7 +14766,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14099
14766
|
const tab = tabManager.getActiveTab();
|
|
14100
14767
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14101
14768
|
return withAction(
|
|
14102
|
-
|
|
14769
|
+
runtime2,
|
|
14103
14770
|
tabManager,
|
|
14104
14771
|
"hover",
|
|
14105
14772
|
{ index, selector },
|
|
@@ -14128,7 +14795,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14128
14795
|
const tab = tabManager.getActiveTab();
|
|
14129
14796
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14130
14797
|
return withAction(
|
|
14131
|
-
|
|
14798
|
+
runtime2,
|
|
14132
14799
|
tabManager,
|
|
14133
14800
|
"focus",
|
|
14134
14801
|
{ index, selector },
|
|
@@ -14242,7 +14909,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14242
14909
|
const tab = tabManager.getActiveTab();
|
|
14243
14910
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14244
14911
|
return withAction(
|
|
14245
|
-
|
|
14912
|
+
runtime2,
|
|
14246
14913
|
tabManager,
|
|
14247
14914
|
"type",
|
|
14248
14915
|
{ index, selector, text, mode },
|
|
@@ -14281,7 +14948,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14281
14948
|
const tab = tabManager.getActiveTab();
|
|
14282
14949
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14283
14950
|
return withAction(
|
|
14284
|
-
|
|
14951
|
+
runtime2,
|
|
14285
14952
|
tabManager,
|
|
14286
14953
|
"type_text",
|
|
14287
14954
|
{ index, selector, text, mode },
|
|
@@ -14318,7 +14985,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14318
14985
|
const tab = tabManager.getActiveTab();
|
|
14319
14986
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14320
14987
|
return withAction(
|
|
14321
|
-
|
|
14988
|
+
runtime2,
|
|
14322
14989
|
tabManager,
|
|
14323
14990
|
"select_option",
|
|
14324
14991
|
{ index, selector, label, value },
|
|
@@ -14340,7 +15007,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14340
15007
|
const tab = tabManager.getActiveTab();
|
|
14341
15008
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14342
15009
|
return withAction(
|
|
14343
|
-
|
|
15010
|
+
runtime2,
|
|
14344
15011
|
tabManager,
|
|
14345
15012
|
"submit_form",
|
|
14346
15013
|
{ index, selector },
|
|
@@ -14373,7 +15040,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14373
15040
|
const tab = tabManager.getActiveTab();
|
|
14374
15041
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14375
15042
|
return withAction(
|
|
14376
|
-
|
|
15043
|
+
runtime2,
|
|
14377
15044
|
tabManager,
|
|
14378
15045
|
"press_key",
|
|
14379
15046
|
{ key, index, selector },
|
|
@@ -14409,7 +15076,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14409
15076
|
const tab = tabManager.getActiveTab();
|
|
14410
15077
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14411
15078
|
return withAction(
|
|
14412
|
-
|
|
15079
|
+
runtime2,
|
|
14413
15080
|
tabManager,
|
|
14414
15081
|
"scroll",
|
|
14415
15082
|
{ direction, amount },
|
|
@@ -14432,7 +15099,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14432
15099
|
const tab = tabManager.getActiveTab();
|
|
14433
15100
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14434
15101
|
return withAction(
|
|
14435
|
-
|
|
15102
|
+
runtime2,
|
|
14436
15103
|
tabManager,
|
|
14437
15104
|
"dismiss_popup",
|
|
14438
15105
|
{},
|
|
@@ -14455,7 +15122,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14455
15122
|
const tab = tabManager.getActiveTab();
|
|
14456
15123
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14457
15124
|
return withAction(
|
|
14458
|
-
|
|
15125
|
+
runtime2,
|
|
14459
15126
|
tabManager,
|
|
14460
15127
|
"clear_overlays",
|
|
14461
15128
|
{ strategy: strategy || "auto" },
|
|
@@ -14481,7 +15148,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14481
15148
|
const tab = tabManager.getActiveTab();
|
|
14482
15149
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14483
15150
|
return withAction(
|
|
14484
|
-
|
|
15151
|
+
runtime2,
|
|
14485
15152
|
tabManager,
|
|
14486
15153
|
"wait_for",
|
|
14487
15154
|
{ text, selector, timeoutMs },
|
|
@@ -14498,7 +15165,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14498
15165
|
url: zod.z.string().optional().describe("URL to open (defaults to about:blank)")
|
|
14499
15166
|
}
|
|
14500
15167
|
},
|
|
14501
|
-
async ({ url }) => withAction(
|
|
15168
|
+
async ({ url }) => withAction(runtime2, tabManager, "create_tab", { url }, async () => {
|
|
14502
15169
|
const id = tabManager.createTab(url || "about:blank");
|
|
14503
15170
|
const tab = tabManager.getActiveTab();
|
|
14504
15171
|
if (tab) {
|
|
@@ -14518,7 +15185,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14518
15185
|
}
|
|
14519
15186
|
},
|
|
14520
15187
|
async ({ tabId, match }) => withAction(
|
|
14521
|
-
|
|
15188
|
+
runtime2,
|
|
14522
15189
|
tabManager,
|
|
14523
15190
|
"switch_tab",
|
|
14524
15191
|
{ tabId, match },
|
|
@@ -14541,7 +15208,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14541
15208
|
tabId: zod.z.string().describe("The tab ID to close")
|
|
14542
15209
|
}
|
|
14543
15210
|
},
|
|
14544
|
-
async ({ tabId }) => withAction(
|
|
15211
|
+
async ({ tabId }) => withAction(runtime2, tabManager, "close_tab", { tabId }, async () => {
|
|
14545
15212
|
tabManager.closeTab(tabId);
|
|
14546
15213
|
return `Closed tab ${tabId}`;
|
|
14547
15214
|
})
|
|
@@ -14557,12 +15224,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14557
15224
|
}
|
|
14558
15225
|
},
|
|
14559
15226
|
async ({ name, note }) => withAction(
|
|
14560
|
-
|
|
15227
|
+
runtime2,
|
|
14561
15228
|
tabManager,
|
|
14562
15229
|
"create_checkpoint",
|
|
14563
15230
|
{ name, note },
|
|
14564
15231
|
async () => {
|
|
14565
|
-
const checkpoint =
|
|
15232
|
+
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
14566
15233
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
14567
15234
|
}
|
|
14568
15235
|
)
|
|
@@ -14578,12 +15245,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14578
15245
|
}
|
|
14579
15246
|
},
|
|
14580
15247
|
async ({ name, note }) => withAction(
|
|
14581
|
-
|
|
15248
|
+
runtime2,
|
|
14582
15249
|
tabManager,
|
|
14583
15250
|
"create_checkpoint",
|
|
14584
15251
|
{ name, note },
|
|
14585
15252
|
async () => {
|
|
14586
|
-
const checkpoint =
|
|
15253
|
+
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
14587
15254
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
14588
15255
|
}
|
|
14589
15256
|
)
|
|
@@ -14599,17 +15266,17 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14599
15266
|
}
|
|
14600
15267
|
},
|
|
14601
15268
|
async ({ checkpointId, name }) => withAction(
|
|
14602
|
-
|
|
15269
|
+
runtime2,
|
|
14603
15270
|
tabManager,
|
|
14604
15271
|
"restore_checkpoint",
|
|
14605
15272
|
{ checkpointId, name },
|
|
14606
15273
|
async () => {
|
|
14607
|
-
const state2 =
|
|
15274
|
+
const state2 = runtime2.getState();
|
|
14608
15275
|
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
14609
15276
|
if (!checkpoint) {
|
|
14610
15277
|
return "Error: No matching checkpoint found";
|
|
14611
15278
|
}
|
|
14612
|
-
|
|
15279
|
+
runtime2.restoreCheckpoint(checkpoint.id);
|
|
14613
15280
|
return `Restored checkpoint ${checkpoint.name}`;
|
|
14614
15281
|
}
|
|
14615
15282
|
)
|
|
@@ -14625,17 +15292,17 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14625
15292
|
}
|
|
14626
15293
|
},
|
|
14627
15294
|
async ({ checkpointId, name }) => withAction(
|
|
14628
|
-
|
|
15295
|
+
runtime2,
|
|
14629
15296
|
tabManager,
|
|
14630
15297
|
"restore_checkpoint",
|
|
14631
15298
|
{ checkpointId, name },
|
|
14632
15299
|
async () => {
|
|
14633
|
-
const state2 =
|
|
15300
|
+
const state2 = runtime2.getState();
|
|
14634
15301
|
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
14635
15302
|
if (!checkpoint) {
|
|
14636
15303
|
return "Error: No matching checkpoint found";
|
|
14637
15304
|
}
|
|
14638
|
-
|
|
15305
|
+
runtime2.restoreCheckpoint(checkpoint.id);
|
|
14639
15306
|
return `Restored checkpoint ${checkpoint.name}`;
|
|
14640
15307
|
}
|
|
14641
15308
|
)
|
|
@@ -14649,7 +15316,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14649
15316
|
name: zod.z.string().describe("Session name such as github-logged-in")
|
|
14650
15317
|
}
|
|
14651
15318
|
},
|
|
14652
|
-
async ({ name }) => withAction(
|
|
15319
|
+
async ({ name }) => withAction(runtime2, tabManager, "save_session", { name }, async () => {
|
|
14653
15320
|
const saved = await saveNamedSession(
|
|
14654
15321
|
tabManager,
|
|
14655
15322
|
name
|
|
@@ -14666,7 +15333,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14666
15333
|
name: zod.z.string().describe("Previously saved session name")
|
|
14667
15334
|
}
|
|
14668
15335
|
},
|
|
14669
|
-
async ({ name }) => withAction(
|
|
15336
|
+
async ({ name }) => withAction(runtime2, tabManager, "load_session", { name }, async () => {
|
|
14670
15337
|
const loaded = await loadNamedSession(
|
|
14671
15338
|
tabManager,
|
|
14672
15339
|
name
|
|
@@ -14680,7 +15347,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14680
15347
|
title: "List Sessions",
|
|
14681
15348
|
description: "List previously saved named browser sessions with cookie and storage counts."
|
|
14682
15349
|
},
|
|
14683
|
-
async () => withAction(
|
|
15350
|
+
async () => withAction(runtime2, tabManager, "list_sessions", {}, async () => {
|
|
14684
15351
|
const sessions2 = listNamedSessions();
|
|
14685
15352
|
if (sessions2.length === 0) return "No saved sessions";
|
|
14686
15353
|
return sessions2.map(
|
|
@@ -14698,7 +15365,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14698
15365
|
}
|
|
14699
15366
|
},
|
|
14700
15367
|
async ({ name }) => withAction(
|
|
14701
|
-
|
|
15368
|
+
runtime2,
|
|
14702
15369
|
tabManager,
|
|
14703
15370
|
"delete_session",
|
|
14704
15371
|
{ name },
|
|
@@ -14785,7 +15452,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14785
15452
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14786
15453
|
const normalizedText = normalizeLooseString(text);
|
|
14787
15454
|
return withAction(
|
|
14788
|
-
|
|
15455
|
+
runtime2,
|
|
14789
15456
|
tabManager,
|
|
14790
15457
|
"highlight",
|
|
14791
15458
|
{
|
|
@@ -14834,7 +15501,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14834
15501
|
const tab = tabManager.getActiveTab();
|
|
14835
15502
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14836
15503
|
return withAction(
|
|
14837
|
-
|
|
15504
|
+
runtime2,
|
|
14838
15505
|
tabManager,
|
|
14839
15506
|
"clear_highlights",
|
|
14840
15507
|
{},
|
|
@@ -14859,7 +15526,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14859
15526
|
}
|
|
14860
15527
|
},
|
|
14861
15528
|
async ({ url }) => {
|
|
14862
|
-
const state2 = getState$
|
|
15529
|
+
const state2 = getState$2();
|
|
14863
15530
|
const activeTab = tabManager.getActiveTab();
|
|
14864
15531
|
const activeUrl = activeTab ? normalizeUrl(activeTab.view.webContents.getURL()) : null;
|
|
14865
15532
|
const activeSavedHighlights = activeUrl ? state2.highlights.filter((highlight) => highlight.url === activeUrl) : [];
|
|
@@ -14990,7 +15657,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14990
15657
|
},
|
|
14991
15658
|
async ({ name, summary }) => {
|
|
14992
15659
|
return withAction(
|
|
14993
|
-
|
|
15660
|
+
runtime2,
|
|
14994
15661
|
tabManager,
|
|
14995
15662
|
"create_bookmark_folder",
|
|
14996
15663
|
{ name, summary },
|
|
@@ -15052,7 +15719,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15052
15719
|
on_duplicate
|
|
15053
15720
|
}) => {
|
|
15054
15721
|
return withAction(
|
|
15055
|
-
|
|
15722
|
+
runtime2,
|
|
15056
15723
|
tabManager,
|
|
15057
15724
|
"save_bookmark",
|
|
15058
15725
|
{
|
|
@@ -15131,7 +15798,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15131
15798
|
},
|
|
15132
15799
|
async ({ folder_id, folder_name }) => {
|
|
15133
15800
|
return withAction(
|
|
15134
|
-
|
|
15801
|
+
runtime2,
|
|
15135
15802
|
tabManager,
|
|
15136
15803
|
"list_bookmarks",
|
|
15137
15804
|
{ folder_id, folder_name },
|
|
@@ -15196,7 +15863,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15196
15863
|
},
|
|
15197
15864
|
async (args) => {
|
|
15198
15865
|
return withAction(
|
|
15199
|
-
|
|
15866
|
+
runtime2,
|
|
15200
15867
|
tabManager,
|
|
15201
15868
|
"organize_bookmark",
|
|
15202
15869
|
args,
|
|
@@ -15263,7 +15930,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15263
15930
|
},
|
|
15264
15931
|
async ({ query }) => {
|
|
15265
15932
|
return withAction(
|
|
15266
|
-
|
|
15933
|
+
runtime2,
|
|
15267
15934
|
tabManager,
|
|
15268
15935
|
"search_bookmarks",
|
|
15269
15936
|
{ query },
|
|
@@ -15294,7 +15961,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15294
15961
|
},
|
|
15295
15962
|
async ({ bookmark_id }) => {
|
|
15296
15963
|
return withAction(
|
|
15297
|
-
|
|
15964
|
+
runtime2,
|
|
15298
15965
|
tabManager,
|
|
15299
15966
|
"remove_bookmark",
|
|
15300
15967
|
{ bookmark_id },
|
|
@@ -15327,7 +15994,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15327
15994
|
},
|
|
15328
15995
|
async ({ bookmark_id, url, title, index, selector, note }) => {
|
|
15329
15996
|
return withAction(
|
|
15330
|
-
|
|
15997
|
+
runtime2,
|
|
15331
15998
|
tabManager,
|
|
15332
15999
|
"archive_bookmark",
|
|
15333
16000
|
{ bookmark_id, url, title, index, selector, note },
|
|
@@ -15397,7 +16064,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15397
16064
|
},
|
|
15398
16065
|
async ({ bookmark_id, new_tab }) => {
|
|
15399
16066
|
return withAction(
|
|
15400
|
-
|
|
16067
|
+
runtime2,
|
|
15401
16068
|
tabManager,
|
|
15402
16069
|
"open_bookmark",
|
|
15403
16070
|
{ bookmark_id, new_tab },
|
|
@@ -15440,7 +16107,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15440
16107
|
},
|
|
15441
16108
|
async ({ folder_id }) => {
|
|
15442
16109
|
return withAction(
|
|
15443
|
-
|
|
16110
|
+
runtime2,
|
|
15444
16111
|
tabManager,
|
|
15445
16112
|
"remove_bookmark_folder",
|
|
15446
16113
|
{ folder_id },
|
|
@@ -15466,7 +16133,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15466
16133
|
},
|
|
15467
16134
|
async ({ folder_id, new_name, summary }) => {
|
|
15468
16135
|
return withAction(
|
|
15469
|
-
|
|
16136
|
+
runtime2,
|
|
15470
16137
|
tabManager,
|
|
15471
16138
|
"rename_bookmark_folder",
|
|
15472
16139
|
{ folder_id, new_name, summary },
|
|
@@ -15503,7 +16170,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15503
16170
|
},
|
|
15504
16171
|
async ({ title, body, folder, tags }) => {
|
|
15505
16172
|
return withAction(
|
|
15506
|
-
|
|
16173
|
+
runtime2,
|
|
15507
16174
|
tabManager,
|
|
15508
16175
|
"memory_note_create",
|
|
15509
16176
|
{ title, folder, tags },
|
|
@@ -15527,7 +16194,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15527
16194
|
},
|
|
15528
16195
|
async ({ note_path, content, heading }) => {
|
|
15529
16196
|
return withAction(
|
|
15530
|
-
|
|
16197
|
+
runtime2,
|
|
15531
16198
|
tabManager,
|
|
15532
16199
|
"memory_note_append",
|
|
15533
16200
|
{ note_path, heading },
|
|
@@ -15554,7 +16221,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15554
16221
|
},
|
|
15555
16222
|
async ({ folder, limit }) => {
|
|
15556
16223
|
return withAction(
|
|
15557
|
-
|
|
16224
|
+
runtime2,
|
|
15558
16225
|
tabManager,
|
|
15559
16226
|
"memory_note_list",
|
|
15560
16227
|
{ folder, limit },
|
|
@@ -15584,7 +16251,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15584
16251
|
},
|
|
15585
16252
|
async ({ query, folder, tags, limit }) => {
|
|
15586
16253
|
return withAction(
|
|
15587
|
-
|
|
16254
|
+
runtime2,
|
|
15588
16255
|
tabManager,
|
|
15589
16256
|
"memory_note_search",
|
|
15590
16257
|
{ query, folder, tags, limit },
|
|
@@ -15617,7 +16284,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15617
16284
|
const tab = tabManager.getActiveTab();
|
|
15618
16285
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15619
16286
|
return withAction(
|
|
15620
|
-
|
|
16287
|
+
runtime2,
|
|
15621
16288
|
tabManager,
|
|
15622
16289
|
"memory_page_capture",
|
|
15623
16290
|
{ title, folder, tags },
|
|
@@ -15654,7 +16321,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15654
16321
|
},
|
|
15655
16322
|
async ({ bookmark_id, note_path, title, folder, note, tags }) => {
|
|
15656
16323
|
return withAction(
|
|
15657
|
-
|
|
16324
|
+
runtime2,
|
|
15658
16325
|
tabManager,
|
|
15659
16326
|
"memory_link_bookmark",
|
|
15660
16327
|
{ bookmark_id, note_path, title, folder, tags },
|
|
@@ -15693,7 +16360,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15693
16360
|
async ({ goal, steps }) => {
|
|
15694
16361
|
const normalizedSteps = coerceStringArray(steps) ?? [];
|
|
15695
16362
|
const tab = tabManager.getActiveTab();
|
|
15696
|
-
const flow =
|
|
16363
|
+
const flow = runtime2.startFlow(
|
|
15697
16364
|
goal,
|
|
15698
16365
|
normalizedSteps,
|
|
15699
16366
|
tab?.view.webContents.getURL()
|
|
@@ -15714,9 +16381,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15714
16381
|
}
|
|
15715
16382
|
},
|
|
15716
16383
|
async ({ detail }) => {
|
|
15717
|
-
const flow =
|
|
16384
|
+
const flow = runtime2.advanceFlow(detail);
|
|
15718
16385
|
if (!flow) return asTextResponse("No active flow to advance");
|
|
15719
|
-
const ctx =
|
|
16386
|
+
const ctx = runtime2.getFlowContext();
|
|
15720
16387
|
return asTextResponse(`Step completed.${ctx}`);
|
|
15721
16388
|
}
|
|
15722
16389
|
);
|
|
@@ -15727,9 +16394,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15727
16394
|
description: "Check the current workflow progress."
|
|
15728
16395
|
},
|
|
15729
16396
|
async () => {
|
|
15730
|
-
const flow =
|
|
16397
|
+
const flow = runtime2.getFlowState();
|
|
15731
16398
|
if (!flow) return asTextResponse("No active workflow.");
|
|
15732
|
-
return asTextResponse(
|
|
16399
|
+
return asTextResponse(runtime2.getFlowContext());
|
|
15733
16400
|
}
|
|
15734
16401
|
);
|
|
15735
16402
|
server.registerTool(
|
|
@@ -15739,7 +16406,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15739
16406
|
description: "Clear the active workflow tracker."
|
|
15740
16407
|
},
|
|
15741
16408
|
async () => {
|
|
15742
|
-
|
|
16409
|
+
runtime2.clearFlow();
|
|
15743
16410
|
return asTextResponse("Workflow ended.");
|
|
15744
16411
|
}
|
|
15745
16412
|
);
|
|
@@ -15768,7 +16435,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15768
16435
|
suggestions.push(`Page: ${page.title || "(untitled)"}`);
|
|
15769
16436
|
suggestions.push(`URL: ${page.url}`);
|
|
15770
16437
|
suggestions.push("");
|
|
15771
|
-
const flowCtx =
|
|
16438
|
+
const flowCtx = runtime2.getFlowContext();
|
|
15772
16439
|
if (flowCtx) {
|
|
15773
16440
|
suggestions.push(flowCtx);
|
|
15774
16441
|
suggestions.push("");
|
|
@@ -15868,7 +16535,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15868
16535
|
const tab = tabManager.getActiveTab();
|
|
15869
16536
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15870
16537
|
return withAction(
|
|
15871
|
-
|
|
16538
|
+
runtime2,
|
|
15872
16539
|
tabManager,
|
|
15873
16540
|
"fill_form",
|
|
15874
16541
|
{ fieldCount: fields.length, submit },
|
|
@@ -15925,7 +16592,7 @@ ${results.join("\n")}`;
|
|
|
15925
16592
|
const tab = tabManager.getActiveTab();
|
|
15926
16593
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15927
16594
|
return withAction(
|
|
15928
|
-
|
|
16595
|
+
runtime2,
|
|
15929
16596
|
tabManager,
|
|
15930
16597
|
"login",
|
|
15931
16598
|
{ url, username: username.slice(0, 3) + "***" },
|
|
@@ -16030,7 +16697,7 @@ ${steps.join("\n")}`;
|
|
|
16030
16697
|
`Error: "${query}" looks like a button label, not a search query. Use the click tool to interact with this element instead.`
|
|
16031
16698
|
);
|
|
16032
16699
|
}
|
|
16033
|
-
return withAction(
|
|
16700
|
+
return withAction(runtime2, tabManager, "search", { query }, async () => {
|
|
16034
16701
|
const wc = tab.view.webContents;
|
|
16035
16702
|
const searchSel = selector || await wc.executeJavaScript(`
|
|
16036
16703
|
(function() {
|
|
@@ -16084,7 +16751,7 @@ ${steps.join("\n")}`;
|
|
|
16084
16751
|
const tab = tabManager.getActiveTab();
|
|
16085
16752
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16086
16753
|
return withAction(
|
|
16087
|
-
|
|
16754
|
+
runtime2,
|
|
16088
16755
|
tabManager,
|
|
16089
16756
|
"paginate",
|
|
16090
16757
|
{ direction },
|
|
@@ -16135,7 +16802,7 @@ ${steps.join("\n")}`;
|
|
|
16135
16802
|
const tab = tabManager.getActiveTab();
|
|
16136
16803
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16137
16804
|
return withAction(
|
|
16138
|
-
|
|
16805
|
+
runtime2,
|
|
16139
16806
|
tabManager,
|
|
16140
16807
|
"vessel_accept_cookies",
|
|
16141
16808
|
{},
|
|
@@ -16193,7 +16860,7 @@ ${steps.join("\n")}`;
|
|
|
16193
16860
|
const tab = tabManager.getActiveTab();
|
|
16194
16861
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16195
16862
|
return withAction(
|
|
16196
|
-
|
|
16863
|
+
runtime2,
|
|
16197
16864
|
tabManager,
|
|
16198
16865
|
"vessel_extract_table",
|
|
16199
16866
|
{ index, selector: rawSelector },
|
|
@@ -16248,7 +16915,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
16248
16915
|
const tab = tabManager.getActiveTab();
|
|
16249
16916
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16250
16917
|
return withAction(
|
|
16251
|
-
|
|
16918
|
+
runtime2,
|
|
16252
16919
|
tabManager,
|
|
16253
16920
|
"vessel_scroll_to_element",
|
|
16254
16921
|
{ index, selector: rawSelector, position },
|
|
@@ -16306,7 +16973,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
16306
16973
|
const tab = tabManager.getActiveTab();
|
|
16307
16974
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16308
16975
|
return withAction(
|
|
16309
|
-
|
|
16976
|
+
runtime2,
|
|
16310
16977
|
tabManager,
|
|
16311
16978
|
"vessel_wait_for_navigation",
|
|
16312
16979
|
{ timeoutMs },
|
|
@@ -16357,7 +17024,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
16357
17024
|
inputSchema: zod.z.object({})
|
|
16358
17025
|
},
|
|
16359
17026
|
async () => {
|
|
16360
|
-
const m =
|
|
17027
|
+
const m = runtime2.getMetrics();
|
|
16361
17028
|
const lines = [
|
|
16362
17029
|
`Session Metrics:`,
|
|
16363
17030
|
` Total actions: ${m.totalActions}`,
|
|
@@ -16517,16 +17184,16 @@ async function resolveSelector(wc, index, selector) {
|
|
|
16517
17184
|
`
|
|
16518
17185
|
);
|
|
16519
17186
|
}
|
|
16520
|
-
function createMcpServer(tabManager,
|
|
17187
|
+
function createMcpServer(tabManager, runtime2) {
|
|
16521
17188
|
const server = new mcp_js.McpServer({
|
|
16522
17189
|
name: "vessel-browser",
|
|
16523
17190
|
version: "0.1.0"
|
|
16524
17191
|
});
|
|
16525
|
-
registerTools(server, tabManager,
|
|
16526
|
-
registerDevTools(server, tabManager,
|
|
17192
|
+
registerTools(server, tabManager, runtime2);
|
|
17193
|
+
registerDevTools(server, tabManager, runtime2);
|
|
16527
17194
|
return server;
|
|
16528
17195
|
}
|
|
16529
|
-
function startMcpServer(tabManager,
|
|
17196
|
+
function startMcpServer(tabManager, runtime2, port) {
|
|
16530
17197
|
setMcpHealth({
|
|
16531
17198
|
configuredPort: port,
|
|
16532
17199
|
activePort: null,
|
|
@@ -16534,6 +17201,7 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16534
17201
|
status: "starting",
|
|
16535
17202
|
message: `Starting MCP server on port ${port}.`
|
|
16536
17203
|
});
|
|
17204
|
+
mcpAuthToken = crypto$1.randomBytes(32).toString("hex");
|
|
16537
17205
|
return new Promise((resolve) => {
|
|
16538
17206
|
const server = http.createServer(async (req, res) => {
|
|
16539
17207
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
@@ -16542,22 +17210,28 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16542
17210
|
res.end("Not found");
|
|
16543
17211
|
return;
|
|
16544
17212
|
}
|
|
16545
|
-
res.setHeader("Access-Control-Allow-Origin", "
|
|
17213
|
+
res.setHeader("Access-Control-Allow-Origin", "null");
|
|
16546
17214
|
res.setHeader(
|
|
16547
17215
|
"Access-Control-Allow-Methods",
|
|
16548
17216
|
"POST, GET, DELETE, OPTIONS"
|
|
16549
17217
|
);
|
|
16550
17218
|
res.setHeader(
|
|
16551
17219
|
"Access-Control-Allow-Headers",
|
|
16552
|
-
"Content-Type, mcp-session-id"
|
|
17220
|
+
"Content-Type, mcp-session-id, Authorization"
|
|
16553
17221
|
);
|
|
16554
17222
|
if (req.method === "OPTIONS") {
|
|
16555
17223
|
res.writeHead(204);
|
|
16556
17224
|
res.end();
|
|
16557
17225
|
return;
|
|
16558
17226
|
}
|
|
17227
|
+
const authHeader = req.headers.authorization;
|
|
17228
|
+
if (!authHeader || authHeader !== `Bearer ${mcpAuthToken}`) {
|
|
17229
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
17230
|
+
res.end(JSON.stringify({ error: "Unauthorized — missing or invalid bearer token" }));
|
|
17231
|
+
return;
|
|
17232
|
+
}
|
|
16559
17233
|
try {
|
|
16560
|
-
const mcpServer = createMcpServer(tabManager,
|
|
17234
|
+
const mcpServer = createMcpServer(tabManager, runtime2);
|
|
16561
17235
|
const transport = new streamableHttp_js.StreamableHTTPServerTransport({
|
|
16562
17236
|
sessionIdGenerator: void 0
|
|
16563
17237
|
});
|
|
@@ -16599,6 +17273,7 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16599
17273
|
configuredPort: port,
|
|
16600
17274
|
activePort: null,
|
|
16601
17275
|
endpoint: null,
|
|
17276
|
+
authToken: null,
|
|
16602
17277
|
error: message
|
|
16603
17278
|
});
|
|
16604
17279
|
});
|
|
@@ -16615,11 +17290,13 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16615
17290
|
message: `MCP server listening on ${endpoint}.`
|
|
16616
17291
|
});
|
|
16617
17292
|
console.log(`[Vessel MCP] Server listening on ${endpoint}`);
|
|
17293
|
+
console.log(`[Vessel MCP] Auth token: ${mcpAuthToken}`);
|
|
16618
17294
|
finish({
|
|
16619
17295
|
ok: true,
|
|
16620
17296
|
configuredPort: port,
|
|
16621
17297
|
activePort: actualPort,
|
|
16622
|
-
endpoint
|
|
17298
|
+
endpoint,
|
|
17299
|
+
authToken: mcpAuthToken
|
|
16623
17300
|
});
|
|
16624
17301
|
});
|
|
16625
17302
|
});
|
|
@@ -16638,6 +17315,7 @@ function stopMcpServer() {
|
|
|
16638
17315
|
}
|
|
16639
17316
|
const server = httpServer;
|
|
16640
17317
|
httpServer = null;
|
|
17318
|
+
mcpAuthToken = null;
|
|
16641
17319
|
server.close(() => {
|
|
16642
17320
|
setMcpHealth({
|
|
16643
17321
|
activePort: null,
|
|
@@ -16651,14 +17329,14 @@ function stopMcpServer() {
|
|
|
16651
17329
|
});
|
|
16652
17330
|
}
|
|
16653
17331
|
let activeChatProvider = null;
|
|
16654
|
-
function registerIpcHandlers(windowState,
|
|
17332
|
+
function registerIpcHandlers(windowState, runtime2) {
|
|
16655
17333
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
16656
17334
|
const sendToRendererViews = (channel, ...args) => {
|
|
16657
17335
|
chromeView.webContents.send(channel, ...args);
|
|
16658
17336
|
sidebarView.webContents.send(channel, ...args);
|
|
16659
17337
|
devtoolsPanelView.webContents.send(channel, ...args);
|
|
16660
17338
|
};
|
|
16661
|
-
|
|
17339
|
+
runtime2.setUpdateListener((state2) => {
|
|
16662
17340
|
sendToRendererViews(Channels.AGENT_RUNTIME_UPDATE, state2);
|
|
16663
17341
|
});
|
|
16664
17342
|
electron.ipcMain.handle(Channels.TAB_CREATE, (_, url) => {
|
|
@@ -16708,7 +17386,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16708
17386
|
(chunk) => sendToRendererViews(Channels.AI_STREAM_CHUNK, chunk),
|
|
16709
17387
|
() => sendToRendererViews(Channels.AI_STREAM_END),
|
|
16710
17388
|
tabManager,
|
|
16711
|
-
|
|
17389
|
+
runtime2,
|
|
16712
17390
|
history
|
|
16713
17391
|
);
|
|
16714
17392
|
} catch (err) {
|
|
@@ -16791,46 +17469,50 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16791
17469
|
});
|
|
16792
17470
|
electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, () => getRuntimeHealth());
|
|
16793
17471
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (_, key, value) => {
|
|
16794
|
-
|
|
17472
|
+
if (!SETTABLE_KEYS.has(key)) {
|
|
17473
|
+
throw new Error(`Unknown setting key: ${key}`);
|
|
17474
|
+
}
|
|
17475
|
+
const settingsKey = key;
|
|
17476
|
+
const updatedSettings = setSetting(settingsKey, value);
|
|
16795
17477
|
if (key === "approvalMode") {
|
|
16796
|
-
|
|
17478
|
+
runtime2.setApprovalMode(value);
|
|
16797
17479
|
}
|
|
16798
17480
|
if (key === "mcpPort") {
|
|
16799
17481
|
await stopMcpServer();
|
|
16800
|
-
await startMcpServer(tabManager,
|
|
17482
|
+
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
16801
17483
|
}
|
|
16802
17484
|
sendToRendererViews(Channels.SETTINGS_UPDATE, updatedSettings);
|
|
16803
17485
|
return updatedSettings;
|
|
16804
17486
|
});
|
|
16805
|
-
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () =>
|
|
16806
|
-
electron.ipcMain.handle(Channels.AGENT_PAUSE, () =>
|
|
16807
|
-
electron.ipcMain.handle(Channels.AGENT_RESUME, () =>
|
|
17487
|
+
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () => runtime2.getState());
|
|
17488
|
+
electron.ipcMain.handle(Channels.AGENT_PAUSE, () => runtime2.pause());
|
|
17489
|
+
electron.ipcMain.handle(Channels.AGENT_RESUME, () => runtime2.resume());
|
|
16808
17490
|
electron.ipcMain.handle(
|
|
16809
17491
|
Channels.AGENT_SET_APPROVAL_MODE,
|
|
16810
17492
|
(_, mode) => {
|
|
16811
17493
|
setSetting("approvalMode", mode);
|
|
16812
|
-
return
|
|
17494
|
+
return runtime2.setApprovalMode(mode);
|
|
16813
17495
|
}
|
|
16814
17496
|
);
|
|
16815
17497
|
electron.ipcMain.handle(
|
|
16816
17498
|
Channels.AGENT_APPROVAL_RESOLVE,
|
|
16817
|
-
(_, approvalId, approved) =>
|
|
17499
|
+
(_, approvalId, approved) => runtime2.resolveApproval(approvalId, approved)
|
|
16818
17500
|
);
|
|
16819
17501
|
electron.ipcMain.handle(
|
|
16820
17502
|
Channels.AGENT_CHECKPOINT_CREATE,
|
|
16821
|
-
(_, name, note) =>
|
|
17503
|
+
(_, name, note) => runtime2.createCheckpoint(name, note)
|
|
16822
17504
|
);
|
|
16823
17505
|
electron.ipcMain.handle(
|
|
16824
17506
|
Channels.AGENT_CHECKPOINT_RESTORE,
|
|
16825
|
-
(_, checkpointId) =>
|
|
17507
|
+
(_, checkpointId) => runtime2.restoreCheckpoint(checkpointId)
|
|
16826
17508
|
);
|
|
16827
17509
|
electron.ipcMain.handle(
|
|
16828
17510
|
Channels.AGENT_SESSION_CAPTURE,
|
|
16829
|
-
(_, note) =>
|
|
17511
|
+
(_, note) => runtime2.captureSession(note)
|
|
16830
17512
|
);
|
|
16831
17513
|
electron.ipcMain.handle(
|
|
16832
17514
|
Channels.AGENT_SESSION_RESTORE,
|
|
16833
|
-
(_, snapshot) =>
|
|
17515
|
+
(_, snapshot) => runtime2.restoreSession(snapshot)
|
|
16834
17516
|
);
|
|
16835
17517
|
electron.ipcMain.handle(Channels.BOOKMARKS_GET, () => {
|
|
16836
17518
|
return getState();
|
|
@@ -16864,36 +17546,12 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16864
17546
|
return { success: false, message: "No active tab" };
|
|
16865
17547
|
}
|
|
16866
17548
|
const wc = activeTab.view.webContents;
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
if (!url || url === "about:blank") {
|
|
16872
|
-
return { success: false, message: "No page loaded" };
|
|
16873
|
-
}
|
|
16874
|
-
const selectedText = await wc.executeJavaScript(`
|
|
16875
|
-
(function() {
|
|
16876
|
-
var sel = window.getSelection();
|
|
16877
|
-
return sel ? sel.toString().trim() : '';
|
|
16878
|
-
})()
|
|
16879
|
-
`);
|
|
16880
|
-
if (!selectedText) {
|
|
16881
|
-
return { success: false, message: "No text selected" };
|
|
17549
|
+
const result = await captureSelectionHighlight(wc);
|
|
17550
|
+
if (result.success && result.text) {
|
|
17551
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
|
|
17552
|
+
});
|
|
16882
17553
|
}
|
|
16883
|
-
|
|
16884
|
-
const highlight = addHighlight(
|
|
16885
|
-
url,
|
|
16886
|
-
void 0,
|
|
16887
|
-
capped,
|
|
16888
|
-
void 0,
|
|
16889
|
-
"yellow",
|
|
16890
|
-
"user"
|
|
16891
|
-
);
|
|
16892
|
-
await highlightOnPage(wc, null, capped, void 0, void 0, "yellow").catch(
|
|
16893
|
-
() => {
|
|
16894
|
-
}
|
|
16895
|
-
);
|
|
16896
|
-
return { success: true, text: capped, id: highlight.id };
|
|
17554
|
+
return result;
|
|
16897
17555
|
} catch {
|
|
16898
17556
|
return { success: false, message: "Could not capture selection" };
|
|
16899
17557
|
}
|
|
@@ -16909,24 +17567,11 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16909
17567
|
if (wc.isDestroyed()) return;
|
|
16910
17568
|
const tab = tabManager.findTabByWebContentsId(wc.id);
|
|
16911
17569
|
if (!tab || !tab.highlightModeActive) return;
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
void 0,
|
|
16918
|
-
capped,
|
|
16919
|
-
void 0,
|
|
16920
|
-
"yellow",
|
|
16921
|
-
"user"
|
|
16922
|
-
);
|
|
16923
|
-
if (!chromeView.webContents.isDestroyed()) {
|
|
16924
|
-
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, {
|
|
16925
|
-
success: true,
|
|
16926
|
-
text: capped,
|
|
16927
|
-
id: highlight.id
|
|
16928
|
-
});
|
|
16929
|
-
}
|
|
17570
|
+
void persistAndMarkHighlight(wc, text).then((result) => {
|
|
17571
|
+
if (result.success && !chromeView.webContents.isDestroyed()) {
|
|
17572
|
+
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
17573
|
+
}
|
|
17574
|
+
});
|
|
16930
17575
|
} catch {
|
|
16931
17576
|
}
|
|
16932
17577
|
});
|
|
@@ -16936,9 +17581,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16936
17581
|
const wc = tab.view.webContents;
|
|
16937
17582
|
if (wc.isDestroyed()) return 0;
|
|
16938
17583
|
try {
|
|
16939
|
-
return wc
|
|
16940
|
-
`document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text').length`
|
|
16941
|
-
);
|
|
17584
|
+
return getHighlightCount(wc);
|
|
16942
17585
|
} catch {
|
|
16943
17586
|
return 0;
|
|
16944
17587
|
}
|
|
@@ -16949,20 +17592,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16949
17592
|
const wc = tab.view.webContents;
|
|
16950
17593
|
if (wc.isDestroyed()) return false;
|
|
16951
17594
|
try {
|
|
16952
|
-
return wc
|
|
16953
|
-
(function() {
|
|
16954
|
-
var highlights = document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text');
|
|
16955
|
-
if (${index} < 0 || ${index} >= highlights.length) return false;
|
|
16956
|
-
// Remove focus ring from all highlights
|
|
16957
|
-
highlights.forEach(function(h) { h.style.removeProperty('outline'); h.style.removeProperty('outline-offset'); });
|
|
16958
|
-
var target = highlights[${index}];
|
|
16959
|
-
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
16960
|
-
// Add focus ring to current highlight
|
|
16961
|
-
target.style.setProperty('outline', '2px solid rgba(255, 255, 255, 0.9)', 'important');
|
|
16962
|
-
target.style.setProperty('outline-offset', '2px', 'important');
|
|
16963
|
-
return true;
|
|
16964
|
-
})()
|
|
16965
|
-
`);
|
|
17595
|
+
return scrollToHighlight(wc, index);
|
|
16966
17596
|
} catch {
|
|
16967
17597
|
return false;
|
|
16968
17598
|
}
|
|
@@ -16973,32 +17603,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16973
17603
|
const wc = tab.view.webContents;
|
|
16974
17604
|
if (wc.isDestroyed()) return false;
|
|
16975
17605
|
try {
|
|
16976
|
-
return wc
|
|
16977
|
-
(function() {
|
|
16978
|
-
var highlights = document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text');
|
|
16979
|
-
if (${index} < 0 || ${index} >= highlights.length) return false;
|
|
16980
|
-
var el = highlights[${index}];
|
|
16981
|
-
// Remove associated label if any
|
|
16982
|
-
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) {
|
|
16983
|
-
if (b.__vesselAnchor === el) b.remove();
|
|
16984
|
-
});
|
|
16985
|
-
// Unwrap text highlights, remove class from element highlights
|
|
16986
|
-
if (el.tagName === 'MARK' && el.classList.contains('__vessel-highlight-text')) {
|
|
16987
|
-
var parent = el.parentNode;
|
|
16988
|
-
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
16989
|
-
parent.removeChild(el);
|
|
16990
|
-
parent.normalize();
|
|
16991
|
-
} else {
|
|
16992
|
-
el.classList.remove('__vessel-highlight');
|
|
16993
|
-
el.style.removeProperty('background');
|
|
16994
|
-
el.style.removeProperty('outline-color');
|
|
16995
|
-
el.style.removeProperty('box-shadow');
|
|
16996
|
-
el.style.removeProperty('outline');
|
|
16997
|
-
el.style.removeProperty('outline-offset');
|
|
16998
|
-
}
|
|
16999
|
-
return true;
|
|
17000
|
-
})()
|
|
17001
|
-
`);
|
|
17606
|
+
return removeHighlightAtIndex(wc, index);
|
|
17002
17607
|
} catch {
|
|
17003
17608
|
return false;
|
|
17004
17609
|
}
|
|
@@ -17009,39 +17614,65 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
17009
17614
|
const wc = tab.view.webContents;
|
|
17010
17615
|
if (wc.isDestroyed()) return false;
|
|
17011
17616
|
try {
|
|
17012
|
-
return wc
|
|
17013
|
-
(function() {
|
|
17014
|
-
// Remove all labels
|
|
17015
|
-
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) { b.remove(); });
|
|
17016
|
-
// Unwrap text highlights
|
|
17017
|
-
document.querySelectorAll('.__vessel-highlight-text').forEach(function(mark) {
|
|
17018
|
-
var parent = mark.parentNode;
|
|
17019
|
-
while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
|
|
17020
|
-
parent.removeChild(mark);
|
|
17021
|
-
parent.normalize();
|
|
17022
|
-
});
|
|
17023
|
-
// Remove element highlights
|
|
17024
|
-
document.querySelectorAll('.__vessel-highlight').forEach(function(el) {
|
|
17025
|
-
el.classList.remove('__vessel-highlight');
|
|
17026
|
-
el.style.removeProperty('background');
|
|
17027
|
-
el.style.removeProperty('outline-color');
|
|
17028
|
-
el.style.removeProperty('box-shadow');
|
|
17029
|
-
el.style.removeProperty('outline');
|
|
17030
|
-
el.style.removeProperty('outline-offset');
|
|
17031
|
-
});
|
|
17032
|
-
return true;
|
|
17033
|
-
})()
|
|
17034
|
-
`);
|
|
17617
|
+
return clearAllHighlightElements(wc);
|
|
17035
17618
|
} catch {
|
|
17036
17619
|
return false;
|
|
17037
17620
|
}
|
|
17038
17621
|
});
|
|
17622
|
+
let findWiredWcId = null;
|
|
17623
|
+
function wireFindEvents(wc) {
|
|
17624
|
+
if (findWiredWcId === wc.id) return;
|
|
17625
|
+
if (findWiredWcId !== null) {
|
|
17626
|
+
const prev = tabManager.findTabByWebContentsId(findWiredWcId);
|
|
17627
|
+
if (prev) prev.view.webContents.removeAllListeners("found-in-page");
|
|
17628
|
+
}
|
|
17629
|
+
findWiredWcId = wc.id;
|
|
17630
|
+
wc.on("found-in-page", (_event, result) => {
|
|
17631
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
17632
|
+
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
17633
|
+
}
|
|
17634
|
+
});
|
|
17635
|
+
}
|
|
17636
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_START, (_, text, options) => {
|
|
17637
|
+
const tab = tabManager.getActiveTab();
|
|
17638
|
+
if (!tab) return null;
|
|
17639
|
+
const wc = tab.view.webContents;
|
|
17640
|
+
if (wc.isDestroyed()) return null;
|
|
17641
|
+
wireFindEvents(wc);
|
|
17642
|
+
return wc.findInPage(text, {
|
|
17643
|
+
forward: options?.forward ?? true,
|
|
17644
|
+
findNext: options?.findNext ?? false
|
|
17645
|
+
});
|
|
17646
|
+
});
|
|
17647
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_NEXT, (_, forward) => {
|
|
17648
|
+
const tab = tabManager.getActiveTab();
|
|
17649
|
+
if (!tab) return null;
|
|
17650
|
+
const wc = tab.view.webContents;
|
|
17651
|
+
if (wc.isDestroyed()) return null;
|
|
17652
|
+
return wc.findInPage("", { forward: forward ?? true, findNext: true });
|
|
17653
|
+
});
|
|
17654
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_STOP, (_, action) => {
|
|
17655
|
+
const tab = tabManager.getActiveTab();
|
|
17656
|
+
if (!tab) return;
|
|
17657
|
+
const wc = tab.view.webContents;
|
|
17658
|
+
if (wc.isDestroyed()) return;
|
|
17659
|
+
wc.stopFindInPage(action ?? "clearSelection");
|
|
17660
|
+
});
|
|
17661
|
+
electron.ipcMain.handle(Channels.HISTORY_GET, () => {
|
|
17662
|
+
return getState$1();
|
|
17663
|
+
});
|
|
17664
|
+
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (_, query) => {
|
|
17665
|
+
return search(query);
|
|
17666
|
+
});
|
|
17667
|
+
electron.ipcMain.handle(Channels.HISTORY_CLEAR, () => {
|
|
17668
|
+
clearAll$1();
|
|
17669
|
+
});
|
|
17039
17670
|
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => {
|
|
17040
17671
|
windowState.uiState.devtoolsPanelOpen = !windowState.uiState.devtoolsPanelOpen;
|
|
17041
17672
|
layoutViews(windowState);
|
|
17042
17673
|
return { open: windowState.uiState.devtoolsPanelOpen };
|
|
17043
17674
|
});
|
|
17044
|
-
electron.ipcMain.handle(
|
|
17675
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE, (_, height) => {
|
|
17045
17676
|
const clamped = Math.max(MIN_DEVTOOLS_PANEL, Math.min(MAX_DEVTOOLS_PANEL, Math.round(height)));
|
|
17046
17677
|
windowState.uiState.devtoolsPanelHeight = clamped;
|
|
17047
17678
|
layoutViews(windowState);
|
|
@@ -17062,6 +17693,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
17062
17693
|
});
|
|
17063
17694
|
}
|
|
17064
17695
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
17696
|
+
const PERSIST_DEBOUNCE_MS = 500;
|
|
17065
17697
|
function clone(value) {
|
|
17066
17698
|
return JSON.parse(JSON.stringify(value));
|
|
17067
17699
|
}
|
|
@@ -17139,7 +17771,7 @@ class AgentRuntime {
|
|
|
17139
17771
|
createCheckpoint(name, note) {
|
|
17140
17772
|
const snapshot = this.captureSession(note);
|
|
17141
17773
|
const checkpoint = {
|
|
17142
|
-
id:
|
|
17774
|
+
id: crypto$1.randomUUID(),
|
|
17143
17775
|
name: name?.trim() || `Checkpoint ${this.state.checkpoints.length + 1}`,
|
|
17144
17776
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17145
17777
|
note: note?.trim() || void 0,
|
|
@@ -17193,7 +17825,7 @@ class AgentRuntime {
|
|
|
17193
17825
|
}
|
|
17194
17826
|
}
|
|
17195
17827
|
const entry = {
|
|
17196
|
-
id:
|
|
17828
|
+
id: crypto$1.randomUUID(),
|
|
17197
17829
|
source: input.source,
|
|
17198
17830
|
kind,
|
|
17199
17831
|
title: input.title?.trim() || void 0,
|
|
@@ -17217,7 +17849,7 @@ class AgentRuntime {
|
|
|
17217
17849
|
// --- Speedee Flow State ---
|
|
17218
17850
|
startFlow(goal, steps, startUrl) {
|
|
17219
17851
|
const flow = {
|
|
17220
|
-
id:
|
|
17852
|
+
id: crypto$1.randomUUID(),
|
|
17221
17853
|
goal,
|
|
17222
17854
|
steps: steps.map((label) => ({ label, status: "pending" })),
|
|
17223
17855
|
currentStepIndex: 0,
|
|
@@ -17403,7 +18035,14 @@ ${progress}
|
|
|
17403
18035
|
return sanitizePersistence(null);
|
|
17404
18036
|
}
|
|
17405
18037
|
}
|
|
17406
|
-
|
|
18038
|
+
persistTimer = null;
|
|
18039
|
+
persistDirty = false;
|
|
18040
|
+
persistNow() {
|
|
18041
|
+
this.persistDirty = false;
|
|
18042
|
+
if (this.persistTimer) {
|
|
18043
|
+
clearTimeout(this.persistTimer);
|
|
18044
|
+
this.persistTimer = null;
|
|
18045
|
+
}
|
|
17407
18046
|
const persisted = {
|
|
17408
18047
|
session: this.state.session,
|
|
17409
18048
|
supervisor: {
|
|
@@ -17414,20 +18053,36 @@ ${progress}
|
|
|
17414
18053
|
actions: this.state.actions.slice(-120),
|
|
17415
18054
|
checkpoints: this.state.checkpoints.slice(-20)
|
|
17416
18055
|
};
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
18056
|
+
try {
|
|
18057
|
+
fs$1.mkdirSync(path$1.dirname(getRuntimeStatePath()), { recursive: true });
|
|
18058
|
+
fs$1.writeFileSync(
|
|
18059
|
+
getRuntimeStatePath(),
|
|
18060
|
+
JSON.stringify(persisted, null, 2),
|
|
18061
|
+
"utf-8"
|
|
18062
|
+
);
|
|
18063
|
+
} catch (err) {
|
|
18064
|
+
console.error("[Vessel] Failed to persist runtime state:", err);
|
|
18065
|
+
}
|
|
18066
|
+
}
|
|
18067
|
+
schedulePersist() {
|
|
18068
|
+
this.persistDirty = true;
|
|
18069
|
+
if (this.persistTimer) return;
|
|
18070
|
+
this.persistTimer = setTimeout(() => {
|
|
18071
|
+
this.persistTimer = null;
|
|
18072
|
+
if (this.persistDirty) this.persistNow();
|
|
18073
|
+
}, PERSIST_DEBOUNCE_MS);
|
|
18074
|
+
}
|
|
18075
|
+
/** Flush any pending debounced persist to disk immediately. Call on shutdown. */
|
|
18076
|
+
flushPersist() {
|
|
18077
|
+
if (this.persistDirty) this.persistNow();
|
|
17423
18078
|
}
|
|
17424
18079
|
emit() {
|
|
17425
|
-
this.
|
|
18080
|
+
this.schedulePersist();
|
|
17426
18081
|
this.updateListener?.(this.getState());
|
|
17427
18082
|
}
|
|
17428
18083
|
startAction(input) {
|
|
17429
18084
|
const action = {
|
|
17430
|
-
id:
|
|
18085
|
+
id: crypto$1.randomUUID(),
|
|
17431
18086
|
source: input.source,
|
|
17432
18087
|
name: input.name,
|
|
17433
18088
|
args: clone(input.args),
|
|
@@ -17461,7 +18116,7 @@ ${progress}
|
|
|
17461
18116
|
/** Aggregate metrics for all completed actions in this session. */
|
|
17462
18117
|
getMetrics() {
|
|
17463
18118
|
const completed = this.state.actions.filter((a) => a.status === "completed");
|
|
17464
|
-
const failed = this.state.actions.filter((a) => a.status === "
|
|
18119
|
+
const failed = this.state.actions.filter((a) => a.status === "failed");
|
|
17465
18120
|
const durations = completed.filter((a) => a.durationMs != null).map((a) => a.durationMs);
|
|
17466
18121
|
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
17467
18122
|
const toolBreakdown = {};
|
|
@@ -17499,7 +18154,7 @@ ${progress}
|
|
|
17499
18154
|
}
|
|
17500
18155
|
awaitApproval(action, reason) {
|
|
17501
18156
|
const approval = {
|
|
17502
|
-
id:
|
|
18157
|
+
id: crypto$1.randomUUID(),
|
|
17503
18158
|
actionId: action.id,
|
|
17504
18159
|
source: action.source,
|
|
17505
18160
|
name: action.name,
|
|
@@ -17625,6 +18280,41 @@ function installAdBlocking(tabManager) {
|
|
|
17625
18280
|
callback({ cancel: shouldBlockRequest(details) });
|
|
17626
18281
|
});
|
|
17627
18282
|
}
|
|
18283
|
+
function installDownloadHandler(chromeView) {
|
|
18284
|
+
electron.session.defaultSession.on("will-download", (_event, item) => {
|
|
18285
|
+
const settings2 = loadSettings();
|
|
18286
|
+
const downloadDir = settings2.downloadPath.trim() || electron.app.getPath("downloads");
|
|
18287
|
+
const filename = item.getFilename();
|
|
18288
|
+
const savePath = path.join(downloadDir, filename);
|
|
18289
|
+
item.setSavePath(savePath);
|
|
18290
|
+
const info = {
|
|
18291
|
+
filename,
|
|
18292
|
+
savePath,
|
|
18293
|
+
totalBytes: item.getTotalBytes(),
|
|
18294
|
+
receivedBytes: 0,
|
|
18295
|
+
state: "progressing"
|
|
18296
|
+
};
|
|
18297
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18298
|
+
chromeView.webContents.send(Channels.DOWNLOAD_STARTED, info);
|
|
18299
|
+
}
|
|
18300
|
+
item.on("updated", (_event2, state2) => {
|
|
18301
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
18302
|
+
info.totalBytes = item.getTotalBytes();
|
|
18303
|
+
info.state = state2 === "progressing" ? "progressing" : "interrupted";
|
|
18304
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18305
|
+
chromeView.webContents.send(Channels.DOWNLOAD_PROGRESS, info);
|
|
18306
|
+
}
|
|
18307
|
+
});
|
|
18308
|
+
item.once("done", (_event2, state2) => {
|
|
18309
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
18310
|
+
info.state = state2 === "completed" ? "completed" : "cancelled";
|
|
18311
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18312
|
+
chromeView.webContents.send(Channels.DOWNLOAD_DONE, info);
|
|
18313
|
+
}
|
|
18314
|
+
});
|
|
18315
|
+
});
|
|
18316
|
+
}
|
|
18317
|
+
let runtime = null;
|
|
17628
18318
|
function rendererUrlFor(view) {
|
|
17629
18319
|
if (!process.env.ELECTRON_RENDERER_URL) return null;
|
|
17630
18320
|
const url = new URL(process.env.ELECTRON_RENDERER_URL);
|
|
@@ -17705,7 +18395,6 @@ async function bootstrap() {
|
|
|
17705
18395
|
if (settings2.clearBookmarksOnLaunch) {
|
|
17706
18396
|
clearAll();
|
|
17707
18397
|
}
|
|
17708
|
-
let runtime = null;
|
|
17709
18398
|
const windowState = createMainWindow((tabs, activeId) => {
|
|
17710
18399
|
windowState.chromeView.webContents.send(
|
|
17711
18400
|
Channels.TAB_STATE_UPDATE,
|
|
@@ -17727,15 +18416,10 @@ async function bootstrap() {
|
|
|
17727
18416
|
const registerHighlightShortcut = () => {
|
|
17728
18417
|
electron.globalShortcut.unregister("CommandOrControl+H");
|
|
17729
18418
|
const success = electron.globalShortcut.register("CommandOrControl+H", () => {
|
|
17730
|
-
console.log("[Vessel] Ctrl+H shortcut triggered");
|
|
17731
18419
|
const activeTab = tabManager.getActiveTab();
|
|
17732
|
-
if (!activeTab)
|
|
17733
|
-
console.log("[Vessel] No active tab");
|
|
17734
|
-
return;
|
|
17735
|
-
}
|
|
18420
|
+
if (!activeTab) return;
|
|
17736
18421
|
tabManager.captureHighlightFromActiveTab();
|
|
17737
18422
|
});
|
|
17738
|
-
console.log("[Vessel] Ctrl+H shortcut registered:", success);
|
|
17739
18423
|
if (!success) {
|
|
17740
18424
|
console.warn("[Vessel] Failed to register Ctrl+H shortcut");
|
|
17741
18425
|
}
|
|
@@ -17761,6 +18445,11 @@ async function bootstrap() {
|
|
|
17761
18445
|
chromeView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|
|
17762
18446
|
sidebarView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|
|
17763
18447
|
});
|
|
18448
|
+
subscribe$1((state2) => {
|
|
18449
|
+
chromeView.webContents.send(Channels.HISTORY_UPDATE, state2);
|
|
18450
|
+
sidebarView.webContents.send(Channels.HISTORY_UPDATE, state2);
|
|
18451
|
+
});
|
|
18452
|
+
installDownloadHandler(chromeView);
|
|
17764
18453
|
const chromeUrl = rendererUrlFor("chrome");
|
|
17765
18454
|
const sidebarUrl = rendererUrlFor("sidebar");
|
|
17766
18455
|
const devtoolsUrl = rendererUrlFor("devtools");
|
|
@@ -17800,6 +18489,7 @@ electron.app.whenReady().then(bootstrap).catch((error) => {
|
|
|
17800
18489
|
});
|
|
17801
18490
|
electron.app.on("window-all-closed", () => {
|
|
17802
18491
|
electron.globalShortcut.unregisterAll();
|
|
18492
|
+
runtime?.flushPersist();
|
|
17803
18493
|
void stopMcpServer().finally(() => {
|
|
17804
18494
|
electron.app.quit();
|
|
17805
18495
|
});
|