@quanta-intellect/vessel-browser 0.1.78 → 0.1.80
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/out/main/index.js
CHANGED
|
@@ -441,7 +441,7 @@ class Tab {
|
|
|
441
441
|
if (!input.control && !input.meta) return;
|
|
442
442
|
if (input.type !== "keyDown") return;
|
|
443
443
|
const key = input.key.toLowerCase();
|
|
444
|
-
const
|
|
444
|
+
const wc = this.view.webContents;
|
|
445
445
|
if (key === "+" || key === "=") {
|
|
446
446
|
this.zoomIn();
|
|
447
447
|
event.preventDefault();
|
|
@@ -457,10 +457,10 @@ class Tab {
|
|
|
457
457
|
event.preventDefault();
|
|
458
458
|
return;
|
|
459
459
|
}
|
|
460
|
-
if (key === "c")
|
|
461
|
-
else if (key === "v")
|
|
462
|
-
else if (key === "x")
|
|
463
|
-
else if (key === "a")
|
|
460
|
+
if (key === "c") wc.copy();
|
|
461
|
+
else if (key === "v") wc.paste();
|
|
462
|
+
else if (key === "x") wc.cut();
|
|
463
|
+
else if (key === "a") wc.selectAll();
|
|
464
464
|
});
|
|
465
465
|
this.setupListeners();
|
|
466
466
|
if (url) {
|
|
@@ -473,8 +473,8 @@ class Tab {
|
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
setupListeners() {
|
|
476
|
-
const
|
|
477
|
-
|
|
476
|
+
const wc = this.view.webContents;
|
|
477
|
+
wc.setWindowOpenHandler(({ url, disposition }) => {
|
|
478
478
|
const error = this.getNavigationBlockReason(url);
|
|
479
479
|
if (error) {
|
|
480
480
|
logger$j.warn(error);
|
|
@@ -493,21 +493,21 @@ class Tab {
|
|
|
493
493
|
event.preventDefault();
|
|
494
494
|
logger$j.warn(`${context}: ${error}`);
|
|
495
495
|
};
|
|
496
|
-
|
|
496
|
+
wc.on("will-navigate", (event, url) => {
|
|
497
497
|
blockNavigation(event, url, "Blocked top-level navigation");
|
|
498
498
|
});
|
|
499
|
-
|
|
499
|
+
wc.on("will-redirect", (event, url) => {
|
|
500
500
|
blockNavigation(event, url, "Blocked redirect");
|
|
501
501
|
});
|
|
502
502
|
const syncNavigationState = () => {
|
|
503
|
-
this._state.title =
|
|
504
|
-
this._state.url =
|
|
503
|
+
this._state.title = wc.getTitle() || this._state.title || "New Tab";
|
|
504
|
+
this._state.url = wc.getURL() || this._state.url;
|
|
505
505
|
this._state.canGoBack = this.urlHistory.length > 0;
|
|
506
506
|
this._state.canGoForward = this.urlForwardStack.length > 0;
|
|
507
507
|
this.onChange();
|
|
508
508
|
};
|
|
509
509
|
const updateSecurityState = () => {
|
|
510
|
-
const url =
|
|
510
|
+
const url = wc.getURL();
|
|
511
511
|
let status = "none";
|
|
512
512
|
if (url.startsWith("https:")) {
|
|
513
513
|
status = "secure";
|
|
@@ -536,40 +536,40 @@ class Tab {
|
|
|
536
536
|
this.lastCommittedUrl = url;
|
|
537
537
|
syncNavigationState();
|
|
538
538
|
};
|
|
539
|
-
|
|
539
|
+
wc.on("did-navigate", (_event, url) => {
|
|
540
540
|
recordNavigation(url);
|
|
541
541
|
updateSecurityState();
|
|
542
542
|
});
|
|
543
|
-
|
|
543
|
+
wc.on("page-title-updated", (_, title) => {
|
|
544
544
|
this._state.title = title;
|
|
545
545
|
this.onChange();
|
|
546
546
|
});
|
|
547
|
-
|
|
547
|
+
wc.on("did-start-loading", () => {
|
|
548
548
|
this._state.isLoading = true;
|
|
549
549
|
this.onChange();
|
|
550
550
|
});
|
|
551
|
-
|
|
551
|
+
wc.on("did-stop-loading", () => {
|
|
552
552
|
this._state.isLoading = false;
|
|
553
553
|
syncNavigationState();
|
|
554
554
|
});
|
|
555
|
-
|
|
555
|
+
wc.on("did-navigate-in-page", (_event, url, isMainFrame) => {
|
|
556
556
|
if (!isMainFrame) return;
|
|
557
557
|
recordNavigation(url);
|
|
558
|
-
this.onPageLoad?.(
|
|
558
|
+
this.onPageLoad?.(wc.getURL(), wc);
|
|
559
559
|
updateSecurityState();
|
|
560
560
|
});
|
|
561
|
-
|
|
561
|
+
wc.on("certificate-error", (event, url, error) => {
|
|
562
562
|
event.preventDefault();
|
|
563
563
|
this._securityState = { status: "error", url, errorMessage: error, canProceed: true };
|
|
564
564
|
this.onChange();
|
|
565
565
|
});
|
|
566
|
-
|
|
566
|
+
wc.on("did-finish-load", () => {
|
|
567
567
|
syncNavigationState();
|
|
568
|
-
this.onPageLoad?.(
|
|
568
|
+
this.onPageLoad?.(wc.getURL(), wc);
|
|
569
569
|
});
|
|
570
|
-
|
|
570
|
+
wc.on("dom-ready", () => {
|
|
571
571
|
syncNavigationState();
|
|
572
|
-
|
|
572
|
+
wc.insertCSS(`
|
|
573
573
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
574
574
|
::-webkit-scrollbar-track { background: transparent; }
|
|
575
575
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
|
|
@@ -577,27 +577,27 @@ class Tab {
|
|
|
577
577
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
578
578
|
`).catch((err) => logger$j.warn("Failed to inject scrollbar CSS:", err));
|
|
579
579
|
});
|
|
580
|
-
|
|
580
|
+
wc.on("page-favicon-updated", (_, favicons) => {
|
|
581
581
|
this._state.favicon = favicons[0] || "";
|
|
582
582
|
this.onChange();
|
|
583
583
|
});
|
|
584
|
-
|
|
584
|
+
wc.on("media-started-playing", () => {
|
|
585
585
|
this._state.isAudible = true;
|
|
586
|
-
this._state.isMuted =
|
|
586
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
587
587
|
this.onChange();
|
|
588
588
|
});
|
|
589
|
-
|
|
589
|
+
wc.on("media-paused", () => {
|
|
590
590
|
setTimeout(() => {
|
|
591
|
-
if (
|
|
592
|
-
this._state.isAudible =
|
|
593
|
-
this._state.isMuted =
|
|
591
|
+
if (wc.isDestroyed()) return;
|
|
592
|
+
this._state.isAudible = wc.isCurrentlyAudible();
|
|
593
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
594
594
|
this.onChange();
|
|
595
595
|
}, 250);
|
|
596
596
|
});
|
|
597
|
-
|
|
597
|
+
wc.on("context-menu", (_event, params) => {
|
|
598
598
|
const x = params.x;
|
|
599
599
|
const y = params.y;
|
|
600
|
-
void
|
|
600
|
+
void wc.executeJavaScript(
|
|
601
601
|
`(function() {
|
|
602
602
|
var el = document.elementFromPoint(${x}, ${y});
|
|
603
603
|
while (el) {
|
|
@@ -609,14 +609,14 @@ class Tab {
|
|
|
609
609
|
return '';
|
|
610
610
|
})()`
|
|
611
611
|
).then((highlightedText) => {
|
|
612
|
-
this.buildContextMenu(
|
|
612
|
+
this.buildContextMenu(wc, params, highlightedText.trim());
|
|
613
613
|
}).catch((err) => {
|
|
614
614
|
logger$j.warn("Failed to inspect highlighted text for context menu:", err);
|
|
615
|
-
this.buildContextMenu(
|
|
615
|
+
this.buildContextMenu(wc, params, "");
|
|
616
616
|
});
|
|
617
617
|
});
|
|
618
618
|
}
|
|
619
|
-
buildContextMenu(
|
|
619
|
+
buildContextMenu(wc, params, highlightedText) {
|
|
620
620
|
const menu = new electron.Menu();
|
|
621
621
|
const colors = [
|
|
622
622
|
"yellow",
|
|
@@ -635,7 +635,7 @@ class Tab {
|
|
|
635
635
|
orange: "Orange"
|
|
636
636
|
};
|
|
637
637
|
if (highlightedText) {
|
|
638
|
-
const url =
|
|
638
|
+
const url = wc.getURL();
|
|
639
639
|
menu.append(
|
|
640
640
|
new electron.MenuItem({
|
|
641
641
|
label: "Remove Highlight",
|
|
@@ -665,7 +665,7 @@ class Tab {
|
|
|
665
665
|
menu.append(
|
|
666
666
|
new electron.MenuItem({
|
|
667
667
|
label: "Highlight Selection",
|
|
668
|
-
click: () => this.onHighlightSelection?.(
|
|
668
|
+
click: () => this.onHighlightSelection?.(wc)
|
|
669
669
|
})
|
|
670
670
|
);
|
|
671
671
|
}
|
|
@@ -792,14 +792,14 @@ class Tab {
|
|
|
792
792
|
this.view.webContents.reload();
|
|
793
793
|
}
|
|
794
794
|
zoomIn() {
|
|
795
|
-
const
|
|
796
|
-
const level =
|
|
797
|
-
|
|
795
|
+
const wc = this.view.webContents;
|
|
796
|
+
const level = wc.getZoomLevel();
|
|
797
|
+
wc.setZoomLevel(level + 0.5);
|
|
798
798
|
}
|
|
799
799
|
zoomOut() {
|
|
800
|
-
const
|
|
801
|
-
const level =
|
|
802
|
-
|
|
800
|
+
const wc = this.view.webContents;
|
|
801
|
+
const level = wc.getZoomLevel();
|
|
802
|
+
wc.setZoomLevel(level - 0.5);
|
|
803
803
|
}
|
|
804
804
|
zoomReset() {
|
|
805
805
|
this.view.webContents.setZoomLevel(0);
|
|
@@ -849,10 +849,10 @@ class Tab {
|
|
|
849
849
|
this.onChange();
|
|
850
850
|
}
|
|
851
851
|
setMuted(muted) {
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
this._state.isMuted =
|
|
855
|
-
this._state.isAudible =
|
|
852
|
+
const wc = this.view.webContents;
|
|
853
|
+
wc.setAudioMuted(muted);
|
|
854
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
855
|
+
this._state.isAudible = wc.isCurrentlyAudible();
|
|
856
856
|
this.onChange();
|
|
857
857
|
}
|
|
858
858
|
toggleMuted() {
|
|
@@ -877,10 +877,10 @@ class Tab {
|
|
|
877
877
|
setHighlightMode(enabled) {
|
|
878
878
|
if (this._highlightModeActive === enabled) return;
|
|
879
879
|
this._highlightModeActive = enabled;
|
|
880
|
-
const
|
|
881
|
-
if (
|
|
880
|
+
const wc = this.view.webContents;
|
|
881
|
+
if (wc.isDestroyed()) return;
|
|
882
882
|
if (enabled) {
|
|
883
|
-
void
|
|
883
|
+
void wc.executeJavaScript(`
|
|
884
884
|
(function() {
|
|
885
885
|
// Ensure highlight CSS is present
|
|
886
886
|
if (!document.getElementById('__vessel-highlight-styles')) {
|
|
@@ -940,7 +940,7 @@ class Tab {
|
|
|
940
940
|
})()
|
|
941
941
|
`).catch((err) => logger$j.warn("Failed to inject highlight listener:", err));
|
|
942
942
|
} else {
|
|
943
|
-
void
|
|
943
|
+
void wc.executeJavaScript(`
|
|
944
944
|
(function() {
|
|
945
945
|
var s = document.getElementById('__vessel-highlight-mode-style');
|
|
946
946
|
if (s) s.remove();
|
|
@@ -1285,9 +1285,9 @@ const VESSEL_HIGHLIGHT_CSS = `
|
|
|
1285
1285
|
opacity: 1;
|
|
1286
1286
|
}
|
|
1287
1287
|
`;
|
|
1288
|
-
async function highlightOnPage(
|
|
1288
|
+
async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, color) {
|
|
1289
1289
|
const c = resolveColor(color);
|
|
1290
|
-
await
|
|
1290
|
+
await wc.executeJavaScript(`
|
|
1291
1291
|
(function() {
|
|
1292
1292
|
if (!document.getElementById('__vessel-highlight-styles')) {
|
|
1293
1293
|
var s = document.createElement('style');
|
|
@@ -1381,7 +1381,7 @@ async function highlightOnPage(wc2, resolvedSelector, text, label, durationMs, c
|
|
|
1381
1381
|
})()
|
|
1382
1382
|
`);
|
|
1383
1383
|
if (resolvedSelector) {
|
|
1384
|
-
return
|
|
1384
|
+
return wc.executeJavaScript(`
|
|
1385
1385
|
(function() {
|
|
1386
1386
|
var el = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
1387
1387
|
if (!el) return 'Element not found';
|
|
@@ -1421,7 +1421,7 @@ async function highlightOnPage(wc2, resolvedSelector, text, label, durationMs, c
|
|
|
1421
1421
|
`);
|
|
1422
1422
|
}
|
|
1423
1423
|
if (text) {
|
|
1424
|
-
return
|
|
1424
|
+
return wc.executeJavaScript(`
|
|
1425
1425
|
(function() {
|
|
1426
1426
|
var searchText = (${JSON.stringify(text)} || '').trim();
|
|
1427
1427
|
var foldedSearchText = searchText.toLowerCase();
|
|
@@ -1529,7 +1529,7 @@ async function highlightOnPage(wc2, resolvedSelector, text, label, durationMs, c
|
|
|
1529
1529
|
}
|
|
1530
1530
|
return "Error: No element or text to highlight";
|
|
1531
1531
|
}
|
|
1532
|
-
async function highlightBatchOnPage(
|
|
1532
|
+
async function highlightBatchOnPage(wc, entries) {
|
|
1533
1533
|
if (entries.length === 0) return;
|
|
1534
1534
|
const serialized = entries.filter((e) => e.selector || e.text).map((e) => ({
|
|
1535
1535
|
selector: e.selector ?? null,
|
|
@@ -1538,7 +1538,7 @@ async function highlightBatchOnPage(wc2, entries) {
|
|
|
1538
1538
|
color: resolveColor(e.color)
|
|
1539
1539
|
}));
|
|
1540
1540
|
if (serialized.length === 0) return;
|
|
1541
|
-
await
|
|
1541
|
+
await wc.executeJavaScript(`
|
|
1542
1542
|
(function() {
|
|
1543
1543
|
if (!document.getElementById('__vessel-highlight-styles')) {
|
|
1544
1544
|
var s = document.createElement('style');
|
|
@@ -1621,18 +1621,18 @@ async function highlightBatchOnPage(wc2, entries) {
|
|
|
1621
1621
|
`);
|
|
1622
1622
|
}
|
|
1623
1623
|
const HIGHLIGHT_SELECTOR = "'.__vessel-highlight, .__vessel-highlight-text'";
|
|
1624
|
-
async function getHighlightCount(
|
|
1625
|
-
if (
|
|
1626
|
-
if (
|
|
1627
|
-
const currentUrl =
|
|
1624
|
+
async function getHighlightCount(wc) {
|
|
1625
|
+
if (wc.isDestroyed()) return 0;
|
|
1626
|
+
if (wc.isLoading()) return 0;
|
|
1627
|
+
const currentUrl = wc.getURL();
|
|
1628
1628
|
if (!currentUrl || currentUrl === "about:blank") return 0;
|
|
1629
|
-
return
|
|
1629
|
+
return wc.executeJavaScript(
|
|
1630
1630
|
`document.querySelectorAll(${HIGHLIGHT_SELECTOR}).length`
|
|
1631
1631
|
);
|
|
1632
1632
|
}
|
|
1633
|
-
async function scrollToHighlight(
|
|
1633
|
+
async function scrollToHighlight(wc, index) {
|
|
1634
1634
|
const safeIndex = Math.floor(Number(index));
|
|
1635
|
-
return
|
|
1635
|
+
return wc.executeJavaScript(`
|
|
1636
1636
|
(function() {
|
|
1637
1637
|
var highlights = document.querySelectorAll(${HIGHLIGHT_SELECTOR});
|
|
1638
1638
|
if (${safeIndex} < 0 || ${safeIndex} >= highlights.length) return false;
|
|
@@ -1645,9 +1645,9 @@ async function scrollToHighlight(wc2, index) {
|
|
|
1645
1645
|
})()
|
|
1646
1646
|
`);
|
|
1647
1647
|
}
|
|
1648
|
-
async function removeHighlightAtIndex(
|
|
1648
|
+
async function removeHighlightAtIndex(wc, index) {
|
|
1649
1649
|
const safeIndex = Math.floor(Number(index));
|
|
1650
|
-
return
|
|
1650
|
+
return wc.executeJavaScript(`
|
|
1651
1651
|
(function() {
|
|
1652
1652
|
var highlights = document.querySelectorAll(${HIGHLIGHT_SELECTOR});
|
|
1653
1653
|
if (${safeIndex} < 0 || ${safeIndex} >= highlights.length) return false;
|
|
@@ -1672,8 +1672,8 @@ async function removeHighlightAtIndex(wc2, index) {
|
|
|
1672
1672
|
})()
|
|
1673
1673
|
`);
|
|
1674
1674
|
}
|
|
1675
|
-
async function clearAllHighlightElements(
|
|
1676
|
-
return
|
|
1675
|
+
async function clearAllHighlightElements(wc) {
|
|
1676
|
+
return wc.executeJavaScript(`
|
|
1677
1677
|
(function() {
|
|
1678
1678
|
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) { b.remove(); });
|
|
1679
1679
|
document.querySelectorAll('.__vessel-highlight-text').forEach(function(mark) {
|
|
@@ -1694,8 +1694,8 @@ async function clearAllHighlightElements(wc2) {
|
|
|
1694
1694
|
})()
|
|
1695
1695
|
`);
|
|
1696
1696
|
}
|
|
1697
|
-
async function clearHighlights(
|
|
1698
|
-
return
|
|
1697
|
+
async function clearHighlights(wc) {
|
|
1698
|
+
return wc.executeJavaScript(`
|
|
1699
1699
|
(function() {
|
|
1700
1700
|
var count = 0;
|
|
1701
1701
|
document.querySelectorAll('.__vessel-highlight').forEach(function(el) {
|
|
@@ -1719,15 +1719,15 @@ async function clearHighlights(wc2) {
|
|
|
1719
1719
|
`);
|
|
1720
1720
|
}
|
|
1721
1721
|
const MAX_HIGHLIGHT_TEXT = 5e3;
|
|
1722
|
-
async function captureSelectionHighlight(
|
|
1723
|
-
if (
|
|
1722
|
+
async function captureSelectionHighlight(wc) {
|
|
1723
|
+
if (wc.isDestroyed()) {
|
|
1724
1724
|
return { success: false, message: "Tab is not available" };
|
|
1725
1725
|
}
|
|
1726
|
-
const url =
|
|
1726
|
+
const url = wc.getURL();
|
|
1727
1727
|
if (!url || url === "about:blank") {
|
|
1728
1728
|
return { success: false, message: "No page loaded" };
|
|
1729
1729
|
}
|
|
1730
|
-
const selectedText = await
|
|
1730
|
+
const selectedText = await wc.executeJavaScript(`
|
|
1731
1731
|
(function() {
|
|
1732
1732
|
var sel = window.getSelection();
|
|
1733
1733
|
return sel ? sel.toString().trim() : '';
|
|
@@ -1738,11 +1738,11 @@ async function captureSelectionHighlight(wc2) {
|
|
|
1738
1738
|
}
|
|
1739
1739
|
return persistHighlight(url, selectedText);
|
|
1740
1740
|
}
|
|
1741
|
-
async function persistAndMarkHighlight(
|
|
1742
|
-
if (
|
|
1741
|
+
async function persistAndMarkHighlight(wc, text) {
|
|
1742
|
+
if (wc.isDestroyed()) {
|
|
1743
1743
|
return { success: false, message: "Tab is not available" };
|
|
1744
1744
|
}
|
|
1745
|
-
const url =
|
|
1745
|
+
const url = wc.getURL();
|
|
1746
1746
|
if (!url || url === "about:blank") {
|
|
1747
1747
|
return { success: false, message: "No page loaded" };
|
|
1748
1748
|
}
|
|
@@ -1845,6 +1845,35 @@ function clearAll$1() {
|
|
|
1845
1845
|
save$1();
|
|
1846
1846
|
emit$1();
|
|
1847
1847
|
}
|
|
1848
|
+
function clearByTimeRange(timeRange) {
|
|
1849
|
+
load$3();
|
|
1850
|
+
if (timeRange === "all") {
|
|
1851
|
+
clearAll$1();
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
const now = Date.now();
|
|
1855
|
+
const cutoff = new Date(now - timeRangeToMs(timeRange));
|
|
1856
|
+
state$3.entries = state$3.entries.filter((entry) => {
|
|
1857
|
+
const visitedAt = new Date(entry.visitedAt).getTime();
|
|
1858
|
+
return Number.isNaN(visitedAt) || visitedAt < cutoff.getTime();
|
|
1859
|
+
});
|
|
1860
|
+
save$1();
|
|
1861
|
+
emit$1();
|
|
1862
|
+
}
|
|
1863
|
+
function timeRangeToMs(range) {
|
|
1864
|
+
switch (range) {
|
|
1865
|
+
case "hour":
|
|
1866
|
+
return 60 * 60 * 1e3;
|
|
1867
|
+
case "day":
|
|
1868
|
+
return 24 * 60 * 60 * 1e3;
|
|
1869
|
+
case "week":
|
|
1870
|
+
return 7 * 24 * 60 * 60 * 1e3;
|
|
1871
|
+
case "month":
|
|
1872
|
+
return 30 * 24 * 60 * 60 * 1e3;
|
|
1873
|
+
case "all":
|
|
1874
|
+
return Infinity;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1848
1877
|
function exportHistoryHtml() {
|
|
1849
1878
|
const current = getState$1();
|
|
1850
1879
|
const lines = [
|
|
@@ -1956,9 +1985,9 @@ const MAX_NETWORK_ENTRIES = 200;
|
|
|
1956
1985
|
const MAX_ERROR_ENTRIES = 200;
|
|
1957
1986
|
const MAX_PENDING_REQUESTS = 500;
|
|
1958
1987
|
class DevToolsSession {
|
|
1959
|
-
constructor(tabId,
|
|
1988
|
+
constructor(tabId, wc) {
|
|
1960
1989
|
this.tabId = tabId;
|
|
1961
|
-
this.wc =
|
|
1990
|
+
this.wc = wc;
|
|
1962
1991
|
}
|
|
1963
1992
|
attached = false;
|
|
1964
1993
|
attachingPromise = null;
|
|
@@ -2698,14 +2727,14 @@ class TabManager {
|
|
|
2698
2727
|
onOpenUrl: ({ url: requestedUrl, background: background2, adBlockingEnabled }) => {
|
|
2699
2728
|
this.createTab(requestedUrl, { background: background2, adBlockingEnabled });
|
|
2700
2729
|
},
|
|
2701
|
-
onPageLoad: (pageUrl,
|
|
2702
|
-
this.reapplyHighlights(pageUrl,
|
|
2730
|
+
onPageLoad: (pageUrl, wc) => {
|
|
2731
|
+
this.reapplyHighlights(pageUrl, wc);
|
|
2703
2732
|
if (!this.isPrivate) {
|
|
2704
|
-
addEntry$1(pageUrl,
|
|
2733
|
+
addEntry$1(pageUrl, wc.getTitle());
|
|
2705
2734
|
}
|
|
2706
|
-
this.pageLoadCallback?.(pageUrl,
|
|
2735
|
+
this.pageLoadCallback?.(pageUrl, wc);
|
|
2707
2736
|
},
|
|
2708
|
-
onHighlightSelection: (
|
|
2737
|
+
onHighlightSelection: (wc) => this.captureHighlightFromPage(wc),
|
|
2709
2738
|
onHighlightRemove: (url2, text) => this.removeHighlightByText(url2, text),
|
|
2710
2739
|
onHighlightRecolor: (url2, text, color) => this.recolorHighlightByText(url2, text, color),
|
|
2711
2740
|
onSavePage: () => {
|
|
@@ -3032,8 +3061,8 @@ class TabManager {
|
|
|
3032
3061
|
this.broadcastState();
|
|
3033
3062
|
}
|
|
3034
3063
|
lastReapply = /* @__PURE__ */ new Map();
|
|
3035
|
-
reapplyHighlights(url,
|
|
3036
|
-
const wcId =
|
|
3064
|
+
reapplyHighlights(url, wc) {
|
|
3065
|
+
const wcId = wc.id;
|
|
3037
3066
|
const now = Date.now();
|
|
3038
3067
|
const last = this.lastReapply.get(wcId);
|
|
3039
3068
|
const normalized = normalizeUrl$1(url);
|
|
@@ -3047,7 +3076,7 @@ class TabManager {
|
|
|
3047
3076
|
color: h.color
|
|
3048
3077
|
}));
|
|
3049
3078
|
if (entries.length > 0) {
|
|
3050
|
-
void highlightBatchOnPage(
|
|
3079
|
+
void highlightBatchOnPage(wc, entries).catch(
|
|
3051
3080
|
(err) => logger$h.warn("Failed to batch highlight:", err)
|
|
3052
3081
|
);
|
|
3053
3082
|
}
|
|
@@ -3060,16 +3089,16 @@ class TabManager {
|
|
|
3060
3089
|
if (!activeTab) {
|
|
3061
3090
|
return { success: false, message: "No active tab" };
|
|
3062
3091
|
}
|
|
3063
|
-
const
|
|
3064
|
-
this.captureHighlightFromPage(
|
|
3092
|
+
const wc = activeTab.view.webContents;
|
|
3093
|
+
this.captureHighlightFromPage(wc);
|
|
3065
3094
|
return null;
|
|
3066
3095
|
}
|
|
3067
|
-
captureHighlightFromPage(
|
|
3096
|
+
captureHighlightFromPage(wc) {
|
|
3068
3097
|
void (async () => {
|
|
3069
3098
|
try {
|
|
3070
|
-
const result = await captureSelectionHighlight(
|
|
3099
|
+
const result = await captureSelectionHighlight(wc);
|
|
3071
3100
|
if (result.success && result.text) {
|
|
3072
|
-
await highlightOnPage(
|
|
3101
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
3073
3102
|
(err) => logger$h.warn("Failed to capture highlight:", err)
|
|
3074
3103
|
);
|
|
3075
3104
|
}
|
|
@@ -3092,12 +3121,12 @@ class TabManager {
|
|
|
3092
3121
|
for (const id of this.order) {
|
|
3093
3122
|
const tab = this.tabs.get(id);
|
|
3094
3123
|
if (!tab) continue;
|
|
3095
|
-
const
|
|
3096
|
-
if (
|
|
3124
|
+
const wc = tab.view.webContents;
|
|
3125
|
+
if (wc.isDestroyed()) continue;
|
|
3097
3126
|
try {
|
|
3098
|
-
const tabUrl = normalizeUrl$1(
|
|
3127
|
+
const tabUrl = normalizeUrl$1(wc.getURL());
|
|
3099
3128
|
if (tabUrl === normalized) {
|
|
3100
|
-
void this.removeHighlightMarksForText(
|
|
3129
|
+
void this.removeHighlightMarksForText(wc, text);
|
|
3101
3130
|
}
|
|
3102
3131
|
} catch (err) {
|
|
3103
3132
|
logger$h.warn("Failed to remove highlight from matching tab:", err);
|
|
@@ -3117,14 +3146,14 @@ class TabManager {
|
|
|
3117
3146
|
for (const id of this.order) {
|
|
3118
3147
|
const tab = this.tabs.get(id);
|
|
3119
3148
|
if (!tab) continue;
|
|
3120
|
-
const
|
|
3121
|
-
if (
|
|
3149
|
+
const wc = tab.view.webContents;
|
|
3150
|
+
if (wc.isDestroyed()) continue;
|
|
3122
3151
|
try {
|
|
3123
|
-
const tabUrl = normalizeUrl$1(
|
|
3152
|
+
const tabUrl = normalizeUrl$1(wc.getURL());
|
|
3124
3153
|
if (tabUrl === normalized) {
|
|
3125
|
-
void this.removeHighlightMarksForText(
|
|
3154
|
+
void this.removeHighlightMarksForText(wc, text).then(() => {
|
|
3126
3155
|
void highlightOnPage(
|
|
3127
|
-
|
|
3156
|
+
wc,
|
|
3128
3157
|
null,
|
|
3129
3158
|
text,
|
|
3130
3159
|
void 0,
|
|
@@ -3162,8 +3191,8 @@ class TabManager {
|
|
|
3162
3191
|
}
|
|
3163
3192
|
this.tabGroups.delete(groupId);
|
|
3164
3193
|
}
|
|
3165
|
-
async removeHighlightMarksForText(
|
|
3166
|
-
await
|
|
3194
|
+
async removeHighlightMarksForText(wc, text) {
|
|
3195
|
+
await wc.executeJavaScript(
|
|
3167
3196
|
`(function() {
|
|
3168
3197
|
var marks = document.querySelectorAll('mark.__vessel-highlight-text[data-vessel-highlight]');
|
|
3169
3198
|
marks.forEach(function(m) {
|
|
@@ -4326,7 +4355,7 @@ function okResult(value) {
|
|
|
4326
4355
|
...value ?? {}
|
|
4327
4356
|
};
|
|
4328
4357
|
}
|
|
4329
|
-
function errorResult
|
|
4358
|
+
function errorResult(error, value) {
|
|
4330
4359
|
return {
|
|
4331
4360
|
ok: false,
|
|
4332
4361
|
error,
|
|
@@ -4425,16 +4454,16 @@ async function getCheckoutUrl(email) {
|
|
|
4425
4454
|
});
|
|
4426
4455
|
if (!res.ok) {
|
|
4427
4456
|
const body = await res.text();
|
|
4428
|
-
return errorResult
|
|
4457
|
+
return errorResult(body || `HTTP ${res.status}`);
|
|
4429
4458
|
}
|
|
4430
4459
|
const { url } = await res.json();
|
|
4431
4460
|
return okResult({ url });
|
|
4432
4461
|
} catch (err) {
|
|
4433
|
-
return errorResult
|
|
4462
|
+
return errorResult(getErrorMessage(err, "Failed to create checkout"));
|
|
4434
4463
|
}
|
|
4435
4464
|
}
|
|
4436
4465
|
async function getPortalUrl() {
|
|
4437
|
-
return errorResult
|
|
4466
|
+
return errorResult(
|
|
4438
4467
|
"Billing portal access is temporarily disabled until authenticated customer access is implemented."
|
|
4439
4468
|
);
|
|
4440
4469
|
}
|
|
@@ -4478,7 +4507,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4478
4507
|
async function requestActivationCode(email) {
|
|
4479
4508
|
const normalizedEmail = email.trim().toLowerCase();
|
|
4480
4509
|
if (!normalizedEmail) {
|
|
4481
|
-
return errorResult
|
|
4510
|
+
return errorResult("Email is required");
|
|
4482
4511
|
}
|
|
4483
4512
|
try {
|
|
4484
4513
|
const res = await fetch(`${VERIFICATION_API}/activate/start`, {
|
|
@@ -4488,27 +4517,27 @@ async function requestActivationCode(email) {
|
|
|
4488
4517
|
});
|
|
4489
4518
|
const data = await res.json().catch(() => ({}));
|
|
4490
4519
|
if (!res.ok || !data.challengeToken) {
|
|
4491
|
-
return errorResult
|
|
4520
|
+
return errorResult(data.error || `HTTP ${res.status}`);
|
|
4492
4521
|
}
|
|
4493
4522
|
return okResult({
|
|
4494
4523
|
email: normalizedEmail,
|
|
4495
4524
|
challengeToken: data.challengeToken
|
|
4496
4525
|
});
|
|
4497
4526
|
} catch (err) {
|
|
4498
|
-
return errorResult
|
|
4527
|
+
return errorResult(getErrorMessage(err, "Failed to send code"));
|
|
4499
4528
|
}
|
|
4500
4529
|
}
|
|
4501
4530
|
async function verifyActivationCode(email, code, challengeToken) {
|
|
4502
4531
|
const normalizedEmail = email.trim().toLowerCase();
|
|
4503
4532
|
const trimmedCode = code.trim();
|
|
4504
4533
|
if (!normalizedEmail) {
|
|
4505
|
-
return errorResult
|
|
4534
|
+
return errorResult("Email is required", { state: getPremiumState() });
|
|
4506
4535
|
}
|
|
4507
4536
|
if (!trimmedCode) {
|
|
4508
|
-
return errorResult
|
|
4537
|
+
return errorResult("Code is required", { state: getPremiumState() });
|
|
4509
4538
|
}
|
|
4510
4539
|
if (!challengeToken.trim()) {
|
|
4511
|
-
return errorResult
|
|
4540
|
+
return errorResult("Request a new activation code and try again.", {
|
|
4512
4541
|
state: getPremiumState()
|
|
4513
4542
|
});
|
|
4514
4543
|
}
|
|
@@ -4524,7 +4553,7 @@ async function verifyActivationCode(email, code, challengeToken) {
|
|
|
4524
4553
|
});
|
|
4525
4554
|
const data = await res.json().catch(() => ({}));
|
|
4526
4555
|
if (!res.ok) {
|
|
4527
|
-
return errorResult
|
|
4556
|
+
return errorResult(data.error || `HTTP ${res.status}`, {
|
|
4528
4557
|
state: getPremiumState()
|
|
4529
4558
|
});
|
|
4530
4559
|
}
|
|
@@ -4537,9 +4566,9 @@ async function verifyActivationCode(email, code, challengeToken) {
|
|
|
4537
4566
|
expiresAt: data.expiresAt || ""
|
|
4538
4567
|
};
|
|
4539
4568
|
setSetting("premium", updated);
|
|
4540
|
-
return isPremiumActiveState(updated) ? okResult({ state: updated }) : errorResult
|
|
4569
|
+
return isPremiumActiveState(updated) ? okResult({ state: updated }) : errorResult("Subscription is not active.", { state: updated });
|
|
4541
4570
|
} catch (err) {
|
|
4542
|
-
return errorResult
|
|
4571
|
+
return errorResult(getErrorMessage(err, "Failed to verify code"), {
|
|
4543
4572
|
state: getPremiumState()
|
|
4544
4573
|
});
|
|
4545
4574
|
}
|
|
@@ -6012,11 +6041,11 @@ function cleanupTimersForWcId(wcId) {
|
|
|
6012
6041
|
lastMutationSnapshotAt.delete(wcId);
|
|
6013
6042
|
lastMutationActivityAt.delete(wcId);
|
|
6014
6043
|
}
|
|
6015
|
-
function attachDestroyCleanup(
|
|
6016
|
-
if (destroyListenerAttached.has(
|
|
6017
|
-
destroyListenerAttached.add(
|
|
6018
|
-
|
|
6019
|
-
cleanupTimersForWcId(
|
|
6044
|
+
function attachDestroyCleanup(wc) {
|
|
6045
|
+
if (destroyListenerAttached.has(wc)) return;
|
|
6046
|
+
destroyListenerAttached.add(wc);
|
|
6047
|
+
wc.once("destroyed", () => {
|
|
6048
|
+
cleanupTimersForWcId(wc.id);
|
|
6020
6049
|
});
|
|
6021
6050
|
}
|
|
6022
6051
|
const MAX_PERSISTED_DIFF_BURSTS = 50;
|
|
@@ -6121,12 +6150,12 @@ function enrichWithBurstHistory(key, diff) {
|
|
|
6121
6150
|
recentBursts: recentBursts.slice().reverse()
|
|
6122
6151
|
};
|
|
6123
6152
|
}
|
|
6124
|
-
async function capturePageSnapshot(url,
|
|
6153
|
+
async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
6125
6154
|
try {
|
|
6126
6155
|
if (!shouldTrackSnapshotUrl(url)) return;
|
|
6127
6156
|
const key = normalizeUrl(url);
|
|
6128
6157
|
const oldSnap = getSnapshot(key);
|
|
6129
|
-
const content = await extractContent(
|
|
6158
|
+
const content = await extractContent(wc);
|
|
6130
6159
|
const textContent = content.content || "";
|
|
6131
6160
|
const title = content.title || "";
|
|
6132
6161
|
const headings = content.headings || [];
|
|
@@ -6154,59 +6183,59 @@ function computeNextSnapshotDueAt(wcId, now, delayMs) {
|
|
|
6154
6183
|
const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
|
|
6155
6184
|
return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
|
|
6156
6185
|
}
|
|
6157
|
-
function scheduleTimerAt(
|
|
6158
|
-
attachDestroyCleanup(
|
|
6159
|
-
const wcId =
|
|
6186
|
+
function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
6187
|
+
attachDestroyCleanup(wc);
|
|
6188
|
+
const wcId = wc.id;
|
|
6160
6189
|
const existing = pendingPageSnapshotTimers.get(wcId);
|
|
6161
6190
|
if (existing) clearTimeout(existing);
|
|
6162
6191
|
const timer = setTimeout(() => {
|
|
6163
6192
|
cleanupTimersForWcId(wcId);
|
|
6164
|
-
if (
|
|
6193
|
+
if (wc.isDestroyed()) return;
|
|
6165
6194
|
lastMutationSnapshotAt.set(wcId, Date.now());
|
|
6166
|
-
void capturePageSnapshot(
|
|
6195
|
+
void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
|
|
6167
6196
|
}, Math.max(0, dueAt - Date.now()));
|
|
6168
6197
|
pendingPageSnapshotTimers.set(wcId, timer);
|
|
6169
6198
|
pendingPageSnapshotDueAt.set(wcId, dueAt);
|
|
6170
6199
|
}
|
|
6171
|
-
function notePageMutationActivity(
|
|
6172
|
-
if (
|
|
6173
|
-
const wcId =
|
|
6200
|
+
function notePageMutationActivity(wc, sendToRendererViews) {
|
|
6201
|
+
if (wc.isDestroyed()) return;
|
|
6202
|
+
const wcId = wc.id;
|
|
6174
6203
|
const now = Date.now();
|
|
6175
6204
|
lastMutationActivityAt.set(wcId, now);
|
|
6176
6205
|
const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
|
|
6177
6206
|
if (existingDueAt == null) return;
|
|
6178
6207
|
const nextDueAt = computeNextSnapshotDueAt(wcId, now, 0);
|
|
6179
6208
|
if (nextDueAt <= existingDueAt) return;
|
|
6180
|
-
scheduleTimerAt(
|
|
6209
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6181
6210
|
}
|
|
6182
|
-
function schedulePageSnapshotCapture(
|
|
6183
|
-
if (
|
|
6184
|
-
const wcId =
|
|
6211
|
+
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
|
|
6212
|
+
if (wc.isDestroyed()) return;
|
|
6213
|
+
const wcId = wc.id;
|
|
6185
6214
|
const now = Date.now();
|
|
6186
6215
|
const nextDueAt = computeNextSnapshotDueAt(wcId, now, delayMs);
|
|
6187
6216
|
const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
|
|
6188
6217
|
if (existingDueAt != null && existingDueAt >= nextDueAt) {
|
|
6189
6218
|
return;
|
|
6190
6219
|
}
|
|
6191
|
-
scheduleTimerAt(
|
|
6220
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6192
6221
|
}
|
|
6193
6222
|
function enableClipboardShortcuts(view) {
|
|
6194
6223
|
view.webContents.on("before-input-event", (event, input) => {
|
|
6195
6224
|
if (!input.control && !input.meta) return;
|
|
6196
6225
|
const key = input.key.toLowerCase();
|
|
6197
|
-
const
|
|
6226
|
+
const wc = view.webContents;
|
|
6198
6227
|
if (input.type === "keyDown") {
|
|
6199
6228
|
if (key === "c") {
|
|
6200
|
-
|
|
6229
|
+
wc.copy();
|
|
6201
6230
|
event.preventDefault();
|
|
6202
6231
|
} else if (key === "v") {
|
|
6203
|
-
|
|
6232
|
+
wc.paste();
|
|
6204
6233
|
event.preventDefault();
|
|
6205
6234
|
} else if (key === "x") {
|
|
6206
|
-
|
|
6235
|
+
wc.cut();
|
|
6207
6236
|
event.preventDefault();
|
|
6208
6237
|
} else if (key === "a") {
|
|
6209
|
-
|
|
6238
|
+
wc.selectAll();
|
|
6210
6239
|
event.preventDefault();
|
|
6211
6240
|
}
|
|
6212
6241
|
}
|
|
@@ -6403,8 +6432,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
6403
6432
|
chromeView.webContents.send(channel, ...args);
|
|
6404
6433
|
sidebarView.webContents.send(channel, ...args);
|
|
6405
6434
|
};
|
|
6406
|
-
tabManager.onPageLoad((url,
|
|
6407
|
-
void capturePageSnapshot(url,
|
|
6435
|
+
tabManager.onPageLoad((url, wc) => {
|
|
6436
|
+
void capturePageSnapshot(url, wc, sendToRendererViews);
|
|
6408
6437
|
});
|
|
6409
6438
|
const state2 = {
|
|
6410
6439
|
mainWindow,
|
|
@@ -11413,7 +11442,7 @@ function pruneToolsForContext(tools, pageType, query = "", options = {}) {
|
|
|
11413
11442
|
function trimText(value) {
|
|
11414
11443
|
return typeof value === "string" ? value.trim() : "";
|
|
11415
11444
|
}
|
|
11416
|
-
async function resolveBookmarkSourceDraft(
|
|
11445
|
+
async function resolveBookmarkSourceDraft(wc, options) {
|
|
11417
11446
|
const explicitUrl = trimText(options.explicitUrl);
|
|
11418
11447
|
const explicitTitle = trimText(options.explicitTitle);
|
|
11419
11448
|
if (explicitUrl) {
|
|
@@ -11423,8 +11452,8 @@ async function resolveBookmarkSourceDraft(wc2, options) {
|
|
|
11423
11452
|
source: "explicit"
|
|
11424
11453
|
};
|
|
11425
11454
|
}
|
|
11426
|
-
if (
|
|
11427
|
-
const result = await
|
|
11455
|
+
if (wc && options.resolvedSelector) {
|
|
11456
|
+
const result = await wc.executeJavaScript(`
|
|
11428
11457
|
(function() {
|
|
11429
11458
|
const el = document.querySelector(${JSON.stringify(options.resolvedSelector)});
|
|
11430
11459
|
if (!el) return { error: "Element not found" };
|
|
@@ -11475,11 +11504,11 @@ async function resolveBookmarkSourceDraft(wc2, options) {
|
|
|
11475
11504
|
source: "link"
|
|
11476
11505
|
};
|
|
11477
11506
|
}
|
|
11478
|
-
const currentUrl =
|
|
11507
|
+
const currentUrl = wc?.getURL().trim() || "";
|
|
11479
11508
|
if (!currentUrl) {
|
|
11480
11509
|
return { error: "No URL provided and no active page to save" };
|
|
11481
11510
|
}
|
|
11482
|
-
const currentTitle =
|
|
11511
|
+
const currentTitle = wc?.getTitle().trim() || currentUrl;
|
|
11483
11512
|
return {
|
|
11484
11513
|
url: currentUrl,
|
|
11485
11514
|
title: explicitTitle || currentTitle || currentUrl,
|
|
@@ -12134,15 +12163,15 @@ function importBookmarksFromJson(content) {
|
|
|
12134
12163
|
function normalizeText(text) {
|
|
12135
12164
|
return text?.trim() ?? "";
|
|
12136
12165
|
}
|
|
12137
|
-
async function captureLiveHighlightSnapshot(
|
|
12138
|
-
if (
|
|
12166
|
+
async function captureLiveHighlightSnapshot(wc, savedHighlights = []) {
|
|
12167
|
+
if (wc.isDestroyed()) {
|
|
12139
12168
|
return { pageHighlights: [] };
|
|
12140
12169
|
}
|
|
12141
12170
|
const savedTexts = new Set(
|
|
12142
12171
|
savedHighlights.map((highlight) => normalizeText(highlight.text)).filter(Boolean)
|
|
12143
12172
|
);
|
|
12144
12173
|
try {
|
|
12145
|
-
const snapshot = await
|
|
12174
|
+
const snapshot = await wc.executeJavaScript(`(() => {
|
|
12146
12175
|
const selection = window.getSelection?.()?.toString().trim() || "";
|
|
12147
12176
|
const pageHighlights = Array.from(
|
|
12148
12177
|
document.querySelectorAll("mark.__vessel-highlight-text[data-vessel-highlight]")
|
|
@@ -12203,13 +12232,13 @@ ${lines.join("\n")}`);
|
|
|
12203
12232
|
function sleep(ms) {
|
|
12204
12233
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12205
12234
|
}
|
|
12206
|
-
function waitForLoad(
|
|
12235
|
+
function waitForLoad(wc, timeout = 5e3) {
|
|
12207
12236
|
return new Promise((resolve) => {
|
|
12208
12237
|
let finished = false;
|
|
12209
12238
|
const cleanup = () => {
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12239
|
+
wc.removeListener("did-finish-load", onLoadEvent);
|
|
12240
|
+
wc.removeListener("did-stop-loading", onLoadEvent);
|
|
12241
|
+
wc.removeListener("did-fail-load", onLoadEvent);
|
|
12213
12242
|
};
|
|
12214
12243
|
const finish = () => {
|
|
12215
12244
|
if (finished) return;
|
|
@@ -12219,50 +12248,50 @@ function waitForLoad(wc2, timeout = 5e3) {
|
|
|
12219
12248
|
resolve();
|
|
12220
12249
|
};
|
|
12221
12250
|
const onLoadEvent = () => {
|
|
12222
|
-
const loading =
|
|
12251
|
+
const loading = wc.isLoading();
|
|
12223
12252
|
if (!loading) {
|
|
12224
12253
|
finish();
|
|
12225
12254
|
}
|
|
12226
12255
|
};
|
|
12227
12256
|
const timer = setTimeout(() => finish(), timeout);
|
|
12228
|
-
if (!
|
|
12257
|
+
if (!wc.isLoading()) {
|
|
12229
12258
|
finish();
|
|
12230
12259
|
return;
|
|
12231
12260
|
}
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12261
|
+
wc.once("did-finish-load", onLoadEvent);
|
|
12262
|
+
wc.once("did-stop-loading", onLoadEvent);
|
|
12263
|
+
wc.once("did-fail-load", onLoadEvent);
|
|
12235
12264
|
});
|
|
12236
12265
|
}
|
|
12237
12266
|
const QUIET_NAVIGATION_WINDOW_MS = 1200;
|
|
12238
|
-
function waitForPotentialNavigation$1(
|
|
12267
|
+
function waitForPotentialNavigation$1(wc, beforeUrl, timeout = 4e3, quietWindowMs) {
|
|
12239
12268
|
return new Promise((resolve) => {
|
|
12240
12269
|
let done = false;
|
|
12241
12270
|
let waitingForLoad = false;
|
|
12242
|
-
const beforeTitle =
|
|
12271
|
+
const beforeTitle = wc.getTitle();
|
|
12243
12272
|
const finish = () => {
|
|
12244
12273
|
if (done) return;
|
|
12245
12274
|
done = true;
|
|
12246
12275
|
clearTimeout(timer);
|
|
12247
12276
|
clearInterval(poller);
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12277
|
+
wc.removeListener("did-start-loading", onStart);
|
|
12278
|
+
wc.removeListener("did-navigate", onNavigate);
|
|
12279
|
+
wc.removeListener("did-navigate-in-page", onNavigateInPage);
|
|
12280
|
+
wc.removeListener("did-stop-loading", onNativeChange);
|
|
12281
|
+
wc.removeListener("page-title-updated", onNativeChange);
|
|
12253
12282
|
resolve();
|
|
12254
12283
|
};
|
|
12255
12284
|
const finishAfterLoad = () => {
|
|
12256
12285
|
if (waitingForLoad) return;
|
|
12257
12286
|
waitingForLoad = true;
|
|
12258
|
-
void waitForLoad(
|
|
12287
|
+
void waitForLoad(wc, timeout).then(finish);
|
|
12259
12288
|
};
|
|
12260
12289
|
const onNativeChange = () => {
|
|
12261
|
-
if (
|
|
12290
|
+
if (wc.isLoading()) {
|
|
12262
12291
|
finishAfterLoad();
|
|
12263
12292
|
return;
|
|
12264
12293
|
}
|
|
12265
|
-
if (
|
|
12294
|
+
if (wc.getURL() !== beforeUrl || wc.getTitle() !== beforeTitle) {
|
|
12266
12295
|
finish();
|
|
12267
12296
|
}
|
|
12268
12297
|
};
|
|
@@ -12278,15 +12307,15 @@ function waitForPotentialNavigation$1(wc2, beforeUrl, timeout = 4e3, quietWindow
|
|
|
12278
12307
|
quietWindowMs != null ? Math.min(timeout, quietWindowMs) : timeout
|
|
12279
12308
|
);
|
|
12280
12309
|
const poller = setInterval(onNativeChange, 100);
|
|
12281
|
-
if (
|
|
12310
|
+
if (wc.getURL() !== beforeUrl || wc.getTitle() !== beforeTitle || wc.isLoading()) {
|
|
12282
12311
|
onNativeChange();
|
|
12283
12312
|
return;
|
|
12284
12313
|
}
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12314
|
+
wc.once("did-start-loading", onStart);
|
|
12315
|
+
wc.once("did-navigate", onNavigate);
|
|
12316
|
+
wc.once("did-navigate-in-page", onNavigateInPage);
|
|
12317
|
+
wc.once("did-stop-loading", onNativeChange);
|
|
12318
|
+
wc.once("page-title-updated", onNativeChange);
|
|
12290
12319
|
});
|
|
12291
12320
|
}
|
|
12292
12321
|
function selectorFromElement(element, index) {
|
|
@@ -12309,10 +12338,10 @@ function findSelectorByIndex(page, index) {
|
|
|
12309
12338
|
}
|
|
12310
12339
|
return null;
|
|
12311
12340
|
}
|
|
12312
|
-
async function resolveSelector(
|
|
12341
|
+
async function resolveSelector(wc, index, selector) {
|
|
12313
12342
|
if (selector) return selector;
|
|
12314
12343
|
if (index == null) return null;
|
|
12315
|
-
const authoritativeSelector = await
|
|
12344
|
+
const authoritativeSelector = await wc.executeJavaScript(
|
|
12316
12345
|
`
|
|
12317
12346
|
(function() {
|
|
12318
12347
|
return window.__vessel?.getElementSelector
|
|
@@ -12323,19 +12352,19 @@ async function resolveSelector(wc2, index, selector) {
|
|
|
12323
12352
|
);
|
|
12324
12353
|
if (typeof authoritativeSelector === "string" && authoritativeSelector) {
|
|
12325
12354
|
if (authoritativeSelector.includes(" >>> ")) {
|
|
12326
|
-
const resolves2 = await
|
|
12355
|
+
const resolves2 = await wc.executeJavaScript(
|
|
12327
12356
|
`!!window.__vessel?.resolveShadowSelector?.(${JSON.stringify(authoritativeSelector)})`
|
|
12328
12357
|
);
|
|
12329
12358
|
if (resolves2) return authoritativeSelector;
|
|
12330
12359
|
return `__vessel_idx:${index}`;
|
|
12331
12360
|
}
|
|
12332
|
-
const resolves = await
|
|
12361
|
+
const resolves = await wc.executeJavaScript(
|
|
12333
12362
|
`!!document.querySelector(${JSON.stringify(authoritativeSelector)})`
|
|
12334
12363
|
);
|
|
12335
12364
|
if (resolves) return authoritativeSelector;
|
|
12336
12365
|
return `__vessel_idx:${index}`;
|
|
12337
12366
|
}
|
|
12338
|
-
const fallbackSelector = await
|
|
12367
|
+
const fallbackSelector = await wc.executeJavaScript(
|
|
12339
12368
|
`
|
|
12340
12369
|
(function() {
|
|
12341
12370
|
${selectorHelpersJS(["data-testid", "name", "form", "aria-label"])}
|
|
@@ -12363,7 +12392,7 @@ async function resolveSelector(wc2, index, selector) {
|
|
|
12363
12392
|
if (typeof fallbackSelector === "string" && fallbackSelector) {
|
|
12364
12393
|
return fallbackSelector;
|
|
12365
12394
|
}
|
|
12366
|
-
const page = await extractContent(
|
|
12395
|
+
const page = await extractContent(wc);
|
|
12367
12396
|
const extractedSelector = findSelectorByIndex(page, index);
|
|
12368
12397
|
if (extractedSelector) return extractedSelector;
|
|
12369
12398
|
return null;
|
|
@@ -12456,13 +12485,13 @@ function formatDeadLinkMessage(label, result) {
|
|
|
12456
12485
|
const logger$c = createLogger("Screenshot");
|
|
12457
12486
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
12458
12487
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
12459
|
-
async function captureScreenshot(
|
|
12488
|
+
async function captureScreenshot(wc) {
|
|
12460
12489
|
for (let attempt = 0; attempt < SCREENSHOT_RETRY_COUNT; attempt += 1) {
|
|
12461
12490
|
await new Promise(
|
|
12462
12491
|
(resolve) => setTimeout(resolve, SCREENSHOT_RETRY_BASE_DELAY_MS * (attempt + 1))
|
|
12463
12492
|
);
|
|
12464
12493
|
try {
|
|
12465
|
-
const image = await
|
|
12494
|
+
const image = await wc.capturePage();
|
|
12466
12495
|
if (!image.isEmpty()) {
|
|
12467
12496
|
const size = image.getSize();
|
|
12468
12497
|
const base64 = image.toPNG().toString("base64");
|
|
@@ -12481,7 +12510,7 @@ async function captureScreenshot(wc2) {
|
|
|
12481
12510
|
);
|
|
12482
12511
|
}
|
|
12483
12512
|
}
|
|
12484
|
-
return errorResult
|
|
12513
|
+
return errorResult("Page image was empty after 3 attempts");
|
|
12485
12514
|
}
|
|
12486
12515
|
const SESSION_VERSION = 1;
|
|
12487
12516
|
function getSessionsDir() {
|
|
@@ -13360,17 +13389,17 @@ function getBookmarkMetadataFromArgs(args) {
|
|
|
13360
13389
|
}
|
|
13361
13390
|
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
|
|
13362
13391
|
const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
13363
|
-
async function loadPermittedUrl(
|
|
13392
|
+
async function loadPermittedUrl(wc, url) {
|
|
13364
13393
|
assertPermittedNavigationURL(url);
|
|
13365
|
-
await
|
|
13394
|
+
await wc.loadURL(url);
|
|
13366
13395
|
}
|
|
13367
13396
|
function pageBusyError(action) {
|
|
13368
13397
|
return `Error: Page is still busy; ${action} timed out waiting for page scripts. Retry in a moment.`;
|
|
13369
13398
|
}
|
|
13370
|
-
async function glanceExtract(
|
|
13399
|
+
async function glanceExtract(wc) {
|
|
13371
13400
|
const startMs = Date.now();
|
|
13372
13401
|
const result = await executePageScript(
|
|
13373
|
-
|
|
13402
|
+
wc,
|
|
13374
13403
|
`(function() {
|
|
13375
13404
|
var vw = window.innerWidth || document.documentElement.clientWidth || 0;
|
|
13376
13405
|
var vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
@@ -13463,8 +13492,8 @@ async function glanceExtract(wc2) {
|
|
|
13463
13492
|
const elapsed = Date.now() - startMs;
|
|
13464
13493
|
if (!result || result === PAGE_SCRIPT_TIMEOUT) {
|
|
13465
13494
|
return [
|
|
13466
|
-
`# ${
|
|
13467
|
-
`URL: ${
|
|
13495
|
+
`# ${wc.getTitle() || "(untitled)"}`,
|
|
13496
|
+
`URL: ${wc.getURL()}`,
|
|
13468
13497
|
"",
|
|
13469
13498
|
"[read_page mode=glance — page JS thread is completely blocked, no content available]",
|
|
13470
13499
|
"[Try: click or type_text to interact directly, or wait a few seconds and retry]"
|
|
@@ -13515,8 +13544,8 @@ function normalizeReadPageMode(mode, pageContent) {
|
|
|
13515
13544
|
}
|
|
13516
13545
|
return pageContent ? chooseAgentReadMode(pageContent) : "visible_only";
|
|
13517
13546
|
}
|
|
13518
|
-
async function executePageScript(
|
|
13519
|
-
if (
|
|
13547
|
+
async function executePageScript(wc, script, options) {
|
|
13548
|
+
if (wc.isDestroyed()) return null;
|
|
13520
13549
|
const timeoutMs = Math.max(
|
|
13521
13550
|
150,
|
|
13522
13551
|
options?.timeoutMs ?? DEFAULT_PAGE_SCRIPT_TIMEOUT_MS
|
|
@@ -13524,7 +13553,7 @@ async function executePageScript(wc2, script, options) {
|
|
|
13524
13553
|
let timer = null;
|
|
13525
13554
|
try {
|
|
13526
13555
|
const result = await Promise.race([
|
|
13527
|
-
|
|
13556
|
+
wc.executeJavaScript(script, options?.userGesture ?? false),
|
|
13528
13557
|
new Promise((resolve) => {
|
|
13529
13558
|
timer = setTimeout(() => resolve(PAGE_SCRIPT_TIMEOUT), timeoutMs);
|
|
13530
13559
|
})
|
|
@@ -13543,10 +13572,10 @@ async function executePageScript(wc2, script, options) {
|
|
|
13543
13572
|
}
|
|
13544
13573
|
}
|
|
13545
13574
|
}
|
|
13546
|
-
async function waitForJsReady(
|
|
13575
|
+
async function waitForJsReady(wc, timeout = 8e3) {
|
|
13547
13576
|
const start = Date.now();
|
|
13548
13577
|
while (Date.now() - start < timeout) {
|
|
13549
|
-
const ready = await executePageScript(
|
|
13578
|
+
const ready = await executePageScript(wc, "1", {
|
|
13550
13579
|
timeoutMs: 250,
|
|
13551
13580
|
userGesture: true,
|
|
13552
13581
|
label: "js-ready probe"
|
|
@@ -13555,20 +13584,20 @@ async function waitForJsReady(wc2, timeout = 8e3) {
|
|
|
13555
13584
|
await sleep(250);
|
|
13556
13585
|
}
|
|
13557
13586
|
}
|
|
13558
|
-
function waitForPotentialNavigation(
|
|
13587
|
+
function waitForPotentialNavigation(wc, beforeUrl, timeout = 2500) {
|
|
13559
13588
|
return waitForPotentialNavigation$1(
|
|
13560
|
-
|
|
13589
|
+
wc,
|
|
13561
13590
|
beforeUrl,
|
|
13562
13591
|
timeout,
|
|
13563
13592
|
QUIET_NAVIGATION_WINDOW_MS
|
|
13564
13593
|
);
|
|
13565
13594
|
}
|
|
13566
|
-
async function getPostNavSummary(
|
|
13567
|
-
const title =
|
|
13595
|
+
async function getPostNavSummary(wc) {
|
|
13596
|
+
const title = wc.getTitle();
|
|
13568
13597
|
const titleLine = title ? `
|
|
13569
13598
|
Page title: ${title}` : "";
|
|
13570
13599
|
const overlaySignal = await executePageScript(
|
|
13571
|
-
|
|
13600
|
+
wc,
|
|
13572
13601
|
`(function() {
|
|
13573
13602
|
var signals = [];
|
|
13574
13603
|
// Body scroll lock is a strong overlay signal
|
|
@@ -13620,11 +13649,11 @@ WARNING: Blocking overlay detected (${overlaySignal}). Call clear_overlays or ac
|
|
|
13620
13649
|
}
|
|
13621
13650
|
return titleLine;
|
|
13622
13651
|
}
|
|
13623
|
-
async function getPostSearchSummary(
|
|
13624
|
-
await waitForLoad(
|
|
13652
|
+
async function getPostSearchSummary(wc) {
|
|
13653
|
+
await waitForLoad(wc, 2e3);
|
|
13625
13654
|
try {
|
|
13626
13655
|
const content = await Promise.race([
|
|
13627
|
-
extractContent(
|
|
13656
|
+
extractContent(wc),
|
|
13628
13657
|
new Promise((resolve) => setTimeout(() => resolve(null), 2500))
|
|
13629
13658
|
]);
|
|
13630
13659
|
if (content && content.content.length > 0) {
|
|
@@ -13638,15 +13667,15 @@ ${truncated}`;
|
|
|
13638
13667
|
} catch (err) {
|
|
13639
13668
|
logger$b.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
13640
13669
|
}
|
|
13641
|
-
const fallback = await getPostNavSummary(
|
|
13670
|
+
const fallback = await getPostNavSummary(wc);
|
|
13642
13671
|
return fallback ? `${fallback}
|
|
13643
13672
|
Search results snapshot unavailable. Use read_page(mode="results_only") if needed.` : `
|
|
13644
13673
|
Search results snapshot unavailable. Use read_page(mode="results_only") if needed.`;
|
|
13645
13674
|
}
|
|
13646
|
-
async function getPostClickNavSummary(
|
|
13675
|
+
async function getPostClickNavSummary(wc, toolProfile) {
|
|
13647
13676
|
try {
|
|
13648
13677
|
const content = await Promise.race([
|
|
13649
|
-
extractContent(
|
|
13678
|
+
extractContent(wc),
|
|
13650
13679
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
13651
13680
|
]);
|
|
13652
13681
|
if (content && content.content.length > 0) {
|
|
@@ -13663,10 +13692,10 @@ ${truncated}`;
|
|
|
13663
13692
|
}
|
|
13664
13693
|
return "";
|
|
13665
13694
|
}
|
|
13666
|
-
async function scrollPage(
|
|
13695
|
+
async function scrollPage(wc, deltaY) {
|
|
13667
13696
|
const getScrollY = async () => {
|
|
13668
13697
|
const scrollY = await executePageScript(
|
|
13669
|
-
|
|
13698
|
+
wc,
|
|
13670
13699
|
`
|
|
13671
13700
|
(function() {
|
|
13672
13701
|
return Math.max(
|
|
@@ -13686,7 +13715,7 @@ async function scrollPage(wc2, deltaY) {
|
|
|
13686
13715
|
};
|
|
13687
13716
|
const beforeY = await getScrollY();
|
|
13688
13717
|
const scrolled = await executePageScript(
|
|
13689
|
-
|
|
13718
|
+
wc,
|
|
13690
13719
|
`window.scrollBy(0, ${deltaY})`,
|
|
13691
13720
|
{
|
|
13692
13721
|
label: "scroll page"
|
|
@@ -13707,9 +13736,9 @@ async function scrollPage(wc2, deltaY) {
|
|
|
13707
13736
|
movedY: Math.round(afterY - beforeY)
|
|
13708
13737
|
};
|
|
13709
13738
|
}
|
|
13710
|
-
async function clickElement(
|
|
13739
|
+
async function clickElement(wc, selector) {
|
|
13711
13740
|
const target = await executePageScript(
|
|
13712
|
-
|
|
13741
|
+
wc,
|
|
13713
13742
|
`
|
|
13714
13743
|
(async function() {
|
|
13715
13744
|
function matchesTarget(candidate, el) {
|
|
@@ -13796,24 +13825,24 @@ async function clickElement(wc2, selector) {
|
|
|
13796
13825
|
return "Error: Could not resolve click coordinates";
|
|
13797
13826
|
}
|
|
13798
13827
|
if (hiddenWindow) {
|
|
13799
|
-
const activationResult = await activateElement(
|
|
13828
|
+
const activationResult = await activateElement(wc, selector);
|
|
13800
13829
|
if (activationResult.startsWith("Error:")) {
|
|
13801
13830
|
return activationResult;
|
|
13802
13831
|
}
|
|
13803
13832
|
await sleep(80);
|
|
13804
13833
|
return "Clicked via DOM activation";
|
|
13805
13834
|
}
|
|
13806
|
-
|
|
13835
|
+
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
13807
13836
|
await sleep(16);
|
|
13808
|
-
|
|
13837
|
+
wc.sendInputEvent({ type: "mouseDown", x, y, button: "left", clickCount: 1 });
|
|
13809
13838
|
await sleep(24);
|
|
13810
|
-
|
|
13839
|
+
wc.sendInputEvent({ type: "mouseUp", x, y, button: "left", clickCount: 1 });
|
|
13811
13840
|
await sleep(80);
|
|
13812
13841
|
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
13813
13842
|
}
|
|
13814
|
-
async function activateElement(
|
|
13843
|
+
async function activateElement(wc, selector) {
|
|
13815
13844
|
const activated = await executePageScript(
|
|
13816
|
-
|
|
13845
|
+
wc,
|
|
13817
13846
|
`
|
|
13818
13847
|
(function() {
|
|
13819
13848
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -13843,9 +13872,9 @@ async function activateElement(wc2, selector) {
|
|
|
13843
13872
|
}
|
|
13844
13873
|
return "Activated element via DOM click";
|
|
13845
13874
|
}
|
|
13846
|
-
async function describeElementForClick(
|
|
13875
|
+
async function describeElementForClick(wc, selector) {
|
|
13847
13876
|
const result = await executePageScript(
|
|
13848
|
-
|
|
13877
|
+
wc,
|
|
13849
13878
|
`
|
|
13850
13879
|
(function() {
|
|
13851
13880
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -13887,9 +13916,9 @@ async function describeElementForClick(wc2, selector) {
|
|
|
13887
13916
|
isInteractive: "isInteractive" in result && typeof result.isInteractive === "boolean" ? result.isInteractive : void 0
|
|
13888
13917
|
};
|
|
13889
13918
|
}
|
|
13890
|
-
async function inspectElement(
|
|
13919
|
+
async function inspectElement(wc, selector, limit = 8) {
|
|
13891
13920
|
const result = await executePageScript(
|
|
13892
|
-
|
|
13921
|
+
wc,
|
|
13893
13922
|
`
|
|
13894
13923
|
(function() {
|
|
13895
13924
|
function text(value) {
|
|
@@ -14099,9 +14128,9 @@ async function inspectElement(wc2, selector, limit = 8) {
|
|
|
14099
14128
|
}
|
|
14100
14129
|
return lines.join("\n");
|
|
14101
14130
|
}
|
|
14102
|
-
async function getLocaleSnapshot(
|
|
14131
|
+
async function getLocaleSnapshot(wc) {
|
|
14103
14132
|
const snapshot = await executePageScript(
|
|
14104
|
-
|
|
14133
|
+
wc,
|
|
14105
14134
|
`
|
|
14106
14135
|
(function() {
|
|
14107
14136
|
return {
|
|
@@ -14124,8 +14153,8 @@ async function getLocaleSnapshot(wc2) {
|
|
|
14124
14153
|
}
|
|
14125
14154
|
return {
|
|
14126
14155
|
lang: typeof snapshot.lang === "string" ? snapshot.lang.trim() : "",
|
|
14127
|
-
url: typeof snapshot.url === "string" ? snapshot.url :
|
|
14128
|
-
title: typeof snapshot.title === "string" ? snapshot.title :
|
|
14156
|
+
url: typeof snapshot.url === "string" ? snapshot.url : wc.getURL(),
|
|
14157
|
+
title: typeof snapshot.title === "string" ? snapshot.title : wc.getTitle()
|
|
14129
14158
|
};
|
|
14130
14159
|
}
|
|
14131
14160
|
function primaryLanguageTag(value) {
|
|
@@ -14141,13 +14170,13 @@ function localeChanged(before, after) {
|
|
|
14141
14170
|
const localeHint = /[?&](lang|locale|language|hl)=|\/(ja|jp|en|fr|de|es|it|ko|zh)(\/|$)/i;
|
|
14142
14171
|
return before.url !== after.url && localeHint.test(after.url);
|
|
14143
14172
|
}
|
|
14144
|
-
async function restoreLocaleSnapshot(
|
|
14145
|
-
if (!snapshot ||
|
|
14173
|
+
async function restoreLocaleSnapshot(wc, snapshot) {
|
|
14174
|
+
if (!snapshot || wc.isDestroyed()) return;
|
|
14146
14175
|
try {
|
|
14147
|
-
if (typeof
|
|
14148
|
-
|
|
14149
|
-
await waitForLoad(
|
|
14150
|
-
const reverted = await getLocaleSnapshot(
|
|
14176
|
+
if (typeof wc.canGoBack === "function" && wc.canGoBack()) {
|
|
14177
|
+
wc.goBack();
|
|
14178
|
+
await waitForLoad(wc, 3e3);
|
|
14179
|
+
const reverted = await getLocaleSnapshot(wc);
|
|
14151
14180
|
if (!localeChanged(snapshot, reverted)) {
|
|
14152
14181
|
return;
|
|
14153
14182
|
}
|
|
@@ -14155,11 +14184,11 @@ async function restoreLocaleSnapshot(wc2, snapshot) {
|
|
|
14155
14184
|
} catch (err) {
|
|
14156
14185
|
logger$b.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
14157
14186
|
}
|
|
14158
|
-
if (snapshot.url && snapshot.url !==
|
|
14187
|
+
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
14159
14188
|
try {
|
|
14160
14189
|
assertSafeURL(snapshot.url);
|
|
14161
|
-
await
|
|
14162
|
-
await waitForLoad(
|
|
14190
|
+
await wc.loadURL(snapshot.url);
|
|
14191
|
+
await waitForLoad(wc, 3e3);
|
|
14163
14192
|
return;
|
|
14164
14193
|
} catch (err) {
|
|
14165
14194
|
logger$b.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
@@ -14167,8 +14196,8 @@ async function restoreLocaleSnapshot(wc2, snapshot) {
|
|
|
14167
14196
|
}
|
|
14168
14197
|
if (snapshot.url) {
|
|
14169
14198
|
try {
|
|
14170
|
-
await
|
|
14171
|
-
await waitForLoad(
|
|
14199
|
+
await wc.reload();
|
|
14200
|
+
await waitForLoad(wc, 3e3);
|
|
14172
14201
|
} catch (err) {
|
|
14173
14202
|
logger$b.warn("Failed to restore locale via page reload:", err);
|
|
14174
14203
|
}
|
|
@@ -14213,10 +14242,10 @@ function isDuplicateCartClick(url, text) {
|
|
|
14213
14242
|
}
|
|
14214
14243
|
return isAddToCartText(text);
|
|
14215
14244
|
}
|
|
14216
|
-
async function getProductPageTitle(
|
|
14245
|
+
async function getProductPageTitle(wc) {
|
|
14217
14246
|
try {
|
|
14218
14247
|
const heading = await executePageScript(
|
|
14219
|
-
|
|
14248
|
+
wc,
|
|
14220
14249
|
`(function() {
|
|
14221
14250
|
var h1 = document.querySelector('h1');
|
|
14222
14251
|
if (h1 && h1.textContent.trim().length > 3 && h1.textContent.trim().length < 200) {
|
|
@@ -14235,7 +14264,7 @@ async function getProductPageTitle(wc2) {
|
|
|
14235
14264
|
}
|
|
14236
14265
|
} catch {
|
|
14237
14266
|
}
|
|
14238
|
-
return
|
|
14267
|
+
return wc.getTitle() || "";
|
|
14239
14268
|
}
|
|
14240
14269
|
function normalizeCartProductKey(url) {
|
|
14241
14270
|
try {
|
|
@@ -14288,11 +14317,11 @@ function clearCartState() {
|
|
|
14288
14317
|
clickStreakUrl = null;
|
|
14289
14318
|
clickStreakCount = 0;
|
|
14290
14319
|
}
|
|
14291
|
-
async function buildCartSuccessSuffix(
|
|
14292
|
-
const productTitle = await getProductPageTitle(
|
|
14320
|
+
async function buildCartSuccessSuffix(wc, productUrl, overlayHint) {
|
|
14321
|
+
const productTitle = await getProductPageTitle(wc);
|
|
14293
14322
|
recordProductAddedToCart(productUrl, productTitle);
|
|
14294
14323
|
const cartSummary = getCartAddedSummary(productUrl);
|
|
14295
|
-
const dismissResult = await tryAutoDismissCartDialog(
|
|
14324
|
+
const dismissResult = await tryAutoDismissCartDialog(wc);
|
|
14296
14325
|
if (dismissResult) {
|
|
14297
14326
|
return `
|
|
14298
14327
|
Item added to cart. ${dismissResult}${cartSummary}
|
|
@@ -14301,20 +14330,20 @@ Go back to search results to select the next product.`;
|
|
|
14301
14330
|
if (!overlayHint) {
|
|
14302
14331
|
return cartSummary;
|
|
14303
14332
|
}
|
|
14304
|
-
const dialogActions = await getCartDialogActions(
|
|
14333
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
14305
14334
|
const actionsSuffix = dialogActions ? `
|
|
14306
14335
|
${dialogActions}
|
|
14307
14336
|
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
14308
14337
|
return `
|
|
14309
14338
|
${overlayHint}${actionsSuffix}${cartSummary}`;
|
|
14310
14339
|
}
|
|
14311
|
-
async function clickResolvedSelector(
|
|
14340
|
+
async function clickResolvedSelector(wc, selector) {
|
|
14312
14341
|
if (selector.startsWith("__vessel_idx:")) {
|
|
14313
14342
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
14314
|
-
const beforeUrl2 =
|
|
14343
|
+
const beforeUrl2 = wc.getURL();
|
|
14315
14344
|
let idxCartMatch = false;
|
|
14316
14345
|
const idxLabel = await executePageScript(
|
|
14317
|
-
|
|
14346
|
+
wc,
|
|
14318
14347
|
`window.__vessel?.getElementText?.(${idx}) || ""`,
|
|
14319
14348
|
{ label: "shadow element text" }
|
|
14320
14349
|
);
|
|
@@ -14327,7 +14356,7 @@ async function clickResolvedSelector(wc2, selector) {
|
|
|
14327
14356
|
Go back and select a different product.`;
|
|
14328
14357
|
}
|
|
14329
14358
|
const result = await executePageScript(
|
|
14330
|
-
|
|
14359
|
+
wc,
|
|
14331
14360
|
`window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`,
|
|
14332
14361
|
{
|
|
14333
14362
|
label: "shadow click by index"
|
|
@@ -14338,24 +14367,24 @@ Go back and select a different product.`;
|
|
|
14338
14367
|
if (idxCartMatch) {
|
|
14339
14368
|
recordCartClick(beforeUrl2, idxLabel);
|
|
14340
14369
|
}
|
|
14341
|
-
await waitForPotentialNavigation(
|
|
14342
|
-
const afterUrl2 =
|
|
14370
|
+
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
14371
|
+
const afterUrl2 = wc.getURL();
|
|
14343
14372
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
14344
|
-
let idxOverlay = await detectPostClickOverlay(
|
|
14373
|
+
let idxOverlay = await detectPostClickOverlay(wc);
|
|
14345
14374
|
if (!idxOverlay && idxCartMatch) {
|
|
14346
14375
|
await sleep(1200);
|
|
14347
|
-
idxOverlay = await detectPostClickOverlay(
|
|
14376
|
+
idxOverlay = await detectPostClickOverlay(wc);
|
|
14348
14377
|
}
|
|
14349
14378
|
if (idxCartMatch) {
|
|
14350
|
-
return `${result}${await buildCartSuccessSuffix(
|
|
14379
|
+
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, idxOverlay)}`;
|
|
14351
14380
|
}
|
|
14352
14381
|
if (!idxOverlay) {
|
|
14353
14382
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
14354
14383
|
if (hrefMatch) {
|
|
14355
14384
|
try {
|
|
14356
|
-
await loadPermittedUrl(
|
|
14357
|
-
await waitForLoad(
|
|
14358
|
-
const hrefUrl =
|
|
14385
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
14386
|
+
await waitForLoad(wc, 8e3);
|
|
14387
|
+
const hrefUrl = wc.getURL();
|
|
14359
14388
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
14360
14389
|
} catch {
|
|
14361
14390
|
}
|
|
@@ -14366,10 +14395,10 @@ ${idxOverlay}` : `${result}
|
|
|
14366
14395
|
Note: Page did not change after click.`;
|
|
14367
14396
|
}
|
|
14368
14397
|
if (selector.includes(" >>> ")) {
|
|
14369
|
-
const beforeUrl2 =
|
|
14398
|
+
const beforeUrl2 = wc.getURL();
|
|
14370
14399
|
let shadowCartMatch = false;
|
|
14371
14400
|
const shadowLabel = await executePageScript(
|
|
14372
|
-
|
|
14401
|
+
wc,
|
|
14373
14402
|
`(function() {
|
|
14374
14403
|
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
14375
14404
|
return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
|
|
@@ -14385,7 +14414,7 @@ Note: Page did not change after click.`;
|
|
|
14385
14414
|
Go back and select a different product.`;
|
|
14386
14415
|
}
|
|
14387
14416
|
const result = await executePageScript(
|
|
14388
|
-
|
|
14417
|
+
wc,
|
|
14389
14418
|
`
|
|
14390
14419
|
(function() {
|
|
14391
14420
|
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
@@ -14405,24 +14434,24 @@ Go back and select a different product.`;
|
|
|
14405
14434
|
if (shadowCartMatch) {
|
|
14406
14435
|
recordCartClick(beforeUrl2, shadowLabel);
|
|
14407
14436
|
}
|
|
14408
|
-
await waitForPotentialNavigation(
|
|
14409
|
-
const afterUrl2 =
|
|
14437
|
+
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
14438
|
+
const afterUrl2 = wc.getURL();
|
|
14410
14439
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
14411
|
-
let shadowOverlay = await detectPostClickOverlay(
|
|
14440
|
+
let shadowOverlay = await detectPostClickOverlay(wc);
|
|
14412
14441
|
if (!shadowOverlay && shadowCartMatch) {
|
|
14413
14442
|
await sleep(1200);
|
|
14414
|
-
shadowOverlay = await detectPostClickOverlay(
|
|
14443
|
+
shadowOverlay = await detectPostClickOverlay(wc);
|
|
14415
14444
|
}
|
|
14416
14445
|
if (shadowCartMatch) {
|
|
14417
|
-
return `${result}${await buildCartSuccessSuffix(
|
|
14446
|
+
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, shadowOverlay)}`;
|
|
14418
14447
|
}
|
|
14419
14448
|
if (!shadowOverlay) {
|
|
14420
14449
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
14421
14450
|
if (hrefMatch) {
|
|
14422
14451
|
try {
|
|
14423
|
-
await loadPermittedUrl(
|
|
14424
|
-
await waitForLoad(
|
|
14425
|
-
const hrefUrl =
|
|
14452
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
14453
|
+
await waitForLoad(wc, 8e3);
|
|
14454
|
+
const hrefUrl = wc.getURL();
|
|
14426
14455
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
14427
14456
|
} catch {
|
|
14428
14457
|
}
|
|
@@ -14432,15 +14461,15 @@ Go back and select a different product.`;
|
|
|
14432
14461
|
${shadowOverlay}` : `${result}
|
|
14433
14462
|
Note: Page did not change after click.`;
|
|
14434
14463
|
}
|
|
14435
|
-
const beforeUrl =
|
|
14436
|
-
const elInfo = await describeElementForClick(
|
|
14464
|
+
const beforeUrl = wc.getURL();
|
|
14465
|
+
const elInfo = await describeElementForClick(wc, selector);
|
|
14437
14466
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
14438
14467
|
const cartMatch = isAddToCartText(elInfo.text);
|
|
14439
14468
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
14440
14469
|
return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
14441
14470
|
}
|
|
14442
14471
|
if (!cartMatch && recentCartClicks.has(beforeUrl)) {
|
|
14443
|
-
const dialogActions = await getCartDialogActions(
|
|
14472
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
14444
14473
|
if (dialogActions) {
|
|
14445
14474
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
14446
14475
|
${dialogActions}
|
|
@@ -14463,18 +14492,18 @@ Go back and select a different product.`;
|
|
|
14463
14492
|
}
|
|
14464
14493
|
const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
|
|
14465
14494
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
14466
|
-
const clickResult = await clickElement(
|
|
14495
|
+
const clickResult = await clickElement(wc, selector);
|
|
14467
14496
|
if (clickResult.startsWith("Error:")) return clickResult;
|
|
14468
|
-
await waitForPotentialNavigation(
|
|
14469
|
-
const afterUrl =
|
|
14497
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
14498
|
+
const afterUrl = wc.getURL();
|
|
14470
14499
|
if (afterUrl !== beforeUrl) {
|
|
14471
14500
|
return `${clickText} -> ${afterUrl}`;
|
|
14472
14501
|
}
|
|
14473
|
-
const overlayHint = await detectPostClickOverlay(
|
|
14502
|
+
const overlayHint = await detectPostClickOverlay(wc);
|
|
14474
14503
|
if (overlayHint) {
|
|
14475
14504
|
if (cartMatch) {
|
|
14476
14505
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
14477
|
-
|
|
14506
|
+
wc,
|
|
14478
14507
|
beforeUrl,
|
|
14479
14508
|
overlayHint
|
|
14480
14509
|
)}`;
|
|
@@ -14484,28 +14513,28 @@ ${overlayHint}`;
|
|
|
14484
14513
|
}
|
|
14485
14514
|
if (cartMatch) {
|
|
14486
14515
|
await sleep(1200);
|
|
14487
|
-
const delayedOverlayHint = await detectPostClickOverlay(
|
|
14516
|
+
const delayedOverlayHint = await detectPostClickOverlay(wc);
|
|
14488
14517
|
if (delayedOverlayHint) {
|
|
14489
14518
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
14490
|
-
|
|
14519
|
+
wc,
|
|
14491
14520
|
beforeUrl,
|
|
14492
14521
|
delayedOverlayHint
|
|
14493
14522
|
)}`;
|
|
14494
14523
|
}
|
|
14495
14524
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
14496
|
-
|
|
14525
|
+
wc,
|
|
14497
14526
|
beforeUrl
|
|
14498
14527
|
)}`;
|
|
14499
14528
|
}
|
|
14500
|
-
const activationResult = await activateElement(
|
|
14529
|
+
const activationResult = await activateElement(wc, selector);
|
|
14501
14530
|
if (!activationResult.startsWith("Error:")) {
|
|
14502
|
-
await waitForPotentialNavigation(
|
|
14503
|
-
const fallbackUrl =
|
|
14531
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
14532
|
+
const fallbackUrl = wc.getURL();
|
|
14504
14533
|
if (fallbackUrl !== beforeUrl) {
|
|
14505
14534
|
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
14506
14535
|
}
|
|
14507
14536
|
}
|
|
14508
|
-
const postActivationOverlayHint = await detectPostClickOverlay(
|
|
14537
|
+
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
14509
14538
|
if (postActivationOverlayHint) {
|
|
14510
14539
|
return `${clickText} (${clickResult})
|
|
14511
14540
|
${postActivationOverlayHint}`;
|
|
@@ -14515,9 +14544,9 @@ ${postActivationOverlayHint}`;
|
|
|
14515
14544
|
const validation = await validateLinkDestination(elInfo.href);
|
|
14516
14545
|
if (validation.status !== "dead") {
|
|
14517
14546
|
try {
|
|
14518
|
-
await loadPermittedUrl(
|
|
14519
|
-
await waitForLoad(
|
|
14520
|
-
const hrefFallbackUrl =
|
|
14547
|
+
await loadPermittedUrl(wc, elInfo.href);
|
|
14548
|
+
await waitForLoad(wc, 8e3);
|
|
14549
|
+
const hrefFallbackUrl = wc.getURL();
|
|
14521
14550
|
if (hrefFallbackUrl !== beforeUrl) {
|
|
14522
14551
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
14523
14552
|
}
|
|
@@ -14531,10 +14560,10 @@ Note: The clicked element (<${elInfo.tag || "unknown"}>) is not a link or button
|
|
|
14531
14560
|
Note: Page did not change after click. The element may need a different interaction method. Consider read_page or inspect_element.`;
|
|
14532
14561
|
return `${clickText} (${clickResult})${nonInteractiveWarning}`;
|
|
14533
14562
|
}
|
|
14534
|
-
async function tryAutoDismissCartDialog(
|
|
14563
|
+
async function tryAutoDismissCartDialog(wc) {
|
|
14535
14564
|
try {
|
|
14536
14565
|
const result = await executePageScript(
|
|
14537
|
-
|
|
14566
|
+
wc,
|
|
14538
14567
|
`
|
|
14539
14568
|
(function() {
|
|
14540
14569
|
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
@@ -14571,9 +14600,9 @@ async function tryAutoDismissCartDialog(wc2) {
|
|
|
14571
14600
|
}
|
|
14572
14601
|
return null;
|
|
14573
14602
|
}
|
|
14574
|
-
async function getCartDialogActions(
|
|
14603
|
+
async function getCartDialogActions(wc) {
|
|
14575
14604
|
const result = await executePageScript(
|
|
14576
|
-
|
|
14605
|
+
wc,
|
|
14577
14606
|
`
|
|
14578
14607
|
(function() {
|
|
14579
14608
|
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
@@ -14616,9 +14645,9 @@ async function getCartDialogActions(wc2) {
|
|
|
14616
14645
|
return `Available dialog actions:
|
|
14617
14646
|
${result.actions.join("\n")}`;
|
|
14618
14647
|
}
|
|
14619
|
-
async function detectPostClickOverlay(
|
|
14648
|
+
async function detectPostClickOverlay(wc) {
|
|
14620
14649
|
const result = await executePageScript(
|
|
14621
|
-
|
|
14650
|
+
wc,
|
|
14622
14651
|
`
|
|
14623
14652
|
(function() {
|
|
14624
14653
|
var vw = window.innerWidth || document.documentElement.clientWidth;
|
|
@@ -14722,8 +14751,8 @@ async function detectPostClickOverlay(wc2) {
|
|
|
14722
14751
|
const desc = result.label ? ` ("${result.label}")` : "";
|
|
14723
14752
|
return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
|
|
14724
14753
|
}
|
|
14725
|
-
async function dismissPopup(
|
|
14726
|
-
const before = await extractContent(
|
|
14754
|
+
async function dismissPopup(wc) {
|
|
14755
|
+
const before = await extractContent(wc);
|
|
14727
14756
|
const initialBlocking = before.overlays.filter(
|
|
14728
14757
|
(overlay) => overlay.blocksInteraction
|
|
14729
14758
|
).length;
|
|
@@ -14746,7 +14775,7 @@ async function dismissPopup(wc2) {
|
|
|
14746
14775
|
];
|
|
14747
14776
|
if (cartSignals.some((s) => overlayText.includes(s))) {
|
|
14748
14777
|
const continueResult = await executePageScript(
|
|
14749
|
-
|
|
14778
|
+
wc,
|
|
14750
14779
|
`
|
|
14751
14780
|
(function() {
|
|
14752
14781
|
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
@@ -14775,14 +14804,14 @@ async function dismissPopup(wc2) {
|
|
|
14775
14804
|
if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
14776
14805
|
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
14777
14806
|
}
|
|
14778
|
-
const dialogActions = await getCartDialogActions(
|
|
14807
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
14779
14808
|
return `Cannot dismiss: this is a cart confirmation dialog. Item is in your cart.${dialogActions ? "\n" + dialogActions + "\nClick one of these instead." : " Use read_page to see dialog actions."}`;
|
|
14780
14809
|
}
|
|
14781
14810
|
}
|
|
14782
14811
|
const initialDormant = before.dormantOverlays.length;
|
|
14783
|
-
const initialLocale = await getLocaleSnapshot(
|
|
14812
|
+
const initialLocale = await getLocaleSnapshot(wc);
|
|
14784
14813
|
const candidates = await executePageScript(
|
|
14785
|
-
|
|
14814
|
+
wc,
|
|
14786
14815
|
`
|
|
14787
14816
|
(function() {
|
|
14788
14817
|
function text(value) {
|
|
@@ -14935,15 +14964,15 @@ async function dismissPopup(wc2) {
|
|
|
14935
14964
|
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
14936
14965
|
continue;
|
|
14937
14966
|
}
|
|
14938
|
-
const result = await clickElement(
|
|
14967
|
+
const result = await clickElement(wc, candidate.selector);
|
|
14939
14968
|
if (result.startsWith("Error:")) continue;
|
|
14940
14969
|
await sleep(250);
|
|
14941
|
-
const postClickLocale = await getLocaleSnapshot(
|
|
14970
|
+
const postClickLocale = await getLocaleSnapshot(wc);
|
|
14942
14971
|
if (localeChanged(initialLocale, postClickLocale)) {
|
|
14943
|
-
await restoreLocaleSnapshot(
|
|
14972
|
+
await restoreLocaleSnapshot(wc, initialLocale);
|
|
14944
14973
|
continue;
|
|
14945
14974
|
}
|
|
14946
|
-
const after = await extractContent(
|
|
14975
|
+
const after = await extractContent(wc);
|
|
14947
14976
|
const blocking = after.overlays.filter(
|
|
14948
14977
|
(overlay) => overlay.blocksInteraction
|
|
14949
14978
|
).length;
|
|
@@ -14953,11 +14982,11 @@ async function dismissPopup(wc2) {
|
|
|
14953
14982
|
}
|
|
14954
14983
|
}
|
|
14955
14984
|
}
|
|
14956
|
-
|
|
14985
|
+
wc.sendInputEvent({ type: "keyDown", keyCode: "Escape" });
|
|
14957
14986
|
await sleep(16);
|
|
14958
|
-
|
|
14987
|
+
wc.sendInputEvent({ type: "keyUp", keyCode: "Escape" });
|
|
14959
14988
|
await sleep(200);
|
|
14960
|
-
const afterEscape = await extractContent(
|
|
14989
|
+
const afterEscape = await extractContent(wc);
|
|
14961
14990
|
const escapeBlocking = afterEscape.overlays.filter(
|
|
14962
14991
|
(overlay) => overlay.blocksInteraction
|
|
14963
14992
|
).length;
|
|
@@ -14975,15 +15004,15 @@ function describeOverlayState(page) {
|
|
|
14975
15004
|
signature: getBlockingOverlaySignature(inventory)
|
|
14976
15005
|
};
|
|
14977
15006
|
}
|
|
14978
|
-
async function clickOverlayCandidate(
|
|
15007
|
+
async function clickOverlayCandidate(wc, action) {
|
|
14979
15008
|
if (!action?.selector) return null;
|
|
14980
|
-
const result = await clickResolvedSelector(
|
|
15009
|
+
const result = await clickResolvedSelector(wc, action.selector);
|
|
14981
15010
|
return `${action.label || action.selector}: ${result}`;
|
|
14982
15011
|
}
|
|
14983
|
-
async function tryDismissConsentIframe(
|
|
15012
|
+
async function tryDismissConsentIframe(wc) {
|
|
14984
15013
|
try {
|
|
14985
15014
|
const hasSignal = await executePageScript(
|
|
14986
|
-
|
|
15015
|
+
wc,
|
|
14987
15016
|
`(function() {
|
|
14988
15017
|
var bs = window.getComputedStyle(document.body);
|
|
14989
15018
|
var hs = window.getComputedStyle(document.documentElement);
|
|
@@ -14995,9 +15024,9 @@ async function tryDismissConsentIframe(wc2) {
|
|
|
14995
15024
|
{ timeoutMs: 1e3, label: "iframe-consent-signal" }
|
|
14996
15025
|
);
|
|
14997
15026
|
if (!hasSignal || hasSignal === PAGE_SCRIPT_TIMEOUT) return null;
|
|
14998
|
-
const frames =
|
|
15027
|
+
const frames = wc.mainFrame.framesInSubtree;
|
|
14999
15028
|
for (const frame of frames) {
|
|
15000
|
-
if (frame ===
|
|
15029
|
+
if (frame === wc.mainFrame) continue;
|
|
15001
15030
|
try {
|
|
15002
15031
|
const result = await frame.executeJavaScript(`
|
|
15003
15032
|
(function() {
|
|
@@ -15045,9 +15074,9 @@ async function tryDismissConsentIframe(wc2) {
|
|
|
15045
15074
|
}
|
|
15046
15075
|
return null;
|
|
15047
15076
|
}
|
|
15048
|
-
async function tryAcceptCookiesQuickly(
|
|
15077
|
+
async function tryAcceptCookiesQuickly(wc) {
|
|
15049
15078
|
const dismissed = await executePageScript(
|
|
15050
|
-
|
|
15079
|
+
wc,
|
|
15051
15080
|
`
|
|
15052
15081
|
(function() {
|
|
15053
15082
|
var selectors = [
|
|
@@ -15114,10 +15143,10 @@ async function tryAcceptCookiesQuickly(wc2) {
|
|
|
15114
15143
|
}
|
|
15115
15144
|
);
|
|
15116
15145
|
if (dismissed) return dismissed;
|
|
15117
|
-
return tryDismissConsentIframe(
|
|
15146
|
+
return tryDismissConsentIframe(wc);
|
|
15118
15147
|
}
|
|
15119
|
-
async function clearOverlays(
|
|
15120
|
-
const quickCookieResult = await tryAcceptCookiesQuickly(
|
|
15148
|
+
async function clearOverlays(wc, strategy = "auto") {
|
|
15149
|
+
const quickCookieResult = await tryAcceptCookiesQuickly(wc);
|
|
15121
15150
|
if (quickCookieResult === PAGE_SCRIPT_TIMEOUT) {
|
|
15122
15151
|
return pageBusyError("clear_overlays");
|
|
15123
15152
|
}
|
|
@@ -15127,19 +15156,19 @@ async function clearOverlays(wc2, strategy = "auto") {
|
|
|
15127
15156
|
"Stopped after a lightweight consent pass to keep the page responsive. Re-run only if the banner is still blocking the page."
|
|
15128
15157
|
].join("\n");
|
|
15129
15158
|
}
|
|
15130
|
-
await waitForJsReady(
|
|
15159
|
+
await waitForJsReady(wc, 1500);
|
|
15131
15160
|
const steps = [];
|
|
15132
15161
|
let cleared = 0;
|
|
15133
15162
|
const maxIterations = 8;
|
|
15134
15163
|
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
15135
|
-
const before = await extractContent(
|
|
15164
|
+
const before = await extractContent(wc);
|
|
15136
15165
|
const beforeState = describeOverlayState(before);
|
|
15137
15166
|
const blockingOverlays = beforeState.inventory.filter(
|
|
15138
15167
|
(overlay2) => overlay2.blocksInteraction
|
|
15139
15168
|
);
|
|
15140
15169
|
if (blockingOverlays.length === 0) {
|
|
15141
15170
|
if (cleared === 0) {
|
|
15142
|
-
const iframeResult = await tryDismissConsentIframe(
|
|
15171
|
+
const iframeResult = await tryDismissConsentIframe(wc);
|
|
15143
15172
|
if (iframeResult) {
|
|
15144
15173
|
steps.push(`Iframe consent: ${iframeResult}`);
|
|
15145
15174
|
await sleep(500);
|
|
@@ -15155,7 +15184,7 @@ async function clearOverlays(wc2, strategy = "auto") {
|
|
|
15155
15184
|
let actionMessage = null;
|
|
15156
15185
|
if (overlay.kind === "cookie_consent") {
|
|
15157
15186
|
actionMessage = await clickOverlayCandidate(
|
|
15158
|
-
|
|
15187
|
+
wc,
|
|
15159
15188
|
overlay.acceptAction || overlay.dismissAction || overlay.actions[0]
|
|
15160
15189
|
);
|
|
15161
15190
|
} else if (overlay.kind === "selection_modal") {
|
|
@@ -15170,14 +15199,14 @@ async function clearOverlays(wc2, strategy = "auto") {
|
|
|
15170
15199
|
}
|
|
15171
15200
|
} else {
|
|
15172
15201
|
const optionResult = await clickOverlayCandidate(
|
|
15173
|
-
|
|
15202
|
+
wc,
|
|
15174
15203
|
overlay.correctOption
|
|
15175
15204
|
);
|
|
15176
15205
|
if (optionResult) {
|
|
15177
15206
|
actionMessage = `Selected likely-correct option: ${optionResult}`;
|
|
15178
15207
|
await sleep(120);
|
|
15179
15208
|
const submitResult = await clickOverlayCandidate(
|
|
15180
|
-
|
|
15209
|
+
wc,
|
|
15181
15210
|
overlay.submitAction || overlay.acceptAction
|
|
15182
15211
|
);
|
|
15183
15212
|
if (submitResult) {
|
|
@@ -15188,7 +15217,7 @@ Submitted modal: ${submitResult}`;
|
|
|
15188
15217
|
}
|
|
15189
15218
|
}
|
|
15190
15219
|
if (!actionMessage) {
|
|
15191
|
-
actionMessage = `Fallback popup handling: ${await dismissPopup(
|
|
15220
|
+
actionMessage = `Fallback popup handling: ${await dismissPopup(wc)}`;
|
|
15192
15221
|
}
|
|
15193
15222
|
steps.push(actionMessage);
|
|
15194
15223
|
if (overlay.kind === "cookie_consent") {
|
|
@@ -15198,7 +15227,7 @@ Submitted modal: ${submitResult}`;
|
|
|
15198
15227
|
return steps.join("\n");
|
|
15199
15228
|
}
|
|
15200
15229
|
await sleep(250);
|
|
15201
|
-
const after = await extractContent(
|
|
15230
|
+
const after = await extractContent(wc);
|
|
15202
15231
|
const afterState = describeOverlayState(after);
|
|
15203
15232
|
steps.push(`Overlays remaining: ${afterState.total}`);
|
|
15204
15233
|
steps.push(`Page still blocked: ${afterState.blocking > 0}`);
|
|
@@ -15214,12 +15243,12 @@ Submitted modal: ${submitResult}`;
|
|
|
15214
15243
|
}
|
|
15215
15244
|
return steps.join("\n");
|
|
15216
15245
|
}
|
|
15217
|
-
async function resolveTargetByText(
|
|
15246
|
+
async function resolveTargetByText(wc, query, mode) {
|
|
15218
15247
|
const trimmed = query.trim();
|
|
15219
15248
|
if (!trimmed) return null;
|
|
15220
15249
|
if (isInvalidTextTargetQuery(trimmed)) return null;
|
|
15221
15250
|
const result = await executePageScript(
|
|
15222
|
-
|
|
15251
|
+
wc,
|
|
15223
15252
|
`(${resolveTextTargetInDocument.toString()})(document, ${JSON.stringify(trimmed)}, ${JSON.stringify(mode)})`,
|
|
15224
15253
|
{
|
|
15225
15254
|
timeoutMs: 2200,
|
|
@@ -15243,15 +15272,15 @@ function describeFillField(field) {
|
|
|
15243
15272
|
if (field.placeholder) return `placeholder=${field.placeholder}`;
|
|
15244
15273
|
return "field";
|
|
15245
15274
|
}
|
|
15246
|
-
async function resolveFieldSelector(
|
|
15247
|
-
const directSelector = await resolveSelector(
|
|
15275
|
+
async function resolveFieldSelector(wc, field) {
|
|
15276
|
+
const directSelector = await resolveSelector(wc, field.index, field.selector);
|
|
15248
15277
|
if (directSelector) return directSelector;
|
|
15249
15278
|
const name = normalizeFieldToken(field.name);
|
|
15250
15279
|
const label = normalizeFieldToken(field.label);
|
|
15251
15280
|
const placeholder = normalizeFieldToken(field.placeholder);
|
|
15252
15281
|
if (!name && !label && !placeholder) return null;
|
|
15253
15282
|
const selector = await executePageScript(
|
|
15254
|
-
|
|
15283
|
+
wc,
|
|
15255
15284
|
`
|
|
15256
15285
|
(function() {
|
|
15257
15286
|
function normalize(value) {
|
|
@@ -15351,10 +15380,10 @@ async function resolveFieldSelector(wc2, field) {
|
|
|
15351
15380
|
);
|
|
15352
15381
|
return typeof selector === "string" && selector ? selector : null;
|
|
15353
15382
|
}
|
|
15354
|
-
async function fillFormFields(
|
|
15383
|
+
async function fillFormFields(wc, fields) {
|
|
15355
15384
|
const results = [];
|
|
15356
15385
|
for (const field of fields) {
|
|
15357
|
-
const selector = await resolveFieldSelector(
|
|
15386
|
+
const selector = await resolveFieldSelector(wc, field);
|
|
15358
15387
|
if (!selector) {
|
|
15359
15388
|
results.push({
|
|
15360
15389
|
field,
|
|
@@ -15364,7 +15393,7 @@ async function fillFormFields(wc2, fields) {
|
|
|
15364
15393
|
continue;
|
|
15365
15394
|
}
|
|
15366
15395
|
const result = await setElementValue(
|
|
15367
|
-
|
|
15396
|
+
wc,
|
|
15368
15397
|
selector,
|
|
15369
15398
|
String(field.value || "")
|
|
15370
15399
|
);
|
|
@@ -15399,18 +15428,18 @@ function isDangerousAction(name) {
|
|
|
15399
15428
|
"paginate"
|
|
15400
15429
|
].includes(name);
|
|
15401
15430
|
}
|
|
15402
|
-
async function setElementValue(
|
|
15431
|
+
async function setElementValue(wc, selector, value) {
|
|
15403
15432
|
if (selector.startsWith("__vessel_idx:")) {
|
|
15404
15433
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
15405
15434
|
const result2 = await executePageScript(
|
|
15406
|
-
|
|
15435
|
+
wc,
|
|
15407
15436
|
`window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
|
|
15408
15437
|
);
|
|
15409
15438
|
return result2 === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result2 || "Error: interactByIndex not available";
|
|
15410
15439
|
}
|
|
15411
15440
|
if (selector.includes(" >>> ")) {
|
|
15412
15441
|
const result2 = await executePageScript(
|
|
15413
|
-
|
|
15442
|
+
wc,
|
|
15414
15443
|
`
|
|
15415
15444
|
(function() {
|
|
15416
15445
|
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
@@ -15446,7 +15475,7 @@ async function setElementValue(wc2, selector, value) {
|
|
|
15446
15475
|
return result2 === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result2 || "Error: Could not type into element";
|
|
15447
15476
|
}
|
|
15448
15477
|
const result = await executePageScript(
|
|
15449
|
-
|
|
15478
|
+
wc,
|
|
15450
15479
|
`
|
|
15451
15480
|
(function() {
|
|
15452
15481
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -15503,9 +15532,9 @@ async function setElementValue(wc2, selector, value) {
|
|
|
15503
15532
|
);
|
|
15504
15533
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
15505
15534
|
}
|
|
15506
|
-
async function typeKeystroke(
|
|
15535
|
+
async function typeKeystroke(wc, selector, value) {
|
|
15507
15536
|
const result = await executePageScript(
|
|
15508
|
-
|
|
15537
|
+
wc,
|
|
15509
15538
|
`
|
|
15510
15539
|
(async function() {
|
|
15511
15540
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -15551,8 +15580,8 @@ async function typeKeystroke(wc2, selector, value) {
|
|
|
15551
15580
|
);
|
|
15552
15581
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
15553
15582
|
}
|
|
15554
|
-
async function hoverElement(
|
|
15555
|
-
const pos = await
|
|
15583
|
+
async function hoverElement(wc, selector) {
|
|
15584
|
+
const pos = await wc.executeJavaScript(`
|
|
15556
15585
|
(function() {
|
|
15557
15586
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
15558
15587
|
if (!el) return { error: 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.' };
|
|
@@ -15577,12 +15606,12 @@ async function hoverElement(wc2, selector) {
|
|
|
15577
15606
|
const y = typeof pos.y === "number" ? pos.y : null;
|
|
15578
15607
|
if (x == null || y == null)
|
|
15579
15608
|
return "Error: Could not resolve hover coordinates";
|
|
15580
|
-
|
|
15609
|
+
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
15581
15610
|
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
15582
15611
|
return `Hovered: ${label}`;
|
|
15583
15612
|
}
|
|
15584
|
-
async function focusElement(
|
|
15585
|
-
return
|
|
15613
|
+
async function focusElement(wc, selector) {
|
|
15614
|
+
return wc.executeJavaScript(`
|
|
15586
15615
|
(function() {
|
|
15587
15616
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
15588
15617
|
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
@@ -15595,20 +15624,20 @@ async function focusElement(wc2, selector) {
|
|
|
15595
15624
|
})()
|
|
15596
15625
|
`);
|
|
15597
15626
|
}
|
|
15598
|
-
async function waitForCondition(
|
|
15627
|
+
async function waitForCondition(wc, args) {
|
|
15599
15628
|
const timeoutMs = Math.max(250, Number(args.timeoutMs) || 5e3);
|
|
15600
15629
|
const selector = typeof args.selector === "string" && args.selector.trim() ? args.selector.trim() : "";
|
|
15601
15630
|
const text = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
|
|
15602
15631
|
if (!selector && !text) {
|
|
15603
15632
|
return "Error: wait_for requires text or selector";
|
|
15604
15633
|
}
|
|
15605
|
-
if (
|
|
15606
|
-
await waitForLoad(
|
|
15634
|
+
if (wc.isLoading()) {
|
|
15635
|
+
await waitForLoad(wc, Math.min(timeoutMs, 5e3));
|
|
15607
15636
|
}
|
|
15608
15637
|
const startedAt = Date.now();
|
|
15609
15638
|
while (Date.now() - startedAt < timeoutMs) {
|
|
15610
15639
|
const result = await executePageScript(
|
|
15611
|
-
|
|
15640
|
+
wc,
|
|
15612
15641
|
`
|
|
15613
15642
|
(function() {
|
|
15614
15643
|
var selector = ${JSON.stringify(selector)};
|
|
@@ -15712,11 +15741,11 @@ function composeFolderAwareResponse(message, createdFolder) {
|
|
|
15712
15741
|
return `${prefix}${message}
|
|
15713
15742
|
${formatFolderStatus()}`;
|
|
15714
15743
|
}
|
|
15715
|
-
async function selectOption(
|
|
15716
|
-
const selector = await resolveSelector(
|
|
15744
|
+
async function selectOption(wc, args) {
|
|
15745
|
+
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
15717
15746
|
if (!selector) return "Error: No select element index or selector provided";
|
|
15718
15747
|
const result = await executePageScript(
|
|
15719
|
-
|
|
15748
|
+
wc,
|
|
15720
15749
|
`
|
|
15721
15750
|
(function() {
|
|
15722
15751
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -15748,12 +15777,12 @@ async function selectOption(wc2, args) {
|
|
|
15748
15777
|
);
|
|
15749
15778
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
|
|
15750
15779
|
}
|
|
15751
|
-
async function submitForm(
|
|
15752
|
-
const beforeUrl =
|
|
15753
|
-
let selector = await resolveSelector(
|
|
15780
|
+
async function submitForm(wc, args) {
|
|
15781
|
+
const beforeUrl = wc.getURL();
|
|
15782
|
+
let selector = await resolveSelector(wc, args.index, args.selector);
|
|
15754
15783
|
if (!selector) {
|
|
15755
15784
|
const discoveredSelector = await executePageScript(
|
|
15756
|
-
|
|
15785
|
+
wc,
|
|
15757
15786
|
`
|
|
15758
15787
|
(function() {
|
|
15759
15788
|
var forms = document.querySelectorAll('form');
|
|
@@ -15776,7 +15805,7 @@ async function submitForm(wc2, args) {
|
|
|
15776
15805
|
if (!selector) return "Error: No form found on the page";
|
|
15777
15806
|
}
|
|
15778
15807
|
const formInfo = await executePageScript(
|
|
15779
|
-
|
|
15808
|
+
wc,
|
|
15780
15809
|
`
|
|
15781
15810
|
(function() {
|
|
15782
15811
|
const target = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -15889,19 +15918,19 @@ async function submitForm(wc2, args) {
|
|
|
15889
15918
|
if (formInfo.params) {
|
|
15890
15919
|
url.search = formInfo.params;
|
|
15891
15920
|
}
|
|
15892
|
-
await loadPermittedUrl(
|
|
15893
|
-
await waitForPotentialNavigation(
|
|
15894
|
-
const afterUrl =
|
|
15921
|
+
await loadPermittedUrl(wc, url.toString());
|
|
15922
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
15923
|
+
const afterUrl = wc.getURL();
|
|
15895
15924
|
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
15896
15925
|
}
|
|
15897
15926
|
if (formInfo.submitted) {
|
|
15898
|
-
await waitForPotentialNavigation(
|
|
15899
|
-
const afterUrl =
|
|
15927
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
15928
|
+
const afterUrl = wc.getURL();
|
|
15900
15929
|
if (afterUrl !== beforeUrl) {
|
|
15901
15930
|
return `Submitted form via ${formInfo.method} -> ${afterUrl}`;
|
|
15902
15931
|
}
|
|
15903
15932
|
await executePageScript(
|
|
15904
|
-
|
|
15933
|
+
wc,
|
|
15905
15934
|
`
|
|
15906
15935
|
(function() {
|
|
15907
15936
|
var active = document.activeElement;
|
|
@@ -15922,29 +15951,29 @@ async function submitForm(wc2, args) {
|
|
|
15922
15951
|
label: "submit form enter fallback"
|
|
15923
15952
|
}
|
|
15924
15953
|
);
|
|
15925
|
-
|
|
15954
|
+
wc.sendInputEvent({ type: "keyDown", keyCode: "Return" });
|
|
15926
15955
|
await new Promise((r) => setTimeout(r, 50));
|
|
15927
|
-
|
|
15928
|
-
await waitForPotentialNavigation(
|
|
15929
|
-
const finalUrl =
|
|
15956
|
+
wc.sendInputEvent({ type: "keyUp", keyCode: "Return" });
|
|
15957
|
+
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
15958
|
+
const finalUrl = wc.getURL();
|
|
15930
15959
|
return finalUrl !== beforeUrl ? `Submitted form (Enter fallback) -> ${finalUrl}` : `Submitted form via ${formInfo.method} (page may have updated dynamically)`;
|
|
15931
15960
|
}
|
|
15932
15961
|
return "Submitted form";
|
|
15933
15962
|
}
|
|
15934
|
-
async function pressKeyDirect(
|
|
15935
|
-
return pressKey(
|
|
15963
|
+
async function pressKeyDirect(wc, key, index, selector) {
|
|
15964
|
+
return pressKey(wc, { key, index, selector });
|
|
15936
15965
|
}
|
|
15937
|
-
async function submitFormDirect(
|
|
15938
|
-
return submitForm(
|
|
15966
|
+
async function submitFormDirect(wc, index, selector) {
|
|
15967
|
+
return submitForm(wc, { index, selector });
|
|
15939
15968
|
}
|
|
15940
|
-
async function selectOptionDirect(
|
|
15941
|
-
return selectOption(
|
|
15969
|
+
async function selectOptionDirect(wc, index, selector, label, value) {
|
|
15970
|
+
return selectOption(wc, { index, selector, label, value });
|
|
15942
15971
|
}
|
|
15943
|
-
async function waitForConditionDirect(
|
|
15944
|
-
return waitForCondition(
|
|
15972
|
+
async function waitForConditionDirect(wc, text, selector, timeoutMs) {
|
|
15973
|
+
return waitForCondition(wc, { text, selector, timeoutMs });
|
|
15945
15974
|
}
|
|
15946
|
-
async function clickElementBySelector(
|
|
15947
|
-
return clickResolvedSelector(
|
|
15975
|
+
async function clickElementBySelector(wc, selector) {
|
|
15976
|
+
return clickResolvedSelector(wc, selector);
|
|
15948
15977
|
}
|
|
15949
15978
|
function normalizeSearchQuery(query) {
|
|
15950
15979
|
return query.replace(/\s+/g, " ").trim();
|
|
@@ -16016,12 +16045,12 @@ function buildDefaultEngineShortcut(rawQuery) {
|
|
|
16016
16045
|
appliedFilters: []
|
|
16017
16046
|
};
|
|
16018
16047
|
}
|
|
16019
|
-
async function locateSearchTarget(
|
|
16048
|
+
async function locateSearchTarget(wc, explicitSelector) {
|
|
16020
16049
|
if (explicitSelector) {
|
|
16021
16050
|
return { selector: explicitSelector, submitSelector: null };
|
|
16022
16051
|
}
|
|
16023
16052
|
return executePageScript(
|
|
16024
|
-
|
|
16053
|
+
wc,
|
|
16025
16054
|
`
|
|
16026
16055
|
(function() {
|
|
16027
16056
|
function text(value) {
|
|
@@ -16200,7 +16229,7 @@ async function locateSearchTarget(wc2, explicitSelector) {
|
|
|
16200
16229
|
}
|
|
16201
16230
|
);
|
|
16202
16231
|
}
|
|
16203
|
-
async function searchPage(
|
|
16232
|
+
async function searchPage(wc, args) {
|
|
16204
16233
|
const query = String(args.query || "");
|
|
16205
16234
|
if (!query) return "Error: No search query provided.";
|
|
16206
16235
|
const queryLower = query.toLowerCase().trim();
|
|
@@ -16233,26 +16262,26 @@ async function searchPage(wc2, args) {
|
|
|
16233
16262
|
if (buttonLikePatterns.some((p) => queryLower.includes(p))) {
|
|
16234
16263
|
return `Error: "${query}" looks like a button label, not a search query. Use the click tool to interact with this element instead.`;
|
|
16235
16264
|
}
|
|
16236
|
-
if (looksLikeCurrentSiteNameQuery(query,
|
|
16237
|
-
return `Error: "${query}" looks like the current site's name, not a product query. You are already on ${
|
|
16265
|
+
if (looksLikeCurrentSiteNameQuery(query, wc.getURL(), wc.getTitle() || "")) {
|
|
16266
|
+
return `Error: "${query}" looks like the current site's name, not a product query. You are already on ${wc.getURL()}. Open a section like staff picks/new releases or search for actual book titles, authors, or genres instead.`;
|
|
16238
16267
|
}
|
|
16239
16268
|
const runShortcut = async (shortcut) => {
|
|
16240
|
-
const beforeUrl2 =
|
|
16241
|
-
await loadPermittedUrl(
|
|
16242
|
-
await waitForPotentialNavigation(
|
|
16243
|
-
const afterUrl2 =
|
|
16269
|
+
const beforeUrl2 = wc.getURL();
|
|
16270
|
+
await loadPermittedUrl(wc, shortcut.url);
|
|
16271
|
+
await waitForPotentialNavigation(wc, beforeUrl2, 4e3);
|
|
16272
|
+
const afterUrl2 = wc.getURL();
|
|
16244
16273
|
const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
|
|
16245
16274
|
const destination = shortcut.section ? ` ${shortcut.section}` : "";
|
|
16246
|
-
return `Searched "${query}" via ${shortcut.source}${destination} shortcut${applied} → ${afterUrl2}${await getPostSearchSummary(
|
|
16275
|
+
return `Searched "${query}" via ${shortcut.source}${destination} shortcut${applied} → ${afterUrl2}${await getPostSearchSummary(wc)}`;
|
|
16247
16276
|
};
|
|
16248
16277
|
if (typeof args.selector !== "string") {
|
|
16249
|
-
const shortcut = buildHuggingFaceSearchShortcut(
|
|
16278
|
+
const shortcut = buildHuggingFaceSearchShortcut(wc.getURL(), query) ?? buildCommonSearchUrlShortcut(wc.getURL(), query);
|
|
16250
16279
|
if (shortcut) {
|
|
16251
16280
|
return runShortcut(shortcut);
|
|
16252
16281
|
}
|
|
16253
16282
|
}
|
|
16254
16283
|
const searchInfo = await locateSearchTarget(
|
|
16255
|
-
|
|
16284
|
+
wc,
|
|
16256
16285
|
typeof args.selector === "string" ? args.selector : void 0
|
|
16257
16286
|
);
|
|
16258
16287
|
if (searchInfo === PAGE_SCRIPT_TIMEOUT) {
|
|
@@ -16267,42 +16296,42 @@ async function searchPage(wc2, args) {
|
|
|
16267
16296
|
}
|
|
16268
16297
|
return 'Error: Could not find a visible search input. Try read_page(mode="visible_only") or provide a selector.';
|
|
16269
16298
|
}
|
|
16270
|
-
const fillResult = await setElementValue(
|
|
16299
|
+
const fillResult = await setElementValue(wc, searchInfo.selector, query);
|
|
16271
16300
|
if (fillResult.startsWith("Error:")) {
|
|
16272
16301
|
return fillResult;
|
|
16273
16302
|
}
|
|
16274
16303
|
await sleep(100);
|
|
16275
|
-
const beforeUrl =
|
|
16276
|
-
const keyResult = await pressKey(
|
|
16304
|
+
const beforeUrl = wc.getURL();
|
|
16305
|
+
const keyResult = await pressKey(wc, {
|
|
16277
16306
|
key: "Enter",
|
|
16278
16307
|
selector: searchInfo.selector
|
|
16279
16308
|
});
|
|
16280
16309
|
if (keyResult.startsWith("Error:")) {
|
|
16281
16310
|
return keyResult;
|
|
16282
16311
|
}
|
|
16283
|
-
await waitForPotentialNavigation(
|
|
16284
|
-
let afterUrl =
|
|
16312
|
+
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
16313
|
+
let afterUrl = wc.getURL();
|
|
16285
16314
|
if (afterUrl !== beforeUrl) {
|
|
16286
|
-
return `Searched "${query}" → ${afterUrl}${await getPostSearchSummary(
|
|
16315
|
+
return `Searched "${query}" → ${afterUrl}${await getPostSearchSummary(wc)}`;
|
|
16287
16316
|
}
|
|
16288
16317
|
if (searchInfo.submitSelector) {
|
|
16289
|
-
const clickResult = await clickElementBySelector(
|
|
16318
|
+
const clickResult = await clickElementBySelector(wc, searchInfo.submitSelector);
|
|
16290
16319
|
if (!clickResult.startsWith("Error:")) {
|
|
16291
|
-
await waitForPotentialNavigation(
|
|
16292
|
-
afterUrl =
|
|
16320
|
+
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
16321
|
+
afterUrl = wc.getURL();
|
|
16293
16322
|
if (afterUrl !== beforeUrl) {
|
|
16294
|
-
return `Searched "${query}" (via search button) → ${afterUrl}${await getPostSearchSummary(
|
|
16323
|
+
return `Searched "${query}" (via search button) → ${afterUrl}${await getPostSearchSummary(wc)}`;
|
|
16295
16324
|
}
|
|
16296
16325
|
}
|
|
16297
16326
|
}
|
|
16298
|
-
return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(
|
|
16327
|
+
return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
|
|
16299
16328
|
}
|
|
16300
|
-
async function pressKey(
|
|
16329
|
+
async function pressKey(wc, args) {
|
|
16301
16330
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
16302
16331
|
if (!key) return "Error: No key provided";
|
|
16303
|
-
const selector = await resolveSelector(
|
|
16332
|
+
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
16304
16333
|
const focusResult = await executePageScript(
|
|
16305
|
-
|
|
16334
|
+
wc,
|
|
16306
16335
|
`
|
|
16307
16336
|
(function() {
|
|
16308
16337
|
const selector = ${JSON.stringify(selector)};
|
|
@@ -16336,22 +16365,22 @@ async function pressKey(wc2, args) {
|
|
|
16336
16365
|
if ("error" in focusResult && typeof focusResult.error === "string") {
|
|
16337
16366
|
return focusResult.error;
|
|
16338
16367
|
}
|
|
16339
|
-
|
|
16368
|
+
wc.focus();
|
|
16340
16369
|
const normalizedKey = key.length === 1 ? key : key[0].toUpperCase() + key.slice(1);
|
|
16341
16370
|
const electronKeyCode = normalizedKey === "Enter" ? "Return" : normalizedKey === "ArrowUp" ? "Up" : normalizedKey === "ArrowDown" ? "Down" : normalizedKey === "ArrowLeft" ? "Left" : normalizedKey === "ArrowRight" ? "Right" : normalizedKey;
|
|
16342
|
-
|
|
16371
|
+
wc.sendInputEvent({ type: "keyDown", keyCode: electronKeyCode });
|
|
16343
16372
|
if (key.length === 1) {
|
|
16344
|
-
|
|
16373
|
+
wc.sendInputEvent({ type: "char", keyCode: key });
|
|
16345
16374
|
}
|
|
16346
16375
|
await sleep(16);
|
|
16347
|
-
|
|
16376
|
+
wc.sendInputEvent({ type: "keyUp", keyCode: electronKeyCode });
|
|
16348
16377
|
const label = "label" in focusResult && typeof focusResult.label === "string" ? focusResult.label : null;
|
|
16349
16378
|
return label ? `Pressed key: ${key} on ${label}` : `Pressed key: ${key}`;
|
|
16350
16379
|
}
|
|
16351
16380
|
async function getPostActionState$1(ctx, name) {
|
|
16352
16381
|
const tab = ctx.tabManager.getActiveTab();
|
|
16353
16382
|
if (!tab) return "";
|
|
16354
|
-
const
|
|
16383
|
+
const wc = tab.view.webContents;
|
|
16355
16384
|
const navActions = [
|
|
16356
16385
|
"navigate",
|
|
16357
16386
|
"open_bookmark",
|
|
@@ -16381,10 +16410,10 @@ async function getPostActionState$1(ctx, name) {
|
|
|
16381
16410
|
"load_session"
|
|
16382
16411
|
];
|
|
16383
16412
|
if (navActions.includes(name)) {
|
|
16384
|
-
if (
|
|
16385
|
-
await waitForLoad(
|
|
16413
|
+
if (wc.isLoading()) {
|
|
16414
|
+
await waitForLoad(wc);
|
|
16386
16415
|
}
|
|
16387
|
-
const currentUrl =
|
|
16416
|
+
const currentUrl = wc.getURL();
|
|
16388
16417
|
let warnings = "";
|
|
16389
16418
|
if (isProductAlreadyInCart(currentUrl)) {
|
|
16390
16419
|
warnings += `
|
|
@@ -16419,12 +16448,12 @@ WARNING: The clicked link appears to be a filter or sort control, not a product.
|
|
|
16419
16448
|
}
|
|
16420
16449
|
}
|
|
16421
16450
|
return `
|
|
16422
|
-
[state: url=${currentUrl}, title=${JSON.stringify(
|
|
16451
|
+
[state: url=${currentUrl}, title=${JSON.stringify(wc.getTitle() || "")}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]${warnings}`;
|
|
16423
16452
|
}
|
|
16424
|
-
if (name === "click" && !
|
|
16453
|
+
if (name === "click" && !wc.isLoading()) {
|
|
16425
16454
|
try {
|
|
16426
16455
|
const emptyPage = await executePageScript(
|
|
16427
|
-
|
|
16456
|
+
wc,
|
|
16428
16457
|
`(function() {
|
|
16429
16458
|
var body = (document.body.textContent || '').toLowerCase();
|
|
16430
16459
|
return /\b(no results|no items found|nothing matched|0 results|zero results|no products|your search.*did not match|no books found)\b/.test(body)
|
|
@@ -16434,7 +16463,7 @@ WARNING: The clicked link appears to be a filter or sort control, not a product.
|
|
|
16434
16463
|
);
|
|
16435
16464
|
if (emptyPage && emptyPage !== PAGE_SCRIPT_TIMEOUT) {
|
|
16436
16465
|
return `
|
|
16437
|
-
[state: url=${
|
|
16466
|
+
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=false]
|
|
16438
16467
|
WARNING: This page shows no results. You likely clicked a filter or category link instead of a product. Call go_back to return to the search results.`;
|
|
16439
16468
|
}
|
|
16440
16469
|
} catch {
|
|
@@ -16442,7 +16471,7 @@ WARNING: This page shows no results. You likely clicked a filter or category lin
|
|
|
16442
16471
|
}
|
|
16443
16472
|
if (interactActions.includes(name)) {
|
|
16444
16473
|
return `
|
|
16445
|
-
[state: url=${
|
|
16474
|
+
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${ctx.tabManager.getActiveTabId()}]`;
|
|
16446
16475
|
}
|
|
16447
16476
|
if (tabActions.includes(name)) {
|
|
16448
16477
|
const activeId = ctx.tabManager.getActiveTabId();
|
|
@@ -16553,7 +16582,7 @@ async function executeAction(name, args, ctx) {
|
|
|
16553
16582
|
if (isToolGated(name)) {
|
|
16554
16583
|
return `This tool (${name}) requires Vessel Premium. Upgrade at Settings > Premium to unlock screenshot, session management, workflow tracking, and more.`;
|
|
16555
16584
|
}
|
|
16556
|
-
const
|
|
16585
|
+
const wc = tab?.view.webContents;
|
|
16557
16586
|
const result = await ctx.runtime.runControlledAction({
|
|
16558
16587
|
source: "ai",
|
|
16559
16588
|
name,
|
|
@@ -16563,13 +16592,13 @@ async function executeAction(name, args, ctx) {
|
|
|
16563
16592
|
executor: async () => {
|
|
16564
16593
|
switch (name) {
|
|
16565
16594
|
case "screenshot": {
|
|
16566
|
-
if (!
|
|
16595
|
+
if (!wc) return "Error: No active tab";
|
|
16567
16596
|
const screenshotStart = Date.now();
|
|
16568
|
-
const shot = await captureScreenshot(
|
|
16597
|
+
const shot = await captureScreenshot(wc);
|
|
16569
16598
|
if (!shot.ok) return `Error: ${shot.error}`;
|
|
16570
16599
|
const screenshotMs = Date.now() - screenshotStart;
|
|
16571
|
-
const title =
|
|
16572
|
-
const url =
|
|
16600
|
+
const title = wc.getTitle() || "(untitled)";
|
|
16601
|
+
const url = wc.getURL();
|
|
16573
16602
|
return makeImageResult(
|
|
16574
16603
|
shot.base64,
|
|
16575
16604
|
`Screenshot of "${title}" (${url}) — ${shot.width}x${shot.height}, captured in ${screenshotMs}ms. Analyze the image to understand the current visual state of the page.`
|
|
@@ -16626,7 +16655,7 @@ async function executeAction(name, args, ctx) {
|
|
|
16626
16655
|
return `Created tab ${createdId}`;
|
|
16627
16656
|
}
|
|
16628
16657
|
case "navigate": {
|
|
16629
|
-
if (!
|
|
16658
|
+
if (!wc || !tabId) return "Error: No active tab";
|
|
16630
16659
|
const taskGoal = ctx.runtime.getState().taskTracker?.goal;
|
|
16631
16660
|
if (taskGoal && typeof args.url === "string") {
|
|
16632
16661
|
const domainDrift = shouldBlockOffGoalDomainNavigation(
|
|
@@ -16637,8 +16666,8 @@ async function executeAction(name, args, ctx) {
|
|
|
16637
16666
|
return `Navigation blocked: ${args.url} drifts away from the requested site ${domainDrift.requestedDomain}. Stay on the requested domain and continue the original task there.`;
|
|
16638
16667
|
}
|
|
16639
16668
|
}
|
|
16640
|
-
if (typeof args.url === "string" && !args.postBody && isRedundantNavigateTarget(
|
|
16641
|
-
return `Already on ${
|
|
16669
|
+
if (typeof args.url === "string" && !args.postBody && isRedundantNavigateTarget(wc.getURL(), args.url)) {
|
|
16670
|
+
return `Already on ${wc.getURL()}. Do not navigate to the same URL again. Use click, inspect_element, read_page, or search for actual book terms instead.`;
|
|
16642
16671
|
}
|
|
16643
16672
|
const navValidation = await validateLinkDestination(args.url);
|
|
16644
16673
|
if (navValidation.status === "dead") {
|
|
@@ -16646,74 +16675,74 @@ async function executeAction(name, args, ctx) {
|
|
|
16646
16675
|
}
|
|
16647
16676
|
const navError = ctx.tabManager.navigateTab(tabId, args.url, args.postBody);
|
|
16648
16677
|
if (navError) return navError;
|
|
16649
|
-
await waitForLoad(
|
|
16650
|
-
return `Navigated to ${
|
|
16678
|
+
await waitForLoad(wc);
|
|
16679
|
+
return `Navigated to ${wc.getURL()}${await getPostNavSummary(wc)}`;
|
|
16651
16680
|
}
|
|
16652
16681
|
case "go_back": {
|
|
16653
|
-
if (!tab || !
|
|
16682
|
+
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
16654
16683
|
if (!tab.canGoBack()) {
|
|
16655
16684
|
return "No previous page in history";
|
|
16656
16685
|
}
|
|
16657
|
-
const beforeUrl =
|
|
16686
|
+
const beforeUrl = wc.getURL();
|
|
16658
16687
|
ctx.tabManager.goBack(tabId);
|
|
16659
|
-
await waitForLoad(
|
|
16660
|
-
const afterUrl =
|
|
16661
|
-
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${await getPostNavSummary(
|
|
16688
|
+
await waitForLoad(wc);
|
|
16689
|
+
const afterUrl = wc.getURL();
|
|
16690
|
+
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${await getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
16662
16691
|
}
|
|
16663
16692
|
case "go_forward": {
|
|
16664
|
-
if (!tab || !
|
|
16693
|
+
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
16665
16694
|
if (!tab.canGoForward()) {
|
|
16666
16695
|
return "No forward page in history";
|
|
16667
16696
|
}
|
|
16668
|
-
const beforeUrl =
|
|
16697
|
+
const beforeUrl = wc.getURL();
|
|
16669
16698
|
ctx.tabManager.goForward(tabId);
|
|
16670
|
-
await waitForLoad(
|
|
16671
|
-
const afterUrl =
|
|
16672
|
-
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${await getPostNavSummary(
|
|
16699
|
+
await waitForLoad(wc);
|
|
16700
|
+
const afterUrl = wc.getURL();
|
|
16701
|
+
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${await getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
16673
16702
|
}
|
|
16674
16703
|
case "reload": {
|
|
16675
|
-
if (!
|
|
16704
|
+
if (!wc || !tabId) return "Error: No active tab";
|
|
16676
16705
|
ctx.tabManager.reloadTab(tabId);
|
|
16677
|
-
await waitForLoad(
|
|
16678
|
-
return `Reloaded ${
|
|
16706
|
+
await waitForLoad(wc);
|
|
16707
|
+
return `Reloaded ${wc.getURL()}`;
|
|
16679
16708
|
}
|
|
16680
16709
|
case "click": {
|
|
16681
|
-
if (!
|
|
16710
|
+
if (!wc) return "Error: No active tab";
|
|
16682
16711
|
let selector = null;
|
|
16683
16712
|
const textTarget = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
|
|
16684
16713
|
if (typeof args.selector === "string" && args.selector.trim()) {
|
|
16685
|
-
selector = await resolveSelector(
|
|
16714
|
+
selector = await resolveSelector(wc, void 0, args.selector);
|
|
16686
16715
|
} else if (textTarget) {
|
|
16687
16716
|
if (isInvalidTextTargetQuery(textTarget)) {
|
|
16688
16717
|
return `Error: "${textTarget}" looks like HTML or markup, not a visible page label. Use a book title, button text, or element index instead.`;
|
|
16689
16718
|
}
|
|
16690
|
-
selector = await resolveTargetByText(
|
|
16719
|
+
selector = await resolveTargetByText(wc, textTarget, "interactive");
|
|
16691
16720
|
if (!selector && typeof args.index === "number") {
|
|
16692
16721
|
selector = `__vessel_idx:${args.index}`;
|
|
16693
16722
|
}
|
|
16694
16723
|
} else if (typeof args.index === "number") {
|
|
16695
|
-
selector = await resolveSelector(
|
|
16724
|
+
selector = await resolveSelector(wc, args.index);
|
|
16696
16725
|
if (!selector) selector = `__vessel_idx:${args.index}`;
|
|
16697
16726
|
} else {
|
|
16698
|
-
selector = await resolveSelector(
|
|
16727
|
+
selector = await resolveSelector(wc, args.index, args.selector);
|
|
16699
16728
|
}
|
|
16700
16729
|
if (selector === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
16701
16730
|
if (!selector) {
|
|
16702
16731
|
return "Error: No element index, selector, or visible text provided";
|
|
16703
16732
|
}
|
|
16704
|
-
return clickResolvedSelector(
|
|
16733
|
+
return clickResolvedSelector(wc, selector);
|
|
16705
16734
|
}
|
|
16706
16735
|
case "inspect_element": {
|
|
16707
|
-
if (!
|
|
16736
|
+
if (!wc) return "Error: No active tab";
|
|
16708
16737
|
let selector = null;
|
|
16709
16738
|
const textTarget = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
|
|
16710
16739
|
if (textTarget) {
|
|
16711
16740
|
if (isInvalidTextTargetQuery(textTarget)) {
|
|
16712
16741
|
return `Error: "${textTarget}" looks like HTML or markup, not visible page text. Use a section title, book title, or element index instead.`;
|
|
16713
16742
|
}
|
|
16714
|
-
selector = await resolveTargetByText(
|
|
16743
|
+
selector = await resolveTargetByText(wc, textTarget, "context");
|
|
16715
16744
|
} else {
|
|
16716
|
-
selector = await resolveSelector(
|
|
16745
|
+
selector = await resolveSelector(wc, args.index, args.selector);
|
|
16717
16746
|
}
|
|
16718
16747
|
if (selector === PAGE_SCRIPT_TIMEOUT) {
|
|
16719
16748
|
return pageBusyError("inspect_element");
|
|
@@ -16722,44 +16751,44 @@ async function executeAction(name, args, ctx) {
|
|
|
16722
16751
|
return "Error: No element index, selector, or visible text provided";
|
|
16723
16752
|
}
|
|
16724
16753
|
return inspectElement(
|
|
16725
|
-
|
|
16754
|
+
wc,
|
|
16726
16755
|
selector,
|
|
16727
16756
|
typeof args.limit === "number" ? args.limit : 8
|
|
16728
16757
|
);
|
|
16729
16758
|
}
|
|
16730
16759
|
case "type_text": {
|
|
16731
|
-
if (!
|
|
16732
|
-
const selector = await resolveSelector(
|
|
16760
|
+
if (!wc) return "Error: No active tab";
|
|
16761
|
+
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
16733
16762
|
if (!selector) return "Error: No element index or selector provided";
|
|
16734
16763
|
const mode = typeof args.mode === "string" ? args.mode : "default";
|
|
16735
16764
|
if (mode === "keystroke") {
|
|
16736
|
-
return typeKeystroke(
|
|
16765
|
+
return typeKeystroke(wc, selector, String(args.text || ""));
|
|
16737
16766
|
}
|
|
16738
|
-
return setElementValue(
|
|
16767
|
+
return setElementValue(wc, selector, String(args.text || ""));
|
|
16739
16768
|
}
|
|
16740
16769
|
case "select_option": {
|
|
16741
|
-
if (!
|
|
16742
|
-
return selectOption(
|
|
16770
|
+
if (!wc) return "Error: No active tab";
|
|
16771
|
+
return selectOption(wc, args);
|
|
16743
16772
|
}
|
|
16744
16773
|
case "submit_form": {
|
|
16745
|
-
if (!
|
|
16746
|
-
const beforeUrl =
|
|
16747
|
-
const result2 = await submitForm(
|
|
16774
|
+
if (!wc) return "Error: No active tab";
|
|
16775
|
+
const beforeUrl = wc.getURL();
|
|
16776
|
+
const result2 = await submitForm(wc, args);
|
|
16748
16777
|
if (result2.startsWith("Error") || result2.startsWith("Target") || result2.startsWith("No parent") || result2.startsWith("Submit control")) {
|
|
16749
16778
|
return result2;
|
|
16750
16779
|
}
|
|
16751
|
-
await waitForPotentialNavigation(
|
|
16752
|
-
const afterUrl =
|
|
16780
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
16781
|
+
const afterUrl = wc.getURL();
|
|
16753
16782
|
return afterUrl !== beforeUrl ? `${result2} -> ${afterUrl}` : result2;
|
|
16754
16783
|
}
|
|
16755
16784
|
case "press_key": {
|
|
16756
|
-
if (!
|
|
16757
|
-
const beforeUrl =
|
|
16758
|
-
const result2 = await pressKey(
|
|
16785
|
+
if (!wc) return "Error: No active tab";
|
|
16786
|
+
const beforeUrl = wc.getURL();
|
|
16787
|
+
const result2 = await pressKey(wc, args);
|
|
16759
16788
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
16760
16789
|
if (key === "Enter") {
|
|
16761
|
-
await waitForPotentialNavigation(
|
|
16762
|
-
const afterUrl =
|
|
16790
|
+
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
16791
|
+
const afterUrl = wc.getURL();
|
|
16763
16792
|
if (afterUrl !== beforeUrl) {
|
|
16764
16793
|
return `${result2} -> ${afterUrl}`;
|
|
16765
16794
|
}
|
|
@@ -16769,23 +16798,23 @@ async function executeAction(name, args, ctx) {
|
|
|
16769
16798
|
return result2;
|
|
16770
16799
|
}
|
|
16771
16800
|
case "scroll": {
|
|
16772
|
-
if (!
|
|
16801
|
+
if (!wc) return "Error: No active tab";
|
|
16773
16802
|
const pixels = coerceOptionalNumber(args.amount) ?? 500;
|
|
16774
16803
|
const dir = args.direction === "up" ? -pixels : pixels;
|
|
16775
|
-
const result2 = await scrollPage(
|
|
16804
|
+
const result2 = await scrollPage(wc, dir);
|
|
16776
16805
|
return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
|
|
16777
16806
|
}
|
|
16778
16807
|
case "hover": {
|
|
16779
|
-
if (!
|
|
16780
|
-
const selector = await resolveSelector(
|
|
16808
|
+
if (!wc) return "Error: No active tab";
|
|
16809
|
+
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
16781
16810
|
if (!selector) return "Error: No element index or selector provided";
|
|
16782
|
-
return hoverElement(
|
|
16811
|
+
return hoverElement(wc, selector);
|
|
16783
16812
|
}
|
|
16784
16813
|
case "focus": {
|
|
16785
|
-
if (!
|
|
16786
|
-
const selector = await resolveSelector(
|
|
16814
|
+
if (!wc) return "Error: No active tab";
|
|
16815
|
+
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
16787
16816
|
if (!selector) return "Error: No element index or selector provided";
|
|
16788
|
-
return focusElement(
|
|
16817
|
+
return focusElement(wc, selector);
|
|
16789
16818
|
}
|
|
16790
16819
|
case "set_ad_blocking": {
|
|
16791
16820
|
const enabled = typeof args.enabled === "boolean" ? args.enabled : null;
|
|
@@ -16812,24 +16841,24 @@ async function executeAction(name, args, ctx) {
|
|
|
16812
16841
|
return `${enabled ? "Enabled" : "Disabled"} ad blocking for "${state2.title}"${shouldReload ? " and reloaded the tab" : ""}`;
|
|
16813
16842
|
}
|
|
16814
16843
|
case "dismiss_popup": {
|
|
16815
|
-
if (!
|
|
16816
|
-
return dismissPopup(
|
|
16844
|
+
if (!wc) return "Error: No active tab";
|
|
16845
|
+
return dismissPopup(wc);
|
|
16817
16846
|
}
|
|
16818
16847
|
case "clear_overlays": {
|
|
16819
|
-
if (!
|
|
16848
|
+
if (!wc) return "Error: No active tab";
|
|
16820
16849
|
const strategy = args.strategy === "interactive" ? "interactive" : "auto";
|
|
16821
|
-
return clearOverlays(
|
|
16850
|
+
return clearOverlays(wc, strategy);
|
|
16822
16851
|
}
|
|
16823
16852
|
case "read_page": {
|
|
16824
|
-
if (!
|
|
16853
|
+
if (!wc) return "Error: No active tab";
|
|
16825
16854
|
const requestedGlance = typeof args.mode === "string" && args.mode.trim().toLowerCase() === "glance";
|
|
16826
16855
|
if (requestedGlance) {
|
|
16827
|
-
return glanceExtract(
|
|
16856
|
+
return glanceExtract(wc);
|
|
16828
16857
|
}
|
|
16829
16858
|
let content = null;
|
|
16830
16859
|
try {
|
|
16831
16860
|
content = await Promise.race([
|
|
16832
|
-
extractContent(
|
|
16861
|
+
extractContent(wc),
|
|
16833
16862
|
new Promise(
|
|
16834
16863
|
(resolve) => setTimeout(() => {
|
|
16835
16864
|
resolve(null);
|
|
@@ -16843,14 +16872,14 @@ async function executeAction(name, args, ctx) {
|
|
|
16843
16872
|
if (!content || content.content.length === 0) {
|
|
16844
16873
|
try {
|
|
16845
16874
|
const iframeResult = await Promise.race([
|
|
16846
|
-
tryDismissConsentIframe(
|
|
16875
|
+
tryDismissConsentIframe(wc),
|
|
16847
16876
|
new Promise((resolve) => setTimeout(() => resolve(null), 2e3))
|
|
16848
16877
|
]);
|
|
16849
16878
|
if (iframeResult) {
|
|
16850
16879
|
await sleep(500);
|
|
16851
16880
|
try {
|
|
16852
16881
|
content = await Promise.race([
|
|
16853
|
-
extractContent(
|
|
16882
|
+
extractContent(wc),
|
|
16854
16883
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
16855
16884
|
]);
|
|
16856
16885
|
} catch (err) {
|
|
@@ -16865,7 +16894,7 @@ async function executeAction(name, args, ctx) {
|
|
|
16865
16894
|
if (content && content.content.length > 0) {
|
|
16866
16895
|
const liveSelectionSection = formatLiveSelectionSection(
|
|
16867
16896
|
await captureLiveHighlightSnapshot(
|
|
16868
|
-
|
|
16897
|
+
wc,
|
|
16869
16898
|
getHighlightsForUrl(content.url)
|
|
16870
16899
|
)
|
|
16871
16900
|
);
|
|
@@ -16895,20 +16924,20 @@ ${truncated}`;
|
|
|
16895
16924
|
`Need more detail? Escalate with read_page(mode="debug") only if the narrow modes are insufficient.`
|
|
16896
16925
|
].filter(Boolean).join("\n\n");
|
|
16897
16926
|
}
|
|
16898
|
-
return glanceExtract(
|
|
16927
|
+
return glanceExtract(wc);
|
|
16899
16928
|
}
|
|
16900
16929
|
case "wait_for": {
|
|
16901
|
-
if (!
|
|
16902
|
-
return waitForCondition(
|
|
16930
|
+
if (!wc) return "Error: No active tab";
|
|
16931
|
+
return waitForCondition(wc, args);
|
|
16903
16932
|
}
|
|
16904
16933
|
case "wait_for_navigation": {
|
|
16905
|
-
if (!
|
|
16934
|
+
if (!wc) return "Error: No active tab";
|
|
16906
16935
|
const timeout = typeof args.timeoutMs === "number" ? args.timeoutMs : 1e4;
|
|
16907
|
-
const beforeUrl =
|
|
16908
|
-
if (
|
|
16936
|
+
const beforeUrl = wc.getURL();
|
|
16937
|
+
if (wc.isLoading()) {
|
|
16909
16938
|
await new Promise((resolve) => {
|
|
16910
16939
|
const timer = setTimeout(resolve, timeout);
|
|
16911
|
-
|
|
16940
|
+
wc.once("did-stop-loading", () => {
|
|
16912
16941
|
clearTimeout(timer);
|
|
16913
16942
|
resolve();
|
|
16914
16943
|
});
|
|
@@ -16922,19 +16951,19 @@ ${truncated}`;
|
|
|
16922
16951
|
},
|
|
16923
16952
|
Math.min(timeout, 2e3)
|
|
16924
16953
|
);
|
|
16925
|
-
|
|
16954
|
+
wc.once("did-start-loading", () => {
|
|
16926
16955
|
navigated = true;
|
|
16927
16956
|
clearTimeout(timer);
|
|
16928
16957
|
const loadTimer = setTimeout(resolve, timeout);
|
|
16929
|
-
|
|
16958
|
+
wc.once("did-stop-loading", () => {
|
|
16930
16959
|
clearTimeout(loadTimer);
|
|
16931
16960
|
resolve();
|
|
16932
16961
|
});
|
|
16933
16962
|
});
|
|
16934
16963
|
});
|
|
16935
16964
|
}
|
|
16936
|
-
const afterUrl =
|
|
16937
|
-
const title =
|
|
16965
|
+
const afterUrl = wc.getURL();
|
|
16966
|
+
const title = wc.getTitle();
|
|
16938
16967
|
if (afterUrl !== beforeUrl) {
|
|
16939
16968
|
return `Navigation complete: ${title} (${afterUrl})`;
|
|
16940
16969
|
}
|
|
@@ -17052,8 +17081,8 @@ ${truncated}`;
|
|
|
17052
17081
|
);
|
|
17053
17082
|
}
|
|
17054
17083
|
case "save_bookmark": {
|
|
17055
|
-
const resolvedSelector =
|
|
17056
|
-
const source = await resolveBookmarkSourceDraft(
|
|
17084
|
+
const resolvedSelector = wc && (typeof args.index === "number" || typeof args.selector === "string") ? await resolveSelector(wc, args.index, args.selector) : null;
|
|
17085
|
+
const source = await resolveBookmarkSourceDraft(wc, {
|
|
17057
17086
|
explicitUrl: args.url,
|
|
17058
17087
|
explicitTitle: args.title,
|
|
17059
17088
|
resolvedSelector
|
|
@@ -17096,8 +17125,8 @@ ${truncated}`;
|
|
|
17096
17125
|
if (target.error) return target.error;
|
|
17097
17126
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
17098
17127
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
17099
|
-
const resolvedSelector =
|
|
17100
|
-
const source = await resolveBookmarkSourceDraft(
|
|
17128
|
+
const resolvedSelector = wc && (typeof args.index === "number" || typeof args.selector === "string") ? await resolveSelector(wc, args.index, args.selector) : null;
|
|
17129
|
+
const source = await resolveBookmarkSourceDraft(wc, {
|
|
17101
17130
|
explicitUrl: args.url,
|
|
17102
17131
|
explicitTitle: args.title,
|
|
17103
17132
|
resolvedSelector
|
|
@@ -17144,8 +17173,8 @@ ${truncated}`;
|
|
|
17144
17173
|
if (target.error) return target.error;
|
|
17145
17174
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
17146
17175
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
17147
|
-
const resolvedSelector =
|
|
17148
|
-
const source = await resolveBookmarkSourceDraft(
|
|
17176
|
+
const resolvedSelector = wc && (typeof args.index === "number" || typeof args.selector === "string") ? await resolveSelector(wc, args.index, args.selector) : null;
|
|
17177
|
+
const source = await resolveBookmarkSourceDraft(wc, {
|
|
17149
17178
|
explicitUrl: args.url,
|
|
17150
17179
|
explicitTitle: args.title,
|
|
17151
17180
|
resolvedSelector
|
|
@@ -17194,7 +17223,7 @@ ${truncated}`;
|
|
|
17194
17223
|
return formatDeadLinkMessage(bookmark.title, validation);
|
|
17195
17224
|
}
|
|
17196
17225
|
const openInNewTab = Boolean(args.newTab);
|
|
17197
|
-
if (openInNewTab || !tabId || !
|
|
17226
|
+
if (openInNewTab || !tabId || !wc) {
|
|
17198
17227
|
const createdId = ctx.tabManager.createTab(bookmark.url);
|
|
17199
17228
|
const created = ctx.tabManager.getActiveTab();
|
|
17200
17229
|
if (created) {
|
|
@@ -17203,15 +17232,15 @@ ${truncated}`;
|
|
|
17203
17232
|
return `Opened bookmark "${bookmark.title}" in new tab ${createdId}`;
|
|
17204
17233
|
}
|
|
17205
17234
|
ctx.tabManager.navigateTab(tabId, bookmark.url);
|
|
17206
|
-
await waitForLoad(
|
|
17235
|
+
await waitForLoad(wc);
|
|
17207
17236
|
return `Opened bookmark "${bookmark.title}" in current tab`;
|
|
17208
17237
|
}
|
|
17209
17238
|
case "highlight": {
|
|
17210
|
-
if (!
|
|
17211
|
-
const selector = await resolveSelector(
|
|
17239
|
+
if (!wc) return "Error: No active tab";
|
|
17240
|
+
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
17212
17241
|
const highlightColor = args.color || "yellow";
|
|
17213
17242
|
const highlightText = normalizeLooseString(args.text);
|
|
17214
|
-
const url =
|
|
17243
|
+
const url = wc.getURL();
|
|
17215
17244
|
if (url && url !== "about:blank") {
|
|
17216
17245
|
addHighlight(
|
|
17217
17246
|
url,
|
|
@@ -17223,7 +17252,7 @@ ${truncated}`;
|
|
|
17223
17252
|
);
|
|
17224
17253
|
}
|
|
17225
17254
|
return highlightOnPage(
|
|
17226
|
-
|
|
17255
|
+
wc,
|
|
17227
17256
|
selector,
|
|
17228
17257
|
highlightText,
|
|
17229
17258
|
args.label,
|
|
@@ -17232,8 +17261,8 @@ ${truncated}`;
|
|
|
17232
17261
|
);
|
|
17233
17262
|
}
|
|
17234
17263
|
case "clear_highlights": {
|
|
17235
|
-
if (!
|
|
17236
|
-
return clearHighlights(
|
|
17264
|
+
if (!wc) return "Error: No active tab";
|
|
17265
|
+
return clearHighlights(wc);
|
|
17237
17266
|
}
|
|
17238
17267
|
// --- Speedee System ---
|
|
17239
17268
|
case "flow_start": {
|
|
@@ -17241,7 +17270,7 @@ ${truncated}`;
|
|
|
17241
17270
|
const steps = coerceStringArray(args.steps) ?? [];
|
|
17242
17271
|
if (!goal || steps.length === 0)
|
|
17243
17272
|
return "Error: goal and steps are required";
|
|
17244
|
-
const flow = ctx.runtime.startFlow(goal, steps,
|
|
17273
|
+
const flow = ctx.runtime.startFlow(goal, steps, wc?.getURL());
|
|
17245
17274
|
return `Flow started: ${flow.goal}
|
|
17246
17275
|
${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`;
|
|
17247
17276
|
}
|
|
@@ -17267,10 +17296,10 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
17267
17296
|
return `Undid action: ${undone}. Browser restored to state before that action.`;
|
|
17268
17297
|
}
|
|
17269
17298
|
case "suggest": {
|
|
17270
|
-
if (!
|
|
17299
|
+
if (!wc) return "No active tab. Use navigate to open a page.";
|
|
17271
17300
|
let page;
|
|
17272
17301
|
try {
|
|
17273
|
-
page = await extractContent(
|
|
17302
|
+
page = await extractContent(wc);
|
|
17274
17303
|
} catch (err) {
|
|
17275
17304
|
logger$b.warn("Failed to extract content for suggest:", err);
|
|
17276
17305
|
return "Could not read page. Try navigate to a working URL.";
|
|
@@ -17368,18 +17397,18 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
17368
17397
|
return suggestions.join("\n");
|
|
17369
17398
|
}
|
|
17370
17399
|
case "fill_form": {
|
|
17371
|
-
if (!
|
|
17400
|
+
if (!wc) return "Error: No active tab";
|
|
17372
17401
|
const fields = Array.isArray(args.fields) ? args.fields : [];
|
|
17373
17402
|
if (fields.length === 0) return "Error: No fields provided";
|
|
17374
|
-
const fillResults = await fillFormFields(
|
|
17403
|
+
const fillResults = await fillFormFields(wc, fields);
|
|
17375
17404
|
const results = fillResults.map((item) => item.result);
|
|
17376
17405
|
if (args.submit) {
|
|
17377
17406
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
17378
17407
|
if (firstSel) {
|
|
17379
|
-
const beforeUrl =
|
|
17380
|
-
const submitResult = await submitForm(
|
|
17381
|
-
await waitForPotentialNavigation(
|
|
17382
|
-
const afterUrl =
|
|
17408
|
+
const beforeUrl = wc.getURL();
|
|
17409
|
+
const submitResult = await submitForm(wc, { selector: firstSel });
|
|
17410
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
17411
|
+
const afterUrl = wc.getURL();
|
|
17383
17412
|
results.push(
|
|
17384
17413
|
afterUrl !== beforeUrl ? `Submitted → ${afterUrl}` : submitResult
|
|
17385
17414
|
);
|
|
@@ -17389,16 +17418,16 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
17389
17418
|
${results.join("\n")}`;
|
|
17390
17419
|
}
|
|
17391
17420
|
case "login": {
|
|
17392
|
-
if (!
|
|
17421
|
+
if (!wc) return "Error: No active tab";
|
|
17393
17422
|
const steps = [];
|
|
17394
17423
|
if (typeof args.url === "string" && args.url.trim()) {
|
|
17395
17424
|
const id = ctx.tabManager.getActiveTabId();
|
|
17396
17425
|
ctx.tabManager.navigateTab(id, args.url);
|
|
17397
|
-
await waitForLoad(
|
|
17398
|
-
steps.push(`Navigated to ${
|
|
17426
|
+
await waitForLoad(wc);
|
|
17427
|
+
steps.push(`Navigated to ${wc.getURL()}`);
|
|
17399
17428
|
}
|
|
17400
17429
|
const userSel = args.username_selector || await executePageScript(
|
|
17401
|
-
|
|
17430
|
+
wc,
|
|
17402
17431
|
`
|
|
17403
17432
|
(function() {
|
|
17404
17433
|
var el = document.querySelector('input[type="email"], input[name="email"], input[name="username"], input[name="user"], input[autocomplete="username"], input[autocomplete="email"], input[type="text"]:not([name="search"]):not([name="q"])');
|
|
@@ -17412,7 +17441,7 @@ ${results.join("\n")}`;
|
|
|
17412
17441
|
if (!userSel)
|
|
17413
17442
|
return "Error: Could not find username/email field. Try providing username_selector.";
|
|
17414
17443
|
const passSel = args.password_selector || await executePageScript(
|
|
17415
|
-
|
|
17444
|
+
wc,
|
|
17416
17445
|
`
|
|
17417
17446
|
(function() {
|
|
17418
17447
|
var el = document.querySelector('input[type="password"]');
|
|
@@ -17426,23 +17455,23 @@ ${results.join("\n")}`;
|
|
|
17426
17455
|
if (!passSel)
|
|
17427
17456
|
return "Error: Could not find password field. Try providing password_selector.";
|
|
17428
17457
|
const userResult = await setElementValue(
|
|
17429
|
-
|
|
17458
|
+
wc,
|
|
17430
17459
|
userSel,
|
|
17431
17460
|
String(args.username || "")
|
|
17432
17461
|
);
|
|
17433
17462
|
steps.push(userResult);
|
|
17434
17463
|
const passResult = await setElementValue(
|
|
17435
|
-
|
|
17464
|
+
wc,
|
|
17436
17465
|
passSel,
|
|
17437
17466
|
String(args.password || "")
|
|
17438
17467
|
);
|
|
17439
17468
|
steps.push(passResult);
|
|
17440
|
-
const beforeUrl =
|
|
17469
|
+
const beforeUrl = wc.getURL();
|
|
17441
17470
|
if (args.submit_selector) {
|
|
17442
|
-
await clickResolvedSelector(
|
|
17471
|
+
await clickResolvedSelector(wc, args.submit_selector);
|
|
17443
17472
|
} else {
|
|
17444
17473
|
const clicked = await executePageScript(
|
|
17445
|
-
|
|
17474
|
+
wc,
|
|
17446
17475
|
`
|
|
17447
17476
|
(function() {
|
|
17448
17477
|
var btn = document.querySelector('button[type="submit"], input[type="submit"], form button:not([type="button"])');
|
|
@@ -17462,8 +17491,8 @@ ${results.join("\n")}`;
|
|
|
17462
17491
|
if (!clicked)
|
|
17463
17492
|
return steps.join("\n") + "\nWarning: Could not find submit button. Credentials filled but form not submitted.";
|
|
17464
17493
|
}
|
|
17465
|
-
await waitForPotentialNavigation(
|
|
17466
|
-
const afterUrl =
|
|
17494
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
17495
|
+
const afterUrl = wc.getURL();
|
|
17467
17496
|
steps.push(
|
|
17468
17497
|
afterUrl !== beforeUrl ? `Submitted → ${afterUrl}` : "Form submitted (same page)"
|
|
17469
17498
|
);
|
|
@@ -17471,18 +17500,18 @@ ${results.join("\n")}`;
|
|
|
17471
17500
|
${steps.join("\n")}`;
|
|
17472
17501
|
}
|
|
17473
17502
|
case "search": {
|
|
17474
|
-
if (!
|
|
17475
|
-
return searchPage(
|
|
17503
|
+
if (!wc) return "Error: No active tab";
|
|
17504
|
+
return searchPage(wc, args);
|
|
17476
17505
|
}
|
|
17477
17506
|
case "paginate": {
|
|
17478
|
-
if (!
|
|
17479
|
-
const beforeUrl =
|
|
17507
|
+
if (!wc) return "Error: No active tab";
|
|
17508
|
+
const beforeUrl = wc.getURL();
|
|
17480
17509
|
if (args.selector) {
|
|
17481
|
-
return clickResolvedSelector(
|
|
17510
|
+
return clickResolvedSelector(wc, args.selector);
|
|
17482
17511
|
}
|
|
17483
17512
|
const isNext = args.direction === "next";
|
|
17484
17513
|
const clicked = await executePageScript(
|
|
17485
|
-
|
|
17514
|
+
wc,
|
|
17486
17515
|
`
|
|
17487
17516
|
(function() {
|
|
17488
17517
|
var patterns = ${isNext ? '["next", "Next", "›", "»", "→", ">", "Next Page", "Load More"]' : '["prev", "Prev", "Previous", "‹", "«", "←", "<", "Previous Page"]'};
|
|
@@ -17512,13 +17541,13 @@ ${steps.join("\n")}`;
|
|
|
17512
17541
|
}
|
|
17513
17542
|
if (!clicked)
|
|
17514
17543
|
return `Error: Could not find ${args.direction} pagination control. Try providing a selector.`;
|
|
17515
|
-
await waitForPotentialNavigation(
|
|
17516
|
-
const afterUrl =
|
|
17544
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
17545
|
+
const afterUrl = wc.getURL();
|
|
17517
17546
|
return afterUrl !== beforeUrl ? `Paginated ${args.direction} → ${afterUrl}` : `Clicked ${args.direction} (page may have updated dynamically)`;
|
|
17518
17547
|
}
|
|
17519
17548
|
case "accept_cookies": {
|
|
17520
|
-
if (!
|
|
17521
|
-
const dismissed = await tryAcceptCookiesQuickly(
|
|
17549
|
+
if (!wc) return "Error: No active tab";
|
|
17550
|
+
const dismissed = await tryAcceptCookiesQuickly(wc);
|
|
17522
17551
|
if (dismissed === PAGE_SCRIPT_TIMEOUT) {
|
|
17523
17552
|
return pageBusyError("accept_cookies");
|
|
17524
17553
|
}
|
|
@@ -17526,9 +17555,9 @@ ${steps.join("\n")}`;
|
|
|
17526
17555
|
return "No cookie consent banner detected. Try dismiss_popup for other overlays.";
|
|
17527
17556
|
}
|
|
17528
17557
|
case "extract_table": {
|
|
17529
|
-
if (!
|
|
17530
|
-
const selector = args.selector ? args.selector : args.index != null ? await resolveSelector(
|
|
17531
|
-
const tableJson = await
|
|
17558
|
+
if (!wc) return "Error: No active tab";
|
|
17559
|
+
const selector = args.selector ? args.selector : args.index != null ? await resolveSelector(wc, args.index, void 0) : null;
|
|
17560
|
+
const tableJson = await wc.executeJavaScript(`
|
|
17532
17561
|
(function() {
|
|
17533
17562
|
var table = ${selector ? `document.querySelector(${JSON.stringify(selector)})` : "document.querySelector('table')"};
|
|
17534
17563
|
if (!table) return null;
|
|
@@ -17577,16 +17606,16 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
17577
17606
|
return lines.join("\n");
|
|
17578
17607
|
}
|
|
17579
17608
|
case "scroll_to_element": {
|
|
17580
|
-
if (!
|
|
17609
|
+
if (!wc) return "Error: No active tab";
|
|
17581
17610
|
let sel = null;
|
|
17582
17611
|
const textTarget = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
|
|
17583
17612
|
if (textTarget) {
|
|
17584
17613
|
if (isInvalidTextTargetQuery(textTarget)) {
|
|
17585
17614
|
return `Error: "${textTarget}" looks like HTML or markup, not visible page text. Use a section title or element index instead.`;
|
|
17586
17615
|
}
|
|
17587
|
-
sel = await resolveTargetByText(
|
|
17616
|
+
sel = await resolveTargetByText(wc, textTarget, "context");
|
|
17588
17617
|
} else {
|
|
17589
|
-
sel = await resolveSelector(
|
|
17618
|
+
sel = await resolveSelector(wc, args.index, args.selector);
|
|
17590
17619
|
}
|
|
17591
17620
|
if (sel === PAGE_SCRIPT_TIMEOUT) return pageBusyError("scroll_to_element");
|
|
17592
17621
|
if (!sel) {
|
|
@@ -17595,7 +17624,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
17595
17624
|
const block = args.position === "top" ? "start" : args.position === "bottom" ? "end" : "center";
|
|
17596
17625
|
if (sel.startsWith("__vessel_idx:")) {
|
|
17597
17626
|
const idx = Number(sel.slice("__vessel_idx:".length));
|
|
17598
|
-
return
|
|
17627
|
+
return wc.executeJavaScript(`
|
|
17599
17628
|
(function() {
|
|
17600
17629
|
var el = window.__vessel?.interactByIndex && Object.values(window.__vessel)[2];
|
|
17601
17630
|
var ref = (function() { try { return document.querySelector('[data-vessel-idx="${idx}"]'); } catch(e) { return null; } })();
|
|
@@ -17606,7 +17635,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
17606
17635
|
`);
|
|
17607
17636
|
}
|
|
17608
17637
|
if (sel.includes(" >>> ")) {
|
|
17609
|
-
return
|
|
17638
|
+
return wc.executeJavaScript(`
|
|
17610
17639
|
(function() {
|
|
17611
17640
|
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(sel)});
|
|
17612
17641
|
if (!el) return "Error: Shadow DOM element not found";
|
|
@@ -17615,7 +17644,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
17615
17644
|
})()
|
|
17616
17645
|
`);
|
|
17617
17646
|
}
|
|
17618
|
-
return
|
|
17647
|
+
return wc.executeJavaScript(`
|
|
17619
17648
|
(function() {
|
|
17620
17649
|
var el = document.querySelector(${JSON.stringify(sel)});
|
|
17621
17650
|
if (!el) return "Error: Element not found";
|
|
@@ -19162,7 +19191,7 @@ function getActiveTabSummary(tabManager) {
|
|
|
19162
19191
|
async function getPostActionState(tabManager, name) {
|
|
19163
19192
|
const tab = tabManager.getActiveTab();
|
|
19164
19193
|
if (!tab) return "";
|
|
19165
|
-
const
|
|
19194
|
+
const wc = tab.view.webContents;
|
|
19166
19195
|
const navActions = [
|
|
19167
19196
|
"navigate",
|
|
19168
19197
|
"go_back",
|
|
@@ -19183,10 +19212,10 @@ async function getPostActionState(tabManager, name) {
|
|
|
19183
19212
|
if (navActions.includes(name)) {
|
|
19184
19213
|
let warning = "";
|
|
19185
19214
|
try {
|
|
19186
|
-
const page = await extractContent(
|
|
19215
|
+
const page = await extractContent(wc);
|
|
19187
19216
|
const issue = getRecoverableAccessIssue(page);
|
|
19188
19217
|
if (issue) {
|
|
19189
|
-
const blockedUrl =
|
|
19218
|
+
const blockedUrl = wc.getURL();
|
|
19190
19219
|
const canRecover = [
|
|
19191
19220
|
"navigate",
|
|
19192
19221
|
"open_bookmark",
|
|
@@ -19196,9 +19225,9 @@ async function getPostActionState(tabManager, name) {
|
|
|
19196
19225
|
"press_key"
|
|
19197
19226
|
].includes(name) && tab.canGoBack();
|
|
19198
19227
|
if (canRecover && tab.goBack()) {
|
|
19199
|
-
await waitForLoad(
|
|
19228
|
+
await waitForLoad(wc);
|
|
19200
19229
|
warning = `
|
|
19201
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${
|
|
19230
|
+
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
|
|
19202
19231
|
} else {
|
|
19203
19232
|
warning = `
|
|
19204
19233
|
[warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
|
|
@@ -19208,11 +19237,11 @@ async function getPostActionState(tabManager, name) {
|
|
|
19208
19237
|
logger$8.warn("Failed to compute post-action state warning:", err);
|
|
19209
19238
|
}
|
|
19210
19239
|
return `${warning}
|
|
19211
|
-
[state: url=${
|
|
19240
|
+
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
19212
19241
|
}
|
|
19213
19242
|
if (interactActions.includes(name)) {
|
|
19214
19243
|
return `
|
|
19215
|
-
[state: url=${
|
|
19244
|
+
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
|
|
19216
19245
|
}
|
|
19217
19246
|
if (tabActions.includes(name)) {
|
|
19218
19247
|
const activeId = tabManager.getActiveTabId();
|
|
@@ -19240,13 +19269,13 @@ async function withAction(runtime2, tabManager, name, args, executor) {
|
|
|
19240
19269
|
return asErrorTextResponse(getErrorMessage(error));
|
|
19241
19270
|
}
|
|
19242
19271
|
}
|
|
19243
|
-
async function waitForConditionMcp(
|
|
19272
|
+
async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
19244
19273
|
const effectiveTimeout = Math.max(250, timeoutMs || 5e3);
|
|
19245
19274
|
const expectedText = (text || "").trim();
|
|
19246
19275
|
const expectedSelector = (selector || "").trim();
|
|
19247
19276
|
const startedAt = Date.now();
|
|
19248
19277
|
const result = await waitForConditionDirect(
|
|
19249
|
-
|
|
19278
|
+
wc,
|
|
19250
19279
|
expectedText,
|
|
19251
19280
|
expectedSelector,
|
|
19252
19281
|
effectiveTimeout
|
|
@@ -19297,7 +19326,7 @@ async function waitForConditionMcp(wc2, text, selector, timeoutMs) {
|
|
|
19297
19326
|
timeout_ms: effectiveTimeout
|
|
19298
19327
|
};
|
|
19299
19328
|
if (expectedSelector) {
|
|
19300
|
-
const diagnostic = await
|
|
19329
|
+
const diagnostic = await wc.executeJavaScript(`
|
|
19301
19330
|
(function() {
|
|
19302
19331
|
try {
|
|
19303
19332
|
var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
|
|
@@ -19388,10 +19417,10 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
19388
19417
|
let pageTitle = "";
|
|
19389
19418
|
if (activeTab) {
|
|
19390
19419
|
try {
|
|
19391
|
-
const
|
|
19392
|
-
pageUrl =
|
|
19393
|
-
pageTitle =
|
|
19394
|
-
const page = await extractContent(
|
|
19420
|
+
const wc = activeTab.view.webContents;
|
|
19421
|
+
pageUrl = wc.getURL();
|
|
19422
|
+
pageTitle = wc.getTitle();
|
|
19423
|
+
const page = await extractContent(wc);
|
|
19395
19424
|
pageType = detectPageType(page);
|
|
19396
19425
|
} catch (err) {
|
|
19397
19426
|
logger$8.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
@@ -19511,13 +19540,13 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
19511
19540
|
"visible_only",
|
|
19512
19541
|
"results_only"
|
|
19513
19542
|
];
|
|
19514
|
-
async function buildExtractResponse(pageContent, mode, adBlockingEnabled,
|
|
19543
|
+
async function buildExtractResponse(pageContent, mode, adBlockingEnabled, wc) {
|
|
19515
19544
|
const adBlockLine = `**Ad Blocking:** ${adBlockingEnabled ? "On" : "Off"}`;
|
|
19516
19545
|
const savedHighlights = getHighlightsForUrl(
|
|
19517
19546
|
pageContent.url
|
|
19518
19547
|
);
|
|
19519
|
-
const liveSelectionSection =
|
|
19520
|
-
await captureLiveHighlightSnapshot(
|
|
19548
|
+
const liveSelectionSection = wc ? formatLiveSelectionSection(
|
|
19549
|
+
await captureLiveHighlightSnapshot(wc, savedHighlights)
|
|
19521
19550
|
) : null;
|
|
19522
19551
|
const livePrefix = liveSelectionSection ? `
|
|
19523
19552
|
|
|
@@ -19838,12 +19867,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19838
19867
|
"click",
|
|
19839
19868
|
{ index, selector },
|
|
19840
19869
|
async () => {
|
|
19841
|
-
const
|
|
19842
|
-
const resolvedSelector = typeof selector === "string" && selector.trim() ? await resolveSelector(
|
|
19870
|
+
const wc = tab.view.webContents;
|
|
19871
|
+
const resolvedSelector = typeof selector === "string" && selector.trim() ? await resolveSelector(wc, void 0, selector) : typeof index === "number" ? `__vessel_idx:${index}` : await resolveSelector(wc, index, selector);
|
|
19843
19872
|
if (!resolvedSelector) {
|
|
19844
19873
|
return "Error: No index or selector provided";
|
|
19845
19874
|
}
|
|
19846
|
-
return clickResolvedSelector(
|
|
19875
|
+
return clickResolvedSelector(wc, resolvedSelector);
|
|
19847
19876
|
}
|
|
19848
19877
|
);
|
|
19849
19878
|
}
|
|
@@ -19867,12 +19896,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19867
19896
|
"hover",
|
|
19868
19897
|
{ index, selector },
|
|
19869
19898
|
async () => {
|
|
19870
|
-
const
|
|
19871
|
-
const resolvedSelector = await resolveSelector(
|
|
19899
|
+
const wc = tab.view.webContents;
|
|
19900
|
+
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
19872
19901
|
if (!resolvedSelector) {
|
|
19873
19902
|
return "Error: No index or selector provided";
|
|
19874
19903
|
}
|
|
19875
|
-
return hoverElement(
|
|
19904
|
+
return hoverElement(wc, resolvedSelector);
|
|
19876
19905
|
}
|
|
19877
19906
|
);
|
|
19878
19907
|
}
|
|
@@ -19896,12 +19925,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19896
19925
|
"focus",
|
|
19897
19926
|
{ index, selector },
|
|
19898
19927
|
async () => {
|
|
19899
|
-
const
|
|
19900
|
-
const resolvedSelector = await resolveSelector(
|
|
19928
|
+
const wc = tab.view.webContents;
|
|
19929
|
+
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
19901
19930
|
if (!resolvedSelector) {
|
|
19902
19931
|
return "Error: No index or selector provided";
|
|
19903
19932
|
}
|
|
19904
|
-
return focusElement(
|
|
19933
|
+
return focusElement(wc, resolvedSelector);
|
|
19905
19934
|
}
|
|
19906
19935
|
);
|
|
19907
19936
|
}
|
|
@@ -19919,12 +19948,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19919
19948
|
async ({ index, selector }) => {
|
|
19920
19949
|
const tab = tabManager.getActiveTab();
|
|
19921
19950
|
if (!tab) return asNoActiveTabResponse();
|
|
19922
|
-
const
|
|
19923
|
-
const resolvedSelector = await resolveSelector(
|
|
19951
|
+
const wc = tab.view.webContents;
|
|
19952
|
+
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
19924
19953
|
if (!resolvedSelector) {
|
|
19925
19954
|
return asErrorTextResponse("No index or selector provided");
|
|
19926
19955
|
}
|
|
19927
|
-
const result = await
|
|
19956
|
+
const result = await wc.executeJavaScript(`
|
|
19928
19957
|
(function() {
|
|
19929
19958
|
try {
|
|
19930
19959
|
const el = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
@@ -20131,12 +20160,12 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
20131
20160
|
"press_key",
|
|
20132
20161
|
{ key, index, selector },
|
|
20133
20162
|
async () => {
|
|
20134
|
-
const
|
|
20135
|
-
const beforeUrl =
|
|
20136
|
-
const result = await pressKeyDirect(
|
|
20163
|
+
const wc = tab.view.webContents;
|
|
20164
|
+
const beforeUrl = wc.getURL();
|
|
20165
|
+
const result = await pressKeyDirect(wc, key, index, selector);
|
|
20137
20166
|
if (key === "Enter") {
|
|
20138
|
-
await waitForPotentialNavigation$1(
|
|
20139
|
-
const afterUrl =
|
|
20167
|
+
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
20168
|
+
const afterUrl = wc.getURL();
|
|
20140
20169
|
if (afterUrl !== beforeUrl) {
|
|
20141
20170
|
return `${result} -> ${afterUrl}`;
|
|
20142
20171
|
}
|
|
@@ -20618,10 +20647,10 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20618
20647
|
color
|
|
20619
20648
|
},
|
|
20620
20649
|
async () => {
|
|
20621
|
-
const
|
|
20622
|
-
const resolvedSelector = await resolveSelector(
|
|
20650
|
+
const wc = tab.view.webContents;
|
|
20651
|
+
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
20623
20652
|
const result = await highlightOnPage(
|
|
20624
|
-
|
|
20653
|
+
wc,
|
|
20625
20654
|
resolvedSelector,
|
|
20626
20655
|
normalizedText,
|
|
20627
20656
|
label,
|
|
@@ -20629,7 +20658,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20629
20658
|
color
|
|
20630
20659
|
);
|
|
20631
20660
|
if (persist && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
|
|
20632
|
-
const url = normalizeUrl$1(
|
|
20661
|
+
const url = normalizeUrl$1(wc.getURL());
|
|
20633
20662
|
addHighlight(
|
|
20634
20663
|
url,
|
|
20635
20664
|
resolvedSelector ?? void 0,
|
|
@@ -20659,10 +20688,10 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20659
20688
|
"clear_highlights",
|
|
20660
20689
|
{},
|
|
20661
20690
|
async () => {
|
|
20662
|
-
const
|
|
20663
|
-
const url = normalizeUrl$1(
|
|
20691
|
+
const wc = tab.view.webContents;
|
|
20692
|
+
const url = normalizeUrl$1(wc.getURL());
|
|
20664
20693
|
clearHighlightsForUrl(url);
|
|
20665
|
-
return clearHighlights(
|
|
20694
|
+
return clearHighlights(wc);
|
|
20666
20695
|
}
|
|
20667
20696
|
);
|
|
20668
20697
|
}
|
|
@@ -20780,12 +20809,12 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20780
20809
|
}
|
|
20781
20810
|
const tab = tabManager.getTab(tabState.id);
|
|
20782
20811
|
if (!tab) continue;
|
|
20783
|
-
const
|
|
20784
|
-
await clearHighlights(
|
|
20812
|
+
const wc = tab.view.webContents;
|
|
20813
|
+
await clearHighlights(wc);
|
|
20785
20814
|
for (const h of remaining) {
|
|
20786
20815
|
if (!h.selector && !h.text) continue;
|
|
20787
20816
|
void highlightOnPage(
|
|
20788
|
-
|
|
20817
|
+
wc,
|
|
20789
20818
|
h.selector ?? null,
|
|
20790
20819
|
h.text,
|
|
20791
20820
|
h.label,
|
|
@@ -21635,10 +21664,10 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21635
21664
|
return asTextResponse(
|
|
21636
21665
|
"No active tab. Use navigate to open a page."
|
|
21637
21666
|
);
|
|
21638
|
-
const
|
|
21667
|
+
const wc = tab.view.webContents;
|
|
21639
21668
|
let page;
|
|
21640
21669
|
try {
|
|
21641
|
-
page = await extractContent(
|
|
21670
|
+
page = await extractContent(wc);
|
|
21642
21671
|
} catch (err) {
|
|
21643
21672
|
logger$8.warn("Failed to extract page while generating suggestions:", err);
|
|
21644
21673
|
return asTextResponse(
|
|
@@ -21754,16 +21783,16 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21754
21783
|
"fill_form",
|
|
21755
21784
|
{ fieldCount: fields.length, submit },
|
|
21756
21785
|
async () => {
|
|
21757
|
-
const
|
|
21758
|
-
const fillResults = await fillFormFields(
|
|
21786
|
+
const wc = tab.view.webContents;
|
|
21787
|
+
const fillResults = await fillFormFields(wc, fields);
|
|
21759
21788
|
const results = fillResults.map((item) => item.result);
|
|
21760
21789
|
if (submit) {
|
|
21761
21790
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
21762
21791
|
if (firstSel) {
|
|
21763
|
-
const beforeUrl =
|
|
21764
|
-
const submitResult = await submitFormDirect(
|
|
21765
|
-
await waitForPotentialNavigation$1(
|
|
21766
|
-
const afterUrl =
|
|
21792
|
+
const beforeUrl = wc.getURL();
|
|
21793
|
+
const submitResult = await submitFormDirect(wc, void 0, firstSel);
|
|
21794
|
+
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
21795
|
+
const afterUrl = wc.getURL();
|
|
21767
21796
|
results.push(
|
|
21768
21797
|
afterUrl !== beforeUrl ? `Submitted → ${afterUrl}` : submitResult
|
|
21769
21798
|
);
|
|
@@ -21811,15 +21840,15 @@ ${results.join("\n")}`;
|
|
|
21811
21840
|
"login",
|
|
21812
21841
|
{ url, username: username.slice(0, 3) + "***" },
|
|
21813
21842
|
async () => {
|
|
21814
|
-
const
|
|
21843
|
+
const wc = tab.view.webContents;
|
|
21815
21844
|
const steps = [];
|
|
21816
21845
|
if (url) {
|
|
21817
21846
|
const id = tabManager.getActiveTabId();
|
|
21818
21847
|
tabManager.navigateTab(id, url);
|
|
21819
|
-
await waitForLoad(
|
|
21820
|
-
steps.push(`Navigated to ${
|
|
21848
|
+
await waitForLoad(wc);
|
|
21849
|
+
steps.push(`Navigated to ${wc.getURL()}`);
|
|
21821
21850
|
}
|
|
21822
|
-
const userSel = username_selector || await
|
|
21851
|
+
const userSel = username_selector || await wc.executeJavaScript(`
|
|
21823
21852
|
(function() {
|
|
21824
21853
|
var el = document.querySelector('input[type="email"], input[name="email"], input[name="username"], input[name="user"], input[autocomplete="username"], input[autocomplete="email"], input[type="text"]:not([name="search"]):not([name="q"])');
|
|
21825
21854
|
return el ? (el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null) : null;
|
|
@@ -21827,7 +21856,7 @@ ${results.join("\n")}`;
|
|
|
21827
21856
|
`);
|
|
21828
21857
|
if (!userSel)
|
|
21829
21858
|
return "Error: Could not find username/email field. Try providing username_selector.";
|
|
21830
|
-
const passSel = password_selector || await
|
|
21859
|
+
const passSel = password_selector || await wc.executeJavaScript(`
|
|
21831
21860
|
(function() {
|
|
21832
21861
|
var el = document.querySelector('input[type="password"]');
|
|
21833
21862
|
return el ? (el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null) : null;
|
|
@@ -21835,15 +21864,15 @@ ${results.join("\n")}`;
|
|
|
21835
21864
|
`);
|
|
21836
21865
|
if (!passSel)
|
|
21837
21866
|
return "Error: Could not find password field. Try providing password_selector.";
|
|
21838
|
-
const userResult = await setElementValue(
|
|
21867
|
+
const userResult = await setElementValue(wc, userSel, username);
|
|
21839
21868
|
steps.push(userResult);
|
|
21840
|
-
const passResult = await setElementValue(
|
|
21869
|
+
const passResult = await setElementValue(wc, passSel, password);
|
|
21841
21870
|
steps.push(passResult);
|
|
21842
|
-
const beforeUrl =
|
|
21871
|
+
const beforeUrl = wc.getURL();
|
|
21843
21872
|
if (submit_selector) {
|
|
21844
|
-
await clickResolvedSelector(
|
|
21873
|
+
await clickResolvedSelector(wc, submit_selector);
|
|
21845
21874
|
} else {
|
|
21846
|
-
const clicked = await
|
|
21875
|
+
const clicked = await wc.executeJavaScript(`
|
|
21847
21876
|
(function() {
|
|
21848
21877
|
var btn = document.querySelector('button[type="submit"], input[type="submit"], form button:not([type="button"])');
|
|
21849
21878
|
if (btn) { btn.click(); return true; }
|
|
@@ -21855,8 +21884,8 @@ ${results.join("\n")}`;
|
|
|
21855
21884
|
if (!clicked)
|
|
21856
21885
|
return steps.join("\n") + "\nWarning: Could not find submit button. Credentials filled but form not submitted.";
|
|
21857
21886
|
}
|
|
21858
|
-
await waitForPotentialNavigation$1(
|
|
21859
|
-
const afterUrl =
|
|
21887
|
+
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
21888
|
+
const afterUrl = wc.getURL();
|
|
21860
21889
|
steps.push(
|
|
21861
21890
|
afterUrl !== beforeUrl ? `Submitted → ${afterUrl}` : "Form submitted (same page)"
|
|
21862
21891
|
);
|
|
@@ -21912,8 +21941,8 @@ ${steps.join("\n")}`;
|
|
|
21912
21941
|
);
|
|
21913
21942
|
}
|
|
21914
21943
|
return withAction(runtime2, tabManager, "search", { query }, async () => {
|
|
21915
|
-
const
|
|
21916
|
-
const searchSel = selector || await
|
|
21944
|
+
const wc = tab.view.webContents;
|
|
21945
|
+
const searchSel = selector || await wc.executeJavaScript(`
|
|
21917
21946
|
(function() {
|
|
21918
21947
|
var el = document.querySelector('input[type="search"], input[name="q"], input[name="query"], input[name="search"], input[role="searchbox"], input[aria-label*="search" i], input[placeholder*="search" i]');
|
|
21919
21948
|
if (!el) {
|
|
@@ -21931,20 +21960,20 @@ ${steps.join("\n")}`;
|
|
|
21931
21960
|
`);
|
|
21932
21961
|
if (!searchSel)
|
|
21933
21962
|
return "Error: Could not find search input. Try providing a selector.";
|
|
21934
|
-
await setElementValue(
|
|
21935
|
-
await
|
|
21963
|
+
await setElementValue(wc, searchSel, query);
|
|
21964
|
+
await wc.executeJavaScript(`
|
|
21936
21965
|
(function() {
|
|
21937
21966
|
var el = document.querySelector(${JSON.stringify(searchSel)});
|
|
21938
21967
|
if (el) el.focus();
|
|
21939
21968
|
})()
|
|
21940
21969
|
`);
|
|
21941
21970
|
await new Promise((r) => setTimeout(r, 50));
|
|
21942
|
-
const beforeUrl =
|
|
21943
|
-
|
|
21971
|
+
const beforeUrl = wc.getURL();
|
|
21972
|
+
wc.sendInputEvent({ type: "keyDown", keyCode: "Return" });
|
|
21944
21973
|
await new Promise((r) => setTimeout(r, 16));
|
|
21945
|
-
|
|
21946
|
-
await waitForPotentialNavigation$1(
|
|
21947
|
-
const afterUrl =
|
|
21974
|
+
wc.sendInputEvent({ type: "keyUp", keyCode: "Return" });
|
|
21975
|
+
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
21976
|
+
const afterUrl = wc.getURL();
|
|
21948
21977
|
return afterUrl !== beforeUrl ? `Searched "${query}" → ${afterUrl}` : `Searched "${query}" (same page — results may have loaded dynamically)`;
|
|
21949
21978
|
});
|
|
21950
21979
|
}
|
|
@@ -21970,13 +21999,13 @@ ${steps.join("\n")}`;
|
|
|
21970
21999
|
"paginate",
|
|
21971
22000
|
{ direction },
|
|
21972
22001
|
async () => {
|
|
21973
|
-
const
|
|
21974
|
-
const beforeUrl =
|
|
22002
|
+
const wc = tab.view.webContents;
|
|
22003
|
+
const beforeUrl = wc.getURL();
|
|
21975
22004
|
if (selector) {
|
|
21976
|
-
return clickResolvedSelector(
|
|
22005
|
+
return clickResolvedSelector(wc, selector);
|
|
21977
22006
|
}
|
|
21978
22007
|
const isNext = direction === "next";
|
|
21979
|
-
const clicked = await
|
|
22008
|
+
const clicked = await wc.executeJavaScript(`
|
|
21980
22009
|
(function() {
|
|
21981
22010
|
var patterns = ${isNext ? '["next", "Next", "›", "»", "→", ">", "Next Page", "Load More"]' : '["prev", "Prev", "Previous", "‹", "«", "←", "<", "Previous Page"]'};
|
|
21982
22011
|
var links = document.querySelectorAll('a, button');
|
|
@@ -21998,8 +22027,8 @@ ${steps.join("\n")}`;
|
|
|
21998
22027
|
`);
|
|
21999
22028
|
if (!clicked)
|
|
22000
22029
|
return `Error: Could not find ${direction} pagination control. Try providing a selector.`;
|
|
22001
|
-
await waitForPotentialNavigation$1(
|
|
22002
|
-
const afterUrl =
|
|
22030
|
+
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
22031
|
+
const afterUrl = wc.getURL();
|
|
22003
22032
|
return afterUrl !== beforeUrl ? `Paginated ${direction} → ${afterUrl}` : `Clicked ${direction} (page may have updated dynamically)`;
|
|
22004
22033
|
}
|
|
22005
22034
|
);
|
|
@@ -22021,8 +22050,8 @@ ${steps.join("\n")}`;
|
|
|
22021
22050
|
"accept_cookies",
|
|
22022
22051
|
{},
|
|
22023
22052
|
async () => {
|
|
22024
|
-
const
|
|
22025
|
-
const dismissed = await
|
|
22053
|
+
const wc = tab.view.webContents;
|
|
22054
|
+
const dismissed = await wc.executeJavaScript(`
|
|
22026
22055
|
(function() {
|
|
22027
22056
|
var selectors = [
|
|
22028
22057
|
'#onetrust-accept-btn-handler',
|
|
@@ -22079,9 +22108,9 @@ ${steps.join("\n")}`;
|
|
|
22079
22108
|
"extract_table",
|
|
22080
22109
|
{ index, selector: rawSelector },
|
|
22081
22110
|
async () => {
|
|
22082
|
-
const
|
|
22083
|
-
const sel = rawSelector || (index != null ? await resolveSelector(
|
|
22084
|
-
const tableJson = await
|
|
22111
|
+
const wc = tab.view.webContents;
|
|
22112
|
+
const sel = rawSelector || (index != null ? await resolveSelector(wc, index) : null);
|
|
22113
|
+
const tableJson = await wc.executeJavaScript(`
|
|
22085
22114
|
(function() {
|
|
22086
22115
|
var table = ${sel ? `document.querySelector(${JSON.stringify(sel)})` : "document.querySelector('table')"};
|
|
22087
22116
|
if (!table) return null;
|
|
@@ -22134,13 +22163,13 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22134
22163
|
"scroll_to_element",
|
|
22135
22164
|
{ index, selector: rawSelector, position },
|
|
22136
22165
|
async () => {
|
|
22137
|
-
const
|
|
22138
|
-
const sel = rawSelector || (index != null ? await resolveSelector(
|
|
22166
|
+
const wc = tab.view.webContents;
|
|
22167
|
+
const sel = rawSelector || (index != null ? await resolveSelector(wc, index) : null);
|
|
22139
22168
|
if (!sel) return "Error: Provide an index or selector.";
|
|
22140
22169
|
const block = position === "top" ? "start" : position === "bottom" ? "end" : "center";
|
|
22141
22170
|
if (sel.startsWith("__vessel_idx:")) {
|
|
22142
22171
|
const idx = Number(sel.slice("__vessel_idx:".length));
|
|
22143
|
-
return
|
|
22172
|
+
return wc.executeJavaScript(`
|
|
22144
22173
|
(function() {
|
|
22145
22174
|
var refs = window.__vessel;
|
|
22146
22175
|
if (!refs || !refs.interactByIndex) return "Error: __vessel not available";
|
|
@@ -22153,7 +22182,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22153
22182
|
`);
|
|
22154
22183
|
}
|
|
22155
22184
|
if (sel.includes(" >>> ")) {
|
|
22156
|
-
return
|
|
22185
|
+
return wc.executeJavaScript(`
|
|
22157
22186
|
(function() {
|
|
22158
22187
|
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(sel)});
|
|
22159
22188
|
if (!el) return "Error: Shadow DOM element not found";
|
|
@@ -22162,7 +22191,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22162
22191
|
})()
|
|
22163
22192
|
`);
|
|
22164
22193
|
}
|
|
22165
|
-
return
|
|
22194
|
+
return wc.executeJavaScript(`
|
|
22166
22195
|
(function() {
|
|
22167
22196
|
var el = document.querySelector(${JSON.stringify(sel)});
|
|
22168
22197
|
if (!el) return "Error: Element not found";
|
|
@@ -22192,13 +22221,13 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22192
22221
|
"wait_for_navigation",
|
|
22193
22222
|
{ timeoutMs },
|
|
22194
22223
|
async () => {
|
|
22195
|
-
const
|
|
22224
|
+
const wc = tab.view.webContents;
|
|
22196
22225
|
const timeout = timeoutMs || 1e4;
|
|
22197
|
-
const beforeUrl =
|
|
22198
|
-
if (
|
|
22226
|
+
const beforeUrl = wc.getURL();
|
|
22227
|
+
if (wc.isLoading()) {
|
|
22199
22228
|
await new Promise((resolve) => {
|
|
22200
22229
|
const timer = setTimeout(resolve, timeout);
|
|
22201
|
-
|
|
22230
|
+
wc.once("did-stop-loading", () => {
|
|
22202
22231
|
clearTimeout(timer);
|
|
22203
22232
|
resolve();
|
|
22204
22233
|
});
|
|
@@ -22212,19 +22241,19 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22212
22241
|
},
|
|
22213
22242
|
Math.min(timeout, 2e3)
|
|
22214
22243
|
);
|
|
22215
|
-
|
|
22244
|
+
wc.once("did-start-loading", () => {
|
|
22216
22245
|
navigated = true;
|
|
22217
22246
|
clearTimeout(timer);
|
|
22218
22247
|
const loadTimer = setTimeout(resolve, timeout);
|
|
22219
|
-
|
|
22248
|
+
wc.once("did-stop-loading", () => {
|
|
22220
22249
|
clearTimeout(loadTimer);
|
|
22221
22250
|
resolve();
|
|
22222
22251
|
});
|
|
22223
22252
|
});
|
|
22224
22253
|
});
|
|
22225
22254
|
}
|
|
22226
|
-
const afterUrl =
|
|
22227
|
-
const title =
|
|
22255
|
+
const afterUrl = wc.getURL();
|
|
22256
|
+
const title = wc.getTitle();
|
|
22228
22257
|
return afterUrl !== beforeUrl ? `Navigation complete: ${title} (${afterUrl})` : `Page loaded: ${title} (${afterUrl})`;
|
|
22229
22258
|
}
|
|
22230
22259
|
);
|
|
@@ -22310,7 +22339,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22310
22339
|
}) => {
|
|
22311
22340
|
const tab = tabManager.getActiveTab();
|
|
22312
22341
|
if (!tab) return asNoActiveTabResponse();
|
|
22313
|
-
const
|
|
22342
|
+
const wc = tab.view.webContents;
|
|
22314
22343
|
let hostname;
|
|
22315
22344
|
try {
|
|
22316
22345
|
hostname = new URL(tab.state.url).hostname;
|
|
@@ -22356,13 +22385,13 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22356
22385
|
}
|
|
22357
22386
|
const results = [];
|
|
22358
22387
|
if (username_index != null) {
|
|
22359
|
-
const usernameResult = await
|
|
22388
|
+
const usernameResult = await wc.executeJavaScript(
|
|
22360
22389
|
`window.__vessel?.interactByIndex?.(${username_index}, "value", ${JSON.stringify(creds.username)}) || "Error: interactByIndex not available"`
|
|
22361
22390
|
);
|
|
22362
22391
|
results.push(`Username: ${usernameResult}`);
|
|
22363
22392
|
}
|
|
22364
22393
|
if (password_index != null) {
|
|
22365
|
-
const passwordResult = await
|
|
22394
|
+
const passwordResult = await wc.executeJavaScript(
|
|
22366
22395
|
`window.__vessel?.interactByIndex?.(${password_index}, "value", ${JSON.stringify(creds.password)}) || "Error: interactByIndex not available"`
|
|
22367
22396
|
);
|
|
22368
22397
|
results.push(`Password: ${passwordResult.replace(/Typed into:.*/, "Typed into: [password field]")}`);
|
|
@@ -22370,7 +22399,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22370
22399
|
recordUsage$1(match.id);
|
|
22371
22400
|
trackVaultAction("login_fill");
|
|
22372
22401
|
if (submit_after && submit_index != null) {
|
|
22373
|
-
const submitResult = await
|
|
22402
|
+
const submitResult = await wc.executeJavaScript(
|
|
22374
22403
|
`window.__vessel?.interactByIndex?.(${submit_index}, "click") || "Error: interactByIndex not available"`
|
|
22375
22404
|
);
|
|
22376
22405
|
results.push(`Submit: ${submitResult}`);
|
|
@@ -22404,7 +22433,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22404
22433
|
async ({ credential_label, code_index, submit_after, submit_index }) => {
|
|
22405
22434
|
const tab = tabManager.getActiveTab();
|
|
22406
22435
|
if (!tab) return asNoActiveTabResponse();
|
|
22407
|
-
const
|
|
22436
|
+
const wc = tab.view.webContents;
|
|
22408
22437
|
let hostname;
|
|
22409
22438
|
try {
|
|
22410
22439
|
hostname = new URL(tab.state.url).hostname;
|
|
@@ -22449,14 +22478,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22449
22478
|
);
|
|
22450
22479
|
}
|
|
22451
22480
|
const code = generateTotpCode(secret);
|
|
22452
|
-
const fillResult = await
|
|
22481
|
+
const fillResult = await wc.executeJavaScript(
|
|
22453
22482
|
`window.__vessel?.interactByIndex?.(${code_index}, "value", ${JSON.stringify(code)}) || "Error: interactByIndex not available"`
|
|
22454
22483
|
);
|
|
22455
22484
|
recordUsage$1(match.id);
|
|
22456
22485
|
trackVaultAction("totp_fill");
|
|
22457
22486
|
const results = [`2FA code filled: ${fillResult.replace(/Typed into:.*/, "Typed into: [2FA field]")}`];
|
|
22458
22487
|
if (submit_after && submit_index != null) {
|
|
22459
|
-
const submitResult = await
|
|
22488
|
+
const submitResult = await wc.executeJavaScript(
|
|
22460
22489
|
`window.__vessel?.interactByIndex?.(${submit_index}, "click") || "Error: interactByIndex not available"`
|
|
22461
22490
|
);
|
|
22462
22491
|
results.push(`Submit: ${submitResult}`);
|
|
@@ -22582,22 +22611,22 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22582
22611
|
if (!decrypted) {
|
|
22583
22612
|
return asErrorTextResponse("Failed to decrypt password.");
|
|
22584
22613
|
}
|
|
22585
|
-
const
|
|
22614
|
+
const wc = tab.view.webContents;
|
|
22586
22615
|
const results = [];
|
|
22587
22616
|
if (username_index != null) {
|
|
22588
|
-
const usernameResult = await
|
|
22617
|
+
const usernameResult = await wc.executeJavaScript(
|
|
22589
22618
|
`window.__vessel?.interactByIndex?.(${username_index}, "value", ${JSON.stringify(entry.username)}) || "Error: interactByIndex not available"`
|
|
22590
22619
|
);
|
|
22591
22620
|
results.push(`Username filled: ${usernameResult.replace(/Typed into:.*/, "Typed into: [username field]")}`);
|
|
22592
22621
|
}
|
|
22593
22622
|
if (password_index != null) {
|
|
22594
|
-
const passwordResult = await
|
|
22623
|
+
const passwordResult = await wc.executeJavaScript(
|
|
22595
22624
|
`window.__vessel?.interactByIndex?.(${password_index}, "value", ${JSON.stringify(decrypted.password)}) || "Error: interactByIndex not available"`
|
|
22596
22625
|
);
|
|
22597
22626
|
results.push(`Password filled: ${passwordResult.replace(/Typed into:.*/, "Typed into: [password field]")}`);
|
|
22598
22627
|
}
|
|
22599
22628
|
if (submit_after && submit_index != null) {
|
|
22600
|
-
const submitResult = await
|
|
22629
|
+
const submitResult = await wc.executeJavaScript(
|
|
22601
22630
|
`window.__vessel?.interactByIndex?.(${submit_index}, "click") || "Error: interactByIndex not available"`
|
|
22602
22631
|
);
|
|
22603
22632
|
results.push(`Submit: ${submitResult}`);
|
|
@@ -22666,23 +22695,23 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22666
22695
|
}
|
|
22667
22696
|
);
|
|
22668
22697
|
}
|
|
22669
|
-
function waitForLoadWithStatus(
|
|
22698
|
+
function waitForLoadWithStatus(wc, timeout = 1e4) {
|
|
22670
22699
|
return new Promise((resolve) => {
|
|
22671
22700
|
let httpStatus = null;
|
|
22672
22701
|
const onNavigate = (_, _url, code) => {
|
|
22673
22702
|
if (code > 0) httpStatus = code;
|
|
22674
22703
|
};
|
|
22675
|
-
|
|
22704
|
+
wc.on("did-navigate", onNavigate);
|
|
22676
22705
|
const finish = () => {
|
|
22677
|
-
|
|
22706
|
+
wc.removeListener("did-navigate", onNavigate);
|
|
22678
22707
|
resolve({ httpStatus });
|
|
22679
22708
|
};
|
|
22680
|
-
if (!
|
|
22709
|
+
if (!wc.isLoading()) {
|
|
22681
22710
|
finish();
|
|
22682
22711
|
return;
|
|
22683
22712
|
}
|
|
22684
22713
|
const timer = setTimeout(finish, timeout);
|
|
22685
|
-
|
|
22714
|
+
wc.once("did-stop-loading", () => {
|
|
22686
22715
|
clearTimeout(timer);
|
|
22687
22716
|
finish();
|
|
22688
22717
|
});
|
|
@@ -22780,7 +22809,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22780
22809
|
if (httpServer === server) {
|
|
22781
22810
|
httpServer = null;
|
|
22782
22811
|
}
|
|
22783
|
-
finish(errorResult
|
|
22812
|
+
finish(errorResult(message, {
|
|
22784
22813
|
configuredPort: port,
|
|
22785
22814
|
activePort: null,
|
|
22786
22815
|
endpoint: null,
|
|
@@ -22913,64 +22942,64 @@ async function installKitFromFile() {
|
|
|
22913
22942
|
properties: ["openFile"]
|
|
22914
22943
|
});
|
|
22915
22944
|
if (canceled || filePaths.length === 0) {
|
|
22916
|
-
return errorResult
|
|
22945
|
+
return errorResult("canceled");
|
|
22917
22946
|
}
|
|
22918
22947
|
let raw;
|
|
22919
22948
|
try {
|
|
22920
22949
|
raw = fs$1.readFileSync(filePaths[0], "utf-8");
|
|
22921
22950
|
} catch {
|
|
22922
|
-
return errorResult
|
|
22951
|
+
return errorResult("Could not read the selected file.");
|
|
22923
22952
|
}
|
|
22924
22953
|
let parsed;
|
|
22925
22954
|
try {
|
|
22926
22955
|
parsed = JSON.parse(raw);
|
|
22927
22956
|
} catch {
|
|
22928
|
-
return errorResult
|
|
22957
|
+
return errorResult("File is not valid JSON.");
|
|
22929
22958
|
}
|
|
22930
22959
|
if (!isValidKit(parsed)) {
|
|
22931
|
-
return errorResult
|
|
22960
|
+
return errorResult(
|
|
22932
22961
|
"File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
22933
22962
|
);
|
|
22934
22963
|
}
|
|
22935
22964
|
if (BUNDLED_KIT_IDS.has(parsed.id)) {
|
|
22936
|
-
return errorResult
|
|
22965
|
+
return errorResult(
|
|
22937
22966
|
`Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
|
|
22938
22967
|
);
|
|
22939
22968
|
}
|
|
22940
22969
|
ensureKitsDir();
|
|
22941
22970
|
const dest = getKitFilePath(parsed.id);
|
|
22942
22971
|
if (!dest) {
|
|
22943
|
-
return errorResult
|
|
22972
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22944
22973
|
}
|
|
22945
22974
|
try {
|
|
22946
22975
|
fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
22947
22976
|
} catch {
|
|
22948
|
-
return errorResult
|
|
22977
|
+
return errorResult("Failed to save the kit file.");
|
|
22949
22978
|
}
|
|
22950
22979
|
return okResult({ kit: parsed });
|
|
22951
22980
|
}
|
|
22952
22981
|
function uninstallKit(id, scheduledKitIds) {
|
|
22953
22982
|
if (BUNDLED_KIT_IDS.has(id)) {
|
|
22954
|
-
return errorResult
|
|
22983
|
+
return errorResult("Built-in kits cannot be removed.");
|
|
22955
22984
|
}
|
|
22956
22985
|
if (scheduledKitIds?.has(id)) {
|
|
22957
|
-
return errorResult
|
|
22986
|
+
return errorResult(
|
|
22958
22987
|
"This kit has active scheduled jobs. Delete or reassign them first."
|
|
22959
22988
|
);
|
|
22960
22989
|
}
|
|
22961
22990
|
ensureKitsDir();
|
|
22962
22991
|
const target = getKitFilePath(id);
|
|
22963
22992
|
if (!target) {
|
|
22964
|
-
return errorResult
|
|
22993
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22965
22994
|
}
|
|
22966
22995
|
if (!fs$1.existsSync(target)) {
|
|
22967
|
-
return errorResult
|
|
22996
|
+
return errorResult("Kit not found.");
|
|
22968
22997
|
}
|
|
22969
22998
|
try {
|
|
22970
22999
|
fs$1.unlinkSync(target);
|
|
22971
23000
|
return okResult();
|
|
22972
23001
|
} catch {
|
|
22973
|
-
return errorResult
|
|
23002
|
+
return errorResult("Failed to remove the kit file.");
|
|
22974
23003
|
}
|
|
22975
23004
|
}
|
|
22976
23005
|
const logger$6 = createLogger("Scheduler");
|
|
@@ -23274,9 +23303,9 @@ function isValidEmail(value) {
|
|
|
23274
23303
|
function getActiveTabInfo(tabManager) {
|
|
23275
23304
|
const tab = tabManager.getActiveTab();
|
|
23276
23305
|
if (!tab) return null;
|
|
23277
|
-
const
|
|
23278
|
-
if (
|
|
23279
|
-
return { tab, wc
|
|
23306
|
+
const wc = tab.view.webContents;
|
|
23307
|
+
if (wc.isDestroyed()) return null;
|
|
23308
|
+
return { tab, wc };
|
|
23280
23309
|
}
|
|
23281
23310
|
const SAVE_DEBOUNCE_MS = 250;
|
|
23282
23311
|
const PROFILE_FIELDS = [
|
|
@@ -23639,9 +23668,9 @@ function registerAutofillHandlers(windowState) {
|
|
|
23639
23668
|
const profile = getProfile(profileId);
|
|
23640
23669
|
if (!profile) throw new Error("Profile not found");
|
|
23641
23670
|
const activeTab = windowState.tabManager.getActiveTab();
|
|
23642
|
-
const
|
|
23643
|
-
if (!
|
|
23644
|
-
const content = await extractContent(
|
|
23671
|
+
const wc = activeTab?.view.webContents;
|
|
23672
|
+
if (!wc) throw new Error("No active tab");
|
|
23673
|
+
const content = await extractContent(wc);
|
|
23645
23674
|
const elements = content.interactiveElements || [];
|
|
23646
23675
|
const matches = matchFields(elements, profile);
|
|
23647
23676
|
if (matches.length === 0) {
|
|
@@ -23652,7 +23681,7 @@ function registerAutofillHandlers(windowState) {
|
|
|
23652
23681
|
selector: match.selector,
|
|
23653
23682
|
value: match.value
|
|
23654
23683
|
}));
|
|
23655
|
-
const results = await fillFormFields(
|
|
23684
|
+
const results = await fillFormFields(wc, fields);
|
|
23656
23685
|
const filled = results.filter(
|
|
23657
23686
|
(result) => result.result.startsWith("Typed into:") || result.result.startsWith("Selected:")
|
|
23658
23687
|
).length;
|
|
@@ -23671,9 +23700,9 @@ function registerAutofillHandlers(windowState) {
|
|
|
23671
23700
|
function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
23672
23701
|
electron.ipcMain.handle(Channels.PAGE_DIFF_GET, () => {
|
|
23673
23702
|
const activeTab = windowState.tabManager.getActiveTab();
|
|
23674
|
-
const
|
|
23675
|
-
if (!
|
|
23676
|
-
return getLatestPageDiff(
|
|
23703
|
+
const wc = activeTab?.view.webContents;
|
|
23704
|
+
if (!wc) return null;
|
|
23705
|
+
return getLatestPageDiff(wc.getURL());
|
|
23677
23706
|
});
|
|
23678
23707
|
electron.ipcMain.handle(Channels.PAGE_DIFF_HISTORY, () => {
|
|
23679
23708
|
try {
|
|
@@ -23681,22 +23710,22 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
|
23681
23710
|
return { error: "Premium required" };
|
|
23682
23711
|
}
|
|
23683
23712
|
const activeTab = windowState.tabManager.getActiveTab();
|
|
23684
|
-
const
|
|
23685
|
-
if (!
|
|
23686
|
-
return getPageDiffBursts(
|
|
23713
|
+
const wc = activeTab?.view.webContents;
|
|
23714
|
+
if (!wc) return [];
|
|
23715
|
+
return getPageDiffBursts(wc.getURL());
|
|
23687
23716
|
} catch {
|
|
23688
23717
|
return [];
|
|
23689
23718
|
}
|
|
23690
23719
|
});
|
|
23691
23720
|
electron.ipcMain.on(Channels.PAGE_DIFF_ACTIVITY, (event) => {
|
|
23692
|
-
const
|
|
23693
|
-
if (!
|
|
23694
|
-
notePageMutationActivity(
|
|
23721
|
+
const wc = event.sender;
|
|
23722
|
+
if (!wc || wc.isDestroyed()) return;
|
|
23723
|
+
notePageMutationActivity(wc, sendToRendererViews);
|
|
23695
23724
|
});
|
|
23696
23725
|
electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
|
|
23697
|
-
const
|
|
23698
|
-
if (!
|
|
23699
|
-
schedulePageSnapshotCapture(
|
|
23726
|
+
const wc = event.sender;
|
|
23727
|
+
if (!wc || wc.isDestroyed()) return;
|
|
23728
|
+
schedulePageSnapshotCapture(wc, sendToRendererViews);
|
|
23700
23729
|
});
|
|
23701
23730
|
}
|
|
23702
23731
|
function registerVaultHandlers() {
|
|
@@ -24228,8 +24257,8 @@ function showGroupContextMenu(tabManager, groupId, parentWindow) {
|
|
|
24228
24257
|
function createFindInPageBridge(tabManager, chromeView) {
|
|
24229
24258
|
let wiredWcId = null;
|
|
24230
24259
|
let findResultListener = null;
|
|
24231
|
-
function wireFindEvents(
|
|
24232
|
-
if (wiredWcId ===
|
|
24260
|
+
function wireFindEvents(wc) {
|
|
24261
|
+
if (wiredWcId === wc.id && findResultListener) return;
|
|
24233
24262
|
if (wiredWcId !== null && findResultListener) {
|
|
24234
24263
|
const prev = tabManager.findTabByWebContentsId(wiredWcId);
|
|
24235
24264
|
const prevWc = prev?.view.webContents;
|
|
@@ -24237,17 +24266,17 @@ function createFindInPageBridge(tabManager, chromeView) {
|
|
|
24237
24266
|
prevWc.removeListener("found-in-page", findResultListener);
|
|
24238
24267
|
}
|
|
24239
24268
|
}
|
|
24240
|
-
wiredWcId =
|
|
24241
|
-
if (
|
|
24269
|
+
wiredWcId = wc.id;
|
|
24270
|
+
if (wc.isDestroyed()) return;
|
|
24242
24271
|
const listener = (_event, result) => {
|
|
24243
24272
|
if (!chromeView.webContents.isDestroyed()) {
|
|
24244
24273
|
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
24245
24274
|
}
|
|
24246
24275
|
};
|
|
24247
24276
|
findResultListener = listener;
|
|
24248
|
-
|
|
24249
|
-
const capturedWcId =
|
|
24250
|
-
|
|
24277
|
+
wc.on("found-in-page", listener);
|
|
24278
|
+
const capturedWcId = wc.id;
|
|
24279
|
+
wc.once("destroyed", () => {
|
|
24251
24280
|
if (wiredWcId === capturedWcId) {
|
|
24252
24281
|
wiredWcId = null;
|
|
24253
24282
|
findResultListener = null;
|
|
@@ -24258,10 +24287,10 @@ function createFindInPageBridge(tabManager, chromeView) {
|
|
|
24258
24287
|
start(text, options) {
|
|
24259
24288
|
const tab = tabManager.getActiveTab();
|
|
24260
24289
|
if (!tab) return null;
|
|
24261
|
-
const
|
|
24262
|
-
if (
|
|
24263
|
-
wireFindEvents(
|
|
24264
|
-
return
|
|
24290
|
+
const wc = tab.view.webContents;
|
|
24291
|
+
if (wc.isDestroyed()) return null;
|
|
24292
|
+
wireFindEvents(wc);
|
|
24293
|
+
return wc.findInPage(text, {
|
|
24265
24294
|
forward: options?.forward ?? true,
|
|
24266
24295
|
findNext: options?.findNext ?? false
|
|
24267
24296
|
});
|
|
@@ -24269,17 +24298,17 @@ function createFindInPageBridge(tabManager, chromeView) {
|
|
|
24269
24298
|
next(forward) {
|
|
24270
24299
|
const tab = tabManager.getActiveTab();
|
|
24271
24300
|
if (!tab) return null;
|
|
24272
|
-
const
|
|
24273
|
-
if (
|
|
24274
|
-
wireFindEvents(
|
|
24275
|
-
return
|
|
24301
|
+
const wc = tab.view.webContents;
|
|
24302
|
+
if (wc.isDestroyed()) return null;
|
|
24303
|
+
wireFindEvents(wc);
|
|
24304
|
+
return wc.findInPage("", { forward: forward ?? true, findNext: true });
|
|
24276
24305
|
},
|
|
24277
24306
|
stop(action) {
|
|
24278
24307
|
const tab = tabManager.getActiveTab();
|
|
24279
24308
|
if (!tab) return;
|
|
24280
|
-
const
|
|
24281
|
-
if (
|
|
24282
|
-
|
|
24309
|
+
const wc = tab.view.webContents;
|
|
24310
|
+
if (wc.isDestroyed()) return;
|
|
24311
|
+
wc.stopFindInPage(action ?? "clearSelection");
|
|
24283
24312
|
}
|
|
24284
24313
|
};
|
|
24285
24314
|
}
|
|
@@ -24890,13 +24919,13 @@ const premiumApiOrigin = process.env.VESSEL_PREMIUM_API ? new URL(process.env.VE
|
|
|
24890
24919
|
function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
24891
24920
|
const watchPremiumCheckoutTab = (tabId) => {
|
|
24892
24921
|
const tab = tabManager.getTab(tabId);
|
|
24893
|
-
const
|
|
24894
|
-
if (!
|
|
24922
|
+
const wc = tab?.view.webContents;
|
|
24923
|
+
if (!wc) return;
|
|
24895
24924
|
let completed = false;
|
|
24896
24925
|
const cleanup = () => {
|
|
24897
|
-
|
|
24898
|
-
|
|
24899
|
-
|
|
24926
|
+
wc.removeListener("did-navigate", onNavigate);
|
|
24927
|
+
wc.removeListener("did-navigate-in-page", onNavigateInPage);
|
|
24928
|
+
wc.removeListener("destroyed", cleanup);
|
|
24900
24929
|
};
|
|
24901
24930
|
const handleUrl = async (rawUrl) => {
|
|
24902
24931
|
if (completed) return;
|
|
@@ -24945,10 +24974,10 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
24945
24974
|
if (!isMainFrame) return;
|
|
24946
24975
|
void handleUrl(url);
|
|
24947
24976
|
};
|
|
24948
|
-
|
|
24949
|
-
|
|
24950
|
-
|
|
24951
|
-
const currentUrl =
|
|
24977
|
+
wc.on("did-navigate", onNavigate);
|
|
24978
|
+
wc.on("did-navigate-in-page", onNavigateInPage);
|
|
24979
|
+
wc.on("destroyed", cleanup);
|
|
24980
|
+
const currentUrl = wc.getURL();
|
|
24952
24981
|
if (currentUrl) {
|
|
24953
24982
|
void handleUrl(currentUrl);
|
|
24954
24983
|
}
|
|
@@ -24959,7 +24988,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
24959
24988
|
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
|
|
24960
24989
|
assertString(email, "email");
|
|
24961
24990
|
if (!isValidEmail(email)) {
|
|
24962
|
-
return errorResult
|
|
24991
|
+
return errorResult("Invalid email format");
|
|
24963
24992
|
}
|
|
24964
24993
|
trackPremiumFunnel("activation_attempted");
|
|
24965
24994
|
const result = await requestActivationCode(email);
|
|
@@ -24975,7 +25004,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
24975
25004
|
assertString(code, "code");
|
|
24976
25005
|
assertString(challengeToken, "challengeToken");
|
|
24977
25006
|
if (!isValidEmail(email)) {
|
|
24978
|
-
return errorResult
|
|
25007
|
+
return errorResult("Invalid email format", {
|
|
24979
25008
|
state: getPremiumState()
|
|
24980
25009
|
});
|
|
24981
25010
|
}
|
|
@@ -25109,9 +25138,9 @@ const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
|
25109
25138
|
async function togglePictureInPicture(tabManager) {
|
|
25110
25139
|
const info = getActiveTabInfo(tabManager);
|
|
25111
25140
|
if (!info) return false;
|
|
25112
|
-
const { wc
|
|
25141
|
+
const { wc } = info;
|
|
25113
25142
|
try {
|
|
25114
|
-
return await
|
|
25143
|
+
return await wc.executeJavaScript(`
|
|
25115
25144
|
(async function() {
|
|
25116
25145
|
const video = document.querySelector('video');
|
|
25117
25146
|
if (!video) return false;
|
|
@@ -25449,7 +25478,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25449
25478
|
sidebarResizeActive = true;
|
|
25450
25479
|
clearSidebarResizeRecoveryTimer();
|
|
25451
25480
|
const [width, height] = windowState.mainWindow.getContentSize();
|
|
25452
|
-
const chromeHeight = windowState.uiState.focusMode ? 0 :
|
|
25481
|
+
const chromeHeight = windowState.uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
25453
25482
|
const sidebarWidth = windowState.uiState.sidebarWidth;
|
|
25454
25483
|
const resizeHandleOverlap = 6;
|
|
25455
25484
|
windowState.sidebarView.setBounds({
|
|
@@ -25569,10 +25598,10 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25569
25598
|
if (!activeTab) {
|
|
25570
25599
|
return { success: false, message: "No active tab" };
|
|
25571
25600
|
}
|
|
25572
|
-
const
|
|
25573
|
-
const result = await captureSelectionHighlight(
|
|
25601
|
+
const wc = activeTab.view.webContents;
|
|
25602
|
+
const result = await captureSelectionHighlight(wc);
|
|
25574
25603
|
if (result.success && result.text) {
|
|
25575
|
-
await highlightOnPage(
|
|
25604
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
25576
25605
|
(err) => logger$4.warn("Failed to highlight captured selection:", err)
|
|
25577
25606
|
);
|
|
25578
25607
|
await emitHighlightCount();
|
|
@@ -25593,11 +25622,11 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25593
25622
|
});
|
|
25594
25623
|
electron.ipcMain.on(Channels.HIGHLIGHT_SELECTION, (event, text) => {
|
|
25595
25624
|
try {
|
|
25596
|
-
const
|
|
25597
|
-
if (
|
|
25598
|
-
const tab = tabManager.findTabByWebContentsId(
|
|
25625
|
+
const wc = event.sender;
|
|
25626
|
+
if (wc.isDestroyed()) return;
|
|
25627
|
+
const tab = tabManager.findTabByWebContentsId(wc.id);
|
|
25599
25628
|
if (!tab || !tab.highlightModeActive) return;
|
|
25600
|
-
void persistAndMarkHighlight(
|
|
25629
|
+
void persistAndMarkHighlight(wc, text).then((result) => {
|
|
25601
25630
|
if (result.success && !chromeView.webContents.isDestroyed()) {
|
|
25602
25631
|
void emitHighlightCount();
|
|
25603
25632
|
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
@@ -25638,7 +25667,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25638
25667
|
const info = getActiveTabInfo(tabManager);
|
|
25639
25668
|
if (!info) return false;
|
|
25640
25669
|
try {
|
|
25641
|
-
const cleared = await clearAllHighlightElements(wc);
|
|
25670
|
+
const cleared = await clearAllHighlightElements(info.wc);
|
|
25642
25671
|
if (cleared) {
|
|
25643
25672
|
await emitHighlightCount();
|
|
25644
25673
|
}
|
|
@@ -25701,7 +25730,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25701
25730
|
await electron.session.defaultSession.clearStorageData({ storages });
|
|
25702
25731
|
}
|
|
25703
25732
|
if (history) {
|
|
25704
|
-
|
|
25733
|
+
clearByTimeRange(timeRange);
|
|
25705
25734
|
}
|
|
25706
25735
|
});
|
|
25707
25736
|
electron.ipcMain.handle(Channels.TAB_TOGGLE_PIP, async () => {
|
|
@@ -27017,11 +27046,11 @@ async function bootstrap() {
|
|
|
27017
27046
|
}
|
|
27018
27047
|
const syncActiveHighlightCount = async (state2) => {
|
|
27019
27048
|
const activeTab = state2.tabManager.getActiveTab();
|
|
27020
|
-
const
|
|
27049
|
+
const wc = activeTab?.view.webContents;
|
|
27021
27050
|
let count = 0;
|
|
27022
|
-
if (
|
|
27051
|
+
if (wc && !wc.isDestroyed()) {
|
|
27023
27052
|
try {
|
|
27024
|
-
count = await getHighlightCount(
|
|
27053
|
+
count = await getHighlightCount(wc) ?? 0;
|
|
27025
27054
|
} catch {
|
|
27026
27055
|
count = 0;
|
|
27027
27056
|
}
|