@quanta-intellect/vessel-browser 0.1.14 → 0.1.16
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 +1255 -542
- package/out/preload/index.js +32 -1
- package/out/renderer/assets/{index-CvRVBELV.js → index-FJxHleYP.js} +314 -308
- package/out/renderer/index.html +2 -1
- package/package.json +7 -7
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,
|
|
@@ -1882,84 +2263,7 @@ class TabManager {
|
|
|
1882
2263
|
broadcastState() {
|
|
1883
2264
|
const states = this.getAllStates();
|
|
1884
2265
|
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
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
settings = { ...defaults };
|
|
1947
|
-
}
|
|
1948
|
-
return settings;
|
|
1949
|
-
}
|
|
1950
|
-
function saveSettings() {
|
|
1951
|
-
fs.mkdirSync(path.dirname(getSettingsPath()), { recursive: true });
|
|
1952
|
-
fs.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2));
|
|
1953
|
-
}
|
|
1954
|
-
function setSetting(key, value) {
|
|
1955
|
-
loadSettings();
|
|
1956
|
-
if (key === "mcpPort") {
|
|
1957
|
-
settings.mcpPort = sanitizePort(value);
|
|
1958
|
-
} else {
|
|
1959
|
-
settings[key] = value;
|
|
1960
|
-
}
|
|
1961
|
-
saveSettings();
|
|
1962
|
-
return { ...settings };
|
|
2266
|
+
}
|
|
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,9 +4401,24 @@ 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
|
}
|
|
4409
|
+
function isRichToolResult(value) {
|
|
4410
|
+
return typeof value === "object" && value !== null && value.__richResult === true;
|
|
4411
|
+
}
|
|
4412
|
+
function makeImageResult(base64, description, mediaType = "image/png") {
|
|
4413
|
+
const result = {
|
|
4414
|
+
__richResult: true,
|
|
4415
|
+
content: [
|
|
4416
|
+
{ type: "text", text: description },
|
|
4417
|
+
{ type: "image", mediaType, base64 }
|
|
4418
|
+
]
|
|
4419
|
+
};
|
|
4420
|
+
return JSON.stringify(result);
|
|
4421
|
+
}
|
|
4065
4422
|
const DEFAULT_MAX_ITERATIONS$1 = 200;
|
|
4066
4423
|
class AnthropicProvider {
|
|
4067
4424
|
client;
|
|
@@ -4093,7 +4450,7 @@ class AnthropicProvider {
|
|
|
4093
4450
|
}
|
|
4094
4451
|
}
|
|
4095
4452
|
} catch (err) {
|
|
4096
|
-
if (err.name !== "AbortError") {
|
|
4453
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
4097
4454
|
onChunk(`
|
|
4098
4455
|
|
|
4099
4456
|
[Error: ${err.message}]`);
|
|
@@ -4116,7 +4473,6 @@ class AnthropicProvider {
|
|
|
4116
4473
|
iterationsUsed = i + 1;
|
|
4117
4474
|
const msgTokenEstimate = JSON.stringify(messages).length;
|
|
4118
4475
|
const sysTokenEstimate = systemPrompt.length;
|
|
4119
|
-
console.log(`[Vessel Agent] iteration=${i} messages=${messages.length} msgChars=${msgTokenEstimate} sysChars=${sysTokenEstimate} tools=${tools.length}`);
|
|
4120
4476
|
const streamStartTime = Date.now();
|
|
4121
4477
|
const stream = this.client.messages.stream(
|
|
4122
4478
|
{
|
|
@@ -4178,9 +4534,7 @@ class AnthropicProvider {
|
|
|
4178
4534
|
} finally {
|
|
4179
4535
|
if (idleTimer) clearTimeout(idleTimer);
|
|
4180
4536
|
}
|
|
4181
|
-
console.log(`[Vessel Agent] stream complete in ${Date.now() - streamStartTime}ms, toolCalls=${toolUseBlocks.length} textLen=${textContent.length}`);
|
|
4182
4537
|
const finalMessage = await stream.finalMessage();
|
|
4183
|
-
console.log(`[Vessel Agent] finalMessage received, stop_reason=${finalMessage.stop_reason}`);
|
|
4184
4538
|
const assistantContent = [];
|
|
4185
4539
|
if (textContent) {
|
|
4186
4540
|
assistantContent.push({ type: "text", text: textContent });
|
|
@@ -4204,19 +4558,43 @@ class AnthropicProvider {
|
|
|
4204
4558
|
<<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
|
|
4205
4559
|
`);
|
|
4206
4560
|
let result;
|
|
4207
|
-
const toolStartTime = Date.now();
|
|
4208
|
-
console.log(`[Vessel Agent] executing tool: ${tb.name}`);
|
|
4209
4561
|
try {
|
|
4210
4562
|
result = await onToolCall(tb.name, tb.input);
|
|
4211
4563
|
} catch (toolErr) {
|
|
4212
|
-
|
|
4564
|
+
const msg = toolErr instanceof Error ? toolErr.message : String(toolErr);
|
|
4565
|
+
result = `Error: Tool execution failed — ${msg}. Try a different approach or call read_page to refresh context.`;
|
|
4566
|
+
}
|
|
4567
|
+
let parsedRich = null;
|
|
4568
|
+
try {
|
|
4569
|
+
const parsed = JSON.parse(result);
|
|
4570
|
+
if (isRichToolResult(parsed)) parsedRich = parsed;
|
|
4571
|
+
} catch {
|
|
4572
|
+
}
|
|
4573
|
+
if (parsedRich) {
|
|
4574
|
+
toolResults.push({
|
|
4575
|
+
type: "tool_result",
|
|
4576
|
+
tool_use_id: tb.id,
|
|
4577
|
+
content: parsedRich.content.map((block) => {
|
|
4578
|
+
if (block.type === "image") {
|
|
4579
|
+
return {
|
|
4580
|
+
type: "image",
|
|
4581
|
+
source: {
|
|
4582
|
+
type: "base64",
|
|
4583
|
+
media_type: block.mediaType,
|
|
4584
|
+
data: block.base64
|
|
4585
|
+
}
|
|
4586
|
+
};
|
|
4587
|
+
}
|
|
4588
|
+
return { type: "text", text: block.text };
|
|
4589
|
+
})
|
|
4590
|
+
});
|
|
4591
|
+
} else {
|
|
4592
|
+
toolResults.push({
|
|
4593
|
+
type: "tool_result",
|
|
4594
|
+
tool_use_id: tb.id,
|
|
4595
|
+
content: result
|
|
4596
|
+
});
|
|
4213
4597
|
}
|
|
4214
|
-
console.log(`[Vessel Agent] tool ${tb.name} completed in ${Date.now() - toolStartTime}ms, resultLen=${result.length}`);
|
|
4215
|
-
toolResults.push({
|
|
4216
|
-
type: "tool_result",
|
|
4217
|
-
tool_use_id: tb.id,
|
|
4218
|
-
content: result
|
|
4219
|
-
});
|
|
4220
4598
|
}
|
|
4221
4599
|
messages.push({ role: "user", content: toolResults });
|
|
4222
4600
|
}
|
|
@@ -4226,7 +4604,7 @@ class AnthropicProvider {
|
|
|
4226
4604
|
[Reached maximum tool call limit (${maxIterations} steps). You can adjust this in Settings → Max Tool Iterations, or continue by sending another message.]`);
|
|
4227
4605
|
}
|
|
4228
4606
|
} catch (err) {
|
|
4229
|
-
if (err.name !== "AbortError") {
|
|
4607
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
4230
4608
|
onChunk(`
|
|
4231
4609
|
|
|
4232
4610
|
[Error: ${err.message}]`);
|
|
@@ -4377,7 +4755,7 @@ class OpenAICompatProvider {
|
|
|
4377
4755
|
}
|
|
4378
4756
|
}
|
|
4379
4757
|
} catch (err) {
|
|
4380
|
-
if (err.name !== "AbortError") {
|
|
4758
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
4381
4759
|
onChunk(`
|
|
4382
4760
|
|
|
4383
4761
|
[Error: ${err.message}]`);
|
|
@@ -4401,7 +4779,6 @@ class OpenAICompatProvider {
|
|
|
4401
4779
|
for (let i = 0; i < maxIterations; i++) {
|
|
4402
4780
|
iterationsUsed = i + 1;
|
|
4403
4781
|
const msgTokenEstimate = JSON.stringify(messages).length;
|
|
4404
|
-
console.log(`[Vessel Agent OpenAI] iteration=${i} messages=${messages.length} msgChars=${msgTokenEstimate} tools=${openAITools.length}`);
|
|
4405
4782
|
const streamStartTime = Date.now();
|
|
4406
4783
|
let textAccum = "";
|
|
4407
4784
|
const toolCallAccums = {};
|
|
@@ -4437,7 +4814,6 @@ class OpenAICompatProvider {
|
|
|
4437
4814
|
}
|
|
4438
4815
|
}
|
|
4439
4816
|
}
|
|
4440
|
-
console.log(`[Vessel Agent OpenAI] stream complete in ${Date.now() - streamStartTime}ms, toolCalls=${Object.keys(toolCallAccums).length} textLen=${textAccum.length} finishReason=${finishReason}`);
|
|
4441
4817
|
const toolCalls = Object.values(toolCallAccums);
|
|
4442
4818
|
for (const tc of Object.values(toolCallAccums)) {
|
|
4443
4819
|
if (!tc.id) tc.id = `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -4482,18 +4858,24 @@ class OpenAICompatProvider {
|
|
|
4482
4858
|
<<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
|
|
4483
4859
|
`);
|
|
4484
4860
|
let result;
|
|
4485
|
-
const toolStartTime = Date.now();
|
|
4486
|
-
console.log(`[Vessel Agent OpenAI] executing tool: ${tc.name}`);
|
|
4487
4861
|
try {
|
|
4488
4862
|
result = await onToolCall(tc.name, args);
|
|
4489
4863
|
} catch (toolErr) {
|
|
4490
|
-
|
|
4864
|
+
const msg = toolErr instanceof Error ? toolErr.message : String(toolErr);
|
|
4865
|
+
result = `Error: Tool execution failed — ${msg}. Try a different approach or call read_page to refresh context.`;
|
|
4866
|
+
}
|
|
4867
|
+
let toolContent = result;
|
|
4868
|
+
try {
|
|
4869
|
+
const parsed = JSON.parse(result);
|
|
4870
|
+
if (isRichToolResult(parsed)) {
|
|
4871
|
+
toolContent = parsed.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
4872
|
+
}
|
|
4873
|
+
} catch {
|
|
4491
4874
|
}
|
|
4492
|
-
console.log(`[Vessel Agent OpenAI] tool ${tc.name} completed in ${Date.now() - toolStartTime}ms, resultLen=${result.length}`);
|
|
4493
4875
|
messages.push({
|
|
4494
4876
|
role: "tool",
|
|
4495
4877
|
tool_call_id: tc.id,
|
|
4496
|
-
content:
|
|
4878
|
+
content: toolContent
|
|
4497
4879
|
});
|
|
4498
4880
|
}
|
|
4499
4881
|
}
|
|
@@ -4503,7 +4885,7 @@ class OpenAICompatProvider {
|
|
|
4503
4885
|
[Reached maximum tool call limit (${maxIterations} steps). You can adjust this in Settings → Max Tool Iterations, or continue by sending another message.]`);
|
|
4504
4886
|
}
|
|
4505
4887
|
} catch (err) {
|
|
4506
|
-
if (err.name !== "AbortError") {
|
|
4888
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
4507
4889
|
onChunk(`
|
|
4508
4890
|
|
|
4509
4891
|
[Error: ${err.message}]`);
|
|
@@ -6356,6 +6738,7 @@ const TOOL_DEFINITIONS = [
|
|
|
6356
6738
|
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
6739
|
inputSchema: {
|
|
6358
6740
|
mode: zod.z.enum([
|
|
6741
|
+
"glance",
|
|
6359
6742
|
"summary",
|
|
6360
6743
|
"interactives_only",
|
|
6361
6744
|
"forms_only",
|
|
@@ -6365,11 +6748,18 @@ const TOOL_DEFINITIONS = [
|
|
|
6365
6748
|
"full",
|
|
6366
6749
|
"debug"
|
|
6367
6750
|
]).optional().describe(
|
|
6368
|
-
"Read mode: visible_only/results_only/forms_only/summary/text_only for narrow reads, full/debug for the complete page dump"
|
|
6751
|
+
"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
6752
|
)
|
|
6370
6753
|
},
|
|
6371
6754
|
tier: 0
|
|
6372
6755
|
},
|
|
6756
|
+
{
|
|
6757
|
+
name: "screenshot",
|
|
6758
|
+
title: "Screenshot",
|
|
6759
|
+
description: "Take a screenshot of the current page — see exactly what the user sees. Returns the image for visual analysis. Use when you need to verify visual layout, check what's actually rendered on screen, or when text extraction fails on heavy pages.",
|
|
6760
|
+
inputSchema: {},
|
|
6761
|
+
tier: 1
|
|
6762
|
+
},
|
|
6373
6763
|
{
|
|
6374
6764
|
name: "wait_for",
|
|
6375
6765
|
title: "Wait For",
|
|
@@ -6805,6 +7195,7 @@ const ALWAYS_FAST_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
6805
7195
|
"accept_cookies",
|
|
6806
7196
|
"wait_for",
|
|
6807
7197
|
"read_page",
|
|
7198
|
+
"screenshot",
|
|
6808
7199
|
"inspect_element"
|
|
6809
7200
|
]);
|
|
6810
7201
|
function inferIntent(query) {
|
|
@@ -7069,8 +7460,12 @@ function load() {
|
|
|
7069
7460
|
return state;
|
|
7070
7461
|
}
|
|
7071
7462
|
function save() {
|
|
7072
|
-
|
|
7073
|
-
|
|
7463
|
+
try {
|
|
7464
|
+
fs.mkdirSync(path.dirname(getBookmarksPath()), { recursive: true });
|
|
7465
|
+
fs.writeFileSync(getBookmarksPath(), JSON.stringify(state, null, 2), "utf-8");
|
|
7466
|
+
} catch (err) {
|
|
7467
|
+
console.error("[Vessel] Failed to save bookmarks:", err);
|
|
7468
|
+
}
|
|
7074
7469
|
}
|
|
7075
7470
|
function emit() {
|
|
7076
7471
|
if (!state) return;
|
|
@@ -7496,6 +7891,39 @@ function formatDeadLinkMessage(label, result) {
|
|
|
7496
7891
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
7497
7892
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
7498
7893
|
}
|
|
7894
|
+
const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
7895
|
+
function isSafeNavigationURL(url) {
|
|
7896
|
+
try {
|
|
7897
|
+
const parsed = new URL(url);
|
|
7898
|
+
return ALLOWED_SCHEMES.has(parsed.protocol);
|
|
7899
|
+
} catch {
|
|
7900
|
+
return false;
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
function assertSafeURL(url) {
|
|
7904
|
+
if (!isSafeNavigationURL(url)) {
|
|
7905
|
+
throw new Error(
|
|
7906
|
+
`Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`
|
|
7907
|
+
);
|
|
7908
|
+
}
|
|
7909
|
+
}
|
|
7910
|
+
async function captureScreenshot(wc) {
|
|
7911
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
7912
|
+
await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
|
|
7913
|
+
try {
|
|
7914
|
+
const image = await wc.capturePage();
|
|
7915
|
+
if (!image.isEmpty()) {
|
|
7916
|
+
const size = image.getSize();
|
|
7917
|
+
const base64 = image.toPNG().toString("base64");
|
|
7918
|
+
if (base64) {
|
|
7919
|
+
return { ok: true, base64, width: size.width, height: size.height };
|
|
7920
|
+
}
|
|
7921
|
+
}
|
|
7922
|
+
} catch {
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7925
|
+
return { ok: false, error: "Page image was empty after 3 attempts" };
|
|
7926
|
+
}
|
|
7499
7927
|
const SESSION_VERSION = 1;
|
|
7500
7928
|
function getSessionsDir() {
|
|
7501
7929
|
return path$1.join(electron.app.getPath("userData"), "named-sessions");
|
|
@@ -7515,7 +7943,7 @@ function normalizeSessionName(name) {
|
|
|
7515
7943
|
function sessionFileName(name) {
|
|
7516
7944
|
const normalized = normalizeSessionName(name).toLowerCase();
|
|
7517
7945
|
const slug = normalized.replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "session";
|
|
7518
|
-
const hash =
|
|
7946
|
+
const hash = crypto$1.createHash("sha256").update(normalized).digest("hex").slice(0, 8);
|
|
7519
7947
|
return `${slug}-${hash}.json`;
|
|
7520
7948
|
}
|
|
7521
7949
|
function getSessionPath(name) {
|
|
@@ -7780,10 +8208,148 @@ const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
|
7780
8208
|
function pageBusyError(action) {
|
|
7781
8209
|
return `Error: Page is still busy; ${action} timed out waiting for page scripts. Retry in a moment.`;
|
|
7782
8210
|
}
|
|
8211
|
+
async function glanceExtract(wc) {
|
|
8212
|
+
const startMs = Date.now();
|
|
8213
|
+
const result = await executePageScript(
|
|
8214
|
+
wc,
|
|
8215
|
+
`(function() {
|
|
8216
|
+
var vw = window.innerWidth || document.documentElement.clientWidth || 0;
|
|
8217
|
+
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
8218
|
+
var sy = window.scrollY || window.pageYOffset || 0;
|
|
8219
|
+
|
|
8220
|
+
function inViewport(el) {
|
|
8221
|
+
var r = el.getBoundingClientRect();
|
|
8222
|
+
return r.bottom > 0 && r.top < vh && r.right > 0 && r.left < vw && r.width > 0 && r.height > 0;
|
|
8223
|
+
}
|
|
8224
|
+
|
|
8225
|
+
function label(el) {
|
|
8226
|
+
return (el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 120);
|
|
8227
|
+
}
|
|
8228
|
+
|
|
8229
|
+
// Headings visible on screen
|
|
8230
|
+
var headings = [];
|
|
8231
|
+
document.querySelectorAll('h1, h2, h3, h4').forEach(function(h) {
|
|
8232
|
+
if (!inViewport(h)) return;
|
|
8233
|
+
var t = (h.textContent || '').trim();
|
|
8234
|
+
if (t && t.length < 200) headings.push(h.tagName.toLowerCase() + ': ' + t);
|
|
8235
|
+
});
|
|
8236
|
+
|
|
8237
|
+
// Links visible on screen (deduplicated by text)
|
|
8238
|
+
var links = [];
|
|
8239
|
+
var seenLinks = {};
|
|
8240
|
+
var idx = 1;
|
|
8241
|
+
document.querySelectorAll('a[href]').forEach(function(a) {
|
|
8242
|
+
if (!inViewport(a)) return;
|
|
8243
|
+
var t = (a.textContent || '').trim().slice(0, 100);
|
|
8244
|
+
if (!t || t.length < 2 || seenLinks[t]) return;
|
|
8245
|
+
seenLinks[t] = true;
|
|
8246
|
+
links.push({ text: t, href: (a.href || '').slice(0, 200), index: idx++ });
|
|
8247
|
+
});
|
|
8248
|
+
|
|
8249
|
+
// Buttons visible on screen
|
|
8250
|
+
var buttons = [];
|
|
8251
|
+
document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]').forEach(function(b) {
|
|
8252
|
+
if (!inViewport(b)) return;
|
|
8253
|
+
var t = label(b);
|
|
8254
|
+
if (!t || t.length < 1) return;
|
|
8255
|
+
buttons.push({ text: t, index: idx++ });
|
|
8256
|
+
});
|
|
8257
|
+
|
|
8258
|
+
// Input fields visible on screen
|
|
8259
|
+
var inputs = [];
|
|
8260
|
+
document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea').forEach(function(inp) {
|
|
8261
|
+
if (!inViewport(inp)) return;
|
|
8262
|
+
var type = (inp.type || inp.tagName.toLowerCase() || '').toLowerCase();
|
|
8263
|
+
var lbl = (inp.getAttribute('aria-label') || inp.getAttribute('placeholder') || inp.name || '').trim();
|
|
8264
|
+
inputs.push({ type: type, label: lbl.slice(0, 80), placeholder: (inp.getAttribute('placeholder') || '').slice(0, 80), index: idx++ });
|
|
8265
|
+
});
|
|
8266
|
+
|
|
8267
|
+
// Content snapshot from main content area using textContent (instant, no reflow)
|
|
8268
|
+
var roots = ['main', 'article', '[role="main"]', '#content', '.content', '.story-body'];
|
|
8269
|
+
var contentRoot = null;
|
|
8270
|
+
for (var i = 0; i < roots.length; i++) {
|
|
8271
|
+
contentRoot = document.querySelector(roots[i]);
|
|
8272
|
+
if (contentRoot && contentRoot.textContent.trim().length > 50) break;
|
|
8273
|
+
contentRoot = null;
|
|
8274
|
+
}
|
|
8275
|
+
var snippet = '';
|
|
8276
|
+
if (contentRoot) {
|
|
8277
|
+
snippet = contentRoot.textContent.replace(/[ \\t]+/g, ' ').replace(/(\\n\\s*){3,}/g, '\\n\\n').trim().slice(0, 8000);
|
|
8278
|
+
} else {
|
|
8279
|
+
// Fallback: grab text from visible elements only
|
|
8280
|
+
var parts = [];
|
|
8281
|
+
document.querySelectorAll('h1, h2, h3, p, li, td, span, div').forEach(function(el) {
|
|
8282
|
+
if (parts.length > 100 || !inViewport(el)) return;
|
|
8283
|
+
var t = (el.textContent || '').trim();
|
|
8284
|
+
if (t.length > 10 && t.length < 500) parts.push(t);
|
|
8285
|
+
});
|
|
8286
|
+
snippet = parts.join('\\n').slice(0, 8000);
|
|
8287
|
+
}
|
|
8288
|
+
|
|
8289
|
+
return {
|
|
8290
|
+
title: document.title || '',
|
|
8291
|
+
url: location.href,
|
|
8292
|
+
headings: headings.slice(0, 20),
|
|
8293
|
+
links: links.slice(0, 40),
|
|
8294
|
+
buttons: buttons.slice(0, 20),
|
|
8295
|
+
inputs: inputs.slice(0, 15),
|
|
8296
|
+
contentSnippet: snippet,
|
|
8297
|
+
viewportHeight: vh,
|
|
8298
|
+
viewportWidth: vw,
|
|
8299
|
+
scrollY: Math.round(sy),
|
|
8300
|
+
};
|
|
8301
|
+
})()`,
|
|
8302
|
+
{ timeoutMs: 2500, label: "glance-extract" }
|
|
8303
|
+
);
|
|
8304
|
+
const elapsed = Date.now() - startMs;
|
|
8305
|
+
if (!result || result === PAGE_SCRIPT_TIMEOUT) {
|
|
8306
|
+
return [
|
|
8307
|
+
`# ${wc.getTitle() || "(untitled)"}`,
|
|
8308
|
+
`URL: ${wc.getURL()}`,
|
|
8309
|
+
"",
|
|
8310
|
+
"[read_page mode=glance — page JS thread is completely blocked, no content available]",
|
|
8311
|
+
"[Try: click or type_text to interact directly, or wait a few seconds and retry]"
|
|
8312
|
+
].join("\n");
|
|
8313
|
+
}
|
|
8314
|
+
const sections = [
|
|
8315
|
+
`# ${result.title}`,
|
|
8316
|
+
`URL: ${result.url}`,
|
|
8317
|
+
`Viewport: ${result.viewportWidth}×${result.viewportHeight} scrollY=${result.scrollY}`,
|
|
8318
|
+
`[read_page mode=glance — ${elapsed}ms, showing what's visible on screen]`
|
|
8319
|
+
];
|
|
8320
|
+
if (result.headings.length > 0) {
|
|
8321
|
+
sections.push("", "## Headings", ...result.headings);
|
|
8322
|
+
}
|
|
8323
|
+
if (result.inputs.length > 0) {
|
|
8324
|
+
sections.push("", "## Input Fields");
|
|
8325
|
+
for (const inp of result.inputs) {
|
|
8326
|
+
const desc = inp.label || inp.placeholder || inp.type;
|
|
8327
|
+
sections.push(` [#${inp.index}] ${inp.type}: ${desc}`);
|
|
8328
|
+
}
|
|
8329
|
+
}
|
|
8330
|
+
if (result.buttons.length > 0) {
|
|
8331
|
+
sections.push("", "## Buttons");
|
|
8332
|
+
for (const btn of result.buttons) {
|
|
8333
|
+
sections.push(` [#${btn.index}] ${btn.text}`);
|
|
8334
|
+
}
|
|
8335
|
+
}
|
|
8336
|
+
if (result.links.length > 0) {
|
|
8337
|
+
sections.push("", "## Visible Links");
|
|
8338
|
+
for (const link of result.links) {
|
|
8339
|
+
sections.push(` [#${link.index}] ${link.text}`);
|
|
8340
|
+
}
|
|
8341
|
+
}
|
|
8342
|
+
if (result.contentSnippet) {
|
|
8343
|
+
const truncated = result.contentSnippet.length > 6e3 ? result.contentSnippet.slice(0, 6e3) + "\n[truncated]" : result.contentSnippet;
|
|
8344
|
+
sections.push("", "## Page Content (viewport)", "", truncated);
|
|
8345
|
+
}
|
|
8346
|
+
return sections.join("\n");
|
|
8347
|
+
}
|
|
7783
8348
|
function normalizeReadPageMode(mode, pageContent) {
|
|
7784
8349
|
if (typeof mode === "string") {
|
|
7785
8350
|
const normalized = mode.trim().toLowerCase();
|
|
7786
8351
|
if (normalized === "debug") return "debug";
|
|
8352
|
+
if (normalized === "glance") return "glance";
|
|
7787
8353
|
if (normalized === "full" || normalized === "summary" || normalized === "interactives_only" || normalized === "forms_only" || normalized === "text_only" || normalized === "visible_only" || normalized === "results_only") {
|
|
7788
8354
|
return normalized;
|
|
7789
8355
|
}
|
|
@@ -7805,9 +8371,6 @@ async function executePageScript(wc, script, options) {
|
|
|
7805
8371
|
})
|
|
7806
8372
|
]);
|
|
7807
8373
|
if (result === PAGE_SCRIPT_TIMEOUT) {
|
|
7808
|
-
console.log(
|
|
7809
|
-
`[Vessel pageScript] timed out after ${timeoutMs}ms (${options?.label || "page-script"})`
|
|
7810
|
-
);
|
|
7811
8374
|
return PAGE_SCRIPT_TIMEOUT;
|
|
7812
8375
|
}
|
|
7813
8376
|
return result;
|
|
@@ -7822,9 +8385,6 @@ async function executePageScript(wc, script, options) {
|
|
|
7822
8385
|
function waitForLoad$1(wc, timeout = 5e3) {
|
|
7823
8386
|
return new Promise((resolve) => {
|
|
7824
8387
|
let finished = false;
|
|
7825
|
-
console.log(
|
|
7826
|
-
`[Vessel waitForLoad] started, isLoading=${wc.isLoading()}, timeout=${timeout}`
|
|
7827
|
-
);
|
|
7828
8388
|
const cleanup = () => {
|
|
7829
8389
|
wc.removeListener("did-finish-load", onLoadEvent);
|
|
7830
8390
|
wc.removeListener("did-stop-loading", onLoadEvent);
|
|
@@ -7833,23 +8393,19 @@ function waitForLoad$1(wc, timeout = 5e3) {
|
|
|
7833
8393
|
const finish = (reason) => {
|
|
7834
8394
|
if (finished) return;
|
|
7835
8395
|
finished = true;
|
|
7836
|
-
console.log(`[Vessel waitForLoad] finished: ${reason}`);
|
|
7837
8396
|
clearTimeout(timer);
|
|
7838
8397
|
cleanup();
|
|
7839
8398
|
resolve();
|
|
7840
8399
|
};
|
|
7841
8400
|
const onLoadEvent = () => {
|
|
7842
8401
|
const loading = wc.isLoading();
|
|
7843
|
-
console.log(
|
|
7844
|
-
`[Vessel waitForLoad] load event fired, isLoading=${loading}`
|
|
7845
|
-
);
|
|
7846
8402
|
if (!loading) {
|
|
7847
|
-
finish(
|
|
8403
|
+
finish();
|
|
7848
8404
|
}
|
|
7849
8405
|
};
|
|
7850
|
-
const timer = setTimeout(() => finish(
|
|
8406
|
+
const timer = setTimeout(() => finish(), timeout);
|
|
7851
8407
|
if (!wc.isLoading()) {
|
|
7852
|
-
finish(
|
|
8408
|
+
finish();
|
|
7853
8409
|
return;
|
|
7854
8410
|
}
|
|
7855
8411
|
wc.on("did-finish-load", onLoadEvent);
|
|
@@ -7911,10 +8467,62 @@ function waitForPotentialNavigation$1(wc, beforeUrl, timeout = 2500) {
|
|
|
7911
8467
|
wc.on("page-title-updated", onNativeChange);
|
|
7912
8468
|
});
|
|
7913
8469
|
}
|
|
7914
|
-
function getPostNavSummary(wc) {
|
|
8470
|
+
async function getPostNavSummary(wc) {
|
|
7915
8471
|
const title = wc.getTitle();
|
|
7916
|
-
|
|
8472
|
+
const titleLine = title ? `
|
|
7917
8473
|
Page title: ${title}` : "";
|
|
8474
|
+
const overlaySignal = await executePageScript(
|
|
8475
|
+
wc,
|
|
8476
|
+
`(function() {
|
|
8477
|
+
var signals = [];
|
|
8478
|
+
// Body scroll lock is a strong overlay signal
|
|
8479
|
+
var bodyStyle = window.getComputedStyle(document.body);
|
|
8480
|
+
var htmlStyle = window.getComputedStyle(document.documentElement);
|
|
8481
|
+
if (bodyStyle.overflow === 'hidden' || htmlStyle.overflow === 'hidden') {
|
|
8482
|
+
signals.push('body-scroll-locked');
|
|
8483
|
+
}
|
|
8484
|
+
// Check for known consent manager containers
|
|
8485
|
+
var consentSelectors = [
|
|
8486
|
+
'#onetrust-consent-sdk', '#CybotCookiebotDialog', '[class*="consent-banner"]',
|
|
8487
|
+
'[class*="cookie-banner"]', '[class*="privacy-banner"]', '[id*="consent"]',
|
|
8488
|
+
'[class*="gdpr"]', '[data-testid*="consent"]', '[data-testid*="cookie"]',
|
|
8489
|
+
'.fc-consent-root', '#sp_message_container_', '[id*="trustarc"]',
|
|
8490
|
+
'[class*="cmp-"]', '[id*="cmp-"]'
|
|
8491
|
+
];
|
|
8492
|
+
for (var i = 0; i < consentSelectors.length; i++) {
|
|
8493
|
+
try {
|
|
8494
|
+
var el = document.querySelector(consentSelectors[i]);
|
|
8495
|
+
if (el && el.offsetHeight > 50) {
|
|
8496
|
+
signals.push('consent-banner:' + consentSelectors[i]);
|
|
8497
|
+
break;
|
|
8498
|
+
}
|
|
8499
|
+
} catch(e) {}
|
|
8500
|
+
}
|
|
8501
|
+
// Check for large fixed/sticky elements covering viewport
|
|
8502
|
+
var vw = window.innerWidth || 0;
|
|
8503
|
+
var vh = window.innerHeight || 0;
|
|
8504
|
+
var vpArea = Math.max(1, vw * vh);
|
|
8505
|
+
var els = document.querySelectorAll('dialog[open], [role="dialog"], [aria-modal="true"]');
|
|
8506
|
+
if (els.length > 0) signals.push('dialog-open');
|
|
8507
|
+
if (signals.length === 0) {
|
|
8508
|
+
var fixed = document.querySelectorAll('div[style*="position: fixed"], div[style*="position:fixed"]');
|
|
8509
|
+
for (var j = 0; j < fixed.length && j < 20; j++) {
|
|
8510
|
+
var r = fixed[j].getBoundingClientRect();
|
|
8511
|
+
if ((r.width * r.height) / vpArea > 0.3) {
|
|
8512
|
+
signals.push('large-fixed-overlay');
|
|
8513
|
+
break;
|
|
8514
|
+
}
|
|
8515
|
+
}
|
|
8516
|
+
}
|
|
8517
|
+
return signals.length > 0 ? signals.join(', ') : null;
|
|
8518
|
+
})()`,
|
|
8519
|
+
{ timeoutMs: 1500, label: "overlay-probe" }
|
|
8520
|
+
);
|
|
8521
|
+
if (overlaySignal && overlaySignal !== PAGE_SCRIPT_TIMEOUT) {
|
|
8522
|
+
return `${titleLine}
|
|
8523
|
+
WARNING: Blocking overlay detected (${overlaySignal}). Call clear_overlays or accept_cookies before reading the page.`;
|
|
8524
|
+
}
|
|
8525
|
+
return titleLine;
|
|
7918
8526
|
}
|
|
7919
8527
|
async function scrollPage$1(wc, deltaY) {
|
|
7920
8528
|
const getScrollY = async () => {
|
|
@@ -8363,6 +8971,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
8363
8971
|
}
|
|
8364
8972
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
8365
8973
|
try {
|
|
8974
|
+
assertSafeURL(snapshot.url);
|
|
8366
8975
|
await wc.loadURL(snapshot.url);
|
|
8367
8976
|
await waitForLoad$1(wc, 3e3);
|
|
8368
8977
|
return;
|
|
@@ -8485,19 +9094,12 @@ ${shadowOverlay}` : result;
|
|
|
8485
9094
|
const elInfo = await describeElementForClick$1(wc, selector);
|
|
8486
9095
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
8487
9096
|
const cartMatch = isAddToCartText(elInfo.text);
|
|
8488
|
-
console.log(
|
|
8489
|
-
`[Vessel cart-guard] text="${elInfo.text}" cartMatch=${cartMatch} url=${beforeUrl} hasPrior=${recentCartClicks.has(beforeUrl)}`
|
|
8490
|
-
);
|
|
8491
9097
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
8492
|
-
console.log(`[Vessel cart-guard] BLOCKED duplicate add-to-cart click`);
|
|
8493
9098
|
return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
8494
9099
|
}
|
|
8495
9100
|
if (!cartMatch && recentCartClicks.has(beforeUrl)) {
|
|
8496
9101
|
const dialogActions = await getCartDialogActions$1(wc);
|
|
8497
9102
|
if (dialogActions) {
|
|
8498
|
-
console.log(
|
|
8499
|
-
`[Vessel cart-guard] BLOCKED background click while cart dialog is open`
|
|
8500
|
-
);
|
|
8501
9103
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
8502
9104
|
${dialogActions}
|
|
8503
9105
|
Click one of these dialog actions instead.`;
|
|
@@ -8510,7 +9112,6 @@ Click one of these dialog actions instead.`;
|
|
|
8510
9112
|
}
|
|
8511
9113
|
}
|
|
8512
9114
|
if (cartMatch) {
|
|
8513
|
-
console.log(`[Vessel cart-guard] RECORDED cart click for url=${beforeUrl}`);
|
|
8514
9115
|
recordCartClick(beforeUrl, elInfo.text);
|
|
8515
9116
|
}
|
|
8516
9117
|
const clickText = `Clicked: ${elInfo.text}`;
|
|
@@ -8750,9 +9351,6 @@ async function dismissPopup$1(wc) {
|
|
|
8750
9351
|
{ timeoutMs: 1500, label: "cart dialog continue shopping" }
|
|
8751
9352
|
);
|
|
8752
9353
|
if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
8753
|
-
console.log(
|
|
8754
|
-
`[Vessel cart-guard] dismiss_popup auto-clicked dialog action: ${continueResult}`
|
|
8755
|
-
);
|
|
8756
9354
|
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
8757
9355
|
}
|
|
8758
9356
|
const dialogActions = await getCartDialogActions$1(wc);
|
|
@@ -9007,6 +9605,71 @@ async function clickOverlayCandidate(wc, action) {
|
|
|
9007
9605
|
const result = await clickResolvedSelector$1(wc, action.selector);
|
|
9008
9606
|
return `${action.label || action.selector}: ${result}`;
|
|
9009
9607
|
}
|
|
9608
|
+
async function tryDismissConsentIframe(wc) {
|
|
9609
|
+
try {
|
|
9610
|
+
const hasSignal = await executePageScript(
|
|
9611
|
+
wc,
|
|
9612
|
+
`(function() {
|
|
9613
|
+
var bs = window.getComputedStyle(document.body);
|
|
9614
|
+
var hs = window.getComputedStyle(document.documentElement);
|
|
9615
|
+
if (bs.overflow === 'hidden' || hs.overflow === 'hidden') return true;
|
|
9616
|
+
var sels = '#onetrust-consent-sdk, [class*="consent"], [class*="cookie-banner"], [id*="consent"], [id*="sp_message"], .fc-consent-root, [class*="cmp-"]';
|
|
9617
|
+
var el = document.querySelector(sels);
|
|
9618
|
+
return !!(el && el.offsetHeight > 20);
|
|
9619
|
+
})()`,
|
|
9620
|
+
{ timeoutMs: 1e3, label: "iframe-consent-signal" }
|
|
9621
|
+
);
|
|
9622
|
+
if (!hasSignal || hasSignal === PAGE_SCRIPT_TIMEOUT) return null;
|
|
9623
|
+
const frames = wc.mainFrame.framesInSubtree;
|
|
9624
|
+
for (const frame of frames) {
|
|
9625
|
+
if (frame === wc.mainFrame) continue;
|
|
9626
|
+
try {
|
|
9627
|
+
const result = await frame.executeJavaScript(`
|
|
9628
|
+
(function() {
|
|
9629
|
+
var selectors = [
|
|
9630
|
+
'button[title*="Accept"], button[title*="Agree"], button[title*="OK"]',
|
|
9631
|
+
'[class*="accept"], [class*="agree"], [class*="consent-accept"]',
|
|
9632
|
+
'button[aria-label*="accept" i], button[aria-label*="agree" i]',
|
|
9633
|
+
'.sp_choice_type_11', '.message-component.message-button',
|
|
9634
|
+
];
|
|
9635
|
+
// Try selectors first
|
|
9636
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
9637
|
+
try {
|
|
9638
|
+
var els = document.querySelectorAll(selectors[i]);
|
|
9639
|
+
for (var j = 0; j < els.length; j++) {
|
|
9640
|
+
var el = els[j];
|
|
9641
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
9642
|
+
var text = (el.textContent || '').trim().toLowerCase();
|
|
9643
|
+
if (/accept|agree|consent|got it|ok|continue|i understand/i.test(text) || el.offsetHeight > 0) {
|
|
9644
|
+
el.click();
|
|
9645
|
+
return 'Clicked iframe consent button: ' + text.slice(0, 60);
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
9648
|
+
} catch(e) {}
|
|
9649
|
+
}
|
|
9650
|
+
// Text-match fallback on all buttons
|
|
9651
|
+
var buttons = document.querySelectorAll('button, [role="button"], a.message-component');
|
|
9652
|
+
for (var k = 0; k < buttons.length; k++) {
|
|
9653
|
+
var btn = buttons[k];
|
|
9654
|
+
var label = (btn.textContent || '').trim().toLowerCase();
|
|
9655
|
+
if (/^(accept|agree|accept all|i agree|i accept|ok|got it|allow|continue|yes)$/i.test(label) ||
|
|
9656
|
+
/accept all|agree and|accept & continue|accept and continue/i.test(label)) {
|
|
9657
|
+
btn.click();
|
|
9658
|
+
return 'Clicked iframe consent button: ' + label.slice(0, 60);
|
|
9659
|
+
}
|
|
9660
|
+
}
|
|
9661
|
+
return null;
|
|
9662
|
+
})()
|
|
9663
|
+
`);
|
|
9664
|
+
if (result) return result;
|
|
9665
|
+
} catch {
|
|
9666
|
+
continue;
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
} catch {
|
|
9670
|
+
}
|
|
9671
|
+
return null;
|
|
9672
|
+
}
|
|
9010
9673
|
async function clearOverlays(wc, strategy = "auto") {
|
|
9011
9674
|
const steps = [];
|
|
9012
9675
|
let cleared = 0;
|
|
@@ -9018,7 +9681,15 @@ async function clearOverlays(wc, strategy = "auto") {
|
|
|
9018
9681
|
(overlay2) => overlay2.blocksInteraction
|
|
9019
9682
|
);
|
|
9020
9683
|
if (blockingOverlays.length === 0) {
|
|
9021
|
-
if (cleared === 0)
|
|
9684
|
+
if (cleared === 0) {
|
|
9685
|
+
const iframeResult = await tryDismissConsentIframe(wc);
|
|
9686
|
+
if (iframeResult) {
|
|
9687
|
+
steps.push(`Iframe consent: ${iframeResult}`);
|
|
9688
|
+
await sleep$1(500);
|
|
9689
|
+
return steps.join("\n");
|
|
9690
|
+
}
|
|
9691
|
+
return "No blocking overlays detected";
|
|
9692
|
+
}
|
|
9022
9693
|
steps.push(`Overlays remaining: ${beforeState.total}`);
|
|
9023
9694
|
steps.push("Page still blocked: false");
|
|
9024
9695
|
return steps.join("\n");
|
|
@@ -9870,6 +10541,7 @@ async function submitForm$1(wc, args) {
|
|
|
9870
10541
|
if (formInfo.params) {
|
|
9871
10542
|
url.search = formInfo.params;
|
|
9872
10543
|
}
|
|
10544
|
+
assertSafeURL(url.toString());
|
|
9873
10545
|
wc.loadURL(url.toString());
|
|
9874
10546
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
9875
10547
|
const afterUrl = wc.getURL();
|
|
@@ -10039,6 +10711,7 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
10039
10711
|
"dismiss_popup",
|
|
10040
10712
|
"clear_overlays",
|
|
10041
10713
|
"read_page",
|
|
10714
|
+
"screenshot",
|
|
10042
10715
|
"wait_for",
|
|
10043
10716
|
"create_checkpoint",
|
|
10044
10717
|
"restore_checkpoint",
|
|
@@ -10118,6 +10791,19 @@ async function executeAction(name, args, ctx) {
|
|
|
10118
10791
|
dangerous: isDangerousAction$1(name),
|
|
10119
10792
|
executor: async () => {
|
|
10120
10793
|
switch (name) {
|
|
10794
|
+
case "screenshot": {
|
|
10795
|
+
if (!wc) return "Error: No active tab";
|
|
10796
|
+
const screenshotStart = Date.now();
|
|
10797
|
+
const shot = await captureScreenshot(wc);
|
|
10798
|
+
if (!shot.ok) return `Error: ${shot.error}`;
|
|
10799
|
+
const screenshotMs = Date.now() - screenshotStart;
|
|
10800
|
+
const title = wc.getTitle() || "(untitled)";
|
|
10801
|
+
const url = wc.getURL();
|
|
10802
|
+
return makeImageResult(
|
|
10803
|
+
shot.base64,
|
|
10804
|
+
`Screenshot of "${title}" (${url}) — ${shot.width}x${shot.height}, captured in ${screenshotMs}ms. Analyze the image to understand the current visual state of the page.`
|
|
10805
|
+
);
|
|
10806
|
+
}
|
|
10121
10807
|
case "current_tab": {
|
|
10122
10808
|
const active = ctx.tabManager.getActiveTab();
|
|
10123
10809
|
const activeId = ctx.tabManager.getActiveTabId();
|
|
@@ -10164,7 +10850,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10164
10850
|
const created = ctx.tabManager.getActiveTab();
|
|
10165
10851
|
if (created) {
|
|
10166
10852
|
await waitForLoad$1(created.view.webContents);
|
|
10167
|
-
return `Created tab ${createdId}${getPostNavSummary(created.view.webContents)}`;
|
|
10853
|
+
return `Created tab ${createdId}${await getPostNavSummary(created.view.webContents)}`;
|
|
10168
10854
|
}
|
|
10169
10855
|
return `Created tab ${createdId}`;
|
|
10170
10856
|
}
|
|
@@ -10176,7 +10862,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10176
10862
|
}
|
|
10177
10863
|
ctx.tabManager.navigateTab(tabId, args.url);
|
|
10178
10864
|
await waitForLoad$1(wc);
|
|
10179
|
-
return `Navigated to ${wc.getURL()}${getPostNavSummary(wc)}`;
|
|
10865
|
+
return `Navigated to ${wc.getURL()}${await getPostNavSummary(wc)}`;
|
|
10180
10866
|
}
|
|
10181
10867
|
case "go_back": {
|
|
10182
10868
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -10187,7 +10873,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10187
10873
|
ctx.tabManager.goBack(tabId);
|
|
10188
10874
|
await waitForLoad$1(wc);
|
|
10189
10875
|
const afterUrl = wc.getURL();
|
|
10190
|
-
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
10876
|
+
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${await getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
10191
10877
|
}
|
|
10192
10878
|
case "go_forward": {
|
|
10193
10879
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -10198,7 +10884,7 @@ async function executeAction(name, args, ctx) {
|
|
|
10198
10884
|
ctx.tabManager.goForward(tabId);
|
|
10199
10885
|
await waitForLoad$1(wc);
|
|
10200
10886
|
const afterUrl = wc.getURL();
|
|
10201
|
-
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
10887
|
+
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${await getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
10202
10888
|
}
|
|
10203
10889
|
case "reload": {
|
|
10204
10890
|
if (!wc || !tabId) return "Error: No active tab";
|
|
@@ -10317,14 +11003,16 @@ async function executeAction(name, args, ctx) {
|
|
|
10317
11003
|
}
|
|
10318
11004
|
case "read_page": {
|
|
10319
11005
|
if (!wc) return "Error: No active tab";
|
|
10320
|
-
|
|
11006
|
+
const requestedGlance = typeof args.mode === "string" && args.mode.trim().toLowerCase() === "glance";
|
|
11007
|
+
if (requestedGlance) {
|
|
11008
|
+
return glanceExtract(wc);
|
|
11009
|
+
}
|
|
10321
11010
|
let content = null;
|
|
10322
11011
|
try {
|
|
10323
11012
|
content = await Promise.race([
|
|
10324
11013
|
extractContent(wc),
|
|
10325
11014
|
new Promise(
|
|
10326
11015
|
(resolve) => setTimeout(() => {
|
|
10327
|
-
console.log("[Vessel read_page] timeout fired, falling back");
|
|
10328
11016
|
resolve(null);
|
|
10329
11017
|
}, 6e3)
|
|
10330
11018
|
)
|
|
@@ -10332,10 +11020,27 @@ async function executeAction(name, args, ctx) {
|
|
|
10332
11020
|
} catch {
|
|
10333
11021
|
content = null;
|
|
10334
11022
|
}
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
11023
|
+
if (!content || content.content.length === 0) {
|
|
11024
|
+
try {
|
|
11025
|
+
const iframeResult = await Promise.race([
|
|
11026
|
+
tryDismissConsentIframe(wc),
|
|
11027
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 2e3))
|
|
11028
|
+
]);
|
|
11029
|
+
if (iframeResult) {
|
|
11030
|
+
await sleep$1(500);
|
|
11031
|
+
try {
|
|
11032
|
+
content = await Promise.race([
|
|
11033
|
+
extractContent(wc),
|
|
11034
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
11035
|
+
]);
|
|
11036
|
+
} catch {
|
|
11037
|
+
content = null;
|
|
11038
|
+
}
|
|
11039
|
+
}
|
|
11040
|
+
} catch {
|
|
11041
|
+
}
|
|
11042
|
+
}
|
|
11043
|
+
if (content && content.content.length > 0) {
|
|
10339
11044
|
const liveSelectionSection = formatLiveSelectionSection(
|
|
10340
11045
|
await captureLiveHighlightSnapshot(
|
|
10341
11046
|
wc,
|
|
@@ -10367,16 +11072,7 @@ ${truncated}`;
|
|
|
10367
11072
|
`Need more detail? Escalate with read_page(mode="debug") only if the narrow modes are insufficient.`
|
|
10368
11073
|
].filter(Boolean).join("\n\n");
|
|
10369
11074
|
}
|
|
10370
|
-
|
|
10371
|
-
const url = wc.getURL();
|
|
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");
|
|
11075
|
+
return glanceExtract(wc);
|
|
10380
11076
|
}
|
|
10381
11077
|
case "wait_for": {
|
|
10382
11078
|
if (!wc) return "Error: No active tab";
|
|
@@ -11078,6 +11774,7 @@ ${steps.join("\n")}`;
|
|
|
11078
11774
|
try {
|
|
11079
11775
|
const url = new URL(searchInfo.formAction);
|
|
11080
11776
|
url.searchParams.set(searchInfo.inputName || "q", query);
|
|
11777
|
+
assertSafeURL(url.toString());
|
|
11081
11778
|
wc.loadURL(url.toString());
|
|
11082
11779
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
11083
11780
|
afterUrl = wc.getURL();
|
|
@@ -11147,9 +11844,20 @@ ${steps.join("\n")}`;
|
|
|
11147
11844
|
'[aria-label="Accept cookies"]',
|
|
11148
11845
|
'[aria-label="Accept all cookies"]',
|
|
11149
11846
|
'[data-testid="cookie-accept"]',
|
|
11847
|
+
// CNN / WarnerMedia / common consent SDKs
|
|
11848
|
+
'[data-testid="consent-accept"]',
|
|
11849
|
+
'[data-testid="accept-all"]',
|
|
11850
|
+
'button[class*="consent"][class*="accept"]',
|
|
11851
|
+
'button[class*="privacy"][class*="accept"]',
|
|
11852
|
+
'.fc-cta-consent',
|
|
11853
|
+
'#sp_choice_button_accept',
|
|
11854
|
+
'.message-component.message-button.no-children.focusable.sp_choice_type_11',
|
|
11855
|
+
'[class*="truste"] [class*="accept"]',
|
|
11856
|
+
'[id*="consent-accept"]',
|
|
11857
|
+
'[class*="cmp-accept"]',
|
|
11150
11858
|
];
|
|
11151
11859
|
// Also try text-matching on buttons
|
|
11152
|
-
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'consent'];
|
|
11860
|
+
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
11861
|
for (var i = 0; i < selectors.length; i++) {
|
|
11154
11862
|
var el = document.querySelector(selectors[i]);
|
|
11155
11863
|
if (el && el instanceof HTMLElement) { el.click(); return "Dismissed cookie banner via: " + selectors[i]; }
|
|
@@ -11175,7 +11883,10 @@ ${steps.join("\n")}`;
|
|
|
11175
11883
|
if (dismissed === PAGE_SCRIPT_TIMEOUT) {
|
|
11176
11884
|
return pageBusyError("accept_cookies");
|
|
11177
11885
|
}
|
|
11178
|
-
|
|
11886
|
+
if (dismissed) return dismissed;
|
|
11887
|
+
const iframeResult = await tryDismissConsentIframe(wc);
|
|
11888
|
+
if (iframeResult) return iframeResult;
|
|
11889
|
+
return "No cookie consent banner detected. Try dismiss_popup for other overlays.";
|
|
11179
11890
|
}
|
|
11180
11891
|
case "extract_table": {
|
|
11181
11892
|
if (!wc) return "Error: No active tab";
|
|
@@ -11273,23 +11984,19 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
11273
11984
|
const flowCtx = ctx.runtime.getFlowContext();
|
|
11274
11985
|
return result + await getPostActionState$1(ctx, name) + flowCtx;
|
|
11275
11986
|
}
|
|
11276
|
-
async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd, tabManager,
|
|
11987
|
+
async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd, tabManager, runtime2, history) {
|
|
11277
11988
|
const lowerQuery = query.toLowerCase().trim();
|
|
11278
11989
|
const isSummarize = lowerQuery.startsWith("summarize") || lowerQuery.startsWith("tldr") || lowerQuery === "summary";
|
|
11279
|
-
if (provider.streamAgentQuery && tabManager && activeWebContents &&
|
|
11990
|
+
if (provider.streamAgentQuery && tabManager && activeWebContents && runtime2) {
|
|
11280
11991
|
try {
|
|
11281
|
-
const extractStart = Date.now();
|
|
11282
11992
|
const pageContent = await extractContent(activeWebContents);
|
|
11283
|
-
console.log(
|
|
11284
|
-
`[Vessel Agent] initial extractContent completed in ${Date.now() - extractStart}ms, contentLen=${pageContent.content.length}`
|
|
11285
|
-
);
|
|
11286
11993
|
const pageType = detectPageType(pageContent);
|
|
11287
11994
|
const defaultReadMode = chooseAgentReadMode(pageContent);
|
|
11288
11995
|
const structuredContext = buildScopedContext(
|
|
11289
11996
|
pageContent,
|
|
11290
11997
|
defaultReadMode
|
|
11291
11998
|
);
|
|
11292
|
-
const runtimeState =
|
|
11999
|
+
const runtimeState = runtime2.getState();
|
|
11293
12000
|
const recentCheckpoints = runtimeState.checkpoints.slice(-3).map((item) => `- ${item.name} (${item.id})`).join("\n");
|
|
11294
12001
|
const activeTabTitle = pageContent.title || "(untitled)";
|
|
11295
12002
|
const activeTabUrl = pageContent.url || activeWebContents.getURL();
|
|
@@ -11332,8 +12039,11 @@ Instructions:
|
|
|
11332
12039
|
- 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
12040
|
- The page brief you start with is intentionally sparse. It is optimized for navigation speed, not completeness.
|
|
11334
12041
|
- 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.
|
|
12042
|
+
- 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.
|
|
12043
|
+
- 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
12044
|
- Use read_page(mode="debug") only as a last resort when the narrower modes are insufficient.
|
|
12045
|
+
- If read_page returns empty or times out, do NOT retry with the same mode. Switch to read_page(mode="glance") or use screenshot to see the page visually.
|
|
12046
|
+
- Use screenshot when you need to see exactly what the user sees — visual layout, rendered content, images, or when text extraction is failing. The screenshot returns the actual rendered page image for visual analysis. It works even when the JS thread is completely blocked.
|
|
11337
12047
|
- 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
12048
|
- 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
12049
|
- 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 +12061,7 @@ Instructions:
|
|
|
11351
12061
|
- 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
12062
|
- 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
12063
|
- NEVER USE EMOJIS unless the user uses them first.`;
|
|
11354
|
-
const actionCtx = { tabManager, runtime };
|
|
12064
|
+
const actionCtx = { tabManager, runtime: runtime2 };
|
|
11355
12065
|
const contextualTools = pruneToolsForContext(
|
|
11356
12066
|
AGENT_TOOLS,
|
|
11357
12067
|
pageType,
|
|
@@ -11779,7 +12489,7 @@ function broadcastState(tabManager) {
|
|
|
11779
12489
|
const tabId = tabManager.getActiveTabId();
|
|
11780
12490
|
stateListener(getDevToolsPanelState(tabId));
|
|
11781
12491
|
}
|
|
11782
|
-
async function withDevToolsAction(
|
|
12492
|
+
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
11783
12493
|
const activityEntry = {
|
|
11784
12494
|
id: ++activityCounter,
|
|
11785
12495
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11796,7 +12506,7 @@ async function withDevToolsAction(runtime, tabManager, name, args, executor) {
|
|
|
11796
12506
|
broadcastState(tabManager);
|
|
11797
12507
|
const startTime = Date.now();
|
|
11798
12508
|
try {
|
|
11799
|
-
const result = await
|
|
12509
|
+
const result = await runtime2.runControlledAction({
|
|
11800
12510
|
source: "mcp",
|
|
11801
12511
|
name,
|
|
11802
12512
|
args,
|
|
@@ -11818,7 +12528,7 @@ async function withDevToolsAction(runtime, tabManager, name, args, executor) {
|
|
|
11818
12528
|
return asTextResponse$1(`Error: ${message}`);
|
|
11819
12529
|
}
|
|
11820
12530
|
}
|
|
11821
|
-
function registerDevTools(server, tabManager,
|
|
12531
|
+
function registerDevTools(server, tabManager, runtime2) {
|
|
11822
12532
|
server.registerTool(
|
|
11823
12533
|
"vessel_devtools_console_logs",
|
|
11824
12534
|
{
|
|
@@ -11830,16 +12540,16 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11830
12540
|
search: zod.z.string().optional().describe("Filter entries containing this text (case-insensitive)")
|
|
11831
12541
|
}
|
|
11832
12542
|
},
|
|
11833
|
-
async ({ level, limit, search }) => {
|
|
12543
|
+
async ({ level, limit, search: search2 }) => {
|
|
11834
12544
|
return withDevToolsAction(
|
|
11835
|
-
|
|
12545
|
+
runtime2,
|
|
11836
12546
|
tabManager,
|
|
11837
12547
|
"devtools_console_logs",
|
|
11838
|
-
{ level, limit, search },
|
|
12548
|
+
{ level, limit, search: search2 },
|
|
11839
12549
|
async () => {
|
|
11840
12550
|
const session = getOrCreateSession(tabManager);
|
|
11841
12551
|
await session.ensureConsoleDomain();
|
|
11842
|
-
const entries = session.getConsoleLogs({ level, limit, search });
|
|
12552
|
+
const entries = session.getConsoleLogs({ level, limit, search: search2 });
|
|
11843
12553
|
if (entries.length === 0) {
|
|
11844
12554
|
return "No console entries captured yet. Console monitoring is now active — new entries will be captured as they occur.";
|
|
11845
12555
|
}
|
|
@@ -11856,7 +12566,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11856
12566
|
},
|
|
11857
12567
|
async () => {
|
|
11858
12568
|
return withDevToolsAction(
|
|
11859
|
-
|
|
12569
|
+
runtime2,
|
|
11860
12570
|
tabManager,
|
|
11861
12571
|
"devtools_console_clear",
|
|
11862
12572
|
{},
|
|
@@ -11883,7 +12593,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11883
12593
|
},
|
|
11884
12594
|
async ({ url_pattern, method, status_min, status_max, limit }) => {
|
|
11885
12595
|
return withDevToolsAction(
|
|
11886
|
-
|
|
12596
|
+
runtime2,
|
|
11887
12597
|
tabManager,
|
|
11888
12598
|
"devtools_network_log",
|
|
11889
12599
|
{ url_pattern, method, status_min, status_max, limit },
|
|
@@ -11915,7 +12625,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11915
12625
|
},
|
|
11916
12626
|
async ({ request_id }) => {
|
|
11917
12627
|
return withDevToolsAction(
|
|
11918
|
-
|
|
12628
|
+
runtime2,
|
|
11919
12629
|
tabManager,
|
|
11920
12630
|
"devtools_network_response_body",
|
|
11921
12631
|
{ request_id },
|
|
@@ -11941,7 +12651,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11941
12651
|
},
|
|
11942
12652
|
async () => {
|
|
11943
12653
|
return withDevToolsAction(
|
|
11944
|
-
|
|
12654
|
+
runtime2,
|
|
11945
12655
|
tabManager,
|
|
11946
12656
|
"devtools_network_clear",
|
|
11947
12657
|
{},
|
|
@@ -11965,7 +12675,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11965
12675
|
},
|
|
11966
12676
|
async ({ selector, include_html }) => {
|
|
11967
12677
|
return withDevToolsAction(
|
|
11968
|
-
|
|
12678
|
+
runtime2,
|
|
11969
12679
|
tabManager,
|
|
11970
12680
|
"devtools_query_dom",
|
|
11971
12681
|
{ selector, include_html },
|
|
@@ -11996,7 +12706,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11996
12706
|
},
|
|
11997
12707
|
async ({ selector, properties }) => {
|
|
11998
12708
|
return withDevToolsAction(
|
|
11999
|
-
|
|
12709
|
+
runtime2,
|
|
12000
12710
|
tabManager,
|
|
12001
12711
|
"devtools_get_styles",
|
|
12002
12712
|
{ selector, properties },
|
|
@@ -12024,7 +12734,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
12024
12734
|
},
|
|
12025
12735
|
async ({ selector, attribute, value }) => {
|
|
12026
12736
|
return withDevToolsAction(
|
|
12027
|
-
|
|
12737
|
+
runtime2,
|
|
12028
12738
|
tabManager,
|
|
12029
12739
|
"devtools_modify_dom",
|
|
12030
12740
|
{ selector, attribute, value },
|
|
@@ -12046,7 +12756,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
12046
12756
|
},
|
|
12047
12757
|
async ({ expression }) => {
|
|
12048
12758
|
return withDevToolsAction(
|
|
12049
|
-
|
|
12759
|
+
runtime2,
|
|
12050
12760
|
tabManager,
|
|
12051
12761
|
"devtools_execute_js",
|
|
12052
12762
|
{ expression: expression.slice(0, 200) },
|
|
@@ -12074,7 +12784,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12074
12784
|
},
|
|
12075
12785
|
async ({ type }) => {
|
|
12076
12786
|
return withDevToolsAction(
|
|
12077
|
-
|
|
12787
|
+
runtime2,
|
|
12078
12788
|
tabManager,
|
|
12079
12789
|
"devtools_get_storage",
|
|
12080
12790
|
{ type },
|
|
@@ -12103,7 +12813,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12103
12813
|
},
|
|
12104
12814
|
async ({ type, key, value }) => {
|
|
12105
12815
|
return withDevToolsAction(
|
|
12106
|
-
|
|
12816
|
+
runtime2,
|
|
12107
12817
|
tabManager,
|
|
12108
12818
|
"devtools_set_storage",
|
|
12109
12819
|
{ type, key, value: value ? value.slice(0, 100) : null },
|
|
@@ -12122,7 +12832,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12122
12832
|
},
|
|
12123
12833
|
async () => {
|
|
12124
12834
|
return withDevToolsAction(
|
|
12125
|
-
|
|
12835
|
+
runtime2,
|
|
12126
12836
|
tabManager,
|
|
12127
12837
|
"devtools_performance",
|
|
12128
12838
|
{},
|
|
@@ -12146,7 +12856,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12146
12856
|
},
|
|
12147
12857
|
async ({ type, limit }) => {
|
|
12148
12858
|
return withDevToolsAction(
|
|
12149
|
-
|
|
12859
|
+
runtime2,
|
|
12150
12860
|
tabManager,
|
|
12151
12861
|
"devtools_get_errors",
|
|
12152
12862
|
{ type, limit },
|
|
@@ -12170,7 +12880,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12170
12880
|
},
|
|
12171
12881
|
async () => {
|
|
12172
12882
|
return withDevToolsAction(
|
|
12173
|
-
|
|
12883
|
+
runtime2,
|
|
12174
12884
|
tabManager,
|
|
12175
12885
|
"devtools_clear_errors",
|
|
12176
12886
|
{},
|
|
@@ -12184,6 +12894,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
12184
12894
|
);
|
|
12185
12895
|
}
|
|
12186
12896
|
let httpServer = null;
|
|
12897
|
+
let mcpAuthToken = null;
|
|
12187
12898
|
function asTextResponse(text) {
|
|
12188
12899
|
return { content: [{ type: "text", text }] };
|
|
12189
12900
|
}
|
|
@@ -13121,9 +13832,9 @@ async function getPostActionState(tabManager, name) {
|
|
|
13121
13832
|
}
|
|
13122
13833
|
return "";
|
|
13123
13834
|
}
|
|
13124
|
-
async function withAction(
|
|
13835
|
+
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
13125
13836
|
try {
|
|
13126
|
-
const result = await
|
|
13837
|
+
const result = await runtime2.runControlledAction({
|
|
13127
13838
|
source: "mcp",
|
|
13128
13839
|
name,
|
|
13129
13840
|
args,
|
|
@@ -13132,7 +13843,7 @@ async function withAction(runtime, tabManager, name, args, executor) {
|
|
|
13132
13843
|
executor
|
|
13133
13844
|
});
|
|
13134
13845
|
const stateInfo = await getPostActionState(tabManager, name);
|
|
13135
|
-
const flowCtx =
|
|
13846
|
+
const flowCtx = runtime2.getFlowContext();
|
|
13136
13847
|
return asTextResponse(result + stateInfo + flowCtx);
|
|
13137
13848
|
} catch (error) {
|
|
13138
13849
|
return asTextResponse(
|
|
@@ -13426,6 +14137,7 @@ async function submitForm(wc, index, selector) {
|
|
|
13426
14137
|
if (formInfo.params) {
|
|
13427
14138
|
url.search = formInfo.params;
|
|
13428
14139
|
}
|
|
14140
|
+
assertSafeURL(url.toString());
|
|
13429
14141
|
wc.loadURL(url.toString());
|
|
13430
14142
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13431
14143
|
const afterUrl = wc.getURL();
|
|
@@ -13548,26 +14260,7 @@ async function waitForCondition(wc, text, selector, timeoutMs) {
|
|
|
13548
14260
|
...diagnostic ? { diagnostic } : {}
|
|
13549
14261
|
});
|
|
13550
14262
|
}
|
|
13551
|
-
|
|
13552
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
13553
|
-
await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
|
|
13554
|
-
const image = await wc.capturePage();
|
|
13555
|
-
if (!image.isEmpty()) {
|
|
13556
|
-
const size = image.getSize();
|
|
13557
|
-
const base64 = image.toPNG().toString("base64");
|
|
13558
|
-
if (base64) {
|
|
13559
|
-
return {
|
|
13560
|
-
ok: true,
|
|
13561
|
-
base64,
|
|
13562
|
-
width: size.width,
|
|
13563
|
-
height: size.height
|
|
13564
|
-
};
|
|
13565
|
-
}
|
|
13566
|
-
}
|
|
13567
|
-
}
|
|
13568
|
-
return { ok: false, error: "page image was empty after 3 attempts" };
|
|
13569
|
-
}
|
|
13570
|
-
function registerTools(server, tabManager, runtime) {
|
|
14263
|
+
function registerTools(server, tabManager, runtime2) {
|
|
13571
14264
|
server.registerPrompt(
|
|
13572
14265
|
"vessel-supervisor-brief",
|
|
13573
14266
|
{
|
|
@@ -13575,7 +14268,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13575
14268
|
description: "A reusable prompt for reviewing the current Vessel runtime state."
|
|
13576
14269
|
},
|
|
13577
14270
|
async () => {
|
|
13578
|
-
const state2 =
|
|
14271
|
+
const state2 = runtime2.getState();
|
|
13579
14272
|
const activeTab = getActiveTabSummary(tabManager);
|
|
13580
14273
|
return asPromptResponse(
|
|
13581
14274
|
[
|
|
@@ -13602,7 +14295,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13602
14295
|
contents: [
|
|
13603
14296
|
{
|
|
13604
14297
|
uri: "vessel://runtime/state",
|
|
13605
|
-
text: JSON.stringify(
|
|
14298
|
+
text: JSON.stringify(runtime2.getState(), null, 2)
|
|
13606
14299
|
}
|
|
13607
14300
|
]
|
|
13608
14301
|
})
|
|
@@ -13718,7 +14411,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13718
14411
|
}
|
|
13719
14412
|
},
|
|
13720
14413
|
async ({ text, stream_id, mode, kind, title }) => {
|
|
13721
|
-
const entry =
|
|
14414
|
+
const entry = runtime2.publishTranscript({
|
|
13722
14415
|
source: "mcp",
|
|
13723
14416
|
text,
|
|
13724
14417
|
streamId: stream_id,
|
|
@@ -13748,7 +14441,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13748
14441
|
description: "Clear the in-browser transcript monitor state."
|
|
13749
14442
|
},
|
|
13750
14443
|
async () => {
|
|
13751
|
-
|
|
14444
|
+
runtime2.clearTranscript();
|
|
13752
14445
|
return asTextResponse("Cleared browser transcript monitor.");
|
|
13753
14446
|
}
|
|
13754
14447
|
);
|
|
@@ -13888,7 +14581,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13888
14581
|
`Navigation blocked: ${url} returned ${preCheck.detail || "dead link"}. Try a different URL or go back and choose another link.`
|
|
13889
14582
|
);
|
|
13890
14583
|
}
|
|
13891
|
-
return withAction(
|
|
14584
|
+
return withAction(runtime2, tabManager, "navigate", { url }, async () => {
|
|
13892
14585
|
const id = tabManager.getActiveTabId();
|
|
13893
14586
|
tabManager.navigateTab(id, url);
|
|
13894
14587
|
const { httpStatus } = await waitForLoadWithStatus(
|
|
@@ -13918,7 +14611,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13918
14611
|
return asTextResponse("Error: No active tab");
|
|
13919
14612
|
}
|
|
13920
14613
|
return withAction(
|
|
13921
|
-
|
|
14614
|
+
runtime2,
|
|
13922
14615
|
tabManager,
|
|
13923
14616
|
"set_ad_blocking",
|
|
13924
14617
|
{ enabled, tabId, match, reload },
|
|
@@ -14007,7 +14700,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14007
14700
|
async () => {
|
|
14008
14701
|
const tab = tabManager.getActiveTab();
|
|
14009
14702
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14010
|
-
return withAction(
|
|
14703
|
+
return withAction(runtime2, tabManager, "go_back", {}, async () => {
|
|
14011
14704
|
if (!tab.canGoBack()) {
|
|
14012
14705
|
return "No previous page in history";
|
|
14013
14706
|
}
|
|
@@ -14028,7 +14721,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14028
14721
|
async () => {
|
|
14029
14722
|
const tab = tabManager.getActiveTab();
|
|
14030
14723
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14031
|
-
return withAction(
|
|
14724
|
+
return withAction(runtime2, tabManager, "go_forward", {}, async () => {
|
|
14032
14725
|
if (!tab.canGoForward()) {
|
|
14033
14726
|
return "No forward page in history";
|
|
14034
14727
|
}
|
|
@@ -14049,7 +14742,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14049
14742
|
async () => {
|
|
14050
14743
|
const tab = tabManager.getActiveTab();
|
|
14051
14744
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14052
|
-
return withAction(
|
|
14745
|
+
return withAction(runtime2, tabManager, "reload", {}, async () => {
|
|
14053
14746
|
tabManager.reloadTab(tabManager.getActiveTabId());
|
|
14054
14747
|
await waitForLoad(tab.view.webContents);
|
|
14055
14748
|
return `Reloaded ${tab.view.webContents.getURL()}`;
|
|
@@ -14070,7 +14763,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14070
14763
|
const tab = tabManager.getActiveTab();
|
|
14071
14764
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14072
14765
|
return withAction(
|
|
14073
|
-
|
|
14766
|
+
runtime2,
|
|
14074
14767
|
tabManager,
|
|
14075
14768
|
"click",
|
|
14076
14769
|
{ index, selector },
|
|
@@ -14099,7 +14792,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14099
14792
|
const tab = tabManager.getActiveTab();
|
|
14100
14793
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14101
14794
|
return withAction(
|
|
14102
|
-
|
|
14795
|
+
runtime2,
|
|
14103
14796
|
tabManager,
|
|
14104
14797
|
"hover",
|
|
14105
14798
|
{ index, selector },
|
|
@@ -14128,7 +14821,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14128
14821
|
const tab = tabManager.getActiveTab();
|
|
14129
14822
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14130
14823
|
return withAction(
|
|
14131
|
-
|
|
14824
|
+
runtime2,
|
|
14132
14825
|
tabManager,
|
|
14133
14826
|
"focus",
|
|
14134
14827
|
{ index, selector },
|
|
@@ -14242,7 +14935,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14242
14935
|
const tab = tabManager.getActiveTab();
|
|
14243
14936
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14244
14937
|
return withAction(
|
|
14245
|
-
|
|
14938
|
+
runtime2,
|
|
14246
14939
|
tabManager,
|
|
14247
14940
|
"type",
|
|
14248
14941
|
{ index, selector, text, mode },
|
|
@@ -14281,7 +14974,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14281
14974
|
const tab = tabManager.getActiveTab();
|
|
14282
14975
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14283
14976
|
return withAction(
|
|
14284
|
-
|
|
14977
|
+
runtime2,
|
|
14285
14978
|
tabManager,
|
|
14286
14979
|
"type_text",
|
|
14287
14980
|
{ index, selector, text, mode },
|
|
@@ -14318,7 +15011,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14318
15011
|
const tab = tabManager.getActiveTab();
|
|
14319
15012
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14320
15013
|
return withAction(
|
|
14321
|
-
|
|
15014
|
+
runtime2,
|
|
14322
15015
|
tabManager,
|
|
14323
15016
|
"select_option",
|
|
14324
15017
|
{ index, selector, label, value },
|
|
@@ -14340,7 +15033,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14340
15033
|
const tab = tabManager.getActiveTab();
|
|
14341
15034
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14342
15035
|
return withAction(
|
|
14343
|
-
|
|
15036
|
+
runtime2,
|
|
14344
15037
|
tabManager,
|
|
14345
15038
|
"submit_form",
|
|
14346
15039
|
{ index, selector },
|
|
@@ -14373,7 +15066,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14373
15066
|
const tab = tabManager.getActiveTab();
|
|
14374
15067
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14375
15068
|
return withAction(
|
|
14376
|
-
|
|
15069
|
+
runtime2,
|
|
14377
15070
|
tabManager,
|
|
14378
15071
|
"press_key",
|
|
14379
15072
|
{ key, index, selector },
|
|
@@ -14409,7 +15102,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14409
15102
|
const tab = tabManager.getActiveTab();
|
|
14410
15103
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14411
15104
|
return withAction(
|
|
14412
|
-
|
|
15105
|
+
runtime2,
|
|
14413
15106
|
tabManager,
|
|
14414
15107
|
"scroll",
|
|
14415
15108
|
{ direction, amount },
|
|
@@ -14432,7 +15125,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14432
15125
|
const tab = tabManager.getActiveTab();
|
|
14433
15126
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14434
15127
|
return withAction(
|
|
14435
|
-
|
|
15128
|
+
runtime2,
|
|
14436
15129
|
tabManager,
|
|
14437
15130
|
"dismiss_popup",
|
|
14438
15131
|
{},
|
|
@@ -14455,7 +15148,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14455
15148
|
const tab = tabManager.getActiveTab();
|
|
14456
15149
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14457
15150
|
return withAction(
|
|
14458
|
-
|
|
15151
|
+
runtime2,
|
|
14459
15152
|
tabManager,
|
|
14460
15153
|
"clear_overlays",
|
|
14461
15154
|
{ strategy: strategy || "auto" },
|
|
@@ -14481,7 +15174,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14481
15174
|
const tab = tabManager.getActiveTab();
|
|
14482
15175
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14483
15176
|
return withAction(
|
|
14484
|
-
|
|
15177
|
+
runtime2,
|
|
14485
15178
|
tabManager,
|
|
14486
15179
|
"wait_for",
|
|
14487
15180
|
{ text, selector, timeoutMs },
|
|
@@ -14498,7 +15191,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14498
15191
|
url: zod.z.string().optional().describe("URL to open (defaults to about:blank)")
|
|
14499
15192
|
}
|
|
14500
15193
|
},
|
|
14501
|
-
async ({ url }) => withAction(
|
|
15194
|
+
async ({ url }) => withAction(runtime2, tabManager, "create_tab", { url }, async () => {
|
|
14502
15195
|
const id = tabManager.createTab(url || "about:blank");
|
|
14503
15196
|
const tab = tabManager.getActiveTab();
|
|
14504
15197
|
if (tab) {
|
|
@@ -14518,7 +15211,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14518
15211
|
}
|
|
14519
15212
|
},
|
|
14520
15213
|
async ({ tabId, match }) => withAction(
|
|
14521
|
-
|
|
15214
|
+
runtime2,
|
|
14522
15215
|
tabManager,
|
|
14523
15216
|
"switch_tab",
|
|
14524
15217
|
{ tabId, match },
|
|
@@ -14541,7 +15234,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14541
15234
|
tabId: zod.z.string().describe("The tab ID to close")
|
|
14542
15235
|
}
|
|
14543
15236
|
},
|
|
14544
|
-
async ({ tabId }) => withAction(
|
|
15237
|
+
async ({ tabId }) => withAction(runtime2, tabManager, "close_tab", { tabId }, async () => {
|
|
14545
15238
|
tabManager.closeTab(tabId);
|
|
14546
15239
|
return `Closed tab ${tabId}`;
|
|
14547
15240
|
})
|
|
@@ -14557,12 +15250,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14557
15250
|
}
|
|
14558
15251
|
},
|
|
14559
15252
|
async ({ name, note }) => withAction(
|
|
14560
|
-
|
|
15253
|
+
runtime2,
|
|
14561
15254
|
tabManager,
|
|
14562
15255
|
"create_checkpoint",
|
|
14563
15256
|
{ name, note },
|
|
14564
15257
|
async () => {
|
|
14565
|
-
const checkpoint =
|
|
15258
|
+
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
14566
15259
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
14567
15260
|
}
|
|
14568
15261
|
)
|
|
@@ -14578,12 +15271,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14578
15271
|
}
|
|
14579
15272
|
},
|
|
14580
15273
|
async ({ name, note }) => withAction(
|
|
14581
|
-
|
|
15274
|
+
runtime2,
|
|
14582
15275
|
tabManager,
|
|
14583
15276
|
"create_checkpoint",
|
|
14584
15277
|
{ name, note },
|
|
14585
15278
|
async () => {
|
|
14586
|
-
const checkpoint =
|
|
15279
|
+
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
14587
15280
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
14588
15281
|
}
|
|
14589
15282
|
)
|
|
@@ -14599,17 +15292,17 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14599
15292
|
}
|
|
14600
15293
|
},
|
|
14601
15294
|
async ({ checkpointId, name }) => withAction(
|
|
14602
|
-
|
|
15295
|
+
runtime2,
|
|
14603
15296
|
tabManager,
|
|
14604
15297
|
"restore_checkpoint",
|
|
14605
15298
|
{ checkpointId, name },
|
|
14606
15299
|
async () => {
|
|
14607
|
-
const state2 =
|
|
15300
|
+
const state2 = runtime2.getState();
|
|
14608
15301
|
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
14609
15302
|
if (!checkpoint) {
|
|
14610
15303
|
return "Error: No matching checkpoint found";
|
|
14611
15304
|
}
|
|
14612
|
-
|
|
15305
|
+
runtime2.restoreCheckpoint(checkpoint.id);
|
|
14613
15306
|
return `Restored checkpoint ${checkpoint.name}`;
|
|
14614
15307
|
}
|
|
14615
15308
|
)
|
|
@@ -14625,17 +15318,17 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14625
15318
|
}
|
|
14626
15319
|
},
|
|
14627
15320
|
async ({ checkpointId, name }) => withAction(
|
|
14628
|
-
|
|
15321
|
+
runtime2,
|
|
14629
15322
|
tabManager,
|
|
14630
15323
|
"restore_checkpoint",
|
|
14631
15324
|
{ checkpointId, name },
|
|
14632
15325
|
async () => {
|
|
14633
|
-
const state2 =
|
|
15326
|
+
const state2 = runtime2.getState();
|
|
14634
15327
|
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
14635
15328
|
if (!checkpoint) {
|
|
14636
15329
|
return "Error: No matching checkpoint found";
|
|
14637
15330
|
}
|
|
14638
|
-
|
|
15331
|
+
runtime2.restoreCheckpoint(checkpoint.id);
|
|
14639
15332
|
return `Restored checkpoint ${checkpoint.name}`;
|
|
14640
15333
|
}
|
|
14641
15334
|
)
|
|
@@ -14649,7 +15342,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14649
15342
|
name: zod.z.string().describe("Session name such as github-logged-in")
|
|
14650
15343
|
}
|
|
14651
15344
|
},
|
|
14652
|
-
async ({ name }) => withAction(
|
|
15345
|
+
async ({ name }) => withAction(runtime2, tabManager, "save_session", { name }, async () => {
|
|
14653
15346
|
const saved = await saveNamedSession(
|
|
14654
15347
|
tabManager,
|
|
14655
15348
|
name
|
|
@@ -14666,7 +15359,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14666
15359
|
name: zod.z.string().describe("Previously saved session name")
|
|
14667
15360
|
}
|
|
14668
15361
|
},
|
|
14669
|
-
async ({ name }) => withAction(
|
|
15362
|
+
async ({ name }) => withAction(runtime2, tabManager, "load_session", { name }, async () => {
|
|
14670
15363
|
const loaded = await loadNamedSession(
|
|
14671
15364
|
tabManager,
|
|
14672
15365
|
name
|
|
@@ -14680,7 +15373,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14680
15373
|
title: "List Sessions",
|
|
14681
15374
|
description: "List previously saved named browser sessions with cookie and storage counts."
|
|
14682
15375
|
},
|
|
14683
|
-
async () => withAction(
|
|
15376
|
+
async () => withAction(runtime2, tabManager, "list_sessions", {}, async () => {
|
|
14684
15377
|
const sessions2 = listNamedSessions();
|
|
14685
15378
|
if (sessions2.length === 0) return "No saved sessions";
|
|
14686
15379
|
return sessions2.map(
|
|
@@ -14698,7 +15391,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14698
15391
|
}
|
|
14699
15392
|
},
|
|
14700
15393
|
async ({ name }) => withAction(
|
|
14701
|
-
|
|
15394
|
+
runtime2,
|
|
14702
15395
|
tabManager,
|
|
14703
15396
|
"delete_session",
|
|
14704
15397
|
{ name },
|
|
@@ -14721,7 +15414,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14721
15414
|
"Error capturing screenshot: active tab has zero-sized bounds"
|
|
14722
15415
|
);
|
|
14723
15416
|
}
|
|
14724
|
-
const screenshot = await
|
|
15417
|
+
const screenshot = await captureScreenshot(tab.view.webContents);
|
|
14725
15418
|
if (!screenshot.ok) {
|
|
14726
15419
|
return asTextResponse(
|
|
14727
15420
|
`Error capturing screenshot: ${screenshot.error}`
|
|
@@ -14785,7 +15478,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14785
15478
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14786
15479
|
const normalizedText = normalizeLooseString(text);
|
|
14787
15480
|
return withAction(
|
|
14788
|
-
|
|
15481
|
+
runtime2,
|
|
14789
15482
|
tabManager,
|
|
14790
15483
|
"highlight",
|
|
14791
15484
|
{
|
|
@@ -14834,7 +15527,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14834
15527
|
const tab = tabManager.getActiveTab();
|
|
14835
15528
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14836
15529
|
return withAction(
|
|
14837
|
-
|
|
15530
|
+
runtime2,
|
|
14838
15531
|
tabManager,
|
|
14839
15532
|
"clear_highlights",
|
|
14840
15533
|
{},
|
|
@@ -14859,7 +15552,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14859
15552
|
}
|
|
14860
15553
|
},
|
|
14861
15554
|
async ({ url }) => {
|
|
14862
|
-
const state2 = getState$
|
|
15555
|
+
const state2 = getState$2();
|
|
14863
15556
|
const activeTab = tabManager.getActiveTab();
|
|
14864
15557
|
const activeUrl = activeTab ? normalizeUrl(activeTab.view.webContents.getURL()) : null;
|
|
14865
15558
|
const activeSavedHighlights = activeUrl ? state2.highlights.filter((highlight) => highlight.url === activeUrl) : [];
|
|
@@ -14990,7 +15683,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14990
15683
|
},
|
|
14991
15684
|
async ({ name, summary }) => {
|
|
14992
15685
|
return withAction(
|
|
14993
|
-
|
|
15686
|
+
runtime2,
|
|
14994
15687
|
tabManager,
|
|
14995
15688
|
"create_bookmark_folder",
|
|
14996
15689
|
{ name, summary },
|
|
@@ -15052,7 +15745,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15052
15745
|
on_duplicate
|
|
15053
15746
|
}) => {
|
|
15054
15747
|
return withAction(
|
|
15055
|
-
|
|
15748
|
+
runtime2,
|
|
15056
15749
|
tabManager,
|
|
15057
15750
|
"save_bookmark",
|
|
15058
15751
|
{
|
|
@@ -15131,7 +15824,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15131
15824
|
},
|
|
15132
15825
|
async ({ folder_id, folder_name }) => {
|
|
15133
15826
|
return withAction(
|
|
15134
|
-
|
|
15827
|
+
runtime2,
|
|
15135
15828
|
tabManager,
|
|
15136
15829
|
"list_bookmarks",
|
|
15137
15830
|
{ folder_id, folder_name },
|
|
@@ -15196,7 +15889,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15196
15889
|
},
|
|
15197
15890
|
async (args) => {
|
|
15198
15891
|
return withAction(
|
|
15199
|
-
|
|
15892
|
+
runtime2,
|
|
15200
15893
|
tabManager,
|
|
15201
15894
|
"organize_bookmark",
|
|
15202
15895
|
args,
|
|
@@ -15263,7 +15956,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15263
15956
|
},
|
|
15264
15957
|
async ({ query }) => {
|
|
15265
15958
|
return withAction(
|
|
15266
|
-
|
|
15959
|
+
runtime2,
|
|
15267
15960
|
tabManager,
|
|
15268
15961
|
"search_bookmarks",
|
|
15269
15962
|
{ query },
|
|
@@ -15294,7 +15987,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15294
15987
|
},
|
|
15295
15988
|
async ({ bookmark_id }) => {
|
|
15296
15989
|
return withAction(
|
|
15297
|
-
|
|
15990
|
+
runtime2,
|
|
15298
15991
|
tabManager,
|
|
15299
15992
|
"remove_bookmark",
|
|
15300
15993
|
{ bookmark_id },
|
|
@@ -15327,7 +16020,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15327
16020
|
},
|
|
15328
16021
|
async ({ bookmark_id, url, title, index, selector, note }) => {
|
|
15329
16022
|
return withAction(
|
|
15330
|
-
|
|
16023
|
+
runtime2,
|
|
15331
16024
|
tabManager,
|
|
15332
16025
|
"archive_bookmark",
|
|
15333
16026
|
{ bookmark_id, url, title, index, selector, note },
|
|
@@ -15397,7 +16090,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15397
16090
|
},
|
|
15398
16091
|
async ({ bookmark_id, new_tab }) => {
|
|
15399
16092
|
return withAction(
|
|
15400
|
-
|
|
16093
|
+
runtime2,
|
|
15401
16094
|
tabManager,
|
|
15402
16095
|
"open_bookmark",
|
|
15403
16096
|
{ bookmark_id, new_tab },
|
|
@@ -15440,7 +16133,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15440
16133
|
},
|
|
15441
16134
|
async ({ folder_id }) => {
|
|
15442
16135
|
return withAction(
|
|
15443
|
-
|
|
16136
|
+
runtime2,
|
|
15444
16137
|
tabManager,
|
|
15445
16138
|
"remove_bookmark_folder",
|
|
15446
16139
|
{ folder_id },
|
|
@@ -15466,7 +16159,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15466
16159
|
},
|
|
15467
16160
|
async ({ folder_id, new_name, summary }) => {
|
|
15468
16161
|
return withAction(
|
|
15469
|
-
|
|
16162
|
+
runtime2,
|
|
15470
16163
|
tabManager,
|
|
15471
16164
|
"rename_bookmark_folder",
|
|
15472
16165
|
{ folder_id, new_name, summary },
|
|
@@ -15503,7 +16196,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15503
16196
|
},
|
|
15504
16197
|
async ({ title, body, folder, tags }) => {
|
|
15505
16198
|
return withAction(
|
|
15506
|
-
|
|
16199
|
+
runtime2,
|
|
15507
16200
|
tabManager,
|
|
15508
16201
|
"memory_note_create",
|
|
15509
16202
|
{ title, folder, tags },
|
|
@@ -15527,7 +16220,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15527
16220
|
},
|
|
15528
16221
|
async ({ note_path, content, heading }) => {
|
|
15529
16222
|
return withAction(
|
|
15530
|
-
|
|
16223
|
+
runtime2,
|
|
15531
16224
|
tabManager,
|
|
15532
16225
|
"memory_note_append",
|
|
15533
16226
|
{ note_path, heading },
|
|
@@ -15554,7 +16247,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15554
16247
|
},
|
|
15555
16248
|
async ({ folder, limit }) => {
|
|
15556
16249
|
return withAction(
|
|
15557
|
-
|
|
16250
|
+
runtime2,
|
|
15558
16251
|
tabManager,
|
|
15559
16252
|
"memory_note_list",
|
|
15560
16253
|
{ folder, limit },
|
|
@@ -15584,7 +16277,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15584
16277
|
},
|
|
15585
16278
|
async ({ query, folder, tags, limit }) => {
|
|
15586
16279
|
return withAction(
|
|
15587
|
-
|
|
16280
|
+
runtime2,
|
|
15588
16281
|
tabManager,
|
|
15589
16282
|
"memory_note_search",
|
|
15590
16283
|
{ query, folder, tags, limit },
|
|
@@ -15617,7 +16310,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15617
16310
|
const tab = tabManager.getActiveTab();
|
|
15618
16311
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15619
16312
|
return withAction(
|
|
15620
|
-
|
|
16313
|
+
runtime2,
|
|
15621
16314
|
tabManager,
|
|
15622
16315
|
"memory_page_capture",
|
|
15623
16316
|
{ title, folder, tags },
|
|
@@ -15654,7 +16347,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15654
16347
|
},
|
|
15655
16348
|
async ({ bookmark_id, note_path, title, folder, note, tags }) => {
|
|
15656
16349
|
return withAction(
|
|
15657
|
-
|
|
16350
|
+
runtime2,
|
|
15658
16351
|
tabManager,
|
|
15659
16352
|
"memory_link_bookmark",
|
|
15660
16353
|
{ bookmark_id, note_path, title, folder, tags },
|
|
@@ -15693,7 +16386,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15693
16386
|
async ({ goal, steps }) => {
|
|
15694
16387
|
const normalizedSteps = coerceStringArray(steps) ?? [];
|
|
15695
16388
|
const tab = tabManager.getActiveTab();
|
|
15696
|
-
const flow =
|
|
16389
|
+
const flow = runtime2.startFlow(
|
|
15697
16390
|
goal,
|
|
15698
16391
|
normalizedSteps,
|
|
15699
16392
|
tab?.view.webContents.getURL()
|
|
@@ -15714,9 +16407,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15714
16407
|
}
|
|
15715
16408
|
},
|
|
15716
16409
|
async ({ detail }) => {
|
|
15717
|
-
const flow =
|
|
16410
|
+
const flow = runtime2.advanceFlow(detail);
|
|
15718
16411
|
if (!flow) return asTextResponse("No active flow to advance");
|
|
15719
|
-
const ctx =
|
|
16412
|
+
const ctx = runtime2.getFlowContext();
|
|
15720
16413
|
return asTextResponse(`Step completed.${ctx}`);
|
|
15721
16414
|
}
|
|
15722
16415
|
);
|
|
@@ -15727,9 +16420,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15727
16420
|
description: "Check the current workflow progress."
|
|
15728
16421
|
},
|
|
15729
16422
|
async () => {
|
|
15730
|
-
const flow =
|
|
16423
|
+
const flow = runtime2.getFlowState();
|
|
15731
16424
|
if (!flow) return asTextResponse("No active workflow.");
|
|
15732
|
-
return asTextResponse(
|
|
16425
|
+
return asTextResponse(runtime2.getFlowContext());
|
|
15733
16426
|
}
|
|
15734
16427
|
);
|
|
15735
16428
|
server.registerTool(
|
|
@@ -15739,7 +16432,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15739
16432
|
description: "Clear the active workflow tracker."
|
|
15740
16433
|
},
|
|
15741
16434
|
async () => {
|
|
15742
|
-
|
|
16435
|
+
runtime2.clearFlow();
|
|
15743
16436
|
return asTextResponse("Workflow ended.");
|
|
15744
16437
|
}
|
|
15745
16438
|
);
|
|
@@ -15768,7 +16461,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15768
16461
|
suggestions.push(`Page: ${page.title || "(untitled)"}`);
|
|
15769
16462
|
suggestions.push(`URL: ${page.url}`);
|
|
15770
16463
|
suggestions.push("");
|
|
15771
|
-
const flowCtx =
|
|
16464
|
+
const flowCtx = runtime2.getFlowContext();
|
|
15772
16465
|
if (flowCtx) {
|
|
15773
16466
|
suggestions.push(flowCtx);
|
|
15774
16467
|
suggestions.push("");
|
|
@@ -15868,7 +16561,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15868
16561
|
const tab = tabManager.getActiveTab();
|
|
15869
16562
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15870
16563
|
return withAction(
|
|
15871
|
-
|
|
16564
|
+
runtime2,
|
|
15872
16565
|
tabManager,
|
|
15873
16566
|
"fill_form",
|
|
15874
16567
|
{ fieldCount: fields.length, submit },
|
|
@@ -15925,7 +16618,7 @@ ${results.join("\n")}`;
|
|
|
15925
16618
|
const tab = tabManager.getActiveTab();
|
|
15926
16619
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15927
16620
|
return withAction(
|
|
15928
|
-
|
|
16621
|
+
runtime2,
|
|
15929
16622
|
tabManager,
|
|
15930
16623
|
"login",
|
|
15931
16624
|
{ url, username: username.slice(0, 3) + "***" },
|
|
@@ -16030,7 +16723,7 @@ ${steps.join("\n")}`;
|
|
|
16030
16723
|
`Error: "${query}" looks like a button label, not a search query. Use the click tool to interact with this element instead.`
|
|
16031
16724
|
);
|
|
16032
16725
|
}
|
|
16033
|
-
return withAction(
|
|
16726
|
+
return withAction(runtime2, tabManager, "search", { query }, async () => {
|
|
16034
16727
|
const wc = tab.view.webContents;
|
|
16035
16728
|
const searchSel = selector || await wc.executeJavaScript(`
|
|
16036
16729
|
(function() {
|
|
@@ -16084,7 +16777,7 @@ ${steps.join("\n")}`;
|
|
|
16084
16777
|
const tab = tabManager.getActiveTab();
|
|
16085
16778
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16086
16779
|
return withAction(
|
|
16087
|
-
|
|
16780
|
+
runtime2,
|
|
16088
16781
|
tabManager,
|
|
16089
16782
|
"paginate",
|
|
16090
16783
|
{ direction },
|
|
@@ -16135,7 +16828,7 @@ ${steps.join("\n")}`;
|
|
|
16135
16828
|
const tab = tabManager.getActiveTab();
|
|
16136
16829
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16137
16830
|
return withAction(
|
|
16138
|
-
|
|
16831
|
+
runtime2,
|
|
16139
16832
|
tabManager,
|
|
16140
16833
|
"vessel_accept_cookies",
|
|
16141
16834
|
{},
|
|
@@ -16193,7 +16886,7 @@ ${steps.join("\n")}`;
|
|
|
16193
16886
|
const tab = tabManager.getActiveTab();
|
|
16194
16887
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16195
16888
|
return withAction(
|
|
16196
|
-
|
|
16889
|
+
runtime2,
|
|
16197
16890
|
tabManager,
|
|
16198
16891
|
"vessel_extract_table",
|
|
16199
16892
|
{ index, selector: rawSelector },
|
|
@@ -16248,7 +16941,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
16248
16941
|
const tab = tabManager.getActiveTab();
|
|
16249
16942
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16250
16943
|
return withAction(
|
|
16251
|
-
|
|
16944
|
+
runtime2,
|
|
16252
16945
|
tabManager,
|
|
16253
16946
|
"vessel_scroll_to_element",
|
|
16254
16947
|
{ index, selector: rawSelector, position },
|
|
@@ -16306,7 +16999,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
16306
16999
|
const tab = tabManager.getActiveTab();
|
|
16307
17000
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
16308
17001
|
return withAction(
|
|
16309
|
-
|
|
17002
|
+
runtime2,
|
|
16310
17003
|
tabManager,
|
|
16311
17004
|
"vessel_wait_for_navigation",
|
|
16312
17005
|
{ timeoutMs },
|
|
@@ -16357,7 +17050,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
16357
17050
|
inputSchema: zod.z.object({})
|
|
16358
17051
|
},
|
|
16359
17052
|
async () => {
|
|
16360
|
-
const m =
|
|
17053
|
+
const m = runtime2.getMetrics();
|
|
16361
17054
|
const lines = [
|
|
16362
17055
|
`Session Metrics:`,
|
|
16363
17056
|
` Total actions: ${m.totalActions}`,
|
|
@@ -16517,16 +17210,16 @@ async function resolveSelector(wc, index, selector) {
|
|
|
16517
17210
|
`
|
|
16518
17211
|
);
|
|
16519
17212
|
}
|
|
16520
|
-
function createMcpServer(tabManager,
|
|
17213
|
+
function createMcpServer(tabManager, runtime2) {
|
|
16521
17214
|
const server = new mcp_js.McpServer({
|
|
16522
17215
|
name: "vessel-browser",
|
|
16523
17216
|
version: "0.1.0"
|
|
16524
17217
|
});
|
|
16525
|
-
registerTools(server, tabManager,
|
|
16526
|
-
registerDevTools(server, tabManager,
|
|
17218
|
+
registerTools(server, tabManager, runtime2);
|
|
17219
|
+
registerDevTools(server, tabManager, runtime2);
|
|
16527
17220
|
return server;
|
|
16528
17221
|
}
|
|
16529
|
-
function startMcpServer(tabManager,
|
|
17222
|
+
function startMcpServer(tabManager, runtime2, port) {
|
|
16530
17223
|
setMcpHealth({
|
|
16531
17224
|
configuredPort: port,
|
|
16532
17225
|
activePort: null,
|
|
@@ -16534,6 +17227,7 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16534
17227
|
status: "starting",
|
|
16535
17228
|
message: `Starting MCP server on port ${port}.`
|
|
16536
17229
|
});
|
|
17230
|
+
mcpAuthToken = crypto$1.randomBytes(32).toString("hex");
|
|
16537
17231
|
return new Promise((resolve) => {
|
|
16538
17232
|
const server = http.createServer(async (req, res) => {
|
|
16539
17233
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
@@ -16542,22 +17236,28 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16542
17236
|
res.end("Not found");
|
|
16543
17237
|
return;
|
|
16544
17238
|
}
|
|
16545
|
-
res.setHeader("Access-Control-Allow-Origin", "
|
|
17239
|
+
res.setHeader("Access-Control-Allow-Origin", "null");
|
|
16546
17240
|
res.setHeader(
|
|
16547
17241
|
"Access-Control-Allow-Methods",
|
|
16548
17242
|
"POST, GET, DELETE, OPTIONS"
|
|
16549
17243
|
);
|
|
16550
17244
|
res.setHeader(
|
|
16551
17245
|
"Access-Control-Allow-Headers",
|
|
16552
|
-
"Content-Type, mcp-session-id"
|
|
17246
|
+
"Content-Type, mcp-session-id, Authorization"
|
|
16553
17247
|
);
|
|
16554
17248
|
if (req.method === "OPTIONS") {
|
|
16555
17249
|
res.writeHead(204);
|
|
16556
17250
|
res.end();
|
|
16557
17251
|
return;
|
|
16558
17252
|
}
|
|
17253
|
+
const authHeader = req.headers.authorization;
|
|
17254
|
+
if (!authHeader || authHeader !== `Bearer ${mcpAuthToken}`) {
|
|
17255
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
17256
|
+
res.end(JSON.stringify({ error: "Unauthorized — missing or invalid bearer token" }));
|
|
17257
|
+
return;
|
|
17258
|
+
}
|
|
16559
17259
|
try {
|
|
16560
|
-
const mcpServer = createMcpServer(tabManager,
|
|
17260
|
+
const mcpServer = createMcpServer(tabManager, runtime2);
|
|
16561
17261
|
const transport = new streamableHttp_js.StreamableHTTPServerTransport({
|
|
16562
17262
|
sessionIdGenerator: void 0
|
|
16563
17263
|
});
|
|
@@ -16599,6 +17299,7 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16599
17299
|
configuredPort: port,
|
|
16600
17300
|
activePort: null,
|
|
16601
17301
|
endpoint: null,
|
|
17302
|
+
authToken: null,
|
|
16602
17303
|
error: message
|
|
16603
17304
|
});
|
|
16604
17305
|
});
|
|
@@ -16615,11 +17316,13 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16615
17316
|
message: `MCP server listening on ${endpoint}.`
|
|
16616
17317
|
});
|
|
16617
17318
|
console.log(`[Vessel MCP] Server listening on ${endpoint}`);
|
|
17319
|
+
console.log(`[Vessel MCP] Auth token: ${mcpAuthToken}`);
|
|
16618
17320
|
finish({
|
|
16619
17321
|
ok: true,
|
|
16620
17322
|
configuredPort: port,
|
|
16621
17323
|
activePort: actualPort,
|
|
16622
|
-
endpoint
|
|
17324
|
+
endpoint,
|
|
17325
|
+
authToken: mcpAuthToken
|
|
16623
17326
|
});
|
|
16624
17327
|
});
|
|
16625
17328
|
});
|
|
@@ -16638,6 +17341,7 @@ function stopMcpServer() {
|
|
|
16638
17341
|
}
|
|
16639
17342
|
const server = httpServer;
|
|
16640
17343
|
httpServer = null;
|
|
17344
|
+
mcpAuthToken = null;
|
|
16641
17345
|
server.close(() => {
|
|
16642
17346
|
setMcpHealth({
|
|
16643
17347
|
activePort: null,
|
|
@@ -16651,14 +17355,14 @@ function stopMcpServer() {
|
|
|
16651
17355
|
});
|
|
16652
17356
|
}
|
|
16653
17357
|
let activeChatProvider = null;
|
|
16654
|
-
function registerIpcHandlers(windowState,
|
|
17358
|
+
function registerIpcHandlers(windowState, runtime2) {
|
|
16655
17359
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
16656
17360
|
const sendToRendererViews = (channel, ...args) => {
|
|
16657
17361
|
chromeView.webContents.send(channel, ...args);
|
|
16658
17362
|
sidebarView.webContents.send(channel, ...args);
|
|
16659
17363
|
devtoolsPanelView.webContents.send(channel, ...args);
|
|
16660
17364
|
};
|
|
16661
|
-
|
|
17365
|
+
runtime2.setUpdateListener((state2) => {
|
|
16662
17366
|
sendToRendererViews(Channels.AGENT_RUNTIME_UPDATE, state2);
|
|
16663
17367
|
});
|
|
16664
17368
|
electron.ipcMain.handle(Channels.TAB_CREATE, (_, url) => {
|
|
@@ -16708,7 +17412,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16708
17412
|
(chunk) => sendToRendererViews(Channels.AI_STREAM_CHUNK, chunk),
|
|
16709
17413
|
() => sendToRendererViews(Channels.AI_STREAM_END),
|
|
16710
17414
|
tabManager,
|
|
16711
|
-
|
|
17415
|
+
runtime2,
|
|
16712
17416
|
history
|
|
16713
17417
|
);
|
|
16714
17418
|
} catch (err) {
|
|
@@ -16791,46 +17495,50 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16791
17495
|
});
|
|
16792
17496
|
electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, () => getRuntimeHealth());
|
|
16793
17497
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (_, key, value) => {
|
|
16794
|
-
|
|
17498
|
+
if (!SETTABLE_KEYS.has(key)) {
|
|
17499
|
+
throw new Error(`Unknown setting key: ${key}`);
|
|
17500
|
+
}
|
|
17501
|
+
const settingsKey = key;
|
|
17502
|
+
const updatedSettings = setSetting(settingsKey, value);
|
|
16795
17503
|
if (key === "approvalMode") {
|
|
16796
|
-
|
|
17504
|
+
runtime2.setApprovalMode(value);
|
|
16797
17505
|
}
|
|
16798
17506
|
if (key === "mcpPort") {
|
|
16799
17507
|
await stopMcpServer();
|
|
16800
|
-
await startMcpServer(tabManager,
|
|
17508
|
+
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
16801
17509
|
}
|
|
16802
17510
|
sendToRendererViews(Channels.SETTINGS_UPDATE, updatedSettings);
|
|
16803
17511
|
return updatedSettings;
|
|
16804
17512
|
});
|
|
16805
|
-
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () =>
|
|
16806
|
-
electron.ipcMain.handle(Channels.AGENT_PAUSE, () =>
|
|
16807
|
-
electron.ipcMain.handle(Channels.AGENT_RESUME, () =>
|
|
17513
|
+
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () => runtime2.getState());
|
|
17514
|
+
electron.ipcMain.handle(Channels.AGENT_PAUSE, () => runtime2.pause());
|
|
17515
|
+
electron.ipcMain.handle(Channels.AGENT_RESUME, () => runtime2.resume());
|
|
16808
17516
|
electron.ipcMain.handle(
|
|
16809
17517
|
Channels.AGENT_SET_APPROVAL_MODE,
|
|
16810
17518
|
(_, mode) => {
|
|
16811
17519
|
setSetting("approvalMode", mode);
|
|
16812
|
-
return
|
|
17520
|
+
return runtime2.setApprovalMode(mode);
|
|
16813
17521
|
}
|
|
16814
17522
|
);
|
|
16815
17523
|
electron.ipcMain.handle(
|
|
16816
17524
|
Channels.AGENT_APPROVAL_RESOLVE,
|
|
16817
|
-
(_, approvalId, approved) =>
|
|
17525
|
+
(_, approvalId, approved) => runtime2.resolveApproval(approvalId, approved)
|
|
16818
17526
|
);
|
|
16819
17527
|
electron.ipcMain.handle(
|
|
16820
17528
|
Channels.AGENT_CHECKPOINT_CREATE,
|
|
16821
|
-
(_, name, note) =>
|
|
17529
|
+
(_, name, note) => runtime2.createCheckpoint(name, note)
|
|
16822
17530
|
);
|
|
16823
17531
|
electron.ipcMain.handle(
|
|
16824
17532
|
Channels.AGENT_CHECKPOINT_RESTORE,
|
|
16825
|
-
(_, checkpointId) =>
|
|
17533
|
+
(_, checkpointId) => runtime2.restoreCheckpoint(checkpointId)
|
|
16826
17534
|
);
|
|
16827
17535
|
electron.ipcMain.handle(
|
|
16828
17536
|
Channels.AGENT_SESSION_CAPTURE,
|
|
16829
|
-
(_, note) =>
|
|
17537
|
+
(_, note) => runtime2.captureSession(note)
|
|
16830
17538
|
);
|
|
16831
17539
|
electron.ipcMain.handle(
|
|
16832
17540
|
Channels.AGENT_SESSION_RESTORE,
|
|
16833
|
-
(_, snapshot) =>
|
|
17541
|
+
(_, snapshot) => runtime2.restoreSession(snapshot)
|
|
16834
17542
|
);
|
|
16835
17543
|
electron.ipcMain.handle(Channels.BOOKMARKS_GET, () => {
|
|
16836
17544
|
return getState();
|
|
@@ -16864,36 +17572,12 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16864
17572
|
return { success: false, message: "No active tab" };
|
|
16865
17573
|
}
|
|
16866
17574
|
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" };
|
|
17575
|
+
const result = await captureSelectionHighlight(wc);
|
|
17576
|
+
if (result.success && result.text) {
|
|
17577
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
|
|
17578
|
+
});
|
|
16882
17579
|
}
|
|
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 };
|
|
17580
|
+
return result;
|
|
16897
17581
|
} catch {
|
|
16898
17582
|
return { success: false, message: "Could not capture selection" };
|
|
16899
17583
|
}
|
|
@@ -16909,24 +17593,11 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16909
17593
|
if (wc.isDestroyed()) return;
|
|
16910
17594
|
const tab = tabManager.findTabByWebContentsId(wc.id);
|
|
16911
17595
|
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
|
-
}
|
|
17596
|
+
void persistAndMarkHighlight(wc, text).then((result) => {
|
|
17597
|
+
if (result.success && !chromeView.webContents.isDestroyed()) {
|
|
17598
|
+
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
17599
|
+
}
|
|
17600
|
+
});
|
|
16930
17601
|
} catch {
|
|
16931
17602
|
}
|
|
16932
17603
|
});
|
|
@@ -16936,9 +17607,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16936
17607
|
const wc = tab.view.webContents;
|
|
16937
17608
|
if (wc.isDestroyed()) return 0;
|
|
16938
17609
|
try {
|
|
16939
|
-
return wc
|
|
16940
|
-
`document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text').length`
|
|
16941
|
-
);
|
|
17610
|
+
return getHighlightCount(wc);
|
|
16942
17611
|
} catch {
|
|
16943
17612
|
return 0;
|
|
16944
17613
|
}
|
|
@@ -16949,20 +17618,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16949
17618
|
const wc = tab.view.webContents;
|
|
16950
17619
|
if (wc.isDestroyed()) return false;
|
|
16951
17620
|
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
|
-
`);
|
|
17621
|
+
return scrollToHighlight(wc, index);
|
|
16966
17622
|
} catch {
|
|
16967
17623
|
return false;
|
|
16968
17624
|
}
|
|
@@ -16973,32 +17629,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16973
17629
|
const wc = tab.view.webContents;
|
|
16974
17630
|
if (wc.isDestroyed()) return false;
|
|
16975
17631
|
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
|
-
`);
|
|
17632
|
+
return removeHighlightAtIndex(wc, index);
|
|
17002
17633
|
} catch {
|
|
17003
17634
|
return false;
|
|
17004
17635
|
}
|
|
@@ -17009,39 +17640,65 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
17009
17640
|
const wc = tab.view.webContents;
|
|
17010
17641
|
if (wc.isDestroyed()) return false;
|
|
17011
17642
|
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
|
-
`);
|
|
17643
|
+
return clearAllHighlightElements(wc);
|
|
17035
17644
|
} catch {
|
|
17036
17645
|
return false;
|
|
17037
17646
|
}
|
|
17038
17647
|
});
|
|
17648
|
+
let findWiredWcId = null;
|
|
17649
|
+
function wireFindEvents(wc) {
|
|
17650
|
+
if (findWiredWcId === wc.id) return;
|
|
17651
|
+
if (findWiredWcId !== null) {
|
|
17652
|
+
const prev = tabManager.findTabByWebContentsId(findWiredWcId);
|
|
17653
|
+
if (prev) prev.view.webContents.removeAllListeners("found-in-page");
|
|
17654
|
+
}
|
|
17655
|
+
findWiredWcId = wc.id;
|
|
17656
|
+
wc.on("found-in-page", (_event, result) => {
|
|
17657
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
17658
|
+
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
17659
|
+
}
|
|
17660
|
+
});
|
|
17661
|
+
}
|
|
17662
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_START, (_, text, options) => {
|
|
17663
|
+
const tab = tabManager.getActiveTab();
|
|
17664
|
+
if (!tab) return null;
|
|
17665
|
+
const wc = tab.view.webContents;
|
|
17666
|
+
if (wc.isDestroyed()) return null;
|
|
17667
|
+
wireFindEvents(wc);
|
|
17668
|
+
return wc.findInPage(text, {
|
|
17669
|
+
forward: options?.forward ?? true,
|
|
17670
|
+
findNext: options?.findNext ?? false
|
|
17671
|
+
});
|
|
17672
|
+
});
|
|
17673
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_NEXT, (_, forward) => {
|
|
17674
|
+
const tab = tabManager.getActiveTab();
|
|
17675
|
+
if (!tab) return null;
|
|
17676
|
+
const wc = tab.view.webContents;
|
|
17677
|
+
if (wc.isDestroyed()) return null;
|
|
17678
|
+
return wc.findInPage("", { forward: forward ?? true, findNext: true });
|
|
17679
|
+
});
|
|
17680
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_STOP, (_, action) => {
|
|
17681
|
+
const tab = tabManager.getActiveTab();
|
|
17682
|
+
if (!tab) return;
|
|
17683
|
+
const wc = tab.view.webContents;
|
|
17684
|
+
if (wc.isDestroyed()) return;
|
|
17685
|
+
wc.stopFindInPage(action ?? "clearSelection");
|
|
17686
|
+
});
|
|
17687
|
+
electron.ipcMain.handle(Channels.HISTORY_GET, () => {
|
|
17688
|
+
return getState$1();
|
|
17689
|
+
});
|
|
17690
|
+
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (_, query) => {
|
|
17691
|
+
return search(query);
|
|
17692
|
+
});
|
|
17693
|
+
electron.ipcMain.handle(Channels.HISTORY_CLEAR, () => {
|
|
17694
|
+
clearAll$1();
|
|
17695
|
+
});
|
|
17039
17696
|
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => {
|
|
17040
17697
|
windowState.uiState.devtoolsPanelOpen = !windowState.uiState.devtoolsPanelOpen;
|
|
17041
17698
|
layoutViews(windowState);
|
|
17042
17699
|
return { open: windowState.uiState.devtoolsPanelOpen };
|
|
17043
17700
|
});
|
|
17044
|
-
electron.ipcMain.handle(
|
|
17701
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE, (_, height) => {
|
|
17045
17702
|
const clamped = Math.max(MIN_DEVTOOLS_PANEL, Math.min(MAX_DEVTOOLS_PANEL, Math.round(height)));
|
|
17046
17703
|
windowState.uiState.devtoolsPanelHeight = clamped;
|
|
17047
17704
|
layoutViews(windowState);
|
|
@@ -17062,6 +17719,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
17062
17719
|
});
|
|
17063
17720
|
}
|
|
17064
17721
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
17722
|
+
const PERSIST_DEBOUNCE_MS = 500;
|
|
17065
17723
|
function clone(value) {
|
|
17066
17724
|
return JSON.parse(JSON.stringify(value));
|
|
17067
17725
|
}
|
|
@@ -17139,7 +17797,7 @@ class AgentRuntime {
|
|
|
17139
17797
|
createCheckpoint(name, note) {
|
|
17140
17798
|
const snapshot = this.captureSession(note);
|
|
17141
17799
|
const checkpoint = {
|
|
17142
|
-
id:
|
|
17800
|
+
id: crypto$1.randomUUID(),
|
|
17143
17801
|
name: name?.trim() || `Checkpoint ${this.state.checkpoints.length + 1}`,
|
|
17144
17802
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17145
17803
|
note: note?.trim() || void 0,
|
|
@@ -17193,7 +17851,7 @@ class AgentRuntime {
|
|
|
17193
17851
|
}
|
|
17194
17852
|
}
|
|
17195
17853
|
const entry = {
|
|
17196
|
-
id:
|
|
17854
|
+
id: crypto$1.randomUUID(),
|
|
17197
17855
|
source: input.source,
|
|
17198
17856
|
kind,
|
|
17199
17857
|
title: input.title?.trim() || void 0,
|
|
@@ -17217,7 +17875,7 @@ class AgentRuntime {
|
|
|
17217
17875
|
// --- Speedee Flow State ---
|
|
17218
17876
|
startFlow(goal, steps, startUrl) {
|
|
17219
17877
|
const flow = {
|
|
17220
|
-
id:
|
|
17878
|
+
id: crypto$1.randomUUID(),
|
|
17221
17879
|
goal,
|
|
17222
17880
|
steps: steps.map((label) => ({ label, status: "pending" })),
|
|
17223
17881
|
currentStepIndex: 0,
|
|
@@ -17300,7 +17958,6 @@ ${progress}
|
|
|
17300
17958
|
mode: "replace"
|
|
17301
17959
|
});
|
|
17302
17960
|
const approvalReason = this.getApprovalReason(dangerous);
|
|
17303
|
-
console.log(`[Vessel Runtime] action=${name} dangerous=${dangerous} approvalReason=${approvalReason} mode=${this.state.supervisor.approvalMode}`);
|
|
17304
17961
|
if (approvalReason) {
|
|
17305
17962
|
this.publishTranscript({
|
|
17306
17963
|
source,
|
|
@@ -17310,9 +17967,7 @@ ${progress}
|
|
|
17310
17967
|
streamId: transcriptStreamId,
|
|
17311
17968
|
mode: "replace"
|
|
17312
17969
|
});
|
|
17313
|
-
console.log(`[Vessel Runtime] awaiting approval for ${name}...`);
|
|
17314
17970
|
const approved = await this.awaitApproval(action, approvalReason);
|
|
17315
|
-
console.log(`[Vessel Runtime] approval result for ${name}: ${approved}`);
|
|
17316
17971
|
if (!approved) {
|
|
17317
17972
|
this.publishTranscript({
|
|
17318
17973
|
source,
|
|
@@ -17403,7 +18058,14 @@ ${progress}
|
|
|
17403
18058
|
return sanitizePersistence(null);
|
|
17404
18059
|
}
|
|
17405
18060
|
}
|
|
17406
|
-
|
|
18061
|
+
persistTimer = null;
|
|
18062
|
+
persistDirty = false;
|
|
18063
|
+
persistNow() {
|
|
18064
|
+
this.persistDirty = false;
|
|
18065
|
+
if (this.persistTimer) {
|
|
18066
|
+
clearTimeout(this.persistTimer);
|
|
18067
|
+
this.persistTimer = null;
|
|
18068
|
+
}
|
|
17407
18069
|
const persisted = {
|
|
17408
18070
|
session: this.state.session,
|
|
17409
18071
|
supervisor: {
|
|
@@ -17414,20 +18076,36 @@ ${progress}
|
|
|
17414
18076
|
actions: this.state.actions.slice(-120),
|
|
17415
18077
|
checkpoints: this.state.checkpoints.slice(-20)
|
|
17416
18078
|
};
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
18079
|
+
try {
|
|
18080
|
+
fs$1.mkdirSync(path$1.dirname(getRuntimeStatePath()), { recursive: true });
|
|
18081
|
+
fs$1.writeFileSync(
|
|
18082
|
+
getRuntimeStatePath(),
|
|
18083
|
+
JSON.stringify(persisted, null, 2),
|
|
18084
|
+
"utf-8"
|
|
18085
|
+
);
|
|
18086
|
+
} catch (err) {
|
|
18087
|
+
console.error("[Vessel] Failed to persist runtime state:", err);
|
|
18088
|
+
}
|
|
18089
|
+
}
|
|
18090
|
+
schedulePersist() {
|
|
18091
|
+
this.persistDirty = true;
|
|
18092
|
+
if (this.persistTimer) return;
|
|
18093
|
+
this.persistTimer = setTimeout(() => {
|
|
18094
|
+
this.persistTimer = null;
|
|
18095
|
+
if (this.persistDirty) this.persistNow();
|
|
18096
|
+
}, PERSIST_DEBOUNCE_MS);
|
|
18097
|
+
}
|
|
18098
|
+
/** Flush any pending debounced persist to disk immediately. Call on shutdown. */
|
|
18099
|
+
flushPersist() {
|
|
18100
|
+
if (this.persistDirty) this.persistNow();
|
|
17423
18101
|
}
|
|
17424
18102
|
emit() {
|
|
17425
|
-
this.
|
|
18103
|
+
this.schedulePersist();
|
|
17426
18104
|
this.updateListener?.(this.getState());
|
|
17427
18105
|
}
|
|
17428
18106
|
startAction(input) {
|
|
17429
18107
|
const action = {
|
|
17430
|
-
id:
|
|
18108
|
+
id: crypto$1.randomUUID(),
|
|
17431
18109
|
source: input.source,
|
|
17432
18110
|
name: input.name,
|
|
17433
18111
|
args: clone(input.args),
|
|
@@ -17461,7 +18139,7 @@ ${progress}
|
|
|
17461
18139
|
/** Aggregate metrics for all completed actions in this session. */
|
|
17462
18140
|
getMetrics() {
|
|
17463
18141
|
const completed = this.state.actions.filter((a) => a.status === "completed");
|
|
17464
|
-
const failed = this.state.actions.filter((a) => a.status === "
|
|
18142
|
+
const failed = this.state.actions.filter((a) => a.status === "failed");
|
|
17465
18143
|
const durations = completed.filter((a) => a.durationMs != null).map((a) => a.durationMs);
|
|
17466
18144
|
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
17467
18145
|
const toolBreakdown = {};
|
|
@@ -17470,7 +18148,7 @@ ${progress}
|
|
|
17470
18148
|
if (!toolBreakdown[name]) toolBreakdown[name] = { count: 0, totalMs: 0, avgMs: 0, errors: 0 };
|
|
17471
18149
|
toolBreakdown[name].count++;
|
|
17472
18150
|
if (action.durationMs != null) toolBreakdown[name].totalMs += action.durationMs;
|
|
17473
|
-
if (action.status === "
|
|
18151
|
+
if (action.status === "failed") toolBreakdown[name].errors++;
|
|
17474
18152
|
}
|
|
17475
18153
|
for (const entry of Object.values(toolBreakdown)) {
|
|
17476
18154
|
entry.avgMs = entry.count > 0 ? Math.round(entry.totalMs / entry.count) : 0;
|
|
@@ -17499,7 +18177,7 @@ ${progress}
|
|
|
17499
18177
|
}
|
|
17500
18178
|
awaitApproval(action, reason) {
|
|
17501
18179
|
const approval = {
|
|
17502
|
-
id:
|
|
18180
|
+
id: crypto$1.randomUUID(),
|
|
17503
18181
|
actionId: action.id,
|
|
17504
18182
|
source: action.source,
|
|
17505
18183
|
name: action.name,
|
|
@@ -17625,6 +18303,41 @@ function installAdBlocking(tabManager) {
|
|
|
17625
18303
|
callback({ cancel: shouldBlockRequest(details) });
|
|
17626
18304
|
});
|
|
17627
18305
|
}
|
|
18306
|
+
function installDownloadHandler(chromeView) {
|
|
18307
|
+
electron.session.defaultSession.on("will-download", (_event, item) => {
|
|
18308
|
+
const settings2 = loadSettings();
|
|
18309
|
+
const downloadDir = settings2.downloadPath.trim() || electron.app.getPath("downloads");
|
|
18310
|
+
const filename = item.getFilename();
|
|
18311
|
+
const savePath = path.join(downloadDir, filename);
|
|
18312
|
+
item.setSavePath(savePath);
|
|
18313
|
+
const info = {
|
|
18314
|
+
filename,
|
|
18315
|
+
savePath,
|
|
18316
|
+
totalBytes: item.getTotalBytes(),
|
|
18317
|
+
receivedBytes: 0,
|
|
18318
|
+
state: "progressing"
|
|
18319
|
+
};
|
|
18320
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18321
|
+
chromeView.webContents.send(Channels.DOWNLOAD_STARTED, info);
|
|
18322
|
+
}
|
|
18323
|
+
item.on("updated", (_event2, state2) => {
|
|
18324
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
18325
|
+
info.totalBytes = item.getTotalBytes();
|
|
18326
|
+
info.state = state2 === "progressing" ? "progressing" : "interrupted";
|
|
18327
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18328
|
+
chromeView.webContents.send(Channels.DOWNLOAD_PROGRESS, info);
|
|
18329
|
+
}
|
|
18330
|
+
});
|
|
18331
|
+
item.once("done", (_event2, state2) => {
|
|
18332
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
18333
|
+
info.state = state2 === "completed" ? "completed" : "cancelled";
|
|
18334
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18335
|
+
chromeView.webContents.send(Channels.DOWNLOAD_DONE, info);
|
|
18336
|
+
}
|
|
18337
|
+
});
|
|
18338
|
+
});
|
|
18339
|
+
}
|
|
18340
|
+
let runtime = null;
|
|
17628
18341
|
function rendererUrlFor(view) {
|
|
17629
18342
|
if (!process.env.ELECTRON_RENDERER_URL) return null;
|
|
17630
18343
|
const url = new URL(process.env.ELECTRON_RENDERER_URL);
|
|
@@ -17705,7 +18418,6 @@ async function bootstrap() {
|
|
|
17705
18418
|
if (settings2.clearBookmarksOnLaunch) {
|
|
17706
18419
|
clearAll();
|
|
17707
18420
|
}
|
|
17708
|
-
let runtime = null;
|
|
17709
18421
|
const windowState = createMainWindow((tabs, activeId) => {
|
|
17710
18422
|
windowState.chromeView.webContents.send(
|
|
17711
18423
|
Channels.TAB_STATE_UPDATE,
|
|
@@ -17727,15 +18439,10 @@ async function bootstrap() {
|
|
|
17727
18439
|
const registerHighlightShortcut = () => {
|
|
17728
18440
|
electron.globalShortcut.unregister("CommandOrControl+H");
|
|
17729
18441
|
const success = electron.globalShortcut.register("CommandOrControl+H", () => {
|
|
17730
|
-
console.log("[Vessel] Ctrl+H shortcut triggered");
|
|
17731
18442
|
const activeTab = tabManager.getActiveTab();
|
|
17732
|
-
if (!activeTab)
|
|
17733
|
-
console.log("[Vessel] No active tab");
|
|
17734
|
-
return;
|
|
17735
|
-
}
|
|
18443
|
+
if (!activeTab) return;
|
|
17736
18444
|
tabManager.captureHighlightFromActiveTab();
|
|
17737
18445
|
});
|
|
17738
|
-
console.log("[Vessel] Ctrl+H shortcut registered:", success);
|
|
17739
18446
|
if (!success) {
|
|
17740
18447
|
console.warn("[Vessel] Failed to register Ctrl+H shortcut");
|
|
17741
18448
|
}
|
|
@@ -17761,6 +18468,11 @@ async function bootstrap() {
|
|
|
17761
18468
|
chromeView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|
|
17762
18469
|
sidebarView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|
|
17763
18470
|
});
|
|
18471
|
+
subscribe$1((state2) => {
|
|
18472
|
+
chromeView.webContents.send(Channels.HISTORY_UPDATE, state2);
|
|
18473
|
+
sidebarView.webContents.send(Channels.HISTORY_UPDATE, state2);
|
|
18474
|
+
});
|
|
18475
|
+
installDownloadHandler(chromeView);
|
|
17764
18476
|
const chromeUrl = rendererUrlFor("chrome");
|
|
17765
18477
|
const sidebarUrl = rendererUrlFor("sidebar");
|
|
17766
18478
|
const devtoolsUrl = rendererUrlFor("devtools");
|
|
@@ -17800,6 +18512,7 @@ electron.app.whenReady().then(bootstrap).catch((error) => {
|
|
|
17800
18512
|
});
|
|
17801
18513
|
electron.app.on("window-all-closed", () => {
|
|
17802
18514
|
electron.globalShortcut.unregisterAll();
|
|
18515
|
+
runtime?.flushPersist();
|
|
17803
18516
|
void stopMcpServer().finally(() => {
|
|
17804
18517
|
electron.app.quit();
|
|
17805
18518
|
});
|