@quanta-intellect/vessel-browser 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/out/main/index.js +1630 -498
- package/out/preload/content-script.js +303 -19
- package/out/preload/index.js +32 -1
- package/out/renderer/assets/{index-DiB_DxLD.js → index-DSaws_sH.js} +429 -410
- package/out/renderer/index.html +2 -1
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -8,11 +8,125 @@ const Anthropic = require("@anthropic-ai/sdk");
|
|
|
8
8
|
const OpenAI = require("openai");
|
|
9
9
|
const zod = require("zod");
|
|
10
10
|
const path$1 = require("node:path");
|
|
11
|
-
const
|
|
11
|
+
const crypto$1 = require("node:crypto");
|
|
12
12
|
const http = require("node:http");
|
|
13
13
|
const os = require("node:os");
|
|
14
14
|
const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
15
15
|
const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
16
|
+
const defaults = {
|
|
17
|
+
defaultUrl: "https://start.duckduckgo.com",
|
|
18
|
+
theme: "dark",
|
|
19
|
+
sidebarWidth: 340,
|
|
20
|
+
mcpPort: 3100,
|
|
21
|
+
autoRestoreSession: true,
|
|
22
|
+
clearBookmarksOnLaunch: false,
|
|
23
|
+
obsidianVaultPath: "",
|
|
24
|
+
approvalMode: "confirm-dangerous",
|
|
25
|
+
agentTranscriptMode: "summary",
|
|
26
|
+
chatProvider: null,
|
|
27
|
+
maxToolIterations: 200,
|
|
28
|
+
domainPolicy: { allowedDomains: [], blockedDomains: [] },
|
|
29
|
+
downloadPath: ""
|
|
30
|
+
};
|
|
31
|
+
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
32
|
+
let settings = null;
|
|
33
|
+
let settingsIssues = [];
|
|
34
|
+
function getSettingsPath() {
|
|
35
|
+
return path.join(electron.app.getPath("userData"), "vessel-settings.json");
|
|
36
|
+
}
|
|
37
|
+
function getSettingsLoadIssues() {
|
|
38
|
+
return settingsIssues.map((issue) => ({ ...issue }));
|
|
39
|
+
}
|
|
40
|
+
function sanitizePort(value) {
|
|
41
|
+
const parsed = Number(value);
|
|
42
|
+
if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535) {
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
settingsIssues.push({
|
|
46
|
+
code: "settings-invalid-mcp-port",
|
|
47
|
+
severity: "warning",
|
|
48
|
+
title: "Invalid MCP port in settings",
|
|
49
|
+
detail: `Expected an integer between 1 and 65535 but found ${JSON.stringify(value)}.`,
|
|
50
|
+
action: `Using default port ${defaults.mcpPort} instead.`
|
|
51
|
+
});
|
|
52
|
+
return defaults.mcpPort;
|
|
53
|
+
}
|
|
54
|
+
function loadSettings() {
|
|
55
|
+
if (settings) return settings;
|
|
56
|
+
settingsIssues = [];
|
|
57
|
+
try {
|
|
58
|
+
const raw = fs.readFileSync(getSettingsPath(), "utf-8");
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
delete parsed.apiKey;
|
|
61
|
+
delete parsed.provider;
|
|
62
|
+
settings = {
|
|
63
|
+
...defaults,
|
|
64
|
+
...parsed,
|
|
65
|
+
mcpPort: sanitizePort(parsed.mcpPort ?? defaults.mcpPort),
|
|
66
|
+
agentTranscriptMode: parsed.agentTranscriptMode === "off" || parsed.agentTranscriptMode === "summary" || parsed.agentTranscriptMode === "full" ? parsed.agentTranscriptMode : parsed.showAgentTranscript === false ? "off" : defaults.agentTranscriptMode
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (fs.existsSync(getSettingsPath())) {
|
|
70
|
+
settingsIssues.push({
|
|
71
|
+
code: "settings-read-failed",
|
|
72
|
+
severity: "warning",
|
|
73
|
+
title: "Could not read Vessel settings",
|
|
74
|
+
detail: error instanceof Error ? error.message : "Unknown settings error.",
|
|
75
|
+
action: "Falling back to built-in defaults for this launch."
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
settings = { ...defaults };
|
|
79
|
+
}
|
|
80
|
+
return settings;
|
|
81
|
+
}
|
|
82
|
+
function saveSettings() {
|
|
83
|
+
try {
|
|
84
|
+
fs.mkdirSync(path.dirname(getSettingsPath()), { recursive: true });
|
|
85
|
+
fs.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("[Vessel] Failed to save settings:", err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function setSetting(key, value) {
|
|
91
|
+
loadSettings();
|
|
92
|
+
if (key === "mcpPort") {
|
|
93
|
+
settings.mcpPort = sanitizePort(value);
|
|
94
|
+
} else {
|
|
95
|
+
settings[key] = value;
|
|
96
|
+
}
|
|
97
|
+
saveSettings();
|
|
98
|
+
return { ...settings };
|
|
99
|
+
}
|
|
100
|
+
function checkDomainPolicy(url) {
|
|
101
|
+
if (!url || url.startsWith("about:")) return null;
|
|
102
|
+
const settings2 = loadSettings();
|
|
103
|
+
const policy = settings2.domainPolicy;
|
|
104
|
+
if (policy.allowedDomains.length === 0 && policy.blockedDomains.length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
let hostname;
|
|
108
|
+
try {
|
|
109
|
+
hostname = new URL(url).hostname.toLowerCase();
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (policy.allowedDomains.length > 0) {
|
|
114
|
+
const allowed = policy.allowedDomains.some(
|
|
115
|
+
(d) => matchesDomain(hostname, d.toLowerCase())
|
|
116
|
+
);
|
|
117
|
+
return allowed ? null : `Navigation blocked by domain policy: ${hostname} is not in the allowed domains list.`;
|
|
118
|
+
}
|
|
119
|
+
if (policy.blockedDomains.length > 0) {
|
|
120
|
+
const blocked = policy.blockedDomains.some(
|
|
121
|
+
(d) => matchesDomain(hostname, d.toLowerCase())
|
|
122
|
+
);
|
|
123
|
+
return blocked ? `Navigation blocked by domain policy: ${hostname} is in the blocked domains list.` : null;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function matchesDomain(hostname, policyDomain) {
|
|
128
|
+
return hostname === policyDomain || hostname.endsWith("." + policyDomain);
|
|
129
|
+
}
|
|
16
130
|
const MAX_CUSTOM_HISTORY = 50;
|
|
17
131
|
class Tab {
|
|
18
132
|
id;
|
|
@@ -60,7 +174,8 @@ class Tab {
|
|
|
60
174
|
canGoBack: false,
|
|
61
175
|
canGoForward: false,
|
|
62
176
|
isReaderMode: false,
|
|
63
|
-
adBlockingEnabled: options?.adBlockingEnabled ?? true
|
|
177
|
+
adBlockingEnabled: options?.adBlockingEnabled ?? true,
|
|
178
|
+
role: options?.role
|
|
64
179
|
};
|
|
65
180
|
this.view.webContents.on("before-input-event", (_event, input) => {
|
|
66
181
|
if (!input.control && !input.meta) return;
|
|
@@ -250,7 +365,13 @@ class Tab {
|
|
|
250
365
|
url = `https://duckduckgo.com/?q=${encodeURIComponent(url)}`;
|
|
251
366
|
}
|
|
252
367
|
}
|
|
368
|
+
if (!/^https?:\/\//i.test(url) && !url.startsWith("about:")) {
|
|
369
|
+
return `Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`;
|
|
370
|
+
}
|
|
371
|
+
const policyError = checkDomainPolicy(url);
|
|
372
|
+
if (policyError) return policyError;
|
|
253
373
|
this.view.webContents.loadURL(url);
|
|
374
|
+
return null;
|
|
254
375
|
}
|
|
255
376
|
goBack() {
|
|
256
377
|
const previousUrl = this.urlHistory.pop();
|
|
@@ -388,31 +509,35 @@ class Tab {
|
|
|
388
509
|
this.view.webContents.close();
|
|
389
510
|
}
|
|
390
511
|
}
|
|
391
|
-
let state$
|
|
392
|
-
const listeners$
|
|
512
|
+
let state$3 = null;
|
|
513
|
+
const listeners$2 = /* @__PURE__ */ new Set();
|
|
393
514
|
function getHighlightsPath() {
|
|
394
515
|
return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
|
|
395
516
|
}
|
|
396
|
-
function load$
|
|
397
|
-
if (state$
|
|
517
|
+
function load$2() {
|
|
518
|
+
if (state$3) return state$3;
|
|
398
519
|
try {
|
|
399
520
|
const raw = fs.readFileSync(getHighlightsPath(), "utf-8");
|
|
400
521
|
const parsed = JSON.parse(raw);
|
|
401
|
-
state$
|
|
522
|
+
state$3 = {
|
|
402
523
|
highlights: Array.isArray(parsed.highlights) ? parsed.highlights : []
|
|
403
524
|
};
|
|
404
525
|
} catch {
|
|
405
|
-
state$
|
|
526
|
+
state$3 = { highlights: [] };
|
|
406
527
|
}
|
|
407
|
-
return state$
|
|
528
|
+
return state$3;
|
|
408
529
|
}
|
|
409
|
-
function save$
|
|
410
|
-
|
|
530
|
+
function save$2() {
|
|
531
|
+
try {
|
|
532
|
+
fs.writeFileSync(getHighlightsPath(), JSON.stringify(state$3, null, 2), "utf-8");
|
|
533
|
+
} catch (err) {
|
|
534
|
+
console.error("[Vessel] Failed to save highlights:", err);
|
|
535
|
+
}
|
|
411
536
|
}
|
|
412
|
-
function emit$
|
|
413
|
-
if (!state$
|
|
414
|
-
const snapshot = { highlights: [...state$
|
|
415
|
-
for (const listener of listeners$
|
|
537
|
+
function emit$2() {
|
|
538
|
+
if (!state$3) return;
|
|
539
|
+
const snapshot = { highlights: [...state$3.highlights] };
|
|
540
|
+
for (const listener of listeners$2) {
|
|
416
541
|
listener(snapshot);
|
|
417
542
|
}
|
|
418
543
|
}
|
|
@@ -425,17 +550,17 @@ function normalizeUrl(rawUrl) {
|
|
|
425
550
|
return rawUrl;
|
|
426
551
|
}
|
|
427
552
|
}
|
|
428
|
-
function getState$
|
|
429
|
-
load$
|
|
430
|
-
return { highlights: [...state$
|
|
553
|
+
function getState$2() {
|
|
554
|
+
load$2();
|
|
555
|
+
return { highlights: [...state$3.highlights] };
|
|
431
556
|
}
|
|
432
557
|
function getHighlightsForUrl(url) {
|
|
433
|
-
load$
|
|
558
|
+
load$2();
|
|
434
559
|
const normalized = normalizeUrl(url);
|
|
435
|
-
return state$
|
|
560
|
+
return state$3.highlights.filter((h) => h.url === normalized);
|
|
436
561
|
}
|
|
437
562
|
function addHighlight(url, selector, text, label, color, source) {
|
|
438
|
-
load$
|
|
563
|
+
load$2();
|
|
439
564
|
const highlight = {
|
|
440
565
|
id: crypto.randomUUID(),
|
|
441
566
|
url: normalizeUrl(url),
|
|
@@ -446,45 +571,45 @@ function addHighlight(url, selector, text, label, color, source) {
|
|
|
446
571
|
source: source || void 0,
|
|
447
572
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
448
573
|
};
|
|
449
|
-
state$
|
|
450
|
-
save$
|
|
451
|
-
emit$
|
|
574
|
+
state$3.highlights.push(highlight);
|
|
575
|
+
save$2();
|
|
576
|
+
emit$2();
|
|
452
577
|
return highlight;
|
|
453
578
|
}
|
|
454
579
|
function removeHighlight(id) {
|
|
455
|
-
load$
|
|
456
|
-
const index = state$
|
|
580
|
+
load$2();
|
|
581
|
+
const index = state$3.highlights.findIndex((h) => h.id === id);
|
|
457
582
|
if (index === -1) return null;
|
|
458
|
-
const [removed] = state$
|
|
459
|
-
save$
|
|
460
|
-
emit$
|
|
583
|
+
const [removed] = state$3.highlights.splice(index, 1);
|
|
584
|
+
save$2();
|
|
585
|
+
emit$2();
|
|
461
586
|
return removed;
|
|
462
587
|
}
|
|
463
588
|
function findHighlightByText(url, text) {
|
|
464
|
-
load$
|
|
589
|
+
load$2();
|
|
465
590
|
const normalized = normalizeUrl(url);
|
|
466
|
-
return state$
|
|
591
|
+
return state$3.highlights.find(
|
|
467
592
|
(h) => h.url === normalized && h.text && h.text === text
|
|
468
593
|
) ?? null;
|
|
469
594
|
}
|
|
470
595
|
function updateHighlightColor(id, color) {
|
|
471
|
-
load$
|
|
472
|
-
const highlight = state$
|
|
596
|
+
load$2();
|
|
597
|
+
const highlight = state$3.highlights.find((h) => h.id === id);
|
|
473
598
|
if (!highlight) return null;
|
|
474
599
|
highlight.color = color;
|
|
475
|
-
save$
|
|
476
|
-
emit$
|
|
600
|
+
save$2();
|
|
601
|
+
emit$2();
|
|
477
602
|
return highlight;
|
|
478
603
|
}
|
|
479
604
|
function clearHighlightsForUrl(url) {
|
|
480
|
-
load$
|
|
605
|
+
load$2();
|
|
481
606
|
const normalized = normalizeUrl(url);
|
|
482
|
-
const before = state$
|
|
483
|
-
state$
|
|
484
|
-
const removed = before - state$
|
|
607
|
+
const before = state$3.highlights.length;
|
|
608
|
+
state$3.highlights = state$3.highlights.filter((h) => h.url !== normalized);
|
|
609
|
+
const removed = before - state$3.highlights.length;
|
|
485
610
|
if (removed > 0) {
|
|
486
|
-
save$
|
|
487
|
-
emit$
|
|
611
|
+
save$2();
|
|
612
|
+
emit$2();
|
|
488
613
|
}
|
|
489
614
|
return removed;
|
|
490
615
|
}
|
|
@@ -710,7 +835,8 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
710
835
|
if (text) {
|
|
711
836
|
return wc.executeJavaScript(`
|
|
712
837
|
(function() {
|
|
713
|
-
var searchText = ${JSON.stringify(text)};
|
|
838
|
+
var searchText = (${JSON.stringify(text)} || '').trim();
|
|
839
|
+
var foldedSearchText = searchText.toLowerCase();
|
|
714
840
|
var solidColor = ${JSON.stringify(c.solid)};
|
|
715
841
|
var bgColor = ${JSON.stringify(c.bg)};
|
|
716
842
|
var labelBg = ${JSON.stringify(c.label)};
|
|
@@ -748,7 +874,11 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
748
874
|
});
|
|
749
875
|
var n;
|
|
750
876
|
while ((n = w.nextNode())) {
|
|
751
|
-
var
|
|
877
|
+
var haystack = n.textContent || '';
|
|
878
|
+
var idx = haystack.indexOf(searchText);
|
|
879
|
+
if (idx === -1 && foldedSearchText) {
|
|
880
|
+
idx = haystack.toLowerCase().indexOf(foldedSearchText);
|
|
881
|
+
}
|
|
752
882
|
if (idx !== -1) {
|
|
753
883
|
matches.push({ node: n, idx: idx });
|
|
754
884
|
if (matches.length >= limit) break;
|
|
@@ -819,6 +949,172 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
819
949
|
}
|
|
820
950
|
return "Error: No element or text to highlight";
|
|
821
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
|
+
}
|
|
822
1118
|
async function clearHighlights(wc) {
|
|
823
1119
|
return wc.executeJavaScript(`
|
|
824
1120
|
(function() {
|
|
@@ -843,6 +1139,134 @@ async function clearHighlights(wc) {
|
|
|
843
1139
|
})()
|
|
844
1140
|
`);
|
|
845
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
|
+
}
|
|
846
1270
|
const MAX_CONSOLE_ENTRIES = 500;
|
|
847
1271
|
const MAX_NETWORK_ENTRIES = 200;
|
|
848
1272
|
const MAX_ERROR_ENTRIES = 200;
|
|
@@ -1567,7 +1991,10 @@ class TabManager {
|
|
|
1567
1991
|
onOpenUrl: ({ url: requestedUrl, background: background2, adBlockingEnabled }) => {
|
|
1568
1992
|
this.createTab(requestedUrl, { background: background2, adBlockingEnabled });
|
|
1569
1993
|
},
|
|
1570
|
-
onPageLoad: (pageUrl, wc) =>
|
|
1994
|
+
onPageLoad: (pageUrl, wc) => {
|
|
1995
|
+
this.reapplyHighlights(pageUrl, wc);
|
|
1996
|
+
addEntry(pageUrl, wc.getTitle());
|
|
1997
|
+
},
|
|
1571
1998
|
onHighlightSelection: (wc) => this.captureHighlightFromPage(wc),
|
|
1572
1999
|
onHighlightRemove: (url2, text) => this.removeHighlightByText(url2, text),
|
|
1573
2000
|
onHighlightRecolor: (url2, text, color) => this.recolorHighlightByText(url2, text, color)
|
|
@@ -1719,16 +2146,14 @@ class TabManager {
|
|
|
1719
2146
|
if (last && last.url === normalized && now - last.at < 500) return;
|
|
1720
2147
|
this.lastReapply.set(wcId, { url: normalized, at: now });
|
|
1721
2148
|
const highlights = getHighlightsForUrl(url);
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
h.color
|
|
1731
|
-
).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(() => {
|
|
1732
2157
|
});
|
|
1733
2158
|
}
|
|
1734
2159
|
}
|
|
@@ -1736,62 +2161,23 @@ class TabManager {
|
|
|
1736
2161
|
this.highlightCaptureCallback = callback;
|
|
1737
2162
|
}
|
|
1738
2163
|
captureHighlightFromActiveTab() {
|
|
1739
|
-
console.log("[Vessel] captureHighlightFromActiveTab called");
|
|
1740
2164
|
const activeTab = this.getActiveTab();
|
|
1741
2165
|
if (!activeTab) {
|
|
1742
|
-
console.log("[Vessel] No active tab in captureHighlightFromActiveTab");
|
|
1743
2166
|
return { success: false, message: "No active tab" };
|
|
1744
2167
|
}
|
|
1745
2168
|
const wc = activeTab.view.webContents;
|
|
1746
|
-
console.log("[Vessel] Calling captureHighlightFromPage for:", wc.getURL());
|
|
1747
2169
|
this.captureHighlightFromPage(wc);
|
|
1748
2170
|
return null;
|
|
1749
2171
|
}
|
|
1750
2172
|
captureHighlightFromPage(wc) {
|
|
1751
|
-
console.log("[Vessel] captureHighlightFromPage called");
|
|
1752
2173
|
void (async () => {
|
|
1753
2174
|
try {
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
const url = wc.getURL();
|
|
1759
|
-
console.log("[Vessel] URL:", url);
|
|
1760
|
-
if (!url || url === "about:blank") {
|
|
1761
|
-
console.log("[Vessel] No URL or about:blank");
|
|
1762
|
-
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
|
+
});
|
|
1763
2179
|
}
|
|
1764
|
-
|
|
1765
|
-
(function() {
|
|
1766
|
-
var sel = window.getSelection();
|
|
1767
|
-
return sel ? sel.toString().trim() : '';
|
|
1768
|
-
})()
|
|
1769
|
-
`);
|
|
1770
|
-
console.log("[Vessel] Selected text:", selectedText?.slice(0, 50));
|
|
1771
|
-
if (!selectedText) return;
|
|
1772
|
-
const capped = selectedText.length > 5e3 ? selectedText.slice(0, 5e3) : selectedText;
|
|
1773
|
-
const highlight = addHighlight(
|
|
1774
|
-
url,
|
|
1775
|
-
void 0,
|
|
1776
|
-
capped,
|
|
1777
|
-
void 0,
|
|
1778
|
-
"yellow",
|
|
1779
|
-
"user"
|
|
1780
|
-
);
|
|
1781
|
-
await highlightOnPage(
|
|
1782
|
-
wc,
|
|
1783
|
-
null,
|
|
1784
|
-
capped,
|
|
1785
|
-
void 0,
|
|
1786
|
-
void 0,
|
|
1787
|
-
"yellow"
|
|
1788
|
-
).catch(() => {
|
|
1789
|
-
});
|
|
1790
|
-
this.highlightCaptureCallback?.({
|
|
1791
|
-
success: true,
|
|
1792
|
-
text: capped,
|
|
1793
|
-
id: highlight.id
|
|
1794
|
-
});
|
|
2180
|
+
this.highlightCaptureCallback?.(result);
|
|
1795
2181
|
} catch {
|
|
1796
2182
|
this.highlightCaptureCallback?.({
|
|
1797
2183
|
success: false,
|
|
@@ -1879,83 +2265,6 @@ class TabManager {
|
|
|
1879
2265
|
this.onStateChange(states, this.activeTabId || "");
|
|
1880
2266
|
}
|
|
1881
2267
|
}
|
|
1882
|
-
const defaults = {
|
|
1883
|
-
defaultUrl: "https://start.duckduckgo.com",
|
|
1884
|
-
theme: "dark",
|
|
1885
|
-
sidebarWidth: 340,
|
|
1886
|
-
mcpPort: 3100,
|
|
1887
|
-
autoRestoreSession: true,
|
|
1888
|
-
clearBookmarksOnLaunch: false,
|
|
1889
|
-
obsidianVaultPath: "",
|
|
1890
|
-
approvalMode: "confirm-dangerous",
|
|
1891
|
-
agentTranscriptMode: "summary",
|
|
1892
|
-
chatProvider: null,
|
|
1893
|
-
maxToolIterations: 200
|
|
1894
|
-
};
|
|
1895
|
-
let settings = null;
|
|
1896
|
-
let settingsIssues = [];
|
|
1897
|
-
function getSettingsPath() {
|
|
1898
|
-
return path.join(electron.app.getPath("userData"), "vessel-settings.json");
|
|
1899
|
-
}
|
|
1900
|
-
function getSettingsLoadIssues() {
|
|
1901
|
-
return settingsIssues.map((issue) => ({ ...issue }));
|
|
1902
|
-
}
|
|
1903
|
-
function sanitizePort(value) {
|
|
1904
|
-
const parsed = Number(value);
|
|
1905
|
-
if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535) {
|
|
1906
|
-
return parsed;
|
|
1907
|
-
}
|
|
1908
|
-
settingsIssues.push({
|
|
1909
|
-
code: "settings-invalid-mcp-port",
|
|
1910
|
-
severity: "warning",
|
|
1911
|
-
title: "Invalid MCP port in settings",
|
|
1912
|
-
detail: `Expected an integer between 1 and 65535 but found ${JSON.stringify(value)}.`,
|
|
1913
|
-
action: `Using default port ${defaults.mcpPort} instead.`
|
|
1914
|
-
});
|
|
1915
|
-
return defaults.mcpPort;
|
|
1916
|
-
}
|
|
1917
|
-
function loadSettings() {
|
|
1918
|
-
if (settings) return settings;
|
|
1919
|
-
settingsIssues = [];
|
|
1920
|
-
try {
|
|
1921
|
-
const raw = fs.readFileSync(getSettingsPath(), "utf-8");
|
|
1922
|
-
const parsed = JSON.parse(raw);
|
|
1923
|
-
delete parsed.apiKey;
|
|
1924
|
-
delete parsed.provider;
|
|
1925
|
-
settings = {
|
|
1926
|
-
...defaults,
|
|
1927
|
-
...parsed,
|
|
1928
|
-
mcpPort: sanitizePort(parsed.mcpPort ?? defaults.mcpPort),
|
|
1929
|
-
agentTranscriptMode: parsed.agentTranscriptMode === "off" || parsed.agentTranscriptMode === "summary" || parsed.agentTranscriptMode === "full" ? parsed.agentTranscriptMode : parsed.showAgentTranscript === false ? "off" : defaults.agentTranscriptMode
|
|
1930
|
-
};
|
|
1931
|
-
} catch (error) {
|
|
1932
|
-
if (fs.existsSync(getSettingsPath())) {
|
|
1933
|
-
settingsIssues.push({
|
|
1934
|
-
code: "settings-read-failed",
|
|
1935
|
-
severity: "warning",
|
|
1936
|
-
title: "Could not read Vessel settings",
|
|
1937
|
-
detail: error instanceof Error ? error.message : "Unknown settings error.",
|
|
1938
|
-
action: "Falling back to built-in defaults for this launch."
|
|
1939
|
-
});
|
|
1940
|
-
}
|
|
1941
|
-
settings = { ...defaults };
|
|
1942
|
-
}
|
|
1943
|
-
return settings;
|
|
1944
|
-
}
|
|
1945
|
-
function saveSettings() {
|
|
1946
|
-
fs.mkdirSync(path.dirname(getSettingsPath()), { recursive: true });
|
|
1947
|
-
fs.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2));
|
|
1948
|
-
}
|
|
1949
|
-
function setSetting(key, value) {
|
|
1950
|
-
loadSettings();
|
|
1951
|
-
if (key === "mcpPort") {
|
|
1952
|
-
settings.mcpPort = sanitizePort(value);
|
|
1953
|
-
} else {
|
|
1954
|
-
settings[key] = value;
|
|
1955
|
-
}
|
|
1956
|
-
saveSettings();
|
|
1957
|
-
return { ...settings };
|
|
1958
|
-
}
|
|
1959
2268
|
const Channels = {
|
|
1960
2269
|
// Tab management
|
|
1961
2270
|
TAB_CREATE: "tab:create",
|
|
@@ -2020,6 +2329,21 @@ const Channels = {
|
|
|
2020
2329
|
// DevTools panel
|
|
2021
2330
|
DEVTOOLS_PANEL_TOGGLE: "devtools-panel:toggle",
|
|
2022
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",
|
|
2023
2347
|
// Window controls
|
|
2024
2348
|
WINDOW_MINIMIZE: "window:minimize",
|
|
2025
2349
|
WINDOW_MAXIMIZE: "window:maximize",
|
|
@@ -3141,6 +3465,16 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3141
3465
|
"dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']"
|
|
3142
3466
|
).forEach(function(el) { candidates.add(el); });
|
|
3143
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
|
+
|
|
3144
3478
|
// Fixed/sticky elements are the other overlay category — walk only
|
|
3145
3479
|
// direct children of body and high-level containers (depth ≤ 3)
|
|
3146
3480
|
// since real overlays are almost always near the top of the DOM tree.
|
|
@@ -3168,13 +3502,23 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3168
3502
|
var cartConfirm = !dialogLike && !drawerLike && isPositioned(style) &&
|
|
3169
3503
|
rect.width >= 160 && rect.height >= 100 &&
|
|
3170
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
|
+
})();
|
|
3171
3512
|
var blocksInteraction = dialogLike ||
|
|
3172
3513
|
drawerLike ||
|
|
3173
3514
|
cartConfirm ||
|
|
3174
3515
|
((style.position === "fixed" || style.position === "sticky") &&
|
|
3175
3516
|
parseZIndex(style) >= 10 &&
|
|
3176
3517
|
areaRatio >= 0.3 &&
|
|
3177
|
-
coversViewportCenter(rect))
|
|
3518
|
+
coversViewportCenter(rect)) ||
|
|
3519
|
+
(bodyLocked &&
|
|
3520
|
+
(style.position === "fixed" || style.position === "sticky") &&
|
|
3521
|
+
areaRatio >= 0.2);
|
|
3178
3522
|
|
|
3179
3523
|
if (!blocksInteraction && type !== "dialog" && type !== "modal") return;
|
|
3180
3524
|
|
|
@@ -4001,9 +4345,12 @@ function generateReaderHTML(page) {
|
|
|
4001
4345
|
function escapeHtml(str) {
|
|
4002
4346
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4003
4347
|
}
|
|
4004
|
-
|
|
4348
|
+
const mcpStatusChangeListeners = /* @__PURE__ */ new Set();
|
|
4005
4349
|
function onMcpStatusChange(listener) {
|
|
4006
|
-
|
|
4350
|
+
mcpStatusChangeListeners.add(listener);
|
|
4351
|
+
return () => {
|
|
4352
|
+
mcpStatusChangeListeners.delete(listener);
|
|
4353
|
+
};
|
|
4007
4354
|
}
|
|
4008
4355
|
function getMcpStatus() {
|
|
4009
4356
|
return state$1.mcp.status;
|
|
@@ -4054,7 +4401,9 @@ function setMcpHealth(update) {
|
|
|
4054
4401
|
state$1.mcp.status = update.status;
|
|
4055
4402
|
state$1.mcp.message = update.message;
|
|
4056
4403
|
if (prevStatus !== state$1.mcp.status) {
|
|
4057
|
-
|
|
4404
|
+
for (const listener of mcpStatusChangeListeners) {
|
|
4405
|
+
listener(state$1.mcp.status);
|
|
4406
|
+
}
|
|
4058
4407
|
}
|
|
4059
4408
|
}
|
|
4060
4409
|
const DEFAULT_MAX_ITERATIONS$1 = 200;
|
|
@@ -4571,6 +4920,158 @@ function createProvider(config) {
|
|
|
4571
4920
|
}
|
|
4572
4921
|
return new OpenAICompatProvider(normalized);
|
|
4573
4922
|
}
|
|
4923
|
+
const CORRECT_HINT_RE = /\b(correct|right choice|this is correct|correct answer|pick this|select this|choose this|right answer)\b/i;
|
|
4924
|
+
const WRONG_HINT_RE = /\b(wrong|incorrect|not this|don't pick|do not pick|bad option|decoy)\b/i;
|
|
4925
|
+
function elementLabel(el) {
|
|
4926
|
+
return el.text?.trim() || el.label?.trim() || el.value?.trim() || el.placeholder?.trim() || void 0;
|
|
4927
|
+
}
|
|
4928
|
+
function isOverlayAction(el) {
|
|
4929
|
+
if (el.type === "button" || el.type === "link") return true;
|
|
4930
|
+
if (el.type !== "input") return false;
|
|
4931
|
+
return ["button", "submit", "radio", "checkbox"].includes(
|
|
4932
|
+
(el.inputType || "").toLowerCase()
|
|
4933
|
+
);
|
|
4934
|
+
}
|
|
4935
|
+
function isRadioOption(el) {
|
|
4936
|
+
return el.role === "radio" || el.type === "input" && (el.inputType || "").toLowerCase() === "radio";
|
|
4937
|
+
}
|
|
4938
|
+
function normalizeAction(el) {
|
|
4939
|
+
return {
|
|
4940
|
+
index: el.index,
|
|
4941
|
+
label: elementLabel(el),
|
|
4942
|
+
selector: el.selector,
|
|
4943
|
+
role: el.role,
|
|
4944
|
+
labelSource: el.labelSource,
|
|
4945
|
+
looksCorrect: el.looksCorrect !== void 0 ? el.looksCorrect : looksLikeCorrectOption(elementLabel(el))
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
function normalizeStoredAction(action) {
|
|
4949
|
+
return {
|
|
4950
|
+
label: action.label,
|
|
4951
|
+
selector: action.selector,
|
|
4952
|
+
role: action.kind === "radio" ? "radio" : void 0
|
|
4953
|
+
};
|
|
4954
|
+
}
|
|
4955
|
+
function normalizeStoredRadioOption(option) {
|
|
4956
|
+
return {
|
|
4957
|
+
label: option.label,
|
|
4958
|
+
selector: option.selector,
|
|
4959
|
+
role: "radio",
|
|
4960
|
+
labelSource: option.labelSource,
|
|
4961
|
+
looksCorrect: option.looksCorrect !== void 0 ? option.looksCorrect : looksLikeCorrectOption(option.label)
|
|
4962
|
+
};
|
|
4963
|
+
}
|
|
4964
|
+
function dedupeCandidates(actions) {
|
|
4965
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4966
|
+
return actions.filter((action) => {
|
|
4967
|
+
const key = [
|
|
4968
|
+
action.selector || "",
|
|
4969
|
+
action.label || "",
|
|
4970
|
+
action.role || "",
|
|
4971
|
+
action.labelSource || ""
|
|
4972
|
+
].join("::");
|
|
4973
|
+
if (seen.has(key)) return false;
|
|
4974
|
+
seen.add(key);
|
|
4975
|
+
return true;
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
function classifyOverlayKind(overlay, radioOptions) {
|
|
4979
|
+
if (overlay.kind) {
|
|
4980
|
+
return overlay.kind;
|
|
4981
|
+
}
|
|
4982
|
+
const haystack = [overlay.label, overlay.text, overlay.role].filter(Boolean).join(" ").toLowerCase();
|
|
4983
|
+
if (/cookie|consent|privacy|gdpr|ccpa|onetrust|trustarc|cookiebot/.test(
|
|
4984
|
+
haystack
|
|
4985
|
+
)) {
|
|
4986
|
+
return "cookie_consent";
|
|
4987
|
+
}
|
|
4988
|
+
if (radioOptions.length > 0) return "selection_modal";
|
|
4989
|
+
if (overlay.role === "alertdialog" || /\b(alert|warning|error)\b/.test(haystack)) {
|
|
4990
|
+
return "alert";
|
|
4991
|
+
}
|
|
4992
|
+
if (overlay.type === "dialog") return "dialog";
|
|
4993
|
+
if (overlay.type === "modal") return "modal";
|
|
4994
|
+
return "overlay";
|
|
4995
|
+
}
|
|
4996
|
+
function findAction(actions, matcher) {
|
|
4997
|
+
return actions.find(
|
|
4998
|
+
(action) => matcher.test((action.label || "").toLowerCase())
|
|
4999
|
+
);
|
|
5000
|
+
}
|
|
5001
|
+
function looksLikeCorrectOption(label) {
|
|
5002
|
+
const text = label?.trim();
|
|
5003
|
+
if (!text) return void 0;
|
|
5004
|
+
if (CORRECT_HINT_RE.test(text)) return true;
|
|
5005
|
+
if (WRONG_HINT_RE.test(text)) return false;
|
|
5006
|
+
return void 0;
|
|
5007
|
+
}
|
|
5008
|
+
function getBlockingOverlaySignature(overlays) {
|
|
5009
|
+
return overlays.filter((overlay) => overlay.blocksInteraction).map(
|
|
5010
|
+
(overlay) => [
|
|
5011
|
+
overlay.kind,
|
|
5012
|
+
overlay.selector || "",
|
|
5013
|
+
overlay.label || "",
|
|
5014
|
+
overlay.text || "",
|
|
5015
|
+
overlay.actions.map((action) => `${action.selector || ""}:${action.label || ""}`).join("|"),
|
|
5016
|
+
overlay.radioOptions.map((option) => `${option.selector || ""}:${option.label || ""}`).join("|")
|
|
5017
|
+
].join("::")
|
|
5018
|
+
).join("||");
|
|
5019
|
+
}
|
|
5020
|
+
function buildOverlayInventory(page) {
|
|
5021
|
+
if (page.overlays.length === 0) return [];
|
|
5022
|
+
return page.overlays.map((overlay) => {
|
|
5023
|
+
const controls = dedupeCandidates([
|
|
5024
|
+
...page.interactiveElements.filter((el) => {
|
|
5025
|
+
if (overlay.selector && el.parentOverlay === overlay.selector) {
|
|
5026
|
+
return true;
|
|
5027
|
+
}
|
|
5028
|
+
return page.overlays.length === 1 && el.context === "dialog";
|
|
5029
|
+
}).filter(isOverlayAction).map(normalizeAction),
|
|
5030
|
+
...(overlay.actions || []).map(normalizeStoredAction)
|
|
5031
|
+
]).filter((action) => action.label || action.selector);
|
|
5032
|
+
const radioOptions = dedupeCandidates([
|
|
5033
|
+
...page.interactiveElements.filter((el) => {
|
|
5034
|
+
if (!isRadioOption(el)) return false;
|
|
5035
|
+
if (overlay.selector && el.parentOverlay === overlay.selector) {
|
|
5036
|
+
return true;
|
|
5037
|
+
}
|
|
5038
|
+
return page.overlays.length === 1 && el.context === "dialog";
|
|
5039
|
+
}).map(normalizeAction),
|
|
5040
|
+
...(overlay.radioOptions || []).map(normalizeStoredRadioOption)
|
|
5041
|
+
]).filter((action) => action.label || action.selector);
|
|
5042
|
+
const kind = classifyOverlayKind(overlay, radioOptions);
|
|
5043
|
+
const dismissAction = findAction(
|
|
5044
|
+
controls,
|
|
5045
|
+
/\b(close|dismiss|skip|cancel|reject|decline|no thanks|not now|maybe later|continue without)\b/
|
|
5046
|
+
);
|
|
5047
|
+
const acceptAction = findAction(
|
|
5048
|
+
controls,
|
|
5049
|
+
/\b(accept|allow|agree|got it|ok|okay|consent)\b/
|
|
5050
|
+
);
|
|
5051
|
+
const submitAction = findAction(
|
|
5052
|
+
controls,
|
|
5053
|
+
/\b(submit|continue|confirm|done|next|save|apply|finish)\b/
|
|
5054
|
+
);
|
|
5055
|
+
const correctOption = radioOptions.find(
|
|
5056
|
+
(option) => option.looksCorrect === true
|
|
5057
|
+
);
|
|
5058
|
+
return {
|
|
5059
|
+
type: overlay.type,
|
|
5060
|
+
kind,
|
|
5061
|
+
role: overlay.role,
|
|
5062
|
+
label: overlay.label,
|
|
5063
|
+
selector: overlay.selector,
|
|
5064
|
+
text: overlay.text,
|
|
5065
|
+
blocksInteraction: overlay.blocksInteraction,
|
|
5066
|
+
actions: controls,
|
|
5067
|
+
radioOptions,
|
|
5068
|
+
dismissAction,
|
|
5069
|
+
acceptAction,
|
|
5070
|
+
submitAction,
|
|
5071
|
+
correctOption
|
|
5072
|
+
};
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
4574
5075
|
const MAX_CONTENT_LENGTH = 6e4;
|
|
4575
5076
|
const MAX_STRUCTURED_ITEMS = 100;
|
|
4576
5077
|
const LARGE_PAGE_HINT_THRESHOLD = 12e3;
|
|
@@ -4638,6 +5139,14 @@ function formatElementMeta(el) {
|
|
|
4638
5139
|
if (el.pattern) {
|
|
4639
5140
|
meta.push(`pattern="${el.pattern}"`);
|
|
4640
5141
|
}
|
|
5142
|
+
if (el.labelSource) {
|
|
5143
|
+
meta.push(`source=${el.labelSource}`);
|
|
5144
|
+
}
|
|
5145
|
+
if (el.looksCorrect === true) {
|
|
5146
|
+
meta.push("likely-correct");
|
|
5147
|
+
} else if (el.looksCorrect === false) {
|
|
5148
|
+
meta.push("likely-wrong");
|
|
5149
|
+
}
|
|
4641
5150
|
if (el.description) {
|
|
4642
5151
|
meta.push(`desc="${el.description.slice(0, 80)}"`);
|
|
4643
5152
|
}
|
|
@@ -4867,7 +5376,7 @@ function formatInteractiveElements(elements) {
|
|
|
4867
5376
|
const parts = [prefix];
|
|
4868
5377
|
if (el.type === "button") {
|
|
4869
5378
|
parts.push(`[${el.text || "Button"}]`);
|
|
4870
|
-
parts.push("button");
|
|
5379
|
+
parts.push(el.role === "radio" ? "radio" : "button");
|
|
4871
5380
|
} else if (el.type === "link") {
|
|
4872
5381
|
parts.push(`[${el.text || "Link"}]`);
|
|
4873
5382
|
parts.push("link");
|
|
@@ -4932,7 +5441,7 @@ function formatForms(forms) {
|
|
|
4932
5441
|
];
|
|
4933
5442
|
if (field.type === "button") {
|
|
4934
5443
|
fieldParts.push(`[${field.text || "Submit"}]`);
|
|
4935
|
-
fieldParts.push("button");
|
|
5444
|
+
fieldParts.push(field.role === "radio" ? "radio" : "button");
|
|
4936
5445
|
} else if (field.type === "input") {
|
|
4937
5446
|
fieldParts.push(`[${field.label || field.placeholder || "Input"}]`);
|
|
4938
5447
|
fieldParts.push(field.inputType || "text");
|
|
@@ -4979,18 +5488,51 @@ function formatLandmarks(landmarks) {
|
|
|
4979
5488
|
function formatViewport(page) {
|
|
4980
5489
|
return `${page.viewport.width}x${page.viewport.height} at scroll (${page.viewport.scrollX}, ${page.viewport.scrollY})`;
|
|
4981
5490
|
}
|
|
4982
|
-
function formatOverlays(
|
|
4983
|
-
if (overlays.length === 0) return "None detected";
|
|
4984
|
-
const items = limitItems(
|
|
5491
|
+
function formatOverlays(page) {
|
|
5492
|
+
if (page.overlays.length === 0) return "None detected";
|
|
5493
|
+
const items = limitItems(buildOverlayInventory(page), 10);
|
|
4985
5494
|
return items.map((overlay) => {
|
|
4986
|
-
const
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
5495
|
+
const lines = [
|
|
5496
|
+
[
|
|
5497
|
+
`- ${overlay.kind}`,
|
|
5498
|
+
overlay.role ? `role=${overlay.role}` : "",
|
|
5499
|
+
overlay.blocksInteraction ? "blocking" : "",
|
|
5500
|
+
overlay.label ? `label="${overlay.label.slice(0, 80)}"` : "",
|
|
5501
|
+
overlay.text ? `text="${overlay.text.slice(0, 100)}"` : ""
|
|
5502
|
+
].filter(Boolean).join(" ")
|
|
5503
|
+
];
|
|
5504
|
+
if (overlay.radioOptions.length > 0) {
|
|
5505
|
+
const options = overlay.radioOptions.slice(0, 4).map((option) => {
|
|
5506
|
+
const tags = [];
|
|
5507
|
+
if (option.labelSource) tags.push(`source=${option.labelSource}`);
|
|
5508
|
+
if (option.looksCorrect === true) tags.push("likely-correct");
|
|
5509
|
+
if (option.looksCorrect === false) tags.push("likely-wrong");
|
|
5510
|
+
const suffix = tags.length > 0 ? ` (${tags.join(", ")})` : "";
|
|
5511
|
+
return `${option.label || option.selector || "radio"}${suffix}`;
|
|
5512
|
+
}).join(" | ");
|
|
5513
|
+
lines.push(` options: ${options}`);
|
|
5514
|
+
}
|
|
5515
|
+
const actionLabels = [
|
|
5516
|
+
overlay.dismissAction?.label ? `dismiss="${overlay.dismissAction.label}"` : "",
|
|
5517
|
+
overlay.acceptAction?.label ? `accept="${overlay.acceptAction.label}"` : "",
|
|
5518
|
+
overlay.submitAction?.label ? `submit="${overlay.submitAction.label}"` : ""
|
|
5519
|
+
].filter(Boolean);
|
|
5520
|
+
if (actionLabels.length > 0) {
|
|
5521
|
+
lines.push(` actions: ${actionLabels.join(" ")}`);
|
|
5522
|
+
}
|
|
5523
|
+
return lines.join("\n");
|
|
4992
5524
|
}).join("\n");
|
|
4993
5525
|
}
|
|
5526
|
+
function getScrollHints(page) {
|
|
5527
|
+
const candidates = page.interactiveElements.filter(
|
|
5528
|
+
(el) => el.visible !== false && el.inViewport === false && el.context !== "nav" && el.context !== "footer" && el.context !== "sidebar" && el.blockedByOverlay !== true && (el.type === "input" || el.type === "textarea" || el.type === "select" || el.type === "button")
|
|
5529
|
+
);
|
|
5530
|
+
if (candidates.length === 0) return [];
|
|
5531
|
+
const labels = limitItems(candidates, 3).map((el) => el.text || el.label || el.placeholder || el.type).filter(Boolean);
|
|
5532
|
+
return [
|
|
5533
|
+
`Scroll to reveal offscreen controls: ${labels.join(", ")}${candidates.length > labels.length ? ", ..." : ""}`
|
|
5534
|
+
];
|
|
5535
|
+
}
|
|
4994
5536
|
function formatDormantOverlays(overlays) {
|
|
4995
5537
|
if (overlays.length === 0) return "None detected";
|
|
4996
5538
|
const items = limitItems(overlays, 10);
|
|
@@ -5327,6 +5869,10 @@ function buildScopedContext(page, mode) {
|
|
|
5327
5869
|
if (page.excerpt) sections.push(`**Summary:** ${page.excerpt}`);
|
|
5328
5870
|
const largePageHint = formatLargePageHint(page);
|
|
5329
5871
|
if (largePageHint) sections.push(`**Reading Hint:** ${largePageHint}`);
|
|
5872
|
+
const scrollHints = getScrollHints(page);
|
|
5873
|
+
if (scrollHints.length > 0) {
|
|
5874
|
+
sections.push(`**Scroll Hint:** ${scrollHints[0]}`);
|
|
5875
|
+
}
|
|
5330
5876
|
sections.push("");
|
|
5331
5877
|
const summaryIntent = analyzePageIntent(page);
|
|
5332
5878
|
if (summaryIntent) {
|
|
@@ -5395,6 +5941,10 @@ function buildScopedContext(page, mode) {
|
|
|
5395
5941
|
sections.push(`**URL:** ${page.url}`);
|
|
5396
5942
|
sections.push(`**Title:** ${page.title}`);
|
|
5397
5943
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
5944
|
+
const interactivesScrollHints = getScrollHints(page);
|
|
5945
|
+
if (interactivesScrollHints.length > 0) {
|
|
5946
|
+
sections.push(`**Scroll Hint:** ${interactivesScrollHints[0]}`);
|
|
5947
|
+
}
|
|
5398
5948
|
sections.push("");
|
|
5399
5949
|
const interactivesIntent = analyzePageIntent(page);
|
|
5400
5950
|
if (interactivesIntent) {
|
|
@@ -5419,7 +5969,7 @@ function buildScopedContext(page, mode) {
|
|
|
5419
5969
|
}
|
|
5420
5970
|
if (page.overlays.length > 0) {
|
|
5421
5971
|
sections.push("### Active Overlays");
|
|
5422
|
-
sections.push(formatOverlays(page
|
|
5972
|
+
sections.push(formatOverlays(page));
|
|
5423
5973
|
sections.push("");
|
|
5424
5974
|
}
|
|
5425
5975
|
if (dialogFocus) {
|
|
@@ -5457,6 +6007,10 @@ function buildScopedContext(page, mode) {
|
|
|
5457
6007
|
sections.push(`**URL:** ${page.url}`);
|
|
5458
6008
|
sections.push(`**Title:** ${page.title}`);
|
|
5459
6009
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
6010
|
+
const visibleScrollHints = getScrollHints(page);
|
|
6011
|
+
if (visibleScrollHints.length > 0) {
|
|
6012
|
+
sections.push(`**Scroll Hint:** ${visibleScrollHints[0]}`);
|
|
6013
|
+
}
|
|
5460
6014
|
sections.push("");
|
|
5461
6015
|
const formsHighlights = getHighlightsForPage(page.url);
|
|
5462
6016
|
if (formsHighlights.length > 0) {
|
|
@@ -5476,7 +6030,7 @@ function buildScopedContext(page, mode) {
|
|
|
5476
6030
|
}
|
|
5477
6031
|
if (page.overlays.length > 0) {
|
|
5478
6032
|
sections.push("### Active Overlays");
|
|
5479
|
-
sections.push(formatOverlays(page
|
|
6033
|
+
sections.push(formatOverlays(page));
|
|
5480
6034
|
sections.push("");
|
|
5481
6035
|
}
|
|
5482
6036
|
if (page.dormantOverlays.length > 0) {
|
|
@@ -5559,7 +6113,7 @@ function buildScopedContext(page, mode) {
|
|
|
5559
6113
|
}
|
|
5560
6114
|
if (page.overlays.length > 0) {
|
|
5561
6115
|
sections.push("### Active Overlays");
|
|
5562
|
-
sections.push(formatOverlays(page
|
|
6116
|
+
sections.push(formatOverlays(page));
|
|
5563
6117
|
sections.push("");
|
|
5564
6118
|
}
|
|
5565
6119
|
if (dialogFocus) {
|
|
@@ -5744,6 +6298,10 @@ function buildStructuredContext(page) {
|
|
|
5744
6298
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
5745
6299
|
if (page.byline) sections.push(`**Author:** ${page.byline}`);
|
|
5746
6300
|
if (page.excerpt) sections.push(`**Summary:** ${page.excerpt}`);
|
|
6301
|
+
const structuredScrollHints = getScrollHints(page);
|
|
6302
|
+
if (structuredScrollHints.length > 0) {
|
|
6303
|
+
sections.push(`**Scroll Hint:** ${structuredScrollHints[0]}`);
|
|
6304
|
+
}
|
|
5747
6305
|
sections.push("");
|
|
5748
6306
|
const pageIntent = analyzePageIntent(page);
|
|
5749
6307
|
if (pageIntent) {
|
|
@@ -5782,7 +6340,7 @@ function buildStructuredContext(page) {
|
|
|
5782
6340
|
sections.push(formatLandmarks(page.landmarks));
|
|
5783
6341
|
sections.push("");
|
|
5784
6342
|
sections.push("### Active Overlays / Modals");
|
|
5785
|
-
sections.push(formatOverlays(page
|
|
6343
|
+
sections.push(formatOverlays(page));
|
|
5786
6344
|
sections.push("");
|
|
5787
6345
|
sections.push("### Dormant Consent / Modal UI");
|
|
5788
6346
|
sections.push(formatDormantOverlays(page.dormantOverlays));
|
|
@@ -5861,6 +6419,83 @@ function buildGeneralPrompt(query) {
|
|
|
5861
6419
|
user: query
|
|
5862
6420
|
};
|
|
5863
6421
|
}
|
|
6422
|
+
const WRAPPING_QUOTES = /* @__PURE__ */ new Set(['"', "'", "`"]);
|
|
6423
|
+
function stripWrappingQuotes(value) {
|
|
6424
|
+
const trimmed = value.trim();
|
|
6425
|
+
if (trimmed.length < 2) return trimmed;
|
|
6426
|
+
const first = trimmed[0];
|
|
6427
|
+
const last = trimmed[trimmed.length - 1];
|
|
6428
|
+
if (first === last && WRAPPING_QUOTES.has(first)) {
|
|
6429
|
+
return trimmed.slice(1, -1).trim();
|
|
6430
|
+
}
|
|
6431
|
+
return trimmed;
|
|
6432
|
+
}
|
|
6433
|
+
function normalizeArrayItem(value) {
|
|
6434
|
+
if (typeof value === "string") {
|
|
6435
|
+
return stripWrappingQuotes(value).trim();
|
|
6436
|
+
}
|
|
6437
|
+
return String(value).trim();
|
|
6438
|
+
}
|
|
6439
|
+
function normalizeLooseString(value) {
|
|
6440
|
+
if (typeof value !== "string") return void 0;
|
|
6441
|
+
const normalized = stripWrappingQuotes(value);
|
|
6442
|
+
return normalized ? normalized : void 0;
|
|
6443
|
+
}
|
|
6444
|
+
function coerceOptionalNumber(value) {
|
|
6445
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
6446
|
+
return value;
|
|
6447
|
+
}
|
|
6448
|
+
const normalized = normalizeLooseString(value);
|
|
6449
|
+
if (!normalized) return void 0;
|
|
6450
|
+
const parsed = Number(normalized);
|
|
6451
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
6452
|
+
}
|
|
6453
|
+
function coerceStringArray(value) {
|
|
6454
|
+
if (Array.isArray(value)) {
|
|
6455
|
+
return value.map(normalizeArrayItem).filter(Boolean);
|
|
6456
|
+
}
|
|
6457
|
+
const normalized = normalizeLooseString(value);
|
|
6458
|
+
if (!normalized) return [];
|
|
6459
|
+
try {
|
|
6460
|
+
const parsed = JSON.parse(normalized);
|
|
6461
|
+
if (Array.isArray(parsed)) {
|
|
6462
|
+
return parsed.map(normalizeArrayItem).filter(Boolean);
|
|
6463
|
+
}
|
|
6464
|
+
} catch {
|
|
6465
|
+
}
|
|
6466
|
+
const lines = normalized.split(/\r?\n/).map((line) => line.replace(/^\s*[-*]\s+/, "").trim()).filter(Boolean);
|
|
6467
|
+
if (lines.length > 1) {
|
|
6468
|
+
return lines;
|
|
6469
|
+
}
|
|
6470
|
+
return [normalized];
|
|
6471
|
+
}
|
|
6472
|
+
function optionalNumberLikeSchema() {
|
|
6473
|
+
return zod.z.preprocess(
|
|
6474
|
+
(value) => {
|
|
6475
|
+
if (value == null) return void 0;
|
|
6476
|
+
return coerceOptionalNumber(value) ?? value;
|
|
6477
|
+
},
|
|
6478
|
+
zod.z.number().finite().optional()
|
|
6479
|
+
);
|
|
6480
|
+
}
|
|
6481
|
+
function normalizedOptionalStringSchema() {
|
|
6482
|
+
return zod.z.preprocess(
|
|
6483
|
+
(value) => {
|
|
6484
|
+
if (value == null) return void 0;
|
|
6485
|
+
return normalizeLooseString(value) ?? value;
|
|
6486
|
+
},
|
|
6487
|
+
zod.z.string().optional()
|
|
6488
|
+
);
|
|
6489
|
+
}
|
|
6490
|
+
function stringArrayLikeSchema() {
|
|
6491
|
+
return zod.z.preprocess(
|
|
6492
|
+
(value) => {
|
|
6493
|
+
if (value == null) return value;
|
|
6494
|
+
return coerceStringArray(value) ?? value;
|
|
6495
|
+
},
|
|
6496
|
+
zod.z.array(zod.z.string().min(1)).min(1)
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
5864
6499
|
const TOOL_DEFINITIONS = [
|
|
5865
6500
|
// --- Tab Management ---
|
|
5866
6501
|
{
|
|
@@ -5989,7 +6624,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5989
6624
|
description: "Scroll the page up or down.",
|
|
5990
6625
|
inputSchema: {
|
|
5991
6626
|
direction: zod.z.enum(["up", "down"]).describe("Scroll direction"),
|
|
5992
|
-
amount:
|
|
6627
|
+
amount: optionalNumberLikeSchema().describe(
|
|
6628
|
+
"Pixels to scroll (default 500)"
|
|
6629
|
+
)
|
|
5993
6630
|
},
|
|
5994
6631
|
tier: 0,
|
|
5995
6632
|
relevance: ["ARTICLE", "SEARCH_RESULTS", "PAGINATED_LIST"]
|
|
@@ -6034,6 +6671,17 @@ const TOOL_DEFINITIONS = [
|
|
|
6034
6671
|
description: "Dismiss a modal, popup, newsletter gate, cookie banner, or overlay using common close/decline actions.",
|
|
6035
6672
|
tier: 1
|
|
6036
6673
|
},
|
|
6674
|
+
{
|
|
6675
|
+
name: "clear_overlays",
|
|
6676
|
+
title: "Clear Overlays",
|
|
6677
|
+
description: "Work through blocking overlays and modals until the page is unblocked, using overlay-specific heuristics for consent banners and radio-selection dialogs.",
|
|
6678
|
+
inputSchema: {
|
|
6679
|
+
strategy: zod.z.enum(["auto", "interactive"]).optional().describe(
|
|
6680
|
+
'How aggressively to clear overlays. "auto" uses heuristics; "interactive" stops earlier when human judgment may be needed.'
|
|
6681
|
+
)
|
|
6682
|
+
},
|
|
6683
|
+
tier: 1
|
|
6684
|
+
},
|
|
6037
6685
|
{
|
|
6038
6686
|
name: "inspect_element",
|
|
6039
6687
|
title: "Inspect Element",
|
|
@@ -6052,6 +6700,7 @@ const TOOL_DEFINITIONS = [
|
|
|
6052
6700
|
description: "Read the current page using a scoped mode. Defaults to a minimal navigation-focused brief; use mode='debug' only when narrower modes are insufficient.",
|
|
6053
6701
|
inputSchema: {
|
|
6054
6702
|
mode: zod.z.enum([
|
|
6703
|
+
"glance",
|
|
6055
6704
|
"summary",
|
|
6056
6705
|
"interactives_only",
|
|
6057
6706
|
"forms_only",
|
|
@@ -6061,7 +6710,7 @@ const TOOL_DEFINITIONS = [
|
|
|
6061
6710
|
"full",
|
|
6062
6711
|
"debug"
|
|
6063
6712
|
]).optional().describe(
|
|
6064
|
-
"Read mode: visible_only/results_only/forms_only/summary/text_only for narrow reads, full/debug for the complete page dump"
|
|
6713
|
+
"Read mode: glance (fastest — viewport snapshot, no JS extraction, ideal for heavy pages), visible_only/results_only/forms_only/summary/text_only for narrow reads, full/debug for the complete page dump"
|
|
6065
6714
|
)
|
|
6066
6715
|
},
|
|
6067
6716
|
tier: 0
|
|
@@ -6233,7 +6882,9 @@ const TOOL_DEFINITIONS = [
|
|
|
6233
6882
|
inputSchema: {
|
|
6234
6883
|
index: zod.z.number().optional().describe("Element index from page content to highlight"),
|
|
6235
6884
|
selector: zod.z.string().optional().describe("CSS selector of element to highlight"),
|
|
6236
|
-
text:
|
|
6885
|
+
text: normalizedOptionalStringSchema().describe(
|
|
6886
|
+
"Text to find and highlight on the page (all occurrences)"
|
|
6887
|
+
),
|
|
6237
6888
|
label: zod.z.string().optional().describe("Annotation label to display near the highlight"),
|
|
6238
6889
|
durationMs: zod.z.number().optional().describe(
|
|
6239
6890
|
"Auto-clear after this many milliseconds (omit for permanent)"
|
|
@@ -6258,7 +6909,7 @@ const TOOL_DEFINITIONS = [
|
|
|
6258
6909
|
goal: zod.z.string().describe(
|
|
6259
6910
|
"What this workflow accomplishes (e.g. 'Purchase item from Amazon')"
|
|
6260
6911
|
),
|
|
6261
|
-
steps:
|
|
6912
|
+
steps: stringArrayLikeSchema().describe(
|
|
6262
6913
|
"Ordered list of step labels (e.g. ['Log in', 'Search', 'Select item', 'Checkout'])"
|
|
6263
6914
|
)
|
|
6264
6915
|
},
|
|
@@ -6495,6 +7146,7 @@ const ALWAYS_FAST_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
6495
7146
|
"search",
|
|
6496
7147
|
"scroll",
|
|
6497
7148
|
"dismiss_popup",
|
|
7149
|
+
"clear_overlays",
|
|
6498
7150
|
"accept_cookies",
|
|
6499
7151
|
"wait_for",
|
|
6500
7152
|
"read_page",
|
|
@@ -6518,6 +7170,9 @@ function inferIntent(query) {
|
|
|
6518
7170
|
}
|
|
6519
7171
|
if (/\b(highlight|mark|annotate)\b/.test(lowered)) intents.add("highlight");
|
|
6520
7172
|
if (/\b(table|csv|rows|columns)\b/.test(lowered)) intents.add("table");
|
|
7173
|
+
if (/\b(overlay|modal|popup|consent|cookie|blocking ui)\b/.test(lowered)) {
|
|
7174
|
+
intents.add("debug");
|
|
7175
|
+
}
|
|
6521
7176
|
if (/\b(debug|diagnose|what should i do|stuck|inspect)\b/.test(lowered)) {
|
|
6522
7177
|
intents.add("debug");
|
|
6523
7178
|
}
|
|
@@ -6759,8 +7414,12 @@ function load() {
|
|
|
6759
7414
|
return state;
|
|
6760
7415
|
}
|
|
6761
7416
|
function save() {
|
|
6762
|
-
|
|
6763
|
-
|
|
7417
|
+
try {
|
|
7418
|
+
fs.mkdirSync(path.dirname(getBookmarksPath()), { recursive: true });
|
|
7419
|
+
fs.writeFileSync(getBookmarksPath(), JSON.stringify(state, null, 2), "utf-8");
|
|
7420
|
+
} catch (err) {
|
|
7421
|
+
console.error("[Vessel] Failed to save bookmarks:", err);
|
|
7422
|
+
}
|
|
6764
7423
|
}
|
|
6765
7424
|
function emit() {
|
|
6766
7425
|
if (!state) return;
|
|
@@ -7186,6 +7845,22 @@ function formatDeadLinkMessage(label, result) {
|
|
|
7186
7845
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
7187
7846
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
7188
7847
|
}
|
|
7848
|
+
const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
7849
|
+
function isSafeNavigationURL(url) {
|
|
7850
|
+
try {
|
|
7851
|
+
const parsed = new URL(url);
|
|
7852
|
+
return ALLOWED_SCHEMES.has(parsed.protocol);
|
|
7853
|
+
} catch {
|
|
7854
|
+
return false;
|
|
7855
|
+
}
|
|
7856
|
+
}
|
|
7857
|
+
function assertSafeURL(url) {
|
|
7858
|
+
if (!isSafeNavigationURL(url)) {
|
|
7859
|
+
throw new Error(
|
|
7860
|
+
`Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`
|
|
7861
|
+
);
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7189
7864
|
const SESSION_VERSION = 1;
|
|
7190
7865
|
function getSessionsDir() {
|
|
7191
7866
|
return path$1.join(electron.app.getPath("userData"), "named-sessions");
|
|
@@ -7205,7 +7880,7 @@ function normalizeSessionName(name) {
|
|
|
7205
7880
|
function sessionFileName(name) {
|
|
7206
7881
|
const normalized = normalizeSessionName(name).toLowerCase();
|
|
7207
7882
|
const slug = normalized.replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "session";
|
|
7208
|
-
const hash =
|
|
7883
|
+
const hash = crypto$1.createHash("sha256").update(normalized).digest("hex").slice(0, 8);
|
|
7209
7884
|
return `${slug}-${hash}.json`;
|
|
7210
7885
|
}
|
|
7211
7886
|
function getSessionPath(name) {
|
|
@@ -7470,10 +8145,148 @@ const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
|
7470
8145
|
function pageBusyError(action) {
|
|
7471
8146
|
return `Error: Page is still busy; ${action} timed out waiting for page scripts. Retry in a moment.`;
|
|
7472
8147
|
}
|
|
8148
|
+
async function glanceExtract(wc) {
|
|
8149
|
+
const startMs = Date.now();
|
|
8150
|
+
const result = await executePageScript(
|
|
8151
|
+
wc,
|
|
8152
|
+
`(function() {
|
|
8153
|
+
var vw = window.innerWidth || document.documentElement.clientWidth || 0;
|
|
8154
|
+
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
8155
|
+
var sy = window.scrollY || window.pageYOffset || 0;
|
|
8156
|
+
|
|
8157
|
+
function inViewport(el) {
|
|
8158
|
+
var r = el.getBoundingClientRect();
|
|
8159
|
+
return r.bottom > 0 && r.top < vh && r.right > 0 && r.left < vw && r.width > 0 && r.height > 0;
|
|
8160
|
+
}
|
|
8161
|
+
|
|
8162
|
+
function label(el) {
|
|
8163
|
+
return (el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 120);
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
// Headings visible on screen
|
|
8167
|
+
var headings = [];
|
|
8168
|
+
document.querySelectorAll('h1, h2, h3, h4').forEach(function(h) {
|
|
8169
|
+
if (!inViewport(h)) return;
|
|
8170
|
+
var t = (h.textContent || '').trim();
|
|
8171
|
+
if (t && t.length < 200) headings.push(h.tagName.toLowerCase() + ': ' + t);
|
|
8172
|
+
});
|
|
8173
|
+
|
|
8174
|
+
// Links visible on screen (deduplicated by text)
|
|
8175
|
+
var links = [];
|
|
8176
|
+
var seenLinks = {};
|
|
8177
|
+
var idx = 1;
|
|
8178
|
+
document.querySelectorAll('a[href]').forEach(function(a) {
|
|
8179
|
+
if (!inViewport(a)) return;
|
|
8180
|
+
var t = (a.textContent || '').trim().slice(0, 100);
|
|
8181
|
+
if (!t || t.length < 2 || seenLinks[t]) return;
|
|
8182
|
+
seenLinks[t] = true;
|
|
8183
|
+
links.push({ text: t, href: (a.href || '').slice(0, 200), index: idx++ });
|
|
8184
|
+
});
|
|
8185
|
+
|
|
8186
|
+
// Buttons visible on screen
|
|
8187
|
+
var buttons = [];
|
|
8188
|
+
document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]').forEach(function(b) {
|
|
8189
|
+
if (!inViewport(b)) return;
|
|
8190
|
+
var t = label(b);
|
|
8191
|
+
if (!t || t.length < 1) return;
|
|
8192
|
+
buttons.push({ text: t, index: idx++ });
|
|
8193
|
+
});
|
|
8194
|
+
|
|
8195
|
+
// Input fields visible on screen
|
|
8196
|
+
var inputs = [];
|
|
8197
|
+
document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea').forEach(function(inp) {
|
|
8198
|
+
if (!inViewport(inp)) return;
|
|
8199
|
+
var type = (inp.type || inp.tagName.toLowerCase() || '').toLowerCase();
|
|
8200
|
+
var lbl = (inp.getAttribute('aria-label') || inp.getAttribute('placeholder') || inp.name || '').trim();
|
|
8201
|
+
inputs.push({ type: type, label: lbl.slice(0, 80), placeholder: (inp.getAttribute('placeholder') || '').slice(0, 80), index: idx++ });
|
|
8202
|
+
});
|
|
8203
|
+
|
|
8204
|
+
// Content snapshot from main content area using textContent (instant, no reflow)
|
|
8205
|
+
var roots = ['main', 'article', '[role="main"]', '#content', '.content', '.story-body'];
|
|
8206
|
+
var contentRoot = null;
|
|
8207
|
+
for (var i = 0; i < roots.length; i++) {
|
|
8208
|
+
contentRoot = document.querySelector(roots[i]);
|
|
8209
|
+
if (contentRoot && contentRoot.textContent.trim().length > 50) break;
|
|
8210
|
+
contentRoot = null;
|
|
8211
|
+
}
|
|
8212
|
+
var snippet = '';
|
|
8213
|
+
if (contentRoot) {
|
|
8214
|
+
snippet = contentRoot.textContent.replace(/[ \\t]+/g, ' ').replace(/(\\n\\s*){3,}/g, '\\n\\n').trim().slice(0, 8000);
|
|
8215
|
+
} else {
|
|
8216
|
+
// Fallback: grab text from visible elements only
|
|
8217
|
+
var parts = [];
|
|
8218
|
+
document.querySelectorAll('h1, h2, h3, p, li, td, span, div').forEach(function(el) {
|
|
8219
|
+
if (parts.length > 100 || !inViewport(el)) return;
|
|
8220
|
+
var t = (el.textContent || '').trim();
|
|
8221
|
+
if (t.length > 10 && t.length < 500) parts.push(t);
|
|
8222
|
+
});
|
|
8223
|
+
snippet = parts.join('\\n').slice(0, 8000);
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8226
|
+
return {
|
|
8227
|
+
title: document.title || '',
|
|
8228
|
+
url: location.href,
|
|
8229
|
+
headings: headings.slice(0, 20),
|
|
8230
|
+
links: links.slice(0, 40),
|
|
8231
|
+
buttons: buttons.slice(0, 20),
|
|
8232
|
+
inputs: inputs.slice(0, 15),
|
|
8233
|
+
contentSnippet: snippet,
|
|
8234
|
+
viewportHeight: vh,
|
|
8235
|
+
viewportWidth: vw,
|
|
8236
|
+
scrollY: Math.round(sy),
|
|
8237
|
+
};
|
|
8238
|
+
})()`,
|
|
8239
|
+
{ timeoutMs: 2500, label: "glance-extract" }
|
|
8240
|
+
);
|
|
8241
|
+
const elapsed = Date.now() - startMs;
|
|
8242
|
+
if (!result || result === PAGE_SCRIPT_TIMEOUT) {
|
|
8243
|
+
return [
|
|
8244
|
+
`# ${wc.getTitle() || "(untitled)"}`,
|
|
8245
|
+
`URL: ${wc.getURL()}`,
|
|
8246
|
+
"",
|
|
8247
|
+
"[read_page mode=glance — page JS thread is completely blocked, no content available]",
|
|
8248
|
+
"[Try: click or type_text to interact directly, or wait a few seconds and retry]"
|
|
8249
|
+
].join("\n");
|
|
8250
|
+
}
|
|
8251
|
+
const sections = [
|
|
8252
|
+
`# ${result.title}`,
|
|
8253
|
+
`URL: ${result.url}`,
|
|
8254
|
+
`Viewport: ${result.viewportWidth}×${result.viewportHeight} scrollY=${result.scrollY}`,
|
|
8255
|
+
`[read_page mode=glance — ${elapsed}ms, showing what's visible on screen]`
|
|
8256
|
+
];
|
|
8257
|
+
if (result.headings.length > 0) {
|
|
8258
|
+
sections.push("", "## Headings", ...result.headings);
|
|
8259
|
+
}
|
|
8260
|
+
if (result.inputs.length > 0) {
|
|
8261
|
+
sections.push("", "## Input Fields");
|
|
8262
|
+
for (const inp of result.inputs) {
|
|
8263
|
+
const desc = inp.label || inp.placeholder || inp.type;
|
|
8264
|
+
sections.push(` [#${inp.index}] ${inp.type}: ${desc}`);
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
if (result.buttons.length > 0) {
|
|
8268
|
+
sections.push("", "## Buttons");
|
|
8269
|
+
for (const btn of result.buttons) {
|
|
8270
|
+
sections.push(` [#${btn.index}] ${btn.text}`);
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
if (result.links.length > 0) {
|
|
8274
|
+
sections.push("", "## Visible Links");
|
|
8275
|
+
for (const link of result.links) {
|
|
8276
|
+
sections.push(` [#${link.index}] ${link.text}`);
|
|
8277
|
+
}
|
|
8278
|
+
}
|
|
8279
|
+
if (result.contentSnippet) {
|
|
8280
|
+
const truncated = result.contentSnippet.length > 6e3 ? result.contentSnippet.slice(0, 6e3) + "\n[truncated]" : result.contentSnippet;
|
|
8281
|
+
sections.push("", "## Page Content (viewport)", "", truncated);
|
|
8282
|
+
}
|
|
8283
|
+
return sections.join("\n");
|
|
8284
|
+
}
|
|
7473
8285
|
function normalizeReadPageMode(mode, pageContent) {
|
|
7474
8286
|
if (typeof mode === "string") {
|
|
7475
8287
|
const normalized = mode.trim().toLowerCase();
|
|
7476
8288
|
if (normalized === "debug") return "debug";
|
|
8289
|
+
if (normalized === "glance") return "glance";
|
|
7477
8290
|
if (normalized === "full" || normalized === "summary" || normalized === "interactives_only" || normalized === "forms_only" || normalized === "text_only" || normalized === "visible_only" || normalized === "results_only") {
|
|
7478
8291
|
return normalized;
|
|
7479
8292
|
}
|
|
@@ -7601,10 +8414,62 @@ function waitForPotentialNavigation$1(wc, beforeUrl, timeout = 2500) {
|
|
|
7601
8414
|
wc.on("page-title-updated", onNativeChange);
|
|
7602
8415
|
});
|
|
7603
8416
|
}
|
|
7604
|
-
function getPostNavSummary(wc) {
|
|
8417
|
+
async function getPostNavSummary(wc) {
|
|
7605
8418
|
const title = wc.getTitle();
|
|
7606
|
-
|
|
8419
|
+
const titleLine = title ? `
|
|
7607
8420
|
Page title: ${title}` : "";
|
|
8421
|
+
const overlaySignal = await executePageScript(
|
|
8422
|
+
wc,
|
|
8423
|
+
`(function() {
|
|
8424
|
+
var signals = [];
|
|
8425
|
+
// Body scroll lock is a strong overlay signal
|
|
8426
|
+
var bodyStyle = window.getComputedStyle(document.body);
|
|
8427
|
+
var htmlStyle = window.getComputedStyle(document.documentElement);
|
|
8428
|
+
if (bodyStyle.overflow === 'hidden' || htmlStyle.overflow === 'hidden') {
|
|
8429
|
+
signals.push('body-scroll-locked');
|
|
8430
|
+
}
|
|
8431
|
+
// Check for known consent manager containers
|
|
8432
|
+
var consentSelectors = [
|
|
8433
|
+
'#onetrust-consent-sdk', '#CybotCookiebotDialog', '[class*="consent-banner"]',
|
|
8434
|
+
'[class*="cookie-banner"]', '[class*="privacy-banner"]', '[id*="consent"]',
|
|
8435
|
+
'[class*="gdpr"]', '[data-testid*="consent"]', '[data-testid*="cookie"]',
|
|
8436
|
+
'.fc-consent-root', '#sp_message_container_', '[id*="trustarc"]',
|
|
8437
|
+
'[class*="cmp-"]', '[id*="cmp-"]'
|
|
8438
|
+
];
|
|
8439
|
+
for (var i = 0; i < consentSelectors.length; i++) {
|
|
8440
|
+
try {
|
|
8441
|
+
var el = document.querySelector(consentSelectors[i]);
|
|
8442
|
+
if (el && el.offsetHeight > 50) {
|
|
8443
|
+
signals.push('consent-banner:' + consentSelectors[i]);
|
|
8444
|
+
break;
|
|
8445
|
+
}
|
|
8446
|
+
} catch(e) {}
|
|
8447
|
+
}
|
|
8448
|
+
// Check for large fixed/sticky elements covering viewport
|
|
8449
|
+
var vw = window.innerWidth || 0;
|
|
8450
|
+
var vh = window.innerHeight || 0;
|
|
8451
|
+
var vpArea = Math.max(1, vw * vh);
|
|
8452
|
+
var els = document.querySelectorAll('dialog[open], [role="dialog"], [aria-modal="true"]');
|
|
8453
|
+
if (els.length > 0) signals.push('dialog-open');
|
|
8454
|
+
if (signals.length === 0) {
|
|
8455
|
+
var fixed = document.querySelectorAll('div[style*="position: fixed"], div[style*="position:fixed"]');
|
|
8456
|
+
for (var j = 0; j < fixed.length && j < 20; j++) {
|
|
8457
|
+
var r = fixed[j].getBoundingClientRect();
|
|
8458
|
+
if ((r.width * r.height) / vpArea > 0.3) {
|
|
8459
|
+
signals.push('large-fixed-overlay');
|
|
8460
|
+
break;
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8464
|
+
return signals.length > 0 ? signals.join(', ') : null;
|
|
8465
|
+
})()`,
|
|
8466
|
+
{ timeoutMs: 1500, label: "overlay-probe" }
|
|
8467
|
+
);
|
|
8468
|
+
if (overlaySignal && overlaySignal !== PAGE_SCRIPT_TIMEOUT) {
|
|
8469
|
+
return `${titleLine}
|
|
8470
|
+
WARNING: Blocking overlay detected (${overlaySignal}). Call clear_overlays or accept_cookies before reading the page.`;
|
|
8471
|
+
}
|
|
8472
|
+
return titleLine;
|
|
7608
8473
|
}
|
|
7609
8474
|
async function scrollPage$1(wc, deltaY) {
|
|
7610
8475
|
const getScrollY = async () => {
|
|
@@ -8053,6 +8918,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
8053
8918
|
}
|
|
8054
8919
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
8055
8920
|
try {
|
|
8921
|
+
assertSafeURL(snapshot.url);
|
|
8056
8922
|
await wc.loadURL(snapshot.url);
|
|
8057
8923
|
await waitForLoad$1(wc, 3e3);
|
|
8058
8924
|
return;
|
|
@@ -8683,6 +9549,166 @@ async function dismissPopup$1(wc) {
|
|
|
8683
9549
|
}
|
|
8684
9550
|
return initialBlocking > 0 ? "Could not dismiss the blocking popup automatically" : initialDormant > 0 ? `No active blocking popup detected. Found ${initialDormant} dormant consent/modal surface(s) in the DOM, likely geo-gated or inactive in this session.` : "No blocking popup detected";
|
|
8685
9551
|
}
|
|
9552
|
+
function describeOverlayState(page) {
|
|
9553
|
+
const inventory = buildOverlayInventory(page);
|
|
9554
|
+
return {
|
|
9555
|
+
inventory,
|
|
9556
|
+
blocking: inventory.filter((overlay) => overlay.blocksInteraction).length,
|
|
9557
|
+
total: inventory.length,
|
|
9558
|
+
signature: getBlockingOverlaySignature(inventory)
|
|
9559
|
+
};
|
|
9560
|
+
}
|
|
9561
|
+
async function clickOverlayCandidate(wc, action) {
|
|
9562
|
+
if (!action?.selector) return null;
|
|
9563
|
+
const result = await clickResolvedSelector$1(wc, action.selector);
|
|
9564
|
+
return `${action.label || action.selector}: ${result}`;
|
|
9565
|
+
}
|
|
9566
|
+
async function tryDismissConsentIframe(wc) {
|
|
9567
|
+
try {
|
|
9568
|
+
const hasSignal = await executePageScript(
|
|
9569
|
+
wc,
|
|
9570
|
+
`(function() {
|
|
9571
|
+
var bs = window.getComputedStyle(document.body);
|
|
9572
|
+
var hs = window.getComputedStyle(document.documentElement);
|
|
9573
|
+
if (bs.overflow === 'hidden' || hs.overflow === 'hidden') return true;
|
|
9574
|
+
var sels = '#onetrust-consent-sdk, [class*="consent"], [class*="cookie-banner"], [id*="consent"], [id*="sp_message"], .fc-consent-root, [class*="cmp-"]';
|
|
9575
|
+
var el = document.querySelector(sels);
|
|
9576
|
+
return !!(el && el.offsetHeight > 20);
|
|
9577
|
+
})()`,
|
|
9578
|
+
{ timeoutMs: 1e3, label: "iframe-consent-signal" }
|
|
9579
|
+
);
|
|
9580
|
+
if (!hasSignal || hasSignal === PAGE_SCRIPT_TIMEOUT) return null;
|
|
9581
|
+
const frames = wc.mainFrame.framesInSubtree;
|
|
9582
|
+
for (const frame of frames) {
|
|
9583
|
+
if (frame === wc.mainFrame) continue;
|
|
9584
|
+
try {
|
|
9585
|
+
const result = await frame.executeJavaScript(`
|
|
9586
|
+
(function() {
|
|
9587
|
+
var selectors = [
|
|
9588
|
+
'button[title*="Accept"], button[title*="Agree"], button[title*="OK"]',
|
|
9589
|
+
'[class*="accept"], [class*="agree"], [class*="consent-accept"]',
|
|
9590
|
+
'button[aria-label*="accept" i], button[aria-label*="agree" i]',
|
|
9591
|
+
'.sp_choice_type_11', '.message-component.message-button',
|
|
9592
|
+
];
|
|
9593
|
+
// Try selectors first
|
|
9594
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
9595
|
+
try {
|
|
9596
|
+
var els = document.querySelectorAll(selectors[i]);
|
|
9597
|
+
for (var j = 0; j < els.length; j++) {
|
|
9598
|
+
var el = els[j];
|
|
9599
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
9600
|
+
var text = (el.textContent || '').trim().toLowerCase();
|
|
9601
|
+
if (/accept|agree|consent|got it|ok|continue|i understand/i.test(text) || el.offsetHeight > 0) {
|
|
9602
|
+
el.click();
|
|
9603
|
+
return 'Clicked iframe consent button: ' + text.slice(0, 60);
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9606
|
+
} catch(e) {}
|
|
9607
|
+
}
|
|
9608
|
+
// Text-match fallback on all buttons
|
|
9609
|
+
var buttons = document.querySelectorAll('button, [role="button"], a.message-component');
|
|
9610
|
+
for (var k = 0; k < buttons.length; k++) {
|
|
9611
|
+
var btn = buttons[k];
|
|
9612
|
+
var label = (btn.textContent || '').trim().toLowerCase();
|
|
9613
|
+
if (/^(accept|agree|accept all|i agree|i accept|ok|got it|allow|continue|yes)$/i.test(label) ||
|
|
9614
|
+
/accept all|agree and|accept & continue|accept and continue/i.test(label)) {
|
|
9615
|
+
btn.click();
|
|
9616
|
+
return 'Clicked iframe consent button: ' + label.slice(0, 60);
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
return null;
|
|
9620
|
+
})()
|
|
9621
|
+
`);
|
|
9622
|
+
if (result) return result;
|
|
9623
|
+
} catch {
|
|
9624
|
+
continue;
|
|
9625
|
+
}
|
|
9626
|
+
}
|
|
9627
|
+
} catch {
|
|
9628
|
+
}
|
|
9629
|
+
return null;
|
|
9630
|
+
}
|
|
9631
|
+
async function clearOverlays(wc, strategy = "auto") {
|
|
9632
|
+
const steps = [];
|
|
9633
|
+
let cleared = 0;
|
|
9634
|
+
const maxIterations = 8;
|
|
9635
|
+
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
9636
|
+
const before = await extractContent(wc);
|
|
9637
|
+
const beforeState = describeOverlayState(before);
|
|
9638
|
+
const blockingOverlays = beforeState.inventory.filter(
|
|
9639
|
+
(overlay2) => overlay2.blocksInteraction
|
|
9640
|
+
);
|
|
9641
|
+
if (blockingOverlays.length === 0) {
|
|
9642
|
+
if (cleared === 0) {
|
|
9643
|
+
const iframeResult = await tryDismissConsentIframe(wc);
|
|
9644
|
+
if (iframeResult) {
|
|
9645
|
+
steps.push(`Iframe consent: ${iframeResult}`);
|
|
9646
|
+
await sleep$1(500);
|
|
9647
|
+
return steps.join("\n");
|
|
9648
|
+
}
|
|
9649
|
+
return "No blocking overlays detected";
|
|
9650
|
+
}
|
|
9651
|
+
steps.push(`Overlays remaining: ${beforeState.total}`);
|
|
9652
|
+
steps.push("Page still blocked: false");
|
|
9653
|
+
return steps.join("\n");
|
|
9654
|
+
}
|
|
9655
|
+
const overlay = blockingOverlays[0];
|
|
9656
|
+
let actionMessage = null;
|
|
9657
|
+
if (overlay.kind === "cookie_consent") {
|
|
9658
|
+
actionMessage = await clickOverlayCandidate(
|
|
9659
|
+
wc,
|
|
9660
|
+
overlay.acceptAction || overlay.dismissAction || overlay.actions[0]
|
|
9661
|
+
);
|
|
9662
|
+
} else if (overlay.kind === "selection_modal") {
|
|
9663
|
+
if (!overlay.correctOption?.selector) {
|
|
9664
|
+
if (strategy === "interactive") {
|
|
9665
|
+
steps.push(
|
|
9666
|
+
"Stopped: selection modal needs human judgment because no likely-correct option was detected."
|
|
9667
|
+
);
|
|
9668
|
+
steps.push(`Overlays remaining: ${beforeState.total}`);
|
|
9669
|
+
steps.push("Page still blocked: true");
|
|
9670
|
+
return steps.join("\n");
|
|
9671
|
+
}
|
|
9672
|
+
} else {
|
|
9673
|
+
const optionResult = await clickOverlayCandidate(
|
|
9674
|
+
wc,
|
|
9675
|
+
overlay.correctOption
|
|
9676
|
+
);
|
|
9677
|
+
if (optionResult) {
|
|
9678
|
+
actionMessage = `Selected likely-correct option: ${optionResult}`;
|
|
9679
|
+
await sleep$1(120);
|
|
9680
|
+
const submitResult = await clickOverlayCandidate(
|
|
9681
|
+
wc,
|
|
9682
|
+
overlay.submitAction || overlay.acceptAction
|
|
9683
|
+
);
|
|
9684
|
+
if (submitResult) {
|
|
9685
|
+
actionMessage += `
|
|
9686
|
+
Submitted modal: ${submitResult}`;
|
|
9687
|
+
}
|
|
9688
|
+
}
|
|
9689
|
+
}
|
|
9690
|
+
}
|
|
9691
|
+
if (!actionMessage) {
|
|
9692
|
+
actionMessage = `Fallback popup handling: ${await dismissPopup$1(wc)}`;
|
|
9693
|
+
}
|
|
9694
|
+
steps.push(actionMessage);
|
|
9695
|
+
await sleep$1(250);
|
|
9696
|
+
const after = await extractContent(wc);
|
|
9697
|
+
const afterState = describeOverlayState(after);
|
|
9698
|
+
steps.push(`Overlays remaining: ${afterState.total}`);
|
|
9699
|
+
steps.push(`Page still blocked: ${afterState.blocking > 0}`);
|
|
9700
|
+
if (afterState.blocking === 0) {
|
|
9701
|
+
return steps.join("\n");
|
|
9702
|
+
}
|
|
9703
|
+
const progressMade = afterState.blocking < beforeState.blocking || afterState.total !== beforeState.total || afterState.signature !== beforeState.signature;
|
|
9704
|
+
if (progressMade) {
|
|
9705
|
+
cleared += 1;
|
|
9706
|
+
continue;
|
|
9707
|
+
}
|
|
9708
|
+
return steps.join("\n");
|
|
9709
|
+
}
|
|
9710
|
+
return steps.join("\n");
|
|
9711
|
+
}
|
|
8686
9712
|
async function resolveSelector$1(wc, index, selector) {
|
|
8687
9713
|
if (selector) return selector;
|
|
8688
9714
|
if (index == null) return null;
|
|
@@ -9473,6 +10499,7 @@ async function submitForm$1(wc, args) {
|
|
|
9473
10499
|
if (formInfo.params) {
|
|
9474
10500
|
url.search = formInfo.params;
|
|
9475
10501
|
}
|
|
10502
|
+
assertSafeURL(url.toString());
|
|
9476
10503
|
wc.loadURL(url.toString());
|
|
9477
10504
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
9478
10505
|
const afterUrl = wc.getURL();
|
|
@@ -9589,7 +10616,8 @@ async function getPostActionState$1(ctx, name) {
|
|
|
9589
10616
|
"hover",
|
|
9590
10617
|
"focus",
|
|
9591
10618
|
"fill_form",
|
|
9592
|
-
"inspect_element"
|
|
10619
|
+
"inspect_element",
|
|
10620
|
+
"clear_overlays"
|
|
9593
10621
|
];
|
|
9594
10622
|
const tabActions = [
|
|
9595
10623
|
"create_tab",
|
|
@@ -9639,6 +10667,7 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
9639
10667
|
"focus",
|
|
9640
10668
|
"set_ad_blocking",
|
|
9641
10669
|
"dismiss_popup",
|
|
10670
|
+
"clear_overlays",
|
|
9642
10671
|
"read_page",
|
|
9643
10672
|
"wait_for",
|
|
9644
10673
|
"create_checkpoint",
|
|
@@ -9765,7 +10794,7 @@ async function executeAction(name, args, ctx) {
|
|
|
9765
10794
|
const created = ctx.tabManager.getActiveTab();
|
|
9766
10795
|
if (created) {
|
|
9767
10796
|
await waitForLoad$1(created.view.webContents);
|
|
9768
|
-
return `Created tab ${createdId}${getPostNavSummary(created.view.webContents)}`;
|
|
10797
|
+
return `Created tab ${createdId}${await getPostNavSummary(created.view.webContents)}`;
|
|
9769
10798
|
}
|
|
9770
10799
|
return `Created tab ${createdId}`;
|
|
9771
10800
|
}
|
|
@@ -9777,7 +10806,7 @@ async function executeAction(name, args, ctx) {
|
|
|
9777
10806
|
}
|
|
9778
10807
|
ctx.tabManager.navigateTab(tabId, args.url);
|
|
9779
10808
|
await waitForLoad$1(wc);
|
|
9780
|
-
return `Navigated to ${wc.getURL()}${getPostNavSummary(wc)}`;
|
|
10809
|
+
return `Navigated to ${wc.getURL()}${await getPostNavSummary(wc)}`;
|
|
9781
10810
|
}
|
|
9782
10811
|
case "go_back": {
|
|
9783
10812
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -9788,7 +10817,7 @@ async function executeAction(name, args, ctx) {
|
|
|
9788
10817
|
ctx.tabManager.goBack(tabId);
|
|
9789
10818
|
await waitForLoad$1(wc);
|
|
9790
10819
|
const afterUrl = wc.getURL();
|
|
9791
|
-
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
10820
|
+
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${await getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
9792
10821
|
}
|
|
9793
10822
|
case "go_forward": {
|
|
9794
10823
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -9799,7 +10828,7 @@ async function executeAction(name, args, ctx) {
|
|
|
9799
10828
|
ctx.tabManager.goForward(tabId);
|
|
9800
10829
|
await waitForLoad$1(wc);
|
|
9801
10830
|
const afterUrl = wc.getURL();
|
|
9802
|
-
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
10831
|
+
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${await getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
9803
10832
|
}
|
|
9804
10833
|
case "reload": {
|
|
9805
10834
|
if (!wc || !tabId) return "Error: No active tab";
|
|
@@ -9866,7 +10895,7 @@ async function executeAction(name, args, ctx) {
|
|
|
9866
10895
|
}
|
|
9867
10896
|
case "scroll": {
|
|
9868
10897
|
if (!wc) return "Error: No active tab";
|
|
9869
|
-
const pixels = args.amount
|
|
10898
|
+
const pixels = coerceOptionalNumber(args.amount) ?? 500;
|
|
9870
10899
|
const dir = args.direction === "up" ? -pixels : pixels;
|
|
9871
10900
|
const result2 = await scrollPage$1(wc, dir);
|
|
9872
10901
|
return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
|
|
@@ -9911,8 +10940,17 @@ async function executeAction(name, args, ctx) {
|
|
|
9911
10940
|
if (!wc) return "Error: No active tab";
|
|
9912
10941
|
return dismissPopup$1(wc);
|
|
9913
10942
|
}
|
|
10943
|
+
case "clear_overlays": {
|
|
10944
|
+
if (!wc) return "Error: No active tab";
|
|
10945
|
+
const strategy = args.strategy === "interactive" ? "interactive" : "auto";
|
|
10946
|
+
return clearOverlays(wc, strategy);
|
|
10947
|
+
}
|
|
9914
10948
|
case "read_page": {
|
|
9915
10949
|
if (!wc) return "Error: No active tab";
|
|
10950
|
+
const requestedGlance = typeof args.mode === "string" && args.mode.trim().toLowerCase() === "glance";
|
|
10951
|
+
if (requestedGlance) {
|
|
10952
|
+
return glanceExtract(wc);
|
|
10953
|
+
}
|
|
9916
10954
|
console.log("[Vessel read_page] starting extraction with 6s timeout");
|
|
9917
10955
|
let content = null;
|
|
9918
10956
|
try {
|
|
@@ -9931,7 +10969,29 @@ async function executeAction(name, args, ctx) {
|
|
|
9931
10969
|
console.log(
|
|
9932
10970
|
`[Vessel read_page] extraction result: ${content ? `content=${content.content.length}` : "null (timeout)"}`
|
|
9933
10971
|
);
|
|
9934
|
-
if (content) {
|
|
10972
|
+
if (!content || content.content.length === 0) {
|
|
10973
|
+
console.log("[Vessel read_page] content empty/null, trying quick iframe dismiss");
|
|
10974
|
+
try {
|
|
10975
|
+
const iframeResult = await Promise.race([
|
|
10976
|
+
tryDismissConsentIframe(wc),
|
|
10977
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 2e3))
|
|
10978
|
+
]);
|
|
10979
|
+
if (iframeResult) {
|
|
10980
|
+
console.log(`[Vessel read_page] iframe dismiss: ${iframeResult}`);
|
|
10981
|
+
await sleep$1(500);
|
|
10982
|
+
try {
|
|
10983
|
+
content = await Promise.race([
|
|
10984
|
+
extractContent(wc),
|
|
10985
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
10986
|
+
]);
|
|
10987
|
+
} catch {
|
|
10988
|
+
content = null;
|
|
10989
|
+
}
|
|
10990
|
+
}
|
|
10991
|
+
} catch {
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
if (content && content.content.length > 0) {
|
|
9935
10995
|
const liveSelectionSection = formatLiveSelectionSection(
|
|
9936
10996
|
await captureLiveHighlightSnapshot(
|
|
9937
10997
|
wc,
|
|
@@ -9963,16 +11023,8 @@ ${truncated}`;
|
|
|
9963
11023
|
`Need more detail? Escalate with read_page(mode="debug") only if the narrow modes are insufficient.`
|
|
9964
11024
|
].filter(Boolean).join("\n\n");
|
|
9965
11025
|
}
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
return [
|
|
9969
|
-
`# ${title}`,
|
|
9970
|
-
`URL: ${url}`,
|
|
9971
|
-
"",
|
|
9972
|
-
"[Page content extraction timed out — the page JS thread is busy.]",
|
|
9973
|
-
"[Use the search tool to search the site, or type_text/click to interact directly.]",
|
|
9974
|
-
"[You can retry read_page in a few seconds once the page finishes loading.]"
|
|
9975
|
-
].join("\n");
|
|
11026
|
+
console.log("[Vessel read_page] falling back to glance mode");
|
|
11027
|
+
return glanceExtract(wc);
|
|
9976
11028
|
}
|
|
9977
11029
|
case "wait_for": {
|
|
9978
11030
|
if (!wc) return "Error: No active tab";
|
|
@@ -10277,9 +11329,9 @@ ${truncated}`;
|
|
|
10277
11329
|
if (!wc) return "Error: No active tab";
|
|
10278
11330
|
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
10279
11331
|
const highlightColor = args.color || "yellow";
|
|
11332
|
+
const highlightText = normalizeLooseString(args.text);
|
|
10280
11333
|
const url = wc.getURL();
|
|
10281
11334
|
if (url && url !== "about:blank") {
|
|
10282
|
-
const highlightText = typeof args.text === "string" ? args.text : void 0;
|
|
10283
11335
|
addHighlight(
|
|
10284
11336
|
url,
|
|
10285
11337
|
typeof selector === "string" ? selector : void 0,
|
|
@@ -10292,7 +11344,7 @@ ${truncated}`;
|
|
|
10292
11344
|
return highlightOnPage(
|
|
10293
11345
|
wc,
|
|
10294
11346
|
selector,
|
|
10295
|
-
|
|
11347
|
+
highlightText,
|
|
10296
11348
|
args.label,
|
|
10297
11349
|
args.durationMs,
|
|
10298
11350
|
highlightColor
|
|
@@ -10305,7 +11357,7 @@ ${truncated}`;
|
|
|
10305
11357
|
// --- Speedee System ---
|
|
10306
11358
|
case "flow_start": {
|
|
10307
11359
|
const goal = typeof args.goal === "string" ? args.goal : "";
|
|
10308
|
-
const steps =
|
|
11360
|
+
const steps = coerceStringArray(args.steps) ?? [];
|
|
10309
11361
|
if (!goal || steps.length === 0)
|
|
10310
11362
|
return "Error: goal and steps are required";
|
|
10311
11363
|
const flow = ctx.runtime.startFlow(goal, steps, wc?.getURL());
|
|
@@ -10365,9 +11417,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
10365
11417
|
const hasOverlays = page.overlays.some((o) => o.blocksInteraction);
|
|
10366
11418
|
if (hasOverlays) {
|
|
10367
11419
|
suggestions.push("BLOCKING OVERLAY detected — dismiss it first:");
|
|
10368
|
-
suggestions.push(
|
|
10369
|
-
|
|
10370
|
-
);
|
|
11420
|
+
suggestions.push(" → clear_overlays for stacked modals");
|
|
11421
|
+
suggestions.push(" → or dismiss_popup for a single popup");
|
|
10371
11422
|
suggestions.push("");
|
|
10372
11423
|
}
|
|
10373
11424
|
if (hasPasswordField) {
|
|
@@ -10675,6 +11726,7 @@ ${steps.join("\n")}`;
|
|
|
10675
11726
|
try {
|
|
10676
11727
|
const url = new URL(searchInfo.formAction);
|
|
10677
11728
|
url.searchParams.set(searchInfo.inputName || "q", query);
|
|
11729
|
+
assertSafeURL(url.toString());
|
|
10678
11730
|
wc.loadURL(url.toString());
|
|
10679
11731
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
10680
11732
|
afterUrl = wc.getURL();
|
|
@@ -10744,9 +11796,20 @@ ${steps.join("\n")}`;
|
|
|
10744
11796
|
'[aria-label="Accept cookies"]',
|
|
10745
11797
|
'[aria-label="Accept all cookies"]',
|
|
10746
11798
|
'[data-testid="cookie-accept"]',
|
|
11799
|
+
// CNN / WarnerMedia / common consent SDKs
|
|
11800
|
+
'[data-testid="consent-accept"]',
|
|
11801
|
+
'[data-testid="accept-all"]',
|
|
11802
|
+
'button[class*="consent"][class*="accept"]',
|
|
11803
|
+
'button[class*="privacy"][class*="accept"]',
|
|
11804
|
+
'.fc-cta-consent',
|
|
11805
|
+
'#sp_choice_button_accept',
|
|
11806
|
+
'.message-component.message-button.no-children.focusable.sp_choice_type_11',
|
|
11807
|
+
'[class*="truste"] [class*="accept"]',
|
|
11808
|
+
'[id*="consent-accept"]',
|
|
11809
|
+
'[class*="cmp-accept"]',
|
|
10747
11810
|
];
|
|
10748
11811
|
// Also try text-matching on buttons
|
|
10749
|
-
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'consent'];
|
|
11812
|
+
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'i accept', 'consent', 'continue', 'accept and continue', 'accept & continue'];
|
|
10750
11813
|
for (var i = 0; i < selectors.length; i++) {
|
|
10751
11814
|
var el = document.querySelector(selectors[i]);
|
|
10752
11815
|
if (el && el instanceof HTMLElement) { el.click(); return "Dismissed cookie banner via: " + selectors[i]; }
|
|
@@ -10772,7 +11835,10 @@ ${steps.join("\n")}`;
|
|
|
10772
11835
|
if (dismissed === PAGE_SCRIPT_TIMEOUT) {
|
|
10773
11836
|
return pageBusyError("accept_cookies");
|
|
10774
11837
|
}
|
|
10775
|
-
|
|
11838
|
+
if (dismissed) return dismissed;
|
|
11839
|
+
const iframeResult = await tryDismissConsentIframe(wc);
|
|
11840
|
+
if (iframeResult) return iframeResult;
|
|
11841
|
+
return "No cookie consent banner detected. Try dismiss_popup for other overlays.";
|
|
10776
11842
|
}
|
|
10777
11843
|
case "extract_table": {
|
|
10778
11844
|
if (!wc) return "Error: No active tab";
|
|
@@ -10870,10 +11936,10 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
10870
11936
|
const flowCtx = ctx.runtime.getFlowContext();
|
|
10871
11937
|
return result + await getPostActionState$1(ctx, name) + flowCtx;
|
|
10872
11938
|
}
|
|
10873
|
-
async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd, tabManager,
|
|
11939
|
+
async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd, tabManager, runtime2, history) {
|
|
10874
11940
|
const lowerQuery = query.toLowerCase().trim();
|
|
10875
11941
|
const isSummarize = lowerQuery.startsWith("summarize") || lowerQuery.startsWith("tldr") || lowerQuery === "summary";
|
|
10876
|
-
if (provider.streamAgentQuery && tabManager && activeWebContents &&
|
|
11942
|
+
if (provider.streamAgentQuery && tabManager && activeWebContents && runtime2) {
|
|
10877
11943
|
try {
|
|
10878
11944
|
const extractStart = Date.now();
|
|
10879
11945
|
const pageContent = await extractContent(activeWebContents);
|
|
@@ -10886,7 +11952,7 @@ async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd,
|
|
|
10886
11952
|
pageContent,
|
|
10887
11953
|
defaultReadMode
|
|
10888
11954
|
);
|
|
10889
|
-
const runtimeState =
|
|
11955
|
+
const runtimeState = runtime2.getState();
|
|
10890
11956
|
const recentCheckpoints = runtimeState.checkpoints.slice(-3).map((item) => `- ${item.name} (${item.id})`).join("\n");
|
|
10891
11957
|
const activeTabTitle = pageContent.title || "(untitled)";
|
|
10892
11958
|
const activeTabUrl = pageContent.url || activeWebContents.getURL();
|
|
@@ -10929,8 +11995,12 @@ Instructions:
|
|
|
10929
11995
|
- After navigating to a new site, DO NOT call read_page immediately. Instead, act on what you already know: use the search tool to search the site, type_text to enter queries in search bars, or click on known navigation patterns. You know what major sites look like — use that knowledge. Only call read_page if you're genuinely stuck and need to discover unfamiliar page structure.
|
|
10930
11996
|
- The page brief you start with is intentionally sparse. It is optimized for navigation speed, not completeness.
|
|
10931
11997
|
- When you only need detail on one product/result/card/form section, use inspect_element instead of reading the page.
|
|
10932
|
-
- Escalate page reads progressively: read_page(mode="visible_only"), read_page(mode="results_only"), read_page(mode="forms_only"), read_page(mode="summary"), or read_page(mode="text_only") depending on what you need.
|
|
11998
|
+
- Escalate page reads progressively: read_page(mode="glance") for a fast viewport snapshot on heavy/slow pages, then read_page(mode="visible_only"), read_page(mode="results_only"), read_page(mode="forms_only"), read_page(mode="summary"), or read_page(mode="text_only") depending on what you need.
|
|
11999
|
+
- Use read_page(mode="glance") when a page is slow to load or extraction times out — it shows what's on screen (headings, links, buttons, inputs) without waiting for heavy JS. It's what a human would see by just looking at the page.
|
|
10933
12000
|
- Use read_page(mode="debug") only as a last resort when the narrower modes are insufficient.
|
|
12001
|
+
- If read_page returns empty or times out, do NOT retry with the same mode. Switch to read_page(mode="glance") or interact directly with click/type_text.
|
|
12002
|
+
- VIEWPORT SYNC: Treat scrolling as a real, user-visible browser action. If you say you are going to scroll, call scroll or scroll_to_element so the human sees the page move too.
|
|
12003
|
+
- read_page inspects the page without moving the human-visible viewport. Do not describe read_page as scrolling. If you want more context without changing the user's view, say you're reading the page; if you want the user to follow along lower on the page, actually scroll first.
|
|
10934
12004
|
- After clicking or submitting a form, prefer wait_for on a specific result signal or a narrow read_page mode. Do not jump straight to read_page(mode="debug").
|
|
10935
12005
|
- If the user says they highlighted or selected text, use read_page before falling back to screenshots because it includes active selection and visible unsaved highlights.
|
|
10936
12006
|
- If a page behaves abnormally or key UI fails to load, consider disabling ad blocking for that tab and reloading before retrying.
|
|
@@ -10946,7 +12016,7 @@ Instructions:
|
|
|
10946
12016
|
- ACT, DON'T HEDGE: You have a full browser — you can navigate to any website, see live content, search, click, add to cart, fill forms, and interact with real pages in real time. Never claim you "don't have access" to a website's inventory, pricing, or content. If the user asks you to go somewhere and do something, start doing it immediately. Don't ask for permission to do what the user just asked you to do — that's redundant and frustrating. Jump straight into action.
|
|
10947
12017
|
- USE YOUR KNOWLEDGE: You have broad, practical knowledge about technology, products, cooking, travel, finance, and countless other domains. When the user asks for recommendations, GIVE them — don't deflect to Reddit, YouTubers, or other sources. You know enough to recommend PC parts, suggest restaurants, pick a good laptop, or advise on most consumer decisions. Make a clear recommendation, explain your reasoning briefly, and then execute. If there's genuine ambiguity (e.g. AMD vs Intel is preference-dependent), state your pick and why, then ask only the questions that would actually change your recommendation. Never refuse a recommendation by claiming you're "not an expert" — the user chose to ask you, so help them.
|
|
10948
12018
|
- NEVER USE EMOJIS unless the user uses them first.`;
|
|
10949
|
-
const actionCtx = { tabManager, runtime };
|
|
12019
|
+
const actionCtx = { tabManager, runtime: runtime2 };
|
|
10950
12020
|
const contextualTools = pruneToolsForContext(
|
|
10951
12021
|
AGENT_TOOLS,
|
|
10952
12022
|
pageType,
|
|
@@ -11374,7 +12444,7 @@ function broadcastState(tabManager) {
|
|
|
11374
12444
|
const tabId = tabManager.getActiveTabId();
|
|
11375
12445
|
stateListener(getDevToolsPanelState(tabId));
|
|
11376
12446
|
}
|
|
11377
|
-
async function withDevToolsAction(
|
|
12447
|
+
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
11378
12448
|
const activityEntry = {
|
|
11379
12449
|
id: ++activityCounter,
|
|
11380
12450
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11391,7 +12461,7 @@ async function withDevToolsAction(runtime, tabManager, name, args, executor) {
|
|
|
11391
12461
|
broadcastState(tabManager);
|
|
11392
12462
|
const startTime = Date.now();
|
|
11393
12463
|
try {
|
|
11394
|
-
const result = await
|
|
12464
|
+
const result = await runtime2.runControlledAction({
|
|
11395
12465
|
source: "mcp",
|
|
11396
12466
|
name,
|
|
11397
12467
|
args,
|
|
@@ -11413,7 +12483,7 @@ async function withDevToolsAction(runtime, tabManager, name, args, executor) {
|
|
|
11413
12483
|
return asTextResponse$1(`Error: ${message}`);
|
|
11414
12484
|
}
|
|
11415
12485
|
}
|
|
11416
|
-
function registerDevTools(server, tabManager,
|
|
12486
|
+
function registerDevTools(server, tabManager, runtime2) {
|
|
11417
12487
|
server.registerTool(
|
|
11418
12488
|
"vessel_devtools_console_logs",
|
|
11419
12489
|
{
|
|
@@ -11425,16 +12495,16 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11425
12495
|
search: zod.z.string().optional().describe("Filter entries containing this text (case-insensitive)")
|
|
11426
12496
|
}
|
|
11427
12497
|
},
|
|
11428
|
-
async ({ level, limit, search }) => {
|
|
12498
|
+
async ({ level, limit, search: search2 }) => {
|
|
11429
12499
|
return withDevToolsAction(
|
|
11430
|
-
|
|
12500
|
+
runtime2,
|
|
11431
12501
|
tabManager,
|
|
11432
12502
|
"devtools_console_logs",
|
|
11433
|
-
{ level, limit, search },
|
|
12503
|
+
{ level, limit, search: search2 },
|
|
11434
12504
|
async () => {
|
|
11435
12505
|
const session = getOrCreateSession(tabManager);
|
|
11436
12506
|
await session.ensureConsoleDomain();
|
|
11437
|
-
const entries = session.getConsoleLogs({ level, limit, search });
|
|
12507
|
+
const entries = session.getConsoleLogs({ level, limit, search: search2 });
|
|
11438
12508
|
if (entries.length === 0) {
|
|
11439
12509
|
return "No console entries captured yet. Console monitoring is now active — new entries will be captured as they occur.";
|
|
11440
12510
|
}
|
|
@@ -11451,7 +12521,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11451
12521
|
},
|
|
11452
12522
|
async () => {
|
|
11453
12523
|
return withDevToolsAction(
|
|
11454
|
-
|
|
12524
|
+
runtime2,
|
|
11455
12525
|
tabManager,
|
|
11456
12526
|
"devtools_console_clear",
|
|
11457
12527
|
{},
|
|
@@ -11478,7 +12548,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11478
12548
|
},
|
|
11479
12549
|
async ({ url_pattern, method, status_min, status_max, limit }) => {
|
|
11480
12550
|
return withDevToolsAction(
|
|
11481
|
-
|
|
12551
|
+
runtime2,
|
|
11482
12552
|
tabManager,
|
|
11483
12553
|
"devtools_network_log",
|
|
11484
12554
|
{ url_pattern, method, status_min, status_max, limit },
|
|
@@ -11510,7 +12580,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11510
12580
|
},
|
|
11511
12581
|
async ({ request_id }) => {
|
|
11512
12582
|
return withDevToolsAction(
|
|
11513
|
-
|
|
12583
|
+
runtime2,
|
|
11514
12584
|
tabManager,
|
|
11515
12585
|
"devtools_network_response_body",
|
|
11516
12586
|
{ request_id },
|
|
@@ -11536,7 +12606,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11536
12606
|
},
|
|
11537
12607
|
async () => {
|
|
11538
12608
|
return withDevToolsAction(
|
|
11539
|
-
|
|
12609
|
+
runtime2,
|
|
11540
12610
|
tabManager,
|
|
11541
12611
|
"devtools_network_clear",
|
|
11542
12612
|
{},
|
|
@@ -11560,7 +12630,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11560
12630
|
},
|
|
11561
12631
|
async ({ selector, include_html }) => {
|
|
11562
12632
|
return withDevToolsAction(
|
|
11563
|
-
|
|
12633
|
+
runtime2,
|
|
11564
12634
|
tabManager,
|
|
11565
12635
|
"devtools_query_dom",
|
|
11566
12636
|
{ selector, include_html },
|
|
@@ -11591,7 +12661,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11591
12661
|
},
|
|
11592
12662
|
async ({ selector, properties }) => {
|
|
11593
12663
|
return withDevToolsAction(
|
|
11594
|
-
|
|
12664
|
+
runtime2,
|
|
11595
12665
|
tabManager,
|
|
11596
12666
|
"devtools_get_styles",
|
|
11597
12667
|
{ selector, properties },
|
|
@@ -11619,7 +12689,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11619
12689
|
},
|
|
11620
12690
|
async ({ selector, attribute, value }) => {
|
|
11621
12691
|
return withDevToolsAction(
|
|
11622
|
-
|
|
12692
|
+
runtime2,
|
|
11623
12693
|
tabManager,
|
|
11624
12694
|
"devtools_modify_dom",
|
|
11625
12695
|
{ selector, attribute, value },
|
|
@@ -11641,7 +12711,7 @@ function registerDevTools(server, tabManager, runtime) {
|
|
|
11641
12711
|
},
|
|
11642
12712
|
async ({ expression }) => {
|
|
11643
12713
|
return withDevToolsAction(
|
|
11644
|
-
|
|
12714
|
+
runtime2,
|
|
11645
12715
|
tabManager,
|
|
11646
12716
|
"devtools_execute_js",
|
|
11647
12717
|
{ expression: expression.slice(0, 200) },
|
|
@@ -11669,7 +12739,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
11669
12739
|
},
|
|
11670
12740
|
async ({ type }) => {
|
|
11671
12741
|
return withDevToolsAction(
|
|
11672
|
-
|
|
12742
|
+
runtime2,
|
|
11673
12743
|
tabManager,
|
|
11674
12744
|
"devtools_get_storage",
|
|
11675
12745
|
{ type },
|
|
@@ -11698,7 +12768,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
11698
12768
|
},
|
|
11699
12769
|
async ({ type, key, value }) => {
|
|
11700
12770
|
return withDevToolsAction(
|
|
11701
|
-
|
|
12771
|
+
runtime2,
|
|
11702
12772
|
tabManager,
|
|
11703
12773
|
"devtools_set_storage",
|
|
11704
12774
|
{ type, key, value: value ? value.slice(0, 100) : null },
|
|
@@ -11717,7 +12787,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
11717
12787
|
},
|
|
11718
12788
|
async () => {
|
|
11719
12789
|
return withDevToolsAction(
|
|
11720
|
-
|
|
12790
|
+
runtime2,
|
|
11721
12791
|
tabManager,
|
|
11722
12792
|
"devtools_performance",
|
|
11723
12793
|
{},
|
|
@@ -11741,7 +12811,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
11741
12811
|
},
|
|
11742
12812
|
async ({ type, limit }) => {
|
|
11743
12813
|
return withDevToolsAction(
|
|
11744
|
-
|
|
12814
|
+
runtime2,
|
|
11745
12815
|
tabManager,
|
|
11746
12816
|
"devtools_get_errors",
|
|
11747
12817
|
{ type, limit },
|
|
@@ -11765,7 +12835,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
11765
12835
|
},
|
|
11766
12836
|
async () => {
|
|
11767
12837
|
return withDevToolsAction(
|
|
11768
|
-
|
|
12838
|
+
runtime2,
|
|
11769
12839
|
tabManager,
|
|
11770
12840
|
"devtools_clear_errors",
|
|
11771
12841
|
{},
|
|
@@ -11779,6 +12849,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
11779
12849
|
);
|
|
11780
12850
|
}
|
|
11781
12851
|
let httpServer = null;
|
|
12852
|
+
let mcpAuthToken = null;
|
|
11782
12853
|
function asTextResponse(text) {
|
|
11783
12854
|
return { content: [{ type: "text", text }] };
|
|
11784
12855
|
}
|
|
@@ -12716,9 +13787,9 @@ async function getPostActionState(tabManager, name) {
|
|
|
12716
13787
|
}
|
|
12717
13788
|
return "";
|
|
12718
13789
|
}
|
|
12719
|
-
async function withAction(
|
|
13790
|
+
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
12720
13791
|
try {
|
|
12721
|
-
const result = await
|
|
13792
|
+
const result = await runtime2.runControlledAction({
|
|
12722
13793
|
source: "mcp",
|
|
12723
13794
|
name,
|
|
12724
13795
|
args,
|
|
@@ -12727,7 +13798,7 @@ async function withAction(runtime, tabManager, name, args, executor) {
|
|
|
12727
13798
|
executor
|
|
12728
13799
|
});
|
|
12729
13800
|
const stateInfo = await getPostActionState(tabManager, name);
|
|
12730
|
-
const flowCtx =
|
|
13801
|
+
const flowCtx = runtime2.getFlowContext();
|
|
12731
13802
|
return asTextResponse(result + stateInfo + flowCtx);
|
|
12732
13803
|
} catch (error) {
|
|
12733
13804
|
return asTextResponse(
|
|
@@ -13021,6 +14092,7 @@ async function submitForm(wc, index, selector) {
|
|
|
13021
14092
|
if (formInfo.params) {
|
|
13022
14093
|
url.search = formInfo.params;
|
|
13023
14094
|
}
|
|
14095
|
+
assertSafeURL(url.toString());
|
|
13024
14096
|
wc.loadURL(url.toString());
|
|
13025
14097
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13026
14098
|
const afterUrl = wc.getURL();
|
|
@@ -13162,7 +14234,7 @@ async function captureScreenshotPayload(wc) {
|
|
|
13162
14234
|
}
|
|
13163
14235
|
return { ok: false, error: "page image was empty after 3 attempts" };
|
|
13164
14236
|
}
|
|
13165
|
-
function registerTools(server, tabManager,
|
|
14237
|
+
function registerTools(server, tabManager, runtime2) {
|
|
13166
14238
|
server.registerPrompt(
|
|
13167
14239
|
"vessel-supervisor-brief",
|
|
13168
14240
|
{
|
|
@@ -13170,7 +14242,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13170
14242
|
description: "A reusable prompt for reviewing the current Vessel runtime state."
|
|
13171
14243
|
},
|
|
13172
14244
|
async () => {
|
|
13173
|
-
const state2 =
|
|
14245
|
+
const state2 = runtime2.getState();
|
|
13174
14246
|
const activeTab = getActiveTabSummary(tabManager);
|
|
13175
14247
|
return asPromptResponse(
|
|
13176
14248
|
[
|
|
@@ -13197,7 +14269,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13197
14269
|
contents: [
|
|
13198
14270
|
{
|
|
13199
14271
|
uri: "vessel://runtime/state",
|
|
13200
|
-
text: JSON.stringify(
|
|
14272
|
+
text: JSON.stringify(runtime2.getState(), null, 2)
|
|
13201
14273
|
}
|
|
13202
14274
|
]
|
|
13203
14275
|
})
|
|
@@ -13313,7 +14385,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13313
14385
|
}
|
|
13314
14386
|
},
|
|
13315
14387
|
async ({ text, stream_id, mode, kind, title }) => {
|
|
13316
|
-
const entry =
|
|
14388
|
+
const entry = runtime2.publishTranscript({
|
|
13317
14389
|
source: "mcp",
|
|
13318
14390
|
text,
|
|
13319
14391
|
streamId: stream_id,
|
|
@@ -13343,7 +14415,7 @@ function registerTools(server, tabManager, runtime) {
|
|
|
13343
14415
|
description: "Clear the in-browser transcript monitor state."
|
|
13344
14416
|
},
|
|
13345
14417
|
async () => {
|
|
13346
|
-
|
|
14418
|
+
runtime2.clearTranscript();
|
|
13347
14419
|
return asTextResponse("Cleared browser transcript monitor.");
|
|
13348
14420
|
}
|
|
13349
14421
|
);
|
|
@@ -13483,7 +14555,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13483
14555
|
`Navigation blocked: ${url} returned ${preCheck.detail || "dead link"}. Try a different URL or go back and choose another link.`
|
|
13484
14556
|
);
|
|
13485
14557
|
}
|
|
13486
|
-
return withAction(
|
|
14558
|
+
return withAction(runtime2, tabManager, "navigate", { url }, async () => {
|
|
13487
14559
|
const id = tabManager.getActiveTabId();
|
|
13488
14560
|
tabManager.navigateTab(id, url);
|
|
13489
14561
|
const { httpStatus } = await waitForLoadWithStatus(
|
|
@@ -13513,7 +14585,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13513
14585
|
return asTextResponse("Error: No active tab");
|
|
13514
14586
|
}
|
|
13515
14587
|
return withAction(
|
|
13516
|
-
|
|
14588
|
+
runtime2,
|
|
13517
14589
|
tabManager,
|
|
13518
14590
|
"set_ad_blocking",
|
|
13519
14591
|
{ enabled, tabId, match, reload },
|
|
@@ -13602,7 +14674,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13602
14674
|
async () => {
|
|
13603
14675
|
const tab = tabManager.getActiveTab();
|
|
13604
14676
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13605
|
-
return withAction(
|
|
14677
|
+
return withAction(runtime2, tabManager, "go_back", {}, async () => {
|
|
13606
14678
|
if (!tab.canGoBack()) {
|
|
13607
14679
|
return "No previous page in history";
|
|
13608
14680
|
}
|
|
@@ -13623,7 +14695,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13623
14695
|
async () => {
|
|
13624
14696
|
const tab = tabManager.getActiveTab();
|
|
13625
14697
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13626
|
-
return withAction(
|
|
14698
|
+
return withAction(runtime2, tabManager, "go_forward", {}, async () => {
|
|
13627
14699
|
if (!tab.canGoForward()) {
|
|
13628
14700
|
return "No forward page in history";
|
|
13629
14701
|
}
|
|
@@ -13644,7 +14716,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13644
14716
|
async () => {
|
|
13645
14717
|
const tab = tabManager.getActiveTab();
|
|
13646
14718
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13647
|
-
return withAction(
|
|
14719
|
+
return withAction(runtime2, tabManager, "reload", {}, async () => {
|
|
13648
14720
|
tabManager.reloadTab(tabManager.getActiveTabId());
|
|
13649
14721
|
await waitForLoad(tab.view.webContents);
|
|
13650
14722
|
return `Reloaded ${tab.view.webContents.getURL()}`;
|
|
@@ -13665,7 +14737,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13665
14737
|
const tab = tabManager.getActiveTab();
|
|
13666
14738
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13667
14739
|
return withAction(
|
|
13668
|
-
|
|
14740
|
+
runtime2,
|
|
13669
14741
|
tabManager,
|
|
13670
14742
|
"click",
|
|
13671
14743
|
{ index, selector },
|
|
@@ -13694,7 +14766,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13694
14766
|
const tab = tabManager.getActiveTab();
|
|
13695
14767
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13696
14768
|
return withAction(
|
|
13697
|
-
|
|
14769
|
+
runtime2,
|
|
13698
14770
|
tabManager,
|
|
13699
14771
|
"hover",
|
|
13700
14772
|
{ index, selector },
|
|
@@ -13723,7 +14795,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13723
14795
|
const tab = tabManager.getActiveTab();
|
|
13724
14796
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13725
14797
|
return withAction(
|
|
13726
|
-
|
|
14798
|
+
runtime2,
|
|
13727
14799
|
tabManager,
|
|
13728
14800
|
"focus",
|
|
13729
14801
|
{ index, selector },
|
|
@@ -13837,7 +14909,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13837
14909
|
const tab = tabManager.getActiveTab();
|
|
13838
14910
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13839
14911
|
return withAction(
|
|
13840
|
-
|
|
14912
|
+
runtime2,
|
|
13841
14913
|
tabManager,
|
|
13842
14914
|
"type",
|
|
13843
14915
|
{ index, selector, text, mode },
|
|
@@ -13876,7 +14948,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13876
14948
|
const tab = tabManager.getActiveTab();
|
|
13877
14949
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13878
14950
|
return withAction(
|
|
13879
|
-
|
|
14951
|
+
runtime2,
|
|
13880
14952
|
tabManager,
|
|
13881
14953
|
"type_text",
|
|
13882
14954
|
{ index, selector, text, mode },
|
|
@@ -13913,7 +14985,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13913
14985
|
const tab = tabManager.getActiveTab();
|
|
13914
14986
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13915
14987
|
return withAction(
|
|
13916
|
-
|
|
14988
|
+
runtime2,
|
|
13917
14989
|
tabManager,
|
|
13918
14990
|
"select_option",
|
|
13919
14991
|
{ index, selector, label, value },
|
|
@@ -13935,7 +15007,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13935
15007
|
const tab = tabManager.getActiveTab();
|
|
13936
15008
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13937
15009
|
return withAction(
|
|
13938
|
-
|
|
15010
|
+
runtime2,
|
|
13939
15011
|
tabManager,
|
|
13940
15012
|
"submit_form",
|
|
13941
15013
|
{ index, selector },
|
|
@@ -13968,7 +15040,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13968
15040
|
const tab = tabManager.getActiveTab();
|
|
13969
15041
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13970
15042
|
return withAction(
|
|
13971
|
-
|
|
15043
|
+
runtime2,
|
|
13972
15044
|
tabManager,
|
|
13973
15045
|
"press_key",
|
|
13974
15046
|
{ key, index, selector },
|
|
@@ -13995,19 +15067,21 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
13995
15067
|
description: "Scroll the page up or down.",
|
|
13996
15068
|
inputSchema: {
|
|
13997
15069
|
direction: zod.z.enum(["up", "down"]).describe("Scroll direction"),
|
|
13998
|
-
amount:
|
|
15070
|
+
amount: optionalNumberLikeSchema().describe(
|
|
15071
|
+
"Pixels to scroll (default 500)"
|
|
15072
|
+
)
|
|
13999
15073
|
}
|
|
14000
15074
|
},
|
|
14001
15075
|
async ({ direction, amount }) => {
|
|
14002
15076
|
const tab = tabManager.getActiveTab();
|
|
14003
15077
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14004
15078
|
return withAction(
|
|
14005
|
-
|
|
15079
|
+
runtime2,
|
|
14006
15080
|
tabManager,
|
|
14007
15081
|
"scroll",
|
|
14008
15082
|
{ direction, amount },
|
|
14009
15083
|
async () => {
|
|
14010
|
-
const pixels = amount
|
|
15084
|
+
const pixels = coerceOptionalNumber(amount) ?? 500;
|
|
14011
15085
|
const dir = direction === "up" ? -pixels : pixels;
|
|
14012
15086
|
const result = await scrollPage(tab.view.webContents, dir);
|
|
14013
15087
|
return `Scrolled ${direction} by ${pixels}px (moved ${Math.abs(result.movedY)}px, now at y=${Math.round(result.afterY)})`;
|
|
@@ -14025,7 +15099,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14025
15099
|
const tab = tabManager.getActiveTab();
|
|
14026
15100
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14027
15101
|
return withAction(
|
|
14028
|
-
|
|
15102
|
+
runtime2,
|
|
14029
15103
|
tabManager,
|
|
14030
15104
|
"dismiss_popup",
|
|
14031
15105
|
{},
|
|
@@ -14033,6 +15107,32 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14033
15107
|
);
|
|
14034
15108
|
}
|
|
14035
15109
|
);
|
|
15110
|
+
server.registerTool(
|
|
15111
|
+
"vessel_clear_overlays",
|
|
15112
|
+
{
|
|
15113
|
+
title: "Clear Overlays",
|
|
15114
|
+
description: "Work through blocking overlays and modals until the page is unblocked, using overlay-specific heuristics for consent banners and radio-selection dialogs.",
|
|
15115
|
+
inputSchema: {
|
|
15116
|
+
strategy: zod.z.enum(["auto", "interactive"]).optional().describe(
|
|
15117
|
+
'How aggressively to clear overlays. "auto" uses heuristics; "interactive" stops earlier when human judgment may be needed.'
|
|
15118
|
+
)
|
|
15119
|
+
}
|
|
15120
|
+
},
|
|
15121
|
+
async ({ strategy }) => {
|
|
15122
|
+
const tab = tabManager.getActiveTab();
|
|
15123
|
+
if (!tab) return asTextResponse("Error: No active tab");
|
|
15124
|
+
return withAction(
|
|
15125
|
+
runtime2,
|
|
15126
|
+
tabManager,
|
|
15127
|
+
"clear_overlays",
|
|
15128
|
+
{ strategy: strategy || "auto" },
|
|
15129
|
+
async () => clearOverlays(
|
|
15130
|
+
tab.view.webContents,
|
|
15131
|
+
strategy === "interactive" ? "interactive" : "auto"
|
|
15132
|
+
)
|
|
15133
|
+
);
|
|
15134
|
+
}
|
|
15135
|
+
);
|
|
14036
15136
|
server.registerTool(
|
|
14037
15137
|
"vessel_wait_for",
|
|
14038
15138
|
{
|
|
@@ -14048,7 +15148,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14048
15148
|
const tab = tabManager.getActiveTab();
|
|
14049
15149
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14050
15150
|
return withAction(
|
|
14051
|
-
|
|
15151
|
+
runtime2,
|
|
14052
15152
|
tabManager,
|
|
14053
15153
|
"wait_for",
|
|
14054
15154
|
{ text, selector, timeoutMs },
|
|
@@ -14065,7 +15165,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14065
15165
|
url: zod.z.string().optional().describe("URL to open (defaults to about:blank)")
|
|
14066
15166
|
}
|
|
14067
15167
|
},
|
|
14068
|
-
async ({ url }) => withAction(
|
|
15168
|
+
async ({ url }) => withAction(runtime2, tabManager, "create_tab", { url }, async () => {
|
|
14069
15169
|
const id = tabManager.createTab(url || "about:blank");
|
|
14070
15170
|
const tab = tabManager.getActiveTab();
|
|
14071
15171
|
if (tab) {
|
|
@@ -14085,7 +15185,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14085
15185
|
}
|
|
14086
15186
|
},
|
|
14087
15187
|
async ({ tabId, match }) => withAction(
|
|
14088
|
-
|
|
15188
|
+
runtime2,
|
|
14089
15189
|
tabManager,
|
|
14090
15190
|
"switch_tab",
|
|
14091
15191
|
{ tabId, match },
|
|
@@ -14108,7 +15208,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14108
15208
|
tabId: zod.z.string().describe("The tab ID to close")
|
|
14109
15209
|
}
|
|
14110
15210
|
},
|
|
14111
|
-
async ({ tabId }) => withAction(
|
|
15211
|
+
async ({ tabId }) => withAction(runtime2, tabManager, "close_tab", { tabId }, async () => {
|
|
14112
15212
|
tabManager.closeTab(tabId);
|
|
14113
15213
|
return `Closed tab ${tabId}`;
|
|
14114
15214
|
})
|
|
@@ -14124,12 +15224,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14124
15224
|
}
|
|
14125
15225
|
},
|
|
14126
15226
|
async ({ name, note }) => withAction(
|
|
14127
|
-
|
|
15227
|
+
runtime2,
|
|
14128
15228
|
tabManager,
|
|
14129
15229
|
"create_checkpoint",
|
|
14130
15230
|
{ name, note },
|
|
14131
15231
|
async () => {
|
|
14132
|
-
const checkpoint =
|
|
15232
|
+
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
14133
15233
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
14134
15234
|
}
|
|
14135
15235
|
)
|
|
@@ -14145,12 +15245,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14145
15245
|
}
|
|
14146
15246
|
},
|
|
14147
15247
|
async ({ name, note }) => withAction(
|
|
14148
|
-
|
|
15248
|
+
runtime2,
|
|
14149
15249
|
tabManager,
|
|
14150
15250
|
"create_checkpoint",
|
|
14151
15251
|
{ name, note },
|
|
14152
15252
|
async () => {
|
|
14153
|
-
const checkpoint =
|
|
15253
|
+
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
14154
15254
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
14155
15255
|
}
|
|
14156
15256
|
)
|
|
@@ -14166,17 +15266,17 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14166
15266
|
}
|
|
14167
15267
|
},
|
|
14168
15268
|
async ({ checkpointId, name }) => withAction(
|
|
14169
|
-
|
|
15269
|
+
runtime2,
|
|
14170
15270
|
tabManager,
|
|
14171
15271
|
"restore_checkpoint",
|
|
14172
15272
|
{ checkpointId, name },
|
|
14173
15273
|
async () => {
|
|
14174
|
-
const state2 =
|
|
15274
|
+
const state2 = runtime2.getState();
|
|
14175
15275
|
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
14176
15276
|
if (!checkpoint) {
|
|
14177
15277
|
return "Error: No matching checkpoint found";
|
|
14178
15278
|
}
|
|
14179
|
-
|
|
15279
|
+
runtime2.restoreCheckpoint(checkpoint.id);
|
|
14180
15280
|
return `Restored checkpoint ${checkpoint.name}`;
|
|
14181
15281
|
}
|
|
14182
15282
|
)
|
|
@@ -14192,17 +15292,17 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14192
15292
|
}
|
|
14193
15293
|
},
|
|
14194
15294
|
async ({ checkpointId, name }) => withAction(
|
|
14195
|
-
|
|
15295
|
+
runtime2,
|
|
14196
15296
|
tabManager,
|
|
14197
15297
|
"restore_checkpoint",
|
|
14198
15298
|
{ checkpointId, name },
|
|
14199
15299
|
async () => {
|
|
14200
|
-
const state2 =
|
|
15300
|
+
const state2 = runtime2.getState();
|
|
14201
15301
|
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
14202
15302
|
if (!checkpoint) {
|
|
14203
15303
|
return "Error: No matching checkpoint found";
|
|
14204
15304
|
}
|
|
14205
|
-
|
|
15305
|
+
runtime2.restoreCheckpoint(checkpoint.id);
|
|
14206
15306
|
return `Restored checkpoint ${checkpoint.name}`;
|
|
14207
15307
|
}
|
|
14208
15308
|
)
|
|
@@ -14216,7 +15316,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14216
15316
|
name: zod.z.string().describe("Session name such as github-logged-in")
|
|
14217
15317
|
}
|
|
14218
15318
|
},
|
|
14219
|
-
async ({ name }) => withAction(
|
|
15319
|
+
async ({ name }) => withAction(runtime2, tabManager, "save_session", { name }, async () => {
|
|
14220
15320
|
const saved = await saveNamedSession(
|
|
14221
15321
|
tabManager,
|
|
14222
15322
|
name
|
|
@@ -14233,7 +15333,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14233
15333
|
name: zod.z.string().describe("Previously saved session name")
|
|
14234
15334
|
}
|
|
14235
15335
|
},
|
|
14236
|
-
async ({ name }) => withAction(
|
|
15336
|
+
async ({ name }) => withAction(runtime2, tabManager, "load_session", { name }, async () => {
|
|
14237
15337
|
const loaded = await loadNamedSession(
|
|
14238
15338
|
tabManager,
|
|
14239
15339
|
name
|
|
@@ -14247,7 +15347,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14247
15347
|
title: "List Sessions",
|
|
14248
15348
|
description: "List previously saved named browser sessions with cookie and storage counts."
|
|
14249
15349
|
},
|
|
14250
|
-
async () => withAction(
|
|
15350
|
+
async () => withAction(runtime2, tabManager, "list_sessions", {}, async () => {
|
|
14251
15351
|
const sessions2 = listNamedSessions();
|
|
14252
15352
|
if (sessions2.length === 0) return "No saved sessions";
|
|
14253
15353
|
return sessions2.map(
|
|
@@ -14265,7 +15365,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
14265
15365
|
}
|
|
14266
15366
|
},
|
|
14267
15367
|
async ({ name }) => withAction(
|
|
14268
|
-
|
|
15368
|
+
runtime2,
|
|
14269
15369
|
tabManager,
|
|
14270
15370
|
"delete_session",
|
|
14271
15371
|
{ name },
|
|
@@ -14332,7 +15432,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14332
15432
|
inputSchema: {
|
|
14333
15433
|
index: zod.z.number().optional().describe("Element index from extracted content to highlight"),
|
|
14334
15434
|
selector: zod.z.string().optional().describe("CSS selector of element to highlight"),
|
|
14335
|
-
text:
|
|
15435
|
+
text: normalizedOptionalStringSchema().describe(
|
|
14336
15436
|
"Text to find and highlight on the page (highlights all occurrences)"
|
|
14337
15437
|
),
|
|
14338
15438
|
label: zod.z.string().optional().describe("Optional annotation label to display near the highlight"),
|
|
@@ -14350,18 +15450,27 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14350
15450
|
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
14351
15451
|
const tab = tabManager.getActiveTab();
|
|
14352
15452
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15453
|
+
const normalizedText = normalizeLooseString(text);
|
|
14353
15454
|
return withAction(
|
|
14354
|
-
|
|
15455
|
+
runtime2,
|
|
14355
15456
|
tabManager,
|
|
14356
15457
|
"highlight",
|
|
14357
|
-
{
|
|
15458
|
+
{
|
|
15459
|
+
index,
|
|
15460
|
+
selector,
|
|
15461
|
+
text: normalizedText,
|
|
15462
|
+
label,
|
|
15463
|
+
durationMs,
|
|
15464
|
+
persist,
|
|
15465
|
+
color
|
|
15466
|
+
},
|
|
14358
15467
|
async () => {
|
|
14359
15468
|
const wc = tab.view.webContents;
|
|
14360
15469
|
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
14361
15470
|
const result = await highlightOnPage(
|
|
14362
15471
|
wc,
|
|
14363
15472
|
resolvedSelector,
|
|
14364
|
-
|
|
15473
|
+
normalizedText,
|
|
14365
15474
|
label,
|
|
14366
15475
|
durationMs,
|
|
14367
15476
|
color
|
|
@@ -14371,7 +15480,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14371
15480
|
addHighlight(
|
|
14372
15481
|
url,
|
|
14373
15482
|
resolvedSelector ?? void 0,
|
|
14374
|
-
|
|
15483
|
+
normalizedText,
|
|
14375
15484
|
label,
|
|
14376
15485
|
color,
|
|
14377
15486
|
"agent"
|
|
@@ -14392,7 +15501,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14392
15501
|
const tab = tabManager.getActiveTab();
|
|
14393
15502
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14394
15503
|
return withAction(
|
|
14395
|
-
|
|
15504
|
+
runtime2,
|
|
14396
15505
|
tabManager,
|
|
14397
15506
|
"clear_highlights",
|
|
14398
15507
|
{},
|
|
@@ -14417,7 +15526,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
14417
15526
|
}
|
|
14418
15527
|
},
|
|
14419
15528
|
async ({ url }) => {
|
|
14420
|
-
const state2 = getState$
|
|
15529
|
+
const state2 = getState$2();
|
|
14421
15530
|
const activeTab = tabManager.getActiveTab();
|
|
14422
15531
|
const activeUrl = activeTab ? normalizeUrl(activeTab.view.webContents.getURL()) : null;
|
|
14423
15532
|
const activeSavedHighlights = activeUrl ? state2.highlights.filter((highlight) => highlight.url === activeUrl) : [];
|
|
@@ -14548,7 +15657,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14548
15657
|
},
|
|
14549
15658
|
async ({ name, summary }) => {
|
|
14550
15659
|
return withAction(
|
|
14551
|
-
|
|
15660
|
+
runtime2,
|
|
14552
15661
|
tabManager,
|
|
14553
15662
|
"create_bookmark_folder",
|
|
14554
15663
|
{ name, summary },
|
|
@@ -14610,7 +15719,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14610
15719
|
on_duplicate
|
|
14611
15720
|
}) => {
|
|
14612
15721
|
return withAction(
|
|
14613
|
-
|
|
15722
|
+
runtime2,
|
|
14614
15723
|
tabManager,
|
|
14615
15724
|
"save_bookmark",
|
|
14616
15725
|
{
|
|
@@ -14689,7 +15798,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14689
15798
|
},
|
|
14690
15799
|
async ({ folder_id, folder_name }) => {
|
|
14691
15800
|
return withAction(
|
|
14692
|
-
|
|
15801
|
+
runtime2,
|
|
14693
15802
|
tabManager,
|
|
14694
15803
|
"list_bookmarks",
|
|
14695
15804
|
{ folder_id, folder_name },
|
|
@@ -14754,7 +15863,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14754
15863
|
},
|
|
14755
15864
|
async (args) => {
|
|
14756
15865
|
return withAction(
|
|
14757
|
-
|
|
15866
|
+
runtime2,
|
|
14758
15867
|
tabManager,
|
|
14759
15868
|
"organize_bookmark",
|
|
14760
15869
|
args,
|
|
@@ -14821,7 +15930,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14821
15930
|
},
|
|
14822
15931
|
async ({ query }) => {
|
|
14823
15932
|
return withAction(
|
|
14824
|
-
|
|
15933
|
+
runtime2,
|
|
14825
15934
|
tabManager,
|
|
14826
15935
|
"search_bookmarks",
|
|
14827
15936
|
{ query },
|
|
@@ -14852,7 +15961,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14852
15961
|
},
|
|
14853
15962
|
async ({ bookmark_id }) => {
|
|
14854
15963
|
return withAction(
|
|
14855
|
-
|
|
15964
|
+
runtime2,
|
|
14856
15965
|
tabManager,
|
|
14857
15966
|
"remove_bookmark",
|
|
14858
15967
|
{ bookmark_id },
|
|
@@ -14885,7 +15994,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14885
15994
|
},
|
|
14886
15995
|
async ({ bookmark_id, url, title, index, selector, note }) => {
|
|
14887
15996
|
return withAction(
|
|
14888
|
-
|
|
15997
|
+
runtime2,
|
|
14889
15998
|
tabManager,
|
|
14890
15999
|
"archive_bookmark",
|
|
14891
16000
|
{ bookmark_id, url, title, index, selector, note },
|
|
@@ -14955,7 +16064,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14955
16064
|
},
|
|
14956
16065
|
async ({ bookmark_id, new_tab }) => {
|
|
14957
16066
|
return withAction(
|
|
14958
|
-
|
|
16067
|
+
runtime2,
|
|
14959
16068
|
tabManager,
|
|
14960
16069
|
"open_bookmark",
|
|
14961
16070
|
{ bookmark_id, new_tab },
|
|
@@ -14998,7 +16107,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
14998
16107
|
},
|
|
14999
16108
|
async ({ folder_id }) => {
|
|
15000
16109
|
return withAction(
|
|
15001
|
-
|
|
16110
|
+
runtime2,
|
|
15002
16111
|
tabManager,
|
|
15003
16112
|
"remove_bookmark_folder",
|
|
15004
16113
|
{ folder_id },
|
|
@@ -15024,7 +16133,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15024
16133
|
},
|
|
15025
16134
|
async ({ folder_id, new_name, summary }) => {
|
|
15026
16135
|
return withAction(
|
|
15027
|
-
|
|
16136
|
+
runtime2,
|
|
15028
16137
|
tabManager,
|
|
15029
16138
|
"rename_bookmark_folder",
|
|
15030
16139
|
{ folder_id, new_name, summary },
|
|
@@ -15061,7 +16170,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15061
16170
|
},
|
|
15062
16171
|
async ({ title, body, folder, tags }) => {
|
|
15063
16172
|
return withAction(
|
|
15064
|
-
|
|
16173
|
+
runtime2,
|
|
15065
16174
|
tabManager,
|
|
15066
16175
|
"memory_note_create",
|
|
15067
16176
|
{ title, folder, tags },
|
|
@@ -15085,7 +16194,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15085
16194
|
},
|
|
15086
16195
|
async ({ note_path, content, heading }) => {
|
|
15087
16196
|
return withAction(
|
|
15088
|
-
|
|
16197
|
+
runtime2,
|
|
15089
16198
|
tabManager,
|
|
15090
16199
|
"memory_note_append",
|
|
15091
16200
|
{ note_path, heading },
|
|
@@ -15112,7 +16221,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15112
16221
|
},
|
|
15113
16222
|
async ({ folder, limit }) => {
|
|
15114
16223
|
return withAction(
|
|
15115
|
-
|
|
16224
|
+
runtime2,
|
|
15116
16225
|
tabManager,
|
|
15117
16226
|
"memory_note_list",
|
|
15118
16227
|
{ folder, limit },
|
|
@@ -15142,7 +16251,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15142
16251
|
},
|
|
15143
16252
|
async ({ query, folder, tags, limit }) => {
|
|
15144
16253
|
return withAction(
|
|
15145
|
-
|
|
16254
|
+
runtime2,
|
|
15146
16255
|
tabManager,
|
|
15147
16256
|
"memory_note_search",
|
|
15148
16257
|
{ query, folder, tags, limit },
|
|
@@ -15175,7 +16284,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15175
16284
|
const tab = tabManager.getActiveTab();
|
|
15176
16285
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15177
16286
|
return withAction(
|
|
15178
|
-
|
|
16287
|
+
runtime2,
|
|
15179
16288
|
tabManager,
|
|
15180
16289
|
"memory_page_capture",
|
|
15181
16290
|
{ title, folder, tags },
|
|
@@ -15212,7 +16321,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15212
16321
|
},
|
|
15213
16322
|
async ({ bookmark_id, note_path, title, folder, note, tags }) => {
|
|
15214
16323
|
return withAction(
|
|
15215
|
-
|
|
16324
|
+
runtime2,
|
|
15216
16325
|
tabManager,
|
|
15217
16326
|
"memory_link_bookmark",
|
|
15218
16327
|
{ bookmark_id, note_path, title, folder, tags },
|
|
@@ -15243,16 +16352,17 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
15243
16352
|
goal: zod.z.string().describe(
|
|
15244
16353
|
"What this workflow accomplishes (e.g. 'Purchase item from Amazon')"
|
|
15245
16354
|
),
|
|
15246
|
-
steps:
|
|
16355
|
+
steps: stringArrayLikeSchema().describe(
|
|
15247
16356
|
"Ordered list of step labels (e.g. ['Log in', 'Search', 'Select item', 'Checkout'])"
|
|
15248
16357
|
)
|
|
15249
16358
|
}
|
|
15250
16359
|
},
|
|
15251
16360
|
async ({ goal, steps }) => {
|
|
16361
|
+
const normalizedSteps = coerceStringArray(steps) ?? [];
|
|
15252
16362
|
const tab = tabManager.getActiveTab();
|
|
15253
|
-
const flow =
|
|
16363
|
+
const flow = runtime2.startFlow(
|
|
15254
16364
|
goal,
|
|
15255
|
-
|
|
16365
|
+
normalizedSteps,
|
|
15256
16366
|
tab?.view.webContents.getURL()
|
|
15257
16367
|
);
|
|
15258
16368
|
return asTextResponse(
|
|
@@ -15271,9 +16381,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15271
16381
|
}
|
|
15272
16382
|
},
|
|
15273
16383
|
async ({ detail }) => {
|
|
15274
|
-
const flow =
|
|
16384
|
+
const flow = runtime2.advanceFlow(detail);
|
|
15275
16385
|
if (!flow) return asTextResponse("No active flow to advance");
|
|
15276
|
-
const ctx =
|
|
16386
|
+
const ctx = runtime2.getFlowContext();
|
|
15277
16387
|
return asTextResponse(`Step completed.${ctx}`);
|
|
15278
16388
|
}
|
|
15279
16389
|
);
|
|
@@ -15284,9 +16394,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15284
16394
|
description: "Check the current workflow progress."
|
|
15285
16395
|
},
|
|
15286
16396
|
async () => {
|
|
15287
|
-
const flow =
|
|
16397
|
+
const flow = runtime2.getFlowState();
|
|
15288
16398
|
if (!flow) return asTextResponse("No active workflow.");
|
|
15289
|
-
return asTextResponse(
|
|
16399
|
+
return asTextResponse(runtime2.getFlowContext());
|
|
15290
16400
|
}
|
|
15291
16401
|
);
|
|
15292
16402
|
server.registerTool(
|
|
@@ -15296,7 +16406,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15296
16406
|
description: "Clear the active workflow tracker."
|
|
15297
16407
|
},
|
|
15298
16408
|
async () => {
|
|
15299
|
-
|
|
16409
|
+
runtime2.clearFlow();
|
|
15300
16410
|
return asTextResponse("Workflow ended.");
|
|
15301
16411
|
}
|
|
15302
16412
|
);
|
|
@@ -15325,7 +16435,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15325
16435
|
suggestions.push(`Page: ${page.title || "(untitled)"}`);
|
|
15326
16436
|
suggestions.push(`URL: ${page.url}`);
|
|
15327
16437
|
suggestions.push("");
|
|
15328
|
-
const flowCtx =
|
|
16438
|
+
const flowCtx = runtime2.getFlowContext();
|
|
15329
16439
|
if (flowCtx) {
|
|
15330
16440
|
suggestions.push(flowCtx);
|
|
15331
16441
|
suggestions.push("");
|
|
@@ -15348,9 +16458,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15348
16458
|
const hasOverlays = page.overlays.some((o) => o.blocksInteraction);
|
|
15349
16459
|
if (hasOverlays) {
|
|
15350
16460
|
suggestions.push("⚠ BLOCKING OVERLAY detected — dismiss it first:");
|
|
15351
|
-
suggestions.push(
|
|
15352
|
-
|
|
15353
|
-
);
|
|
16461
|
+
suggestions.push(" → vessel_clear_overlays for stacked modals");
|
|
16462
|
+
suggestions.push(" → or vessel_dismiss_popup for a single popup");
|
|
15354
16463
|
suggestions.push("");
|
|
15355
16464
|
}
|
|
15356
16465
|
if (hasPasswordField) {
|
|
@@ -15426,7 +16535,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15426
16535
|
const tab = tabManager.getActiveTab();
|
|
15427
16536
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15428
16537
|
return withAction(
|
|
15429
|
-
|
|
16538
|
+
runtime2,
|
|
15430
16539
|
tabManager,
|
|
15431
16540
|
"fill_form",
|
|
15432
16541
|
{ fieldCount: fields.length, submit },
|
|
@@ -15483,7 +16592,7 @@ ${results.join("\n")}`;
|
|
|
15483
16592
|
const tab = tabManager.getActiveTab();
|
|
15484
16593
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15485
16594
|
return withAction(
|
|
15486
|
-
|
|
16595
|
+
runtime2,
|
|
15487
16596
|
tabManager,
|
|
15488
16597
|
"login",
|
|
15489
16598
|
{ url, username: username.slice(0, 3) + "***" },
|
|
@@ -15588,7 +16697,7 @@ ${steps.join("\n")}`;
|
|
|
15588
16697
|
`Error: "${query}" looks like a button label, not a search query. Use the click tool to interact with this element instead.`
|
|
15589
16698
|
);
|
|
15590
16699
|
}
|
|
15591
|
-
return withAction(
|
|
16700
|
+
return withAction(runtime2, tabManager, "search", { query }, async () => {
|
|
15592
16701
|
const wc = tab.view.webContents;
|
|
15593
16702
|
const searchSel = selector || await wc.executeJavaScript(`
|
|
15594
16703
|
(function() {
|
|
@@ -15642,7 +16751,7 @@ ${steps.join("\n")}`;
|
|
|
15642
16751
|
const tab = tabManager.getActiveTab();
|
|
15643
16752
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15644
16753
|
return withAction(
|
|
15645
|
-
|
|
16754
|
+
runtime2,
|
|
15646
16755
|
tabManager,
|
|
15647
16756
|
"paginate",
|
|
15648
16757
|
{ direction },
|
|
@@ -15693,7 +16802,7 @@ ${steps.join("\n")}`;
|
|
|
15693
16802
|
const tab = tabManager.getActiveTab();
|
|
15694
16803
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15695
16804
|
return withAction(
|
|
15696
|
-
|
|
16805
|
+
runtime2,
|
|
15697
16806
|
tabManager,
|
|
15698
16807
|
"vessel_accept_cookies",
|
|
15699
16808
|
{},
|
|
@@ -15751,7 +16860,7 @@ ${steps.join("\n")}`;
|
|
|
15751
16860
|
const tab = tabManager.getActiveTab();
|
|
15752
16861
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15753
16862
|
return withAction(
|
|
15754
|
-
|
|
16863
|
+
runtime2,
|
|
15755
16864
|
tabManager,
|
|
15756
16865
|
"vessel_extract_table",
|
|
15757
16866
|
{ index, selector: rawSelector },
|
|
@@ -15806,7 +16915,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
15806
16915
|
const tab = tabManager.getActiveTab();
|
|
15807
16916
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15808
16917
|
return withAction(
|
|
15809
|
-
|
|
16918
|
+
runtime2,
|
|
15810
16919
|
tabManager,
|
|
15811
16920
|
"vessel_scroll_to_element",
|
|
15812
16921
|
{ index, selector: rawSelector, position },
|
|
@@ -15864,7 +16973,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
15864
16973
|
const tab = tabManager.getActiveTab();
|
|
15865
16974
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
15866
16975
|
return withAction(
|
|
15867
|
-
|
|
16976
|
+
runtime2,
|
|
15868
16977
|
tabManager,
|
|
15869
16978
|
"vessel_wait_for_navigation",
|
|
15870
16979
|
{ timeoutMs },
|
|
@@ -15915,7 +17024,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
15915
17024
|
inputSchema: zod.z.object({})
|
|
15916
17025
|
},
|
|
15917
17026
|
async () => {
|
|
15918
|
-
const m =
|
|
17027
|
+
const m = runtime2.getMetrics();
|
|
15919
17028
|
const lines = [
|
|
15920
17029
|
`Session Metrics:`,
|
|
15921
17030
|
` Total actions: ${m.totalActions}`,
|
|
@@ -16075,16 +17184,16 @@ async function resolveSelector(wc, index, selector) {
|
|
|
16075
17184
|
`
|
|
16076
17185
|
);
|
|
16077
17186
|
}
|
|
16078
|
-
function createMcpServer(tabManager,
|
|
17187
|
+
function createMcpServer(tabManager, runtime2) {
|
|
16079
17188
|
const server = new mcp_js.McpServer({
|
|
16080
17189
|
name: "vessel-browser",
|
|
16081
17190
|
version: "0.1.0"
|
|
16082
17191
|
});
|
|
16083
|
-
registerTools(server, tabManager,
|
|
16084
|
-
registerDevTools(server, tabManager,
|
|
17192
|
+
registerTools(server, tabManager, runtime2);
|
|
17193
|
+
registerDevTools(server, tabManager, runtime2);
|
|
16085
17194
|
return server;
|
|
16086
17195
|
}
|
|
16087
|
-
function startMcpServer(tabManager,
|
|
17196
|
+
function startMcpServer(tabManager, runtime2, port) {
|
|
16088
17197
|
setMcpHealth({
|
|
16089
17198
|
configuredPort: port,
|
|
16090
17199
|
activePort: null,
|
|
@@ -16092,6 +17201,7 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16092
17201
|
status: "starting",
|
|
16093
17202
|
message: `Starting MCP server on port ${port}.`
|
|
16094
17203
|
});
|
|
17204
|
+
mcpAuthToken = crypto$1.randomBytes(32).toString("hex");
|
|
16095
17205
|
return new Promise((resolve) => {
|
|
16096
17206
|
const server = http.createServer(async (req, res) => {
|
|
16097
17207
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
@@ -16100,22 +17210,28 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16100
17210
|
res.end("Not found");
|
|
16101
17211
|
return;
|
|
16102
17212
|
}
|
|
16103
|
-
res.setHeader("Access-Control-Allow-Origin", "
|
|
17213
|
+
res.setHeader("Access-Control-Allow-Origin", "null");
|
|
16104
17214
|
res.setHeader(
|
|
16105
17215
|
"Access-Control-Allow-Methods",
|
|
16106
17216
|
"POST, GET, DELETE, OPTIONS"
|
|
16107
17217
|
);
|
|
16108
17218
|
res.setHeader(
|
|
16109
17219
|
"Access-Control-Allow-Headers",
|
|
16110
|
-
"Content-Type, mcp-session-id"
|
|
17220
|
+
"Content-Type, mcp-session-id, Authorization"
|
|
16111
17221
|
);
|
|
16112
17222
|
if (req.method === "OPTIONS") {
|
|
16113
17223
|
res.writeHead(204);
|
|
16114
17224
|
res.end();
|
|
16115
17225
|
return;
|
|
16116
17226
|
}
|
|
17227
|
+
const authHeader = req.headers.authorization;
|
|
17228
|
+
if (!authHeader || authHeader !== `Bearer ${mcpAuthToken}`) {
|
|
17229
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
17230
|
+
res.end(JSON.stringify({ error: "Unauthorized — missing or invalid bearer token" }));
|
|
17231
|
+
return;
|
|
17232
|
+
}
|
|
16117
17233
|
try {
|
|
16118
|
-
const mcpServer = createMcpServer(tabManager,
|
|
17234
|
+
const mcpServer = createMcpServer(tabManager, runtime2);
|
|
16119
17235
|
const transport = new streamableHttp_js.StreamableHTTPServerTransport({
|
|
16120
17236
|
sessionIdGenerator: void 0
|
|
16121
17237
|
});
|
|
@@ -16157,6 +17273,7 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16157
17273
|
configuredPort: port,
|
|
16158
17274
|
activePort: null,
|
|
16159
17275
|
endpoint: null,
|
|
17276
|
+
authToken: null,
|
|
16160
17277
|
error: message
|
|
16161
17278
|
});
|
|
16162
17279
|
});
|
|
@@ -16173,11 +17290,13 @@ function startMcpServer(tabManager, runtime, port) {
|
|
|
16173
17290
|
message: `MCP server listening on ${endpoint}.`
|
|
16174
17291
|
});
|
|
16175
17292
|
console.log(`[Vessel MCP] Server listening on ${endpoint}`);
|
|
17293
|
+
console.log(`[Vessel MCP] Auth token: ${mcpAuthToken}`);
|
|
16176
17294
|
finish({
|
|
16177
17295
|
ok: true,
|
|
16178
17296
|
configuredPort: port,
|
|
16179
17297
|
activePort: actualPort,
|
|
16180
|
-
endpoint
|
|
17298
|
+
endpoint,
|
|
17299
|
+
authToken: mcpAuthToken
|
|
16181
17300
|
});
|
|
16182
17301
|
});
|
|
16183
17302
|
});
|
|
@@ -16196,6 +17315,7 @@ function stopMcpServer() {
|
|
|
16196
17315
|
}
|
|
16197
17316
|
const server = httpServer;
|
|
16198
17317
|
httpServer = null;
|
|
17318
|
+
mcpAuthToken = null;
|
|
16199
17319
|
server.close(() => {
|
|
16200
17320
|
setMcpHealth({
|
|
16201
17321
|
activePort: null,
|
|
@@ -16209,14 +17329,14 @@ function stopMcpServer() {
|
|
|
16209
17329
|
});
|
|
16210
17330
|
}
|
|
16211
17331
|
let activeChatProvider = null;
|
|
16212
|
-
function registerIpcHandlers(windowState,
|
|
17332
|
+
function registerIpcHandlers(windowState, runtime2) {
|
|
16213
17333
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
16214
17334
|
const sendToRendererViews = (channel, ...args) => {
|
|
16215
17335
|
chromeView.webContents.send(channel, ...args);
|
|
16216
17336
|
sidebarView.webContents.send(channel, ...args);
|
|
16217
17337
|
devtoolsPanelView.webContents.send(channel, ...args);
|
|
16218
17338
|
};
|
|
16219
|
-
|
|
17339
|
+
runtime2.setUpdateListener((state2) => {
|
|
16220
17340
|
sendToRendererViews(Channels.AGENT_RUNTIME_UPDATE, state2);
|
|
16221
17341
|
});
|
|
16222
17342
|
electron.ipcMain.handle(Channels.TAB_CREATE, (_, url) => {
|
|
@@ -16266,7 +17386,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16266
17386
|
(chunk) => sendToRendererViews(Channels.AI_STREAM_CHUNK, chunk),
|
|
16267
17387
|
() => sendToRendererViews(Channels.AI_STREAM_END),
|
|
16268
17388
|
tabManager,
|
|
16269
|
-
|
|
17389
|
+
runtime2,
|
|
16270
17390
|
history
|
|
16271
17391
|
);
|
|
16272
17392
|
} catch (err) {
|
|
@@ -16349,46 +17469,50 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16349
17469
|
});
|
|
16350
17470
|
electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, () => getRuntimeHealth());
|
|
16351
17471
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (_, key, value) => {
|
|
16352
|
-
|
|
17472
|
+
if (!SETTABLE_KEYS.has(key)) {
|
|
17473
|
+
throw new Error(`Unknown setting key: ${key}`);
|
|
17474
|
+
}
|
|
17475
|
+
const settingsKey = key;
|
|
17476
|
+
const updatedSettings = setSetting(settingsKey, value);
|
|
16353
17477
|
if (key === "approvalMode") {
|
|
16354
|
-
|
|
17478
|
+
runtime2.setApprovalMode(value);
|
|
16355
17479
|
}
|
|
16356
17480
|
if (key === "mcpPort") {
|
|
16357
17481
|
await stopMcpServer();
|
|
16358
|
-
await startMcpServer(tabManager,
|
|
17482
|
+
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
16359
17483
|
}
|
|
16360
17484
|
sendToRendererViews(Channels.SETTINGS_UPDATE, updatedSettings);
|
|
16361
17485
|
return updatedSettings;
|
|
16362
17486
|
});
|
|
16363
|
-
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () =>
|
|
16364
|
-
electron.ipcMain.handle(Channels.AGENT_PAUSE, () =>
|
|
16365
|
-
electron.ipcMain.handle(Channels.AGENT_RESUME, () =>
|
|
17487
|
+
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () => runtime2.getState());
|
|
17488
|
+
electron.ipcMain.handle(Channels.AGENT_PAUSE, () => runtime2.pause());
|
|
17489
|
+
electron.ipcMain.handle(Channels.AGENT_RESUME, () => runtime2.resume());
|
|
16366
17490
|
electron.ipcMain.handle(
|
|
16367
17491
|
Channels.AGENT_SET_APPROVAL_MODE,
|
|
16368
17492
|
(_, mode) => {
|
|
16369
17493
|
setSetting("approvalMode", mode);
|
|
16370
|
-
return
|
|
17494
|
+
return runtime2.setApprovalMode(mode);
|
|
16371
17495
|
}
|
|
16372
17496
|
);
|
|
16373
17497
|
electron.ipcMain.handle(
|
|
16374
17498
|
Channels.AGENT_APPROVAL_RESOLVE,
|
|
16375
|
-
(_, approvalId, approved) =>
|
|
17499
|
+
(_, approvalId, approved) => runtime2.resolveApproval(approvalId, approved)
|
|
16376
17500
|
);
|
|
16377
17501
|
electron.ipcMain.handle(
|
|
16378
17502
|
Channels.AGENT_CHECKPOINT_CREATE,
|
|
16379
|
-
(_, name, note) =>
|
|
17503
|
+
(_, name, note) => runtime2.createCheckpoint(name, note)
|
|
16380
17504
|
);
|
|
16381
17505
|
electron.ipcMain.handle(
|
|
16382
17506
|
Channels.AGENT_CHECKPOINT_RESTORE,
|
|
16383
|
-
(_, checkpointId) =>
|
|
17507
|
+
(_, checkpointId) => runtime2.restoreCheckpoint(checkpointId)
|
|
16384
17508
|
);
|
|
16385
17509
|
electron.ipcMain.handle(
|
|
16386
17510
|
Channels.AGENT_SESSION_CAPTURE,
|
|
16387
|
-
(_, note) =>
|
|
17511
|
+
(_, note) => runtime2.captureSession(note)
|
|
16388
17512
|
);
|
|
16389
17513
|
electron.ipcMain.handle(
|
|
16390
17514
|
Channels.AGENT_SESSION_RESTORE,
|
|
16391
|
-
(_, snapshot) =>
|
|
17515
|
+
(_, snapshot) => runtime2.restoreSession(snapshot)
|
|
16392
17516
|
);
|
|
16393
17517
|
electron.ipcMain.handle(Channels.BOOKMARKS_GET, () => {
|
|
16394
17518
|
return getState();
|
|
@@ -16422,36 +17546,12 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16422
17546
|
return { success: false, message: "No active tab" };
|
|
16423
17547
|
}
|
|
16424
17548
|
const wc = activeTab.view.webContents;
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
if (!url || url === "about:blank") {
|
|
16430
|
-
return { success: false, message: "No page loaded" };
|
|
16431
|
-
}
|
|
16432
|
-
const selectedText = await wc.executeJavaScript(`
|
|
16433
|
-
(function() {
|
|
16434
|
-
var sel = window.getSelection();
|
|
16435
|
-
return sel ? sel.toString().trim() : '';
|
|
16436
|
-
})()
|
|
16437
|
-
`);
|
|
16438
|
-
if (!selectedText) {
|
|
16439
|
-
return { success: false, message: "No text selected" };
|
|
17549
|
+
const result = await captureSelectionHighlight(wc);
|
|
17550
|
+
if (result.success && result.text) {
|
|
17551
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
|
|
17552
|
+
});
|
|
16440
17553
|
}
|
|
16441
|
-
|
|
16442
|
-
const highlight = addHighlight(
|
|
16443
|
-
url,
|
|
16444
|
-
void 0,
|
|
16445
|
-
capped,
|
|
16446
|
-
void 0,
|
|
16447
|
-
"yellow",
|
|
16448
|
-
"user"
|
|
16449
|
-
);
|
|
16450
|
-
await highlightOnPage(wc, null, capped, void 0, void 0, "yellow").catch(
|
|
16451
|
-
() => {
|
|
16452
|
-
}
|
|
16453
|
-
);
|
|
16454
|
-
return { success: true, text: capped, id: highlight.id };
|
|
17554
|
+
return result;
|
|
16455
17555
|
} catch {
|
|
16456
17556
|
return { success: false, message: "Could not capture selection" };
|
|
16457
17557
|
}
|
|
@@ -16467,24 +17567,11 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16467
17567
|
if (wc.isDestroyed()) return;
|
|
16468
17568
|
const tab = tabManager.findTabByWebContentsId(wc.id);
|
|
16469
17569
|
if (!tab || !tab.highlightModeActive) return;
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
void 0,
|
|
16476
|
-
capped,
|
|
16477
|
-
void 0,
|
|
16478
|
-
"yellow",
|
|
16479
|
-
"user"
|
|
16480
|
-
);
|
|
16481
|
-
if (!chromeView.webContents.isDestroyed()) {
|
|
16482
|
-
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, {
|
|
16483
|
-
success: true,
|
|
16484
|
-
text: capped,
|
|
16485
|
-
id: highlight.id
|
|
16486
|
-
});
|
|
16487
|
-
}
|
|
17570
|
+
void persistAndMarkHighlight(wc, text).then((result) => {
|
|
17571
|
+
if (result.success && !chromeView.webContents.isDestroyed()) {
|
|
17572
|
+
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
17573
|
+
}
|
|
17574
|
+
});
|
|
16488
17575
|
} catch {
|
|
16489
17576
|
}
|
|
16490
17577
|
});
|
|
@@ -16494,9 +17581,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16494
17581
|
const wc = tab.view.webContents;
|
|
16495
17582
|
if (wc.isDestroyed()) return 0;
|
|
16496
17583
|
try {
|
|
16497
|
-
return wc
|
|
16498
|
-
`document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text').length`
|
|
16499
|
-
);
|
|
17584
|
+
return getHighlightCount(wc);
|
|
16500
17585
|
} catch {
|
|
16501
17586
|
return 0;
|
|
16502
17587
|
}
|
|
@@ -16507,20 +17592,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16507
17592
|
const wc = tab.view.webContents;
|
|
16508
17593
|
if (wc.isDestroyed()) return false;
|
|
16509
17594
|
try {
|
|
16510
|
-
return wc
|
|
16511
|
-
(function() {
|
|
16512
|
-
var highlights = document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text');
|
|
16513
|
-
if (${index} < 0 || ${index} >= highlights.length) return false;
|
|
16514
|
-
// Remove focus ring from all highlights
|
|
16515
|
-
highlights.forEach(function(h) { h.style.removeProperty('outline'); h.style.removeProperty('outline-offset'); });
|
|
16516
|
-
var target = highlights[${index}];
|
|
16517
|
-
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
16518
|
-
// Add focus ring to current highlight
|
|
16519
|
-
target.style.setProperty('outline', '2px solid rgba(255, 255, 255, 0.9)', 'important');
|
|
16520
|
-
target.style.setProperty('outline-offset', '2px', 'important');
|
|
16521
|
-
return true;
|
|
16522
|
-
})()
|
|
16523
|
-
`);
|
|
17595
|
+
return scrollToHighlight(wc, index);
|
|
16524
17596
|
} catch {
|
|
16525
17597
|
return false;
|
|
16526
17598
|
}
|
|
@@ -16531,32 +17603,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16531
17603
|
const wc = tab.view.webContents;
|
|
16532
17604
|
if (wc.isDestroyed()) return false;
|
|
16533
17605
|
try {
|
|
16534
|
-
return wc
|
|
16535
|
-
(function() {
|
|
16536
|
-
var highlights = document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text');
|
|
16537
|
-
if (${index} < 0 || ${index} >= highlights.length) return false;
|
|
16538
|
-
var el = highlights[${index}];
|
|
16539
|
-
// Remove associated label if any
|
|
16540
|
-
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) {
|
|
16541
|
-
if (b.__vesselAnchor === el) b.remove();
|
|
16542
|
-
});
|
|
16543
|
-
// Unwrap text highlights, remove class from element highlights
|
|
16544
|
-
if (el.tagName === 'MARK' && el.classList.contains('__vessel-highlight-text')) {
|
|
16545
|
-
var parent = el.parentNode;
|
|
16546
|
-
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
16547
|
-
parent.removeChild(el);
|
|
16548
|
-
parent.normalize();
|
|
16549
|
-
} else {
|
|
16550
|
-
el.classList.remove('__vessel-highlight');
|
|
16551
|
-
el.style.removeProperty('background');
|
|
16552
|
-
el.style.removeProperty('outline-color');
|
|
16553
|
-
el.style.removeProperty('box-shadow');
|
|
16554
|
-
el.style.removeProperty('outline');
|
|
16555
|
-
el.style.removeProperty('outline-offset');
|
|
16556
|
-
}
|
|
16557
|
-
return true;
|
|
16558
|
-
})()
|
|
16559
|
-
`);
|
|
17606
|
+
return removeHighlightAtIndex(wc, index);
|
|
16560
17607
|
} catch {
|
|
16561
17608
|
return false;
|
|
16562
17609
|
}
|
|
@@ -16567,39 +17614,65 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16567
17614
|
const wc = tab.view.webContents;
|
|
16568
17615
|
if (wc.isDestroyed()) return false;
|
|
16569
17616
|
try {
|
|
16570
|
-
return wc
|
|
16571
|
-
(function() {
|
|
16572
|
-
// Remove all labels
|
|
16573
|
-
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) { b.remove(); });
|
|
16574
|
-
// Unwrap text highlights
|
|
16575
|
-
document.querySelectorAll('.__vessel-highlight-text').forEach(function(mark) {
|
|
16576
|
-
var parent = mark.parentNode;
|
|
16577
|
-
while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
|
|
16578
|
-
parent.removeChild(mark);
|
|
16579
|
-
parent.normalize();
|
|
16580
|
-
});
|
|
16581
|
-
// Remove element highlights
|
|
16582
|
-
document.querySelectorAll('.__vessel-highlight').forEach(function(el) {
|
|
16583
|
-
el.classList.remove('__vessel-highlight');
|
|
16584
|
-
el.style.removeProperty('background');
|
|
16585
|
-
el.style.removeProperty('outline-color');
|
|
16586
|
-
el.style.removeProperty('box-shadow');
|
|
16587
|
-
el.style.removeProperty('outline');
|
|
16588
|
-
el.style.removeProperty('outline-offset');
|
|
16589
|
-
});
|
|
16590
|
-
return true;
|
|
16591
|
-
})()
|
|
16592
|
-
`);
|
|
17617
|
+
return clearAllHighlightElements(wc);
|
|
16593
17618
|
} catch {
|
|
16594
17619
|
return false;
|
|
16595
17620
|
}
|
|
16596
17621
|
});
|
|
17622
|
+
let findWiredWcId = null;
|
|
17623
|
+
function wireFindEvents(wc) {
|
|
17624
|
+
if (findWiredWcId === wc.id) return;
|
|
17625
|
+
if (findWiredWcId !== null) {
|
|
17626
|
+
const prev = tabManager.findTabByWebContentsId(findWiredWcId);
|
|
17627
|
+
if (prev) prev.view.webContents.removeAllListeners("found-in-page");
|
|
17628
|
+
}
|
|
17629
|
+
findWiredWcId = wc.id;
|
|
17630
|
+
wc.on("found-in-page", (_event, result) => {
|
|
17631
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
17632
|
+
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
17633
|
+
}
|
|
17634
|
+
});
|
|
17635
|
+
}
|
|
17636
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_START, (_, text, options) => {
|
|
17637
|
+
const tab = tabManager.getActiveTab();
|
|
17638
|
+
if (!tab) return null;
|
|
17639
|
+
const wc = tab.view.webContents;
|
|
17640
|
+
if (wc.isDestroyed()) return null;
|
|
17641
|
+
wireFindEvents(wc);
|
|
17642
|
+
return wc.findInPage(text, {
|
|
17643
|
+
forward: options?.forward ?? true,
|
|
17644
|
+
findNext: options?.findNext ?? false
|
|
17645
|
+
});
|
|
17646
|
+
});
|
|
17647
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_NEXT, (_, forward) => {
|
|
17648
|
+
const tab = tabManager.getActiveTab();
|
|
17649
|
+
if (!tab) return null;
|
|
17650
|
+
const wc = tab.view.webContents;
|
|
17651
|
+
if (wc.isDestroyed()) return null;
|
|
17652
|
+
return wc.findInPage("", { forward: forward ?? true, findNext: true });
|
|
17653
|
+
});
|
|
17654
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_STOP, (_, action) => {
|
|
17655
|
+
const tab = tabManager.getActiveTab();
|
|
17656
|
+
if (!tab) return;
|
|
17657
|
+
const wc = tab.view.webContents;
|
|
17658
|
+
if (wc.isDestroyed()) return;
|
|
17659
|
+
wc.stopFindInPage(action ?? "clearSelection");
|
|
17660
|
+
});
|
|
17661
|
+
electron.ipcMain.handle(Channels.HISTORY_GET, () => {
|
|
17662
|
+
return getState$1();
|
|
17663
|
+
});
|
|
17664
|
+
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (_, query) => {
|
|
17665
|
+
return search(query);
|
|
17666
|
+
});
|
|
17667
|
+
electron.ipcMain.handle(Channels.HISTORY_CLEAR, () => {
|
|
17668
|
+
clearAll$1();
|
|
17669
|
+
});
|
|
16597
17670
|
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => {
|
|
16598
17671
|
windowState.uiState.devtoolsPanelOpen = !windowState.uiState.devtoolsPanelOpen;
|
|
16599
17672
|
layoutViews(windowState);
|
|
16600
17673
|
return { open: windowState.uiState.devtoolsPanelOpen };
|
|
16601
17674
|
});
|
|
16602
|
-
electron.ipcMain.handle(
|
|
17675
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE, (_, height) => {
|
|
16603
17676
|
const clamped = Math.max(MIN_DEVTOOLS_PANEL, Math.min(MAX_DEVTOOLS_PANEL, Math.round(height)));
|
|
16604
17677
|
windowState.uiState.devtoolsPanelHeight = clamped;
|
|
16605
17678
|
layoutViews(windowState);
|
|
@@ -16620,6 +17693,7 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
16620
17693
|
});
|
|
16621
17694
|
}
|
|
16622
17695
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
17696
|
+
const PERSIST_DEBOUNCE_MS = 500;
|
|
16623
17697
|
function clone(value) {
|
|
16624
17698
|
return JSON.parse(JSON.stringify(value));
|
|
16625
17699
|
}
|
|
@@ -16697,7 +17771,7 @@ class AgentRuntime {
|
|
|
16697
17771
|
createCheckpoint(name, note) {
|
|
16698
17772
|
const snapshot = this.captureSession(note);
|
|
16699
17773
|
const checkpoint = {
|
|
16700
|
-
id:
|
|
17774
|
+
id: crypto$1.randomUUID(),
|
|
16701
17775
|
name: name?.trim() || `Checkpoint ${this.state.checkpoints.length + 1}`,
|
|
16702
17776
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16703
17777
|
note: note?.trim() || void 0,
|
|
@@ -16751,7 +17825,7 @@ class AgentRuntime {
|
|
|
16751
17825
|
}
|
|
16752
17826
|
}
|
|
16753
17827
|
const entry = {
|
|
16754
|
-
id:
|
|
17828
|
+
id: crypto$1.randomUUID(),
|
|
16755
17829
|
source: input.source,
|
|
16756
17830
|
kind,
|
|
16757
17831
|
title: input.title?.trim() || void 0,
|
|
@@ -16775,7 +17849,7 @@ class AgentRuntime {
|
|
|
16775
17849
|
// --- Speedee Flow State ---
|
|
16776
17850
|
startFlow(goal, steps, startUrl) {
|
|
16777
17851
|
const flow = {
|
|
16778
|
-
id:
|
|
17852
|
+
id: crypto$1.randomUUID(),
|
|
16779
17853
|
goal,
|
|
16780
17854
|
steps: steps.map((label) => ({ label, status: "pending" })),
|
|
16781
17855
|
currentStepIndex: 0,
|
|
@@ -16961,7 +18035,14 @@ ${progress}
|
|
|
16961
18035
|
return sanitizePersistence(null);
|
|
16962
18036
|
}
|
|
16963
18037
|
}
|
|
16964
|
-
|
|
18038
|
+
persistTimer = null;
|
|
18039
|
+
persistDirty = false;
|
|
18040
|
+
persistNow() {
|
|
18041
|
+
this.persistDirty = false;
|
|
18042
|
+
if (this.persistTimer) {
|
|
18043
|
+
clearTimeout(this.persistTimer);
|
|
18044
|
+
this.persistTimer = null;
|
|
18045
|
+
}
|
|
16965
18046
|
const persisted = {
|
|
16966
18047
|
session: this.state.session,
|
|
16967
18048
|
supervisor: {
|
|
@@ -16972,20 +18053,36 @@ ${progress}
|
|
|
16972
18053
|
actions: this.state.actions.slice(-120),
|
|
16973
18054
|
checkpoints: this.state.checkpoints.slice(-20)
|
|
16974
18055
|
};
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
18056
|
+
try {
|
|
18057
|
+
fs$1.mkdirSync(path$1.dirname(getRuntimeStatePath()), { recursive: true });
|
|
18058
|
+
fs$1.writeFileSync(
|
|
18059
|
+
getRuntimeStatePath(),
|
|
18060
|
+
JSON.stringify(persisted, null, 2),
|
|
18061
|
+
"utf-8"
|
|
18062
|
+
);
|
|
18063
|
+
} catch (err) {
|
|
18064
|
+
console.error("[Vessel] Failed to persist runtime state:", err);
|
|
18065
|
+
}
|
|
18066
|
+
}
|
|
18067
|
+
schedulePersist() {
|
|
18068
|
+
this.persistDirty = true;
|
|
18069
|
+
if (this.persistTimer) return;
|
|
18070
|
+
this.persistTimer = setTimeout(() => {
|
|
18071
|
+
this.persistTimer = null;
|
|
18072
|
+
if (this.persistDirty) this.persistNow();
|
|
18073
|
+
}, PERSIST_DEBOUNCE_MS);
|
|
18074
|
+
}
|
|
18075
|
+
/** Flush any pending debounced persist to disk immediately. Call on shutdown. */
|
|
18076
|
+
flushPersist() {
|
|
18077
|
+
if (this.persistDirty) this.persistNow();
|
|
16981
18078
|
}
|
|
16982
18079
|
emit() {
|
|
16983
|
-
this.
|
|
18080
|
+
this.schedulePersist();
|
|
16984
18081
|
this.updateListener?.(this.getState());
|
|
16985
18082
|
}
|
|
16986
18083
|
startAction(input) {
|
|
16987
18084
|
const action = {
|
|
16988
|
-
id:
|
|
18085
|
+
id: crypto$1.randomUUID(),
|
|
16989
18086
|
source: input.source,
|
|
16990
18087
|
name: input.name,
|
|
16991
18088
|
args: clone(input.args),
|
|
@@ -17019,7 +18116,7 @@ ${progress}
|
|
|
17019
18116
|
/** Aggregate metrics for all completed actions in this session. */
|
|
17020
18117
|
getMetrics() {
|
|
17021
18118
|
const completed = this.state.actions.filter((a) => a.status === "completed");
|
|
17022
|
-
const failed = this.state.actions.filter((a) => a.status === "
|
|
18119
|
+
const failed = this.state.actions.filter((a) => a.status === "failed");
|
|
17023
18120
|
const durations = completed.filter((a) => a.durationMs != null).map((a) => a.durationMs);
|
|
17024
18121
|
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
17025
18122
|
const toolBreakdown = {};
|
|
@@ -17057,7 +18154,7 @@ ${progress}
|
|
|
17057
18154
|
}
|
|
17058
18155
|
awaitApproval(action, reason) {
|
|
17059
18156
|
const approval = {
|
|
17060
|
-
id:
|
|
18157
|
+
id: crypto$1.randomUUID(),
|
|
17061
18158
|
actionId: action.id,
|
|
17062
18159
|
source: action.source,
|
|
17063
18160
|
name: action.name,
|
|
@@ -17183,6 +18280,41 @@ function installAdBlocking(tabManager) {
|
|
|
17183
18280
|
callback({ cancel: shouldBlockRequest(details) });
|
|
17184
18281
|
});
|
|
17185
18282
|
}
|
|
18283
|
+
function installDownloadHandler(chromeView) {
|
|
18284
|
+
electron.session.defaultSession.on("will-download", (_event, item) => {
|
|
18285
|
+
const settings2 = loadSettings();
|
|
18286
|
+
const downloadDir = settings2.downloadPath.trim() || electron.app.getPath("downloads");
|
|
18287
|
+
const filename = item.getFilename();
|
|
18288
|
+
const savePath = path.join(downloadDir, filename);
|
|
18289
|
+
item.setSavePath(savePath);
|
|
18290
|
+
const info = {
|
|
18291
|
+
filename,
|
|
18292
|
+
savePath,
|
|
18293
|
+
totalBytes: item.getTotalBytes(),
|
|
18294
|
+
receivedBytes: 0,
|
|
18295
|
+
state: "progressing"
|
|
18296
|
+
};
|
|
18297
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18298
|
+
chromeView.webContents.send(Channels.DOWNLOAD_STARTED, info);
|
|
18299
|
+
}
|
|
18300
|
+
item.on("updated", (_event2, state2) => {
|
|
18301
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
18302
|
+
info.totalBytes = item.getTotalBytes();
|
|
18303
|
+
info.state = state2 === "progressing" ? "progressing" : "interrupted";
|
|
18304
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18305
|
+
chromeView.webContents.send(Channels.DOWNLOAD_PROGRESS, info);
|
|
18306
|
+
}
|
|
18307
|
+
});
|
|
18308
|
+
item.once("done", (_event2, state2) => {
|
|
18309
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
18310
|
+
info.state = state2 === "completed" ? "completed" : "cancelled";
|
|
18311
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
18312
|
+
chromeView.webContents.send(Channels.DOWNLOAD_DONE, info);
|
|
18313
|
+
}
|
|
18314
|
+
});
|
|
18315
|
+
});
|
|
18316
|
+
}
|
|
18317
|
+
let runtime = null;
|
|
17186
18318
|
function rendererUrlFor(view) {
|
|
17187
18319
|
if (!process.env.ELECTRON_RENDERER_URL) return null;
|
|
17188
18320
|
const url = new URL(process.env.ELECTRON_RENDERER_URL);
|
|
@@ -17263,7 +18395,6 @@ async function bootstrap() {
|
|
|
17263
18395
|
if (settings2.clearBookmarksOnLaunch) {
|
|
17264
18396
|
clearAll();
|
|
17265
18397
|
}
|
|
17266
|
-
let runtime = null;
|
|
17267
18398
|
const windowState = createMainWindow((tabs, activeId) => {
|
|
17268
18399
|
windowState.chromeView.webContents.send(
|
|
17269
18400
|
Channels.TAB_STATE_UPDATE,
|
|
@@ -17285,15 +18416,10 @@ async function bootstrap() {
|
|
|
17285
18416
|
const registerHighlightShortcut = () => {
|
|
17286
18417
|
electron.globalShortcut.unregister("CommandOrControl+H");
|
|
17287
18418
|
const success = electron.globalShortcut.register("CommandOrControl+H", () => {
|
|
17288
|
-
console.log("[Vessel] Ctrl+H shortcut triggered");
|
|
17289
18419
|
const activeTab = tabManager.getActiveTab();
|
|
17290
|
-
if (!activeTab)
|
|
17291
|
-
console.log("[Vessel] No active tab");
|
|
17292
|
-
return;
|
|
17293
|
-
}
|
|
18420
|
+
if (!activeTab) return;
|
|
17294
18421
|
tabManager.captureHighlightFromActiveTab();
|
|
17295
18422
|
});
|
|
17296
|
-
console.log("[Vessel] Ctrl+H shortcut registered:", success);
|
|
17297
18423
|
if (!success) {
|
|
17298
18424
|
console.warn("[Vessel] Failed to register Ctrl+H shortcut");
|
|
17299
18425
|
}
|
|
@@ -17319,6 +18445,11 @@ async function bootstrap() {
|
|
|
17319
18445
|
chromeView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|
|
17320
18446
|
sidebarView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|
|
17321
18447
|
});
|
|
18448
|
+
subscribe$1((state2) => {
|
|
18449
|
+
chromeView.webContents.send(Channels.HISTORY_UPDATE, state2);
|
|
18450
|
+
sidebarView.webContents.send(Channels.HISTORY_UPDATE, state2);
|
|
18451
|
+
});
|
|
18452
|
+
installDownloadHandler(chromeView);
|
|
17322
18453
|
const chromeUrl = rendererUrlFor("chrome");
|
|
17323
18454
|
const sidebarUrl = rendererUrlFor("sidebar");
|
|
17324
18455
|
const devtoolsUrl = rendererUrlFor("devtools");
|
|
@@ -17358,6 +18489,7 @@ electron.app.whenReady().then(bootstrap).catch((error) => {
|
|
|
17358
18489
|
});
|
|
17359
18490
|
electron.app.on("window-all-closed", () => {
|
|
17360
18491
|
electron.globalShortcut.unregisterAll();
|
|
18492
|
+
runtime?.flushPersist();
|
|
17361
18493
|
void stopMcpServer().finally(() => {
|
|
17362
18494
|
electron.app.quit();
|
|
17363
18495
|
});
|