@quanta-intellect/vessel-browser 0.1.12 → 0.1.14
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 +2404 -208
- package/out/preload/content-script.js +303 -19
- package/out/preload/index.js +9 -0
- package/out/renderer/assets/{index-Do3B3G1W.js → index-CvRVBELV.js} +293 -103
- package/out/renderer/index.html +1 -1
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -710,7 +710,8 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
710
710
|
if (text) {
|
|
711
711
|
return wc.executeJavaScript(`
|
|
712
712
|
(function() {
|
|
713
|
-
var searchText = ${JSON.stringify(text)};
|
|
713
|
+
var searchText = (${JSON.stringify(text)} || '').trim();
|
|
714
|
+
var foldedSearchText = searchText.toLowerCase();
|
|
714
715
|
var solidColor = ${JSON.stringify(c.solid)};
|
|
715
716
|
var bgColor = ${JSON.stringify(c.bg)};
|
|
716
717
|
var labelBg = ${JSON.stringify(c.label)};
|
|
@@ -748,7 +749,11 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
748
749
|
});
|
|
749
750
|
var n;
|
|
750
751
|
while ((n = w.nextNode())) {
|
|
751
|
-
var
|
|
752
|
+
var haystack = n.textContent || '';
|
|
753
|
+
var idx = haystack.indexOf(searchText);
|
|
754
|
+
if (idx === -1 && foldedSearchText) {
|
|
755
|
+
idx = haystack.toLowerCase().indexOf(foldedSearchText);
|
|
756
|
+
}
|
|
752
757
|
if (idx !== -1) {
|
|
753
758
|
matches.push({ node: n, idx: idx });
|
|
754
759
|
if (matches.length >= limit) break;
|
|
@@ -2004,6 +2009,7 @@ const Channels = {
|
|
|
2004
2009
|
BOOKMARKS_UPDATE: "bookmarks:update",
|
|
2005
2010
|
BOOKMARK_SAVE: "bookmarks:save",
|
|
2006
2011
|
BOOKMARK_REMOVE: "bookmarks:remove",
|
|
2012
|
+
BOOKMARK_ADD_CONTEXT_TO_CHAT: "bookmarks:add-context-to-chat",
|
|
2007
2013
|
FOLDER_CREATE: "bookmarks:folder-create",
|
|
2008
2014
|
FOLDER_REMOVE: "bookmarks:folder-remove",
|
|
2009
2015
|
FOLDER_RENAME: "bookmarks:folder-rename",
|
|
@@ -2062,6 +2068,10 @@ async function getSidebarContextTarget(sidebarView, x, y) {
|
|
|
2062
2068
|
return {
|
|
2063
2069
|
inHighlightNav: !!nav,
|
|
2064
2070
|
canRemoveCurrent: /\\d+\\s*\\/\\s*\\d+/.test(label),
|
|
2071
|
+
bookmarkId:
|
|
2072
|
+
el && typeof el.closest === "function"
|
|
2073
|
+
? el.closest("[data-bookmark-id]")?.getAttribute("data-bookmark-id") || undefined
|
|
2074
|
+
: undefined,
|
|
2065
2075
|
};
|
|
2066
2076
|
})()`,
|
|
2067
2077
|
true
|
|
@@ -2095,6 +2105,20 @@ async function showSidebarContextMenu(mainWindow, sidebarView, params) {
|
|
|
2095
2105
|
})
|
|
2096
2106
|
);
|
|
2097
2107
|
}
|
|
2108
|
+
if (target.bookmarkId) {
|
|
2109
|
+
if (menu.items.length > 0) {
|
|
2110
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
2111
|
+
}
|
|
2112
|
+
menu.append(
|
|
2113
|
+
new electron.MenuItem({
|
|
2114
|
+
label: "Add Context to Chat",
|
|
2115
|
+
click: () => sidebarView.webContents.send(
|
|
2116
|
+
Channels.BOOKMARK_ADD_CONTEXT_TO_CHAT,
|
|
2117
|
+
target.bookmarkId
|
|
2118
|
+
)
|
|
2119
|
+
})
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2098
2122
|
if (params.isEditable) {
|
|
2099
2123
|
if (menu.items.length > 0) {
|
|
2100
2124
|
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
@@ -2226,7 +2250,14 @@ function createMainWindow(onTabStateChange) {
|
|
|
2226
2250
|
return state2;
|
|
2227
2251
|
}
|
|
2228
2252
|
function layoutViews(state2) {
|
|
2229
|
-
const {
|
|
2253
|
+
const {
|
|
2254
|
+
mainWindow,
|
|
2255
|
+
chromeView,
|
|
2256
|
+
sidebarView,
|
|
2257
|
+
devtoolsPanelView,
|
|
2258
|
+
tabManager,
|
|
2259
|
+
uiState
|
|
2260
|
+
} = state2;
|
|
2230
2261
|
const [width, height] = mainWindow.getContentSize();
|
|
2231
2262
|
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
2232
2263
|
const sidebarWidth = uiState.sidebarOpen ? uiState.sidebarWidth : 0;
|
|
@@ -2971,11 +3002,25 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
2971
3002
|
}
|
|
2972
3003
|
|
|
2973
3004
|
function viewportWidth() {
|
|
2974
|
-
return
|
|
3005
|
+
return Math.max(
|
|
3006
|
+
window.innerWidth || 0,
|
|
3007
|
+
window.visualViewport?.width || 0,
|
|
3008
|
+
document.documentElement?.clientWidth || 0,
|
|
3009
|
+
document.scrollingElement?.clientWidth || 0,
|
|
3010
|
+
document.body?.clientWidth || 0,
|
|
3011
|
+
window.screen?.availWidth || 0,
|
|
3012
|
+
);
|
|
2975
3013
|
}
|
|
2976
3014
|
|
|
2977
3015
|
function viewportHeight() {
|
|
2978
|
-
return
|
|
3016
|
+
return Math.max(
|
|
3017
|
+
window.innerHeight || 0,
|
|
3018
|
+
window.visualViewport?.height || 0,
|
|
3019
|
+
document.documentElement?.clientHeight || 0,
|
|
3020
|
+
document.scrollingElement?.clientHeight || 0,
|
|
3021
|
+
document.body?.clientHeight || 0,
|
|
3022
|
+
window.screen?.availHeight || 0,
|
|
3023
|
+
);
|
|
2979
3024
|
}
|
|
2980
3025
|
|
|
2981
3026
|
function scrollingElement() {
|
|
@@ -3032,6 +3077,60 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3032
3077
|
rect.bottom >= centerY;
|
|
3033
3078
|
}
|
|
3034
3079
|
|
|
3080
|
+
function touchesViewportEdge(rect) {
|
|
3081
|
+
const edgePadding = 24;
|
|
3082
|
+
return rect.left <= edgePadding ||
|
|
3083
|
+
rect.top <= edgePadding ||
|
|
3084
|
+
rect.right >= viewportWidth() - edgePadding ||
|
|
3085
|
+
rect.bottom >= viewportHeight() - edgePadding;
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
function hasFixedAncestor(el) {
|
|
3089
|
+
var cur = el.parentElement;
|
|
3090
|
+
while (cur && cur !== document.body) {
|
|
3091
|
+
var ps = getComputedStyle(cur).position;
|
|
3092
|
+
if (ps === "fixed" || ps === "sticky") return true;
|
|
3093
|
+
cur = cur.parentElement;
|
|
3094
|
+
}
|
|
3095
|
+
return false;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
function isPositioned(style) {
|
|
3099
|
+
return style.position === "fixed" || style.position === "sticky" ||
|
|
3100
|
+
style.position === "absolute";
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
function effectiveZIndex(style, el) {
|
|
3104
|
+
var z = parseZIndex(style);
|
|
3105
|
+
if (z > 0) return z;
|
|
3106
|
+
var cur = el.parentElement;
|
|
3107
|
+
while (cur && cur !== document.body) {
|
|
3108
|
+
var pz = parseZIndex(getComputedStyle(cur));
|
|
3109
|
+
if (pz > 0) return pz;
|
|
3110
|
+
cur = cur.parentElement;
|
|
3111
|
+
}
|
|
3112
|
+
return 0;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
function looksLikeDrawer(style, rect, areaRatio, el) {
|
|
3116
|
+
if (rect.width < 220 || rect.height < 160 || areaRatio < 0.08) return false;
|
|
3117
|
+
if (!touchesViewportEdge(rect)) return false;
|
|
3118
|
+
if (style.position === "fixed" || style.position === "sticky") {
|
|
3119
|
+
return effectiveZIndex(style, el) >= 5;
|
|
3120
|
+
}
|
|
3121
|
+
if (style.position === "absolute" && hasFixedAncestor(el)) {
|
|
3122
|
+
return effectiveZIndex(style, el) >= 5;
|
|
3123
|
+
}
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
function looksLikeCartConfirmation(node) {
|
|
3128
|
+
var t = (node.textContent || "").slice(0, 500).toLowerCase();
|
|
3129
|
+
var signals = ["added to cart", "added to bag", "added to basket",
|
|
3130
|
+
"added to your cart", "added to your bag", "added to your basket"];
|
|
3131
|
+
return signals.some(function(s) { return t.indexOf(s) !== -1; });
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3035
3134
|
function detectOverlays() {
|
|
3036
3135
|
if (!document.body) return [];
|
|
3037
3136
|
const viewportArea = Math.max(1, viewportWidth() * viewportHeight());
|
|
@@ -3070,7 +3169,13 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3070
3169
|
var type = overlayType(node);
|
|
3071
3170
|
var dialogLike = type === "dialog" || type === "modal";
|
|
3072
3171
|
var areaRatio = (rect.width * rect.height) / viewportArea;
|
|
3172
|
+
var drawerLike = looksLikeDrawer(style, rect, areaRatio, node);
|
|
3173
|
+
var cartConfirm = !dialogLike && !drawerLike && isPositioned(style) &&
|
|
3174
|
+
rect.width >= 160 && rect.height >= 100 &&
|
|
3175
|
+
looksLikeCartConfirmation(node);
|
|
3073
3176
|
var blocksInteraction = dialogLike ||
|
|
3177
|
+
drawerLike ||
|
|
3178
|
+
cartConfirm ||
|
|
3074
3179
|
((style.position === "fixed" || style.position === "sticky") &&
|
|
3075
3180
|
parseZIndex(style) >= 10 &&
|
|
3076
3181
|
areaRatio >= 0.3 &&
|
|
@@ -3188,6 +3293,11 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3188
3293
|
}
|
|
3189
3294
|
|
|
3190
3295
|
function contextOf(el) {
|
|
3296
|
+
const overlayRoot = overlays.find(
|
|
3297
|
+
(overlay) => overlay.element === el || overlay.element.contains(el),
|
|
3298
|
+
);
|
|
3299
|
+
if (overlayRoot) return "dialog";
|
|
3300
|
+
|
|
3191
3301
|
let current = el.parentElement;
|
|
3192
3302
|
while (current) {
|
|
3193
3303
|
const tag = current.tagName.toLowerCase();
|
|
@@ -4466,6 +4576,158 @@ function createProvider(config) {
|
|
|
4466
4576
|
}
|
|
4467
4577
|
return new OpenAICompatProvider(normalized);
|
|
4468
4578
|
}
|
|
4579
|
+
const CORRECT_HINT_RE = /\b(correct|right choice|this is correct|correct answer|pick this|select this|choose this|right answer)\b/i;
|
|
4580
|
+
const WRONG_HINT_RE = /\b(wrong|incorrect|not this|don't pick|do not pick|bad option|decoy)\b/i;
|
|
4581
|
+
function elementLabel(el) {
|
|
4582
|
+
return el.text?.trim() || el.label?.trim() || el.value?.trim() || el.placeholder?.trim() || void 0;
|
|
4583
|
+
}
|
|
4584
|
+
function isOverlayAction(el) {
|
|
4585
|
+
if (el.type === "button" || el.type === "link") return true;
|
|
4586
|
+
if (el.type !== "input") return false;
|
|
4587
|
+
return ["button", "submit", "radio", "checkbox"].includes(
|
|
4588
|
+
(el.inputType || "").toLowerCase()
|
|
4589
|
+
);
|
|
4590
|
+
}
|
|
4591
|
+
function isRadioOption(el) {
|
|
4592
|
+
return el.role === "radio" || el.type === "input" && (el.inputType || "").toLowerCase() === "radio";
|
|
4593
|
+
}
|
|
4594
|
+
function normalizeAction(el) {
|
|
4595
|
+
return {
|
|
4596
|
+
index: el.index,
|
|
4597
|
+
label: elementLabel(el),
|
|
4598
|
+
selector: el.selector,
|
|
4599
|
+
role: el.role,
|
|
4600
|
+
labelSource: el.labelSource,
|
|
4601
|
+
looksCorrect: el.looksCorrect !== void 0 ? el.looksCorrect : looksLikeCorrectOption(elementLabel(el))
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
function normalizeStoredAction(action) {
|
|
4605
|
+
return {
|
|
4606
|
+
label: action.label,
|
|
4607
|
+
selector: action.selector,
|
|
4608
|
+
role: action.kind === "radio" ? "radio" : void 0
|
|
4609
|
+
};
|
|
4610
|
+
}
|
|
4611
|
+
function normalizeStoredRadioOption(option) {
|
|
4612
|
+
return {
|
|
4613
|
+
label: option.label,
|
|
4614
|
+
selector: option.selector,
|
|
4615
|
+
role: "radio",
|
|
4616
|
+
labelSource: option.labelSource,
|
|
4617
|
+
looksCorrect: option.looksCorrect !== void 0 ? option.looksCorrect : looksLikeCorrectOption(option.label)
|
|
4618
|
+
};
|
|
4619
|
+
}
|
|
4620
|
+
function dedupeCandidates(actions) {
|
|
4621
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4622
|
+
return actions.filter((action) => {
|
|
4623
|
+
const key = [
|
|
4624
|
+
action.selector || "",
|
|
4625
|
+
action.label || "",
|
|
4626
|
+
action.role || "",
|
|
4627
|
+
action.labelSource || ""
|
|
4628
|
+
].join("::");
|
|
4629
|
+
if (seen.has(key)) return false;
|
|
4630
|
+
seen.add(key);
|
|
4631
|
+
return true;
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
function classifyOverlayKind(overlay, radioOptions) {
|
|
4635
|
+
if (overlay.kind) {
|
|
4636
|
+
return overlay.kind;
|
|
4637
|
+
}
|
|
4638
|
+
const haystack = [overlay.label, overlay.text, overlay.role].filter(Boolean).join(" ").toLowerCase();
|
|
4639
|
+
if (/cookie|consent|privacy|gdpr|ccpa|onetrust|trustarc|cookiebot/.test(
|
|
4640
|
+
haystack
|
|
4641
|
+
)) {
|
|
4642
|
+
return "cookie_consent";
|
|
4643
|
+
}
|
|
4644
|
+
if (radioOptions.length > 0) return "selection_modal";
|
|
4645
|
+
if (overlay.role === "alertdialog" || /\b(alert|warning|error)\b/.test(haystack)) {
|
|
4646
|
+
return "alert";
|
|
4647
|
+
}
|
|
4648
|
+
if (overlay.type === "dialog") return "dialog";
|
|
4649
|
+
if (overlay.type === "modal") return "modal";
|
|
4650
|
+
return "overlay";
|
|
4651
|
+
}
|
|
4652
|
+
function findAction(actions, matcher) {
|
|
4653
|
+
return actions.find(
|
|
4654
|
+
(action) => matcher.test((action.label || "").toLowerCase())
|
|
4655
|
+
);
|
|
4656
|
+
}
|
|
4657
|
+
function looksLikeCorrectOption(label) {
|
|
4658
|
+
const text = label?.trim();
|
|
4659
|
+
if (!text) return void 0;
|
|
4660
|
+
if (CORRECT_HINT_RE.test(text)) return true;
|
|
4661
|
+
if (WRONG_HINT_RE.test(text)) return false;
|
|
4662
|
+
return void 0;
|
|
4663
|
+
}
|
|
4664
|
+
function getBlockingOverlaySignature(overlays) {
|
|
4665
|
+
return overlays.filter((overlay) => overlay.blocksInteraction).map(
|
|
4666
|
+
(overlay) => [
|
|
4667
|
+
overlay.kind,
|
|
4668
|
+
overlay.selector || "",
|
|
4669
|
+
overlay.label || "",
|
|
4670
|
+
overlay.text || "",
|
|
4671
|
+
overlay.actions.map((action) => `${action.selector || ""}:${action.label || ""}`).join("|"),
|
|
4672
|
+
overlay.radioOptions.map((option) => `${option.selector || ""}:${option.label || ""}`).join("|")
|
|
4673
|
+
].join("::")
|
|
4674
|
+
).join("||");
|
|
4675
|
+
}
|
|
4676
|
+
function buildOverlayInventory(page) {
|
|
4677
|
+
if (page.overlays.length === 0) return [];
|
|
4678
|
+
return page.overlays.map((overlay) => {
|
|
4679
|
+
const controls = dedupeCandidates([
|
|
4680
|
+
...page.interactiveElements.filter((el) => {
|
|
4681
|
+
if (overlay.selector && el.parentOverlay === overlay.selector) {
|
|
4682
|
+
return true;
|
|
4683
|
+
}
|
|
4684
|
+
return page.overlays.length === 1 && el.context === "dialog";
|
|
4685
|
+
}).filter(isOverlayAction).map(normalizeAction),
|
|
4686
|
+
...(overlay.actions || []).map(normalizeStoredAction)
|
|
4687
|
+
]).filter((action) => action.label || action.selector);
|
|
4688
|
+
const radioOptions = dedupeCandidates([
|
|
4689
|
+
...page.interactiveElements.filter((el) => {
|
|
4690
|
+
if (!isRadioOption(el)) return false;
|
|
4691
|
+
if (overlay.selector && el.parentOverlay === overlay.selector) {
|
|
4692
|
+
return true;
|
|
4693
|
+
}
|
|
4694
|
+
return page.overlays.length === 1 && el.context === "dialog";
|
|
4695
|
+
}).map(normalizeAction),
|
|
4696
|
+
...(overlay.radioOptions || []).map(normalizeStoredRadioOption)
|
|
4697
|
+
]).filter((action) => action.label || action.selector);
|
|
4698
|
+
const kind = classifyOverlayKind(overlay, radioOptions);
|
|
4699
|
+
const dismissAction = findAction(
|
|
4700
|
+
controls,
|
|
4701
|
+
/\b(close|dismiss|skip|cancel|reject|decline|no thanks|not now|maybe later|continue without)\b/
|
|
4702
|
+
);
|
|
4703
|
+
const acceptAction = findAction(
|
|
4704
|
+
controls,
|
|
4705
|
+
/\b(accept|allow|agree|got it|ok|okay|consent)\b/
|
|
4706
|
+
);
|
|
4707
|
+
const submitAction = findAction(
|
|
4708
|
+
controls,
|
|
4709
|
+
/\b(submit|continue|confirm|done|next|save|apply|finish)\b/
|
|
4710
|
+
);
|
|
4711
|
+
const correctOption = radioOptions.find(
|
|
4712
|
+
(option) => option.looksCorrect === true
|
|
4713
|
+
);
|
|
4714
|
+
return {
|
|
4715
|
+
type: overlay.type,
|
|
4716
|
+
kind,
|
|
4717
|
+
role: overlay.role,
|
|
4718
|
+
label: overlay.label,
|
|
4719
|
+
selector: overlay.selector,
|
|
4720
|
+
text: overlay.text,
|
|
4721
|
+
blocksInteraction: overlay.blocksInteraction,
|
|
4722
|
+
actions: controls,
|
|
4723
|
+
radioOptions,
|
|
4724
|
+
dismissAction,
|
|
4725
|
+
acceptAction,
|
|
4726
|
+
submitAction,
|
|
4727
|
+
correctOption
|
|
4728
|
+
};
|
|
4729
|
+
});
|
|
4730
|
+
}
|
|
4469
4731
|
const MAX_CONTENT_LENGTH = 6e4;
|
|
4470
4732
|
const MAX_STRUCTURED_ITEMS = 100;
|
|
4471
4733
|
const LARGE_PAGE_HINT_THRESHOLD = 12e3;
|
|
@@ -4533,10 +4795,18 @@ function formatElementMeta(el) {
|
|
|
4533
4795
|
if (el.pattern) {
|
|
4534
4796
|
meta.push(`pattern="${el.pattern}"`);
|
|
4535
4797
|
}
|
|
4798
|
+
if (el.labelSource) {
|
|
4799
|
+
meta.push(`source=${el.labelSource}`);
|
|
4800
|
+
}
|
|
4801
|
+
if (el.looksCorrect === true) {
|
|
4802
|
+
meta.push("likely-correct");
|
|
4803
|
+
} else if (el.looksCorrect === false) {
|
|
4804
|
+
meta.push("likely-wrong");
|
|
4805
|
+
}
|
|
4536
4806
|
if (el.description) {
|
|
4537
4807
|
meta.push(`desc="${el.description.slice(0, 80)}"`);
|
|
4538
4808
|
}
|
|
4539
|
-
if (el.value) {
|
|
4809
|
+
if (el.value !== void 0 && el.value !== null && el.value !== "") {
|
|
4540
4810
|
meta.push(`value="${el.value.slice(0, 60)}"`);
|
|
4541
4811
|
}
|
|
4542
4812
|
if (el.selector) {
|
|
@@ -4545,14 +4815,207 @@ function formatElementMeta(el) {
|
|
|
4545
4815
|
}
|
|
4546
4816
|
return meta;
|
|
4547
4817
|
}
|
|
4818
|
+
function summarizeElementValue(el) {
|
|
4819
|
+
const value = typeof el.value === "string" && el.value.trim() ? el.value.trim() : "";
|
|
4820
|
+
if (!value) return null;
|
|
4821
|
+
if (el.type === "select") {
|
|
4822
|
+
return { label: "selected", value: value.slice(0, 60) };
|
|
4823
|
+
}
|
|
4824
|
+
if (el.type === "textarea") {
|
|
4825
|
+
return { label: "current", value: value.slice(0, 60) };
|
|
4826
|
+
}
|
|
4827
|
+
if (el.type === "input") {
|
|
4828
|
+
return { label: "current", value: value.slice(0, 60) };
|
|
4829
|
+
}
|
|
4830
|
+
return null;
|
|
4831
|
+
}
|
|
4832
|
+
function isQuantityLike(el) {
|
|
4833
|
+
const text = [
|
|
4834
|
+
el.label,
|
|
4835
|
+
el.name,
|
|
4836
|
+
el.placeholder,
|
|
4837
|
+
el.text,
|
|
4838
|
+
el.description,
|
|
4839
|
+
el.selector
|
|
4840
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
4841
|
+
return /\b(qty|quantity|count|items?)\b/.test(text) || el.inputType === "number" && (/\b(quantity|qty|count|items?)\b/.test(text) || el.name === "quantity" || el.name === "qty");
|
|
4842
|
+
}
|
|
4843
|
+
function getQuantityElements(page) {
|
|
4844
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4845
|
+
const elements = [
|
|
4846
|
+
...page.interactiveElements,
|
|
4847
|
+
...page.forms.flatMap((form) => form.fields)
|
|
4848
|
+
];
|
|
4849
|
+
return elements.filter((el) => {
|
|
4850
|
+
if (!isQuantityLike(el)) return false;
|
|
4851
|
+
const key = String(
|
|
4852
|
+
el.index ?? el.selector ?? `${el.type}|${el.name || ""}|${el.label || ""}|${el.value || ""}`
|
|
4853
|
+
);
|
|
4854
|
+
if (seen.has(key)) return false;
|
|
4855
|
+
seen.add(key);
|
|
4856
|
+
return true;
|
|
4857
|
+
});
|
|
4858
|
+
}
|
|
4859
|
+
function formatQuantityElements(elements) {
|
|
4860
|
+
if (elements.length === 0) return "None detected";
|
|
4861
|
+
return limitItems(elements, 12).map((el) => {
|
|
4862
|
+
const prefix = el.index ? `[#${el.index}]` : "-";
|
|
4863
|
+
const name = el.label || el.name || el.placeholder || "Quantity";
|
|
4864
|
+
const summary = summarizeElementValue(el);
|
|
4865
|
+
const parts = [prefix, `[${name}]`, el.type];
|
|
4866
|
+
if (summary) {
|
|
4867
|
+
parts.push(`${summary.label}="${summary.value}"`);
|
|
4868
|
+
}
|
|
4869
|
+
const meta = formatElementMeta({
|
|
4870
|
+
...el,
|
|
4871
|
+
value: void 0
|
|
4872
|
+
});
|
|
4873
|
+
if (meta.length > 0) {
|
|
4874
|
+
parts.push(`(${meta.join(", ")})`);
|
|
4875
|
+
}
|
|
4876
|
+
return parts.join(" ");
|
|
4877
|
+
}).join("\n");
|
|
4878
|
+
}
|
|
4879
|
+
function isCartLikePage(page) {
|
|
4880
|
+
const url = page.url.toLowerCase();
|
|
4881
|
+
const text = `${page.title}
|
|
4882
|
+
${page.content}`.toLowerCase();
|
|
4883
|
+
return url.includes("cart") || url.includes("checkout") || url.includes("basket") || url.includes("bag") || getQuantityElements(page).length > 0 || /\b(subtotal|order total|cart total|checkout|shopping cart)\b/.test(text);
|
|
4884
|
+
}
|
|
4885
|
+
function getCartItemLinks(page) {
|
|
4886
|
+
const blockedText = /\b(remove|delete|wishlist|save for later|move to|checkout|view cart|continue shopping|edit|details?)\b/i;
|
|
4887
|
+
const blockedHref = /\/(cart|checkout|wishlist|account|login|signin|remove|delete)(\/|$)|[?&](remove|delete|wishlist)=/i;
|
|
4888
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4889
|
+
return page.interactiveElements.filter((el) => el.type === "link").filter((el) => {
|
|
4890
|
+
const text = (el.text || "").trim();
|
|
4891
|
+
const href = (el.href || "").trim();
|
|
4892
|
+
if (!text || text.length < 3 || !href) return false;
|
|
4893
|
+
if (el.context === "nav" || el.context === "footer" || el.context === "sidebar") {
|
|
4894
|
+
return false;
|
|
4895
|
+
}
|
|
4896
|
+
if (blockedText.test(text) || blockedHref.test(href)) return false;
|
|
4897
|
+
const key = `${normalizeComparable(text)}|${normalizeUrlForMatch(href) || href}`;
|
|
4898
|
+
if (seen.has(key)) return false;
|
|
4899
|
+
seen.add(key);
|
|
4900
|
+
return true;
|
|
4901
|
+
}).slice(0, 12);
|
|
4902
|
+
}
|
|
4903
|
+
function extractCartTotals(content) {
|
|
4904
|
+
const lines = content.split(/\n+/).map((line) => line.trim()).filter(Boolean);
|
|
4905
|
+
const totalLines = [];
|
|
4906
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4907
|
+
const keyword = /\b(subtotal|order total|estimated total|total|tax|shipping|discount|savings?)\b/i;
|
|
4908
|
+
const money = /([$€£]\s?\d[\d,]*(?:\.\d{2})?|\d[\d,]*(?:\.\d{2})?\s?(?:usd|eur|gbp))/i;
|
|
4909
|
+
for (const line of lines) {
|
|
4910
|
+
if (!keyword.test(line)) continue;
|
|
4911
|
+
if (!money.test(line) && line.length > 90) continue;
|
|
4912
|
+
const cleaned = line.replace(/\s+/g, " ").trim();
|
|
4913
|
+
if (seen.has(cleaned.toLowerCase())) continue;
|
|
4914
|
+
seen.add(cleaned.toLowerCase());
|
|
4915
|
+
totalLines.push(cleaned);
|
|
4916
|
+
if (totalLines.length >= 6) break;
|
|
4917
|
+
}
|
|
4918
|
+
return totalLines;
|
|
4919
|
+
}
|
|
4920
|
+
function formatCartSnapshot(page) {
|
|
4921
|
+
if (!isCartLikePage(page)) return null;
|
|
4922
|
+
const itemLinks = getCartItemLinks(page);
|
|
4923
|
+
const quantityElements = getQuantityElements(page);
|
|
4924
|
+
const quantityValues = quantityElements.map((el) => summarizeElementValue(el)?.value || "").filter(Boolean);
|
|
4925
|
+
const numericQuantities = quantityValues.map((value) => Number.parseFloat(value)).filter((value) => Number.isFinite(value) && value >= 0);
|
|
4926
|
+
const totalLines = extractCartTotals(page.content);
|
|
4927
|
+
const lines = [];
|
|
4928
|
+
if (itemLinks.length > 0) {
|
|
4929
|
+
lines.push(`Distinct items: ${itemLinks.length}`);
|
|
4930
|
+
lines.push(
|
|
4931
|
+
`Items: ${itemLinks.slice(0, 8).map((item) => item.text || item.label || "Untitled item").join(" | ")}`
|
|
4932
|
+
);
|
|
4933
|
+
}
|
|
4934
|
+
if (quantityElements.length > 0) {
|
|
4935
|
+
if (numericQuantities.length === quantityElements.length && numericQuantities.length > 0) {
|
|
4936
|
+
const unique = Array.from(new Set(numericQuantities));
|
|
4937
|
+
const totalUnits = numericQuantities.reduce(
|
|
4938
|
+
(sum, value) => sum + value,
|
|
4939
|
+
0
|
|
4940
|
+
);
|
|
4941
|
+
lines.push(
|
|
4942
|
+
unique.length === 1 ? `Quantity controls: ${quantityElements.length} (all set to ${unique[0]})` : `Quantity controls: ${quantityElements.length} (${numericQuantities.join(", ")})`
|
|
4943
|
+
);
|
|
4944
|
+
lines.push(`Total units inferred: ${totalUnits}`);
|
|
4945
|
+
if (itemLinks.length > 0 && totalUnits > itemLinks.length) {
|
|
4946
|
+
lines.push(
|
|
4947
|
+
`Attention: ${itemLinks.length} distinct items but ${totalUnits} total units. Check for duplicate quantities.`
|
|
4948
|
+
);
|
|
4949
|
+
}
|
|
4950
|
+
} else {
|
|
4951
|
+
lines.push(
|
|
4952
|
+
`Quantity controls: ${quantityElements.length}${quantityValues.length > 0 ? ` (${quantityValues.join(", ")})` : ""}`
|
|
4953
|
+
);
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
if (totalLines.length > 0) {
|
|
4957
|
+
lines.push("Totals:");
|
|
4958
|
+
totalLines.forEach((line) => lines.push(`- ${line}`));
|
|
4959
|
+
}
|
|
4960
|
+
if (lines.length === 0) return null;
|
|
4961
|
+
return lines.join("\n");
|
|
4962
|
+
}
|
|
4548
4963
|
function isVisibleToUser(el) {
|
|
4549
4964
|
return el.visible === true && el.inViewport === true && el.obscured !== true && el.blockedByOverlay !== true;
|
|
4550
4965
|
}
|
|
4966
|
+
function getDialogFocusedElements(page) {
|
|
4967
|
+
return page.interactiveElements.filter(
|
|
4968
|
+
(el) => isVisibleToUser(el) && el.context === "dialog"
|
|
4969
|
+
);
|
|
4970
|
+
}
|
|
4971
|
+
function normalizeOverlayText(value) {
|
|
4972
|
+
return (value || "").trim().toLowerCase();
|
|
4973
|
+
}
|
|
4974
|
+
function isCartConfirmationLike(page) {
|
|
4975
|
+
const overlayText = page.overlays.map(
|
|
4976
|
+
(overlay) => normalizeOverlayText(
|
|
4977
|
+
[overlay.label, overlay.text].filter(Boolean).join(" ")
|
|
4978
|
+
)
|
|
4979
|
+
).join(" ");
|
|
4980
|
+
const dialogText = getDialogFocusedElements(page).map((el) => normalizeOverlayText(el.text || el.label || el.description)).join(" ");
|
|
4981
|
+
const haystack = `${overlayText} ${dialogText}`.trim();
|
|
4982
|
+
if (!haystack) return false;
|
|
4983
|
+
const cartSignals = [
|
|
4984
|
+
"added to cart",
|
|
4985
|
+
"added to bag",
|
|
4986
|
+
"added to basket",
|
|
4987
|
+
"shopping cart",
|
|
4988
|
+
"view cart",
|
|
4989
|
+
"go to cart",
|
|
4990
|
+
"continue shopping",
|
|
4991
|
+
"keep shopping",
|
|
4992
|
+
"checkout"
|
|
4993
|
+
];
|
|
4994
|
+
return cartSignals.some((signal) => haystack.includes(signal));
|
|
4995
|
+
}
|
|
4996
|
+
function formatDialogFocus(page) {
|
|
4997
|
+
const dialogElements = getDialogFocusedElements(page);
|
|
4998
|
+
if (dialogElements.length === 0) return null;
|
|
4999
|
+
const lines = [];
|
|
5000
|
+
lines.push(
|
|
5001
|
+
"A live dialog/modal is open. Prioritize its controls before acting on the page behind it."
|
|
5002
|
+
);
|
|
5003
|
+
if (isCartConfirmationLike(page)) {
|
|
5004
|
+
lines.push(
|
|
5005
|
+
"Cart confirmation detected: choose a dialog action such as Continue Shopping, View Cart, or Checkout. Do not click background Add to Cart again."
|
|
5006
|
+
);
|
|
5007
|
+
}
|
|
5008
|
+
lines.push("");
|
|
5009
|
+
lines.push("Visible dialog controls:");
|
|
5010
|
+
lines.push(formatInteractiveElements(dialogElements));
|
|
5011
|
+
return lines.join("\n");
|
|
5012
|
+
}
|
|
4551
5013
|
function formatInteractiveElements(elements) {
|
|
4552
5014
|
if (elements.length === 0) return "None";
|
|
4553
5015
|
const sorted = [...elements].sort((a, b) => {
|
|
4554
5016
|
const scoreEl = (el) => {
|
|
4555
5017
|
let s = 0;
|
|
5018
|
+
if (el.context === "dialog") s -= 40;
|
|
4556
5019
|
if (el.visible === false) s += 100;
|
|
4557
5020
|
if (el.inViewport === false) s += 50;
|
|
4558
5021
|
if (el.context === "nav" || el.context === "footer" || el.context === "sidebar")
|
|
@@ -4569,7 +5032,7 @@ function formatInteractiveElements(elements) {
|
|
|
4569
5032
|
const parts = [prefix];
|
|
4570
5033
|
if (el.type === "button") {
|
|
4571
5034
|
parts.push(`[${el.text || "Button"}]`);
|
|
4572
|
-
parts.push("button");
|
|
5035
|
+
parts.push(el.role === "radio" ? "radio" : "button");
|
|
4573
5036
|
} else if (el.type === "link") {
|
|
4574
5037
|
parts.push(`[${el.text || "Link"}]`);
|
|
4575
5038
|
parts.push("link");
|
|
@@ -4578,10 +5041,14 @@ function formatInteractiveElements(elements) {
|
|
|
4578
5041
|
parts.push(`[${el.label || el.placeholder || "Input"}]`);
|
|
4579
5042
|
parts.push(el.inputType || "text");
|
|
4580
5043
|
parts.push("input");
|
|
5044
|
+
const summary = summarizeElementValue(el);
|
|
5045
|
+
if (summary) parts.push(`${summary.label}="${summary.value}"`);
|
|
4581
5046
|
if (el.required) parts.push("(required)");
|
|
4582
5047
|
} else if (el.type === "select") {
|
|
4583
5048
|
parts.push(`[${el.label || "Select"}]`);
|
|
4584
5049
|
parts.push("dropdown");
|
|
5050
|
+
const summary = summarizeElementValue(el);
|
|
5051
|
+
if (summary) parts.push(`${summary.label}="${summary.value}"`);
|
|
4585
5052
|
if (el.options?.length) {
|
|
4586
5053
|
parts.push(
|
|
4587
5054
|
`options=${el.options.slice(0, 5).map((o) => typeof o === "string" ? o : o.label || o.value).join("|")}`
|
|
@@ -4590,6 +5057,8 @@ function formatInteractiveElements(elements) {
|
|
|
4590
5057
|
} else if (el.type === "textarea") {
|
|
4591
5058
|
parts.push(`[${el.label || "Text Area"}]`);
|
|
4592
5059
|
parts.push("textarea");
|
|
5060
|
+
const summary = summarizeElementValue(el);
|
|
5061
|
+
if (summary) parts.push(`${summary.label}="${summary.value}"`);
|
|
4593
5062
|
}
|
|
4594
5063
|
const meta = formatElementMeta(el);
|
|
4595
5064
|
if (meta.length > 0) parts.push(`(${meta.join(", ")})`);
|
|
@@ -4628,14 +5097,18 @@ function formatForms(forms) {
|
|
|
4628
5097
|
];
|
|
4629
5098
|
if (field.type === "button") {
|
|
4630
5099
|
fieldParts.push(`[${field.text || "Submit"}]`);
|
|
4631
|
-
fieldParts.push("button");
|
|
5100
|
+
fieldParts.push(field.role === "radio" ? "radio" : "button");
|
|
4632
5101
|
} else if (field.type === "input") {
|
|
4633
5102
|
fieldParts.push(`[${field.label || field.placeholder || "Input"}]`);
|
|
4634
5103
|
fieldParts.push(field.inputType || "text");
|
|
5104
|
+
const summary = summarizeElementValue(field);
|
|
5105
|
+
if (summary) fieldParts.push(`${summary.label}="${summary.value}"`);
|
|
4635
5106
|
if (field.required) fieldParts.push("(required)");
|
|
4636
5107
|
} else if (field.type === "select") {
|
|
4637
5108
|
fieldParts.push(`[${field.label || "Select"}]`);
|
|
4638
5109
|
fieldParts.push("dropdown");
|
|
5110
|
+
const summary = summarizeElementValue(field);
|
|
5111
|
+
if (summary) fieldParts.push(`${summary.label}="${summary.value}"`);
|
|
4639
5112
|
if (field.options?.length) {
|
|
4640
5113
|
fieldParts.push(
|
|
4641
5114
|
`options=${field.options.slice(0, 5).map((o) => typeof o === "string" ? o : o.label || o.value).join("|")}`
|
|
@@ -4644,6 +5117,8 @@ function formatForms(forms) {
|
|
|
4644
5117
|
} else if (field.type === "textarea") {
|
|
4645
5118
|
fieldParts.push(`[${field.label || "Text"}]`);
|
|
4646
5119
|
fieldParts.push("textarea");
|
|
5120
|
+
const summary = summarizeElementValue(field);
|
|
5121
|
+
if (summary) fieldParts.push(`${summary.label}="${summary.value}"`);
|
|
4647
5122
|
}
|
|
4648
5123
|
const meta = formatElementMeta(field);
|
|
4649
5124
|
if (meta.length > 0) fieldParts.push(`(${meta.join(", ")})`);
|
|
@@ -4669,18 +5144,51 @@ function formatLandmarks(landmarks) {
|
|
|
4669
5144
|
function formatViewport(page) {
|
|
4670
5145
|
return `${page.viewport.width}x${page.viewport.height} at scroll (${page.viewport.scrollX}, ${page.viewport.scrollY})`;
|
|
4671
5146
|
}
|
|
4672
|
-
function formatOverlays(
|
|
4673
|
-
if (overlays.length === 0) return "None detected";
|
|
4674
|
-
const items = limitItems(
|
|
5147
|
+
function formatOverlays(page) {
|
|
5148
|
+
if (page.overlays.length === 0) return "None detected";
|
|
5149
|
+
const items = limitItems(buildOverlayInventory(page), 10);
|
|
4675
5150
|
return items.map((overlay) => {
|
|
4676
|
-
const
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
5151
|
+
const lines = [
|
|
5152
|
+
[
|
|
5153
|
+
`- ${overlay.kind}`,
|
|
5154
|
+
overlay.role ? `role=${overlay.role}` : "",
|
|
5155
|
+
overlay.blocksInteraction ? "blocking" : "",
|
|
5156
|
+
overlay.label ? `label="${overlay.label.slice(0, 80)}"` : "",
|
|
5157
|
+
overlay.text ? `text="${overlay.text.slice(0, 100)}"` : ""
|
|
5158
|
+
].filter(Boolean).join(" ")
|
|
5159
|
+
];
|
|
5160
|
+
if (overlay.radioOptions.length > 0) {
|
|
5161
|
+
const options = overlay.radioOptions.slice(0, 4).map((option) => {
|
|
5162
|
+
const tags = [];
|
|
5163
|
+
if (option.labelSource) tags.push(`source=${option.labelSource}`);
|
|
5164
|
+
if (option.looksCorrect === true) tags.push("likely-correct");
|
|
5165
|
+
if (option.looksCorrect === false) tags.push("likely-wrong");
|
|
5166
|
+
const suffix = tags.length > 0 ? ` (${tags.join(", ")})` : "";
|
|
5167
|
+
return `${option.label || option.selector || "radio"}${suffix}`;
|
|
5168
|
+
}).join(" | ");
|
|
5169
|
+
lines.push(` options: ${options}`);
|
|
5170
|
+
}
|
|
5171
|
+
const actionLabels = [
|
|
5172
|
+
overlay.dismissAction?.label ? `dismiss="${overlay.dismissAction.label}"` : "",
|
|
5173
|
+
overlay.acceptAction?.label ? `accept="${overlay.acceptAction.label}"` : "",
|
|
5174
|
+
overlay.submitAction?.label ? `submit="${overlay.submitAction.label}"` : ""
|
|
5175
|
+
].filter(Boolean);
|
|
5176
|
+
if (actionLabels.length > 0) {
|
|
5177
|
+
lines.push(` actions: ${actionLabels.join(" ")}`);
|
|
5178
|
+
}
|
|
5179
|
+
return lines.join("\n");
|
|
4682
5180
|
}).join("\n");
|
|
4683
5181
|
}
|
|
5182
|
+
function getScrollHints(page) {
|
|
5183
|
+
const candidates = page.interactiveElements.filter(
|
|
5184
|
+
(el) => el.visible !== false && el.inViewport === false && el.context !== "nav" && el.context !== "footer" && el.context !== "sidebar" && el.blockedByOverlay !== true && (el.type === "input" || el.type === "textarea" || el.type === "select" || el.type === "button")
|
|
5185
|
+
);
|
|
5186
|
+
if (candidates.length === 0) return [];
|
|
5187
|
+
const labels = limitItems(candidates, 3).map((el) => el.text || el.label || el.placeholder || el.type).filter(Boolean);
|
|
5188
|
+
return [
|
|
5189
|
+
`Scroll to reveal offscreen controls: ${labels.join(", ")}${candidates.length > labels.length ? ", ..." : ""}`
|
|
5190
|
+
];
|
|
5191
|
+
}
|
|
4684
5192
|
function formatDormantOverlays(overlays) {
|
|
4685
5193
|
if (overlays.length === 0) return "None detected";
|
|
4686
5194
|
const items = limitItems(overlays, 10);
|
|
@@ -5009,6 +5517,7 @@ function buildScopedContext(page, mode) {
|
|
|
5009
5517
|
switch (mode) {
|
|
5010
5518
|
case "summary": {
|
|
5011
5519
|
const sections = [];
|
|
5520
|
+
const cartSnapshot = formatCartSnapshot(page);
|
|
5012
5521
|
sections.push(`**URL:** ${page.url}`);
|
|
5013
5522
|
sections.push(`**Title:** ${page.title}`);
|
|
5014
5523
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
@@ -5016,12 +5525,21 @@ function buildScopedContext(page, mode) {
|
|
|
5016
5525
|
if (page.excerpt) sections.push(`**Summary:** ${page.excerpt}`);
|
|
5017
5526
|
const largePageHint = formatLargePageHint(page);
|
|
5018
5527
|
if (largePageHint) sections.push(`**Reading Hint:** ${largePageHint}`);
|
|
5528
|
+
const scrollHints = getScrollHints(page);
|
|
5529
|
+
if (scrollHints.length > 0) {
|
|
5530
|
+
sections.push(`**Scroll Hint:** ${scrollHints[0]}`);
|
|
5531
|
+
}
|
|
5019
5532
|
sections.push("");
|
|
5020
5533
|
const summaryIntent = analyzePageIntent(page);
|
|
5021
5534
|
if (summaryIntent) {
|
|
5022
5535
|
sections.push(summaryIntent);
|
|
5023
5536
|
sections.push("");
|
|
5024
5537
|
}
|
|
5538
|
+
if (cartSnapshot) {
|
|
5539
|
+
sections.push("### Cart Snapshot");
|
|
5540
|
+
sections.push(cartSnapshot);
|
|
5541
|
+
sections.push("");
|
|
5542
|
+
}
|
|
5025
5543
|
if ((page.pageIssues?.length ?? 0) > 0) {
|
|
5026
5544
|
sections.push("### Page Access Warnings");
|
|
5027
5545
|
sections.push(formatPageIssues(page.pageIssues ?? []));
|
|
@@ -5073,9 +5591,16 @@ function buildScopedContext(page, mode) {
|
|
|
5073
5591
|
}
|
|
5074
5592
|
case "interactives_only": {
|
|
5075
5593
|
const sections = [];
|
|
5594
|
+
const quantityElements = getQuantityElements(page);
|
|
5595
|
+
const cartSnapshot = formatCartSnapshot(page);
|
|
5596
|
+
const dialogFocus = formatDialogFocus(page);
|
|
5076
5597
|
sections.push(`**URL:** ${page.url}`);
|
|
5077
5598
|
sections.push(`**Title:** ${page.title}`);
|
|
5078
5599
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
5600
|
+
const interactivesScrollHints = getScrollHints(page);
|
|
5601
|
+
if (interactivesScrollHints.length > 0) {
|
|
5602
|
+
sections.push(`**Scroll Hint:** ${interactivesScrollHints[0]}`);
|
|
5603
|
+
}
|
|
5079
5604
|
sections.push("");
|
|
5080
5605
|
const interactivesIntent = analyzePageIntent(page);
|
|
5081
5606
|
if (interactivesIntent) {
|
|
@@ -5088,6 +5613,11 @@ function buildScopedContext(page, mode) {
|
|
|
5088
5613
|
sections.push(formatHighlights(interactivesHighlights));
|
|
5089
5614
|
sections.push("");
|
|
5090
5615
|
}
|
|
5616
|
+
if (cartSnapshot) {
|
|
5617
|
+
sections.push("### Cart Snapshot");
|
|
5618
|
+
sections.push(cartSnapshot);
|
|
5619
|
+
sections.push("");
|
|
5620
|
+
}
|
|
5091
5621
|
if ((page.pageIssues?.length ?? 0) > 0) {
|
|
5092
5622
|
sections.push("### Page Access Warnings");
|
|
5093
5623
|
sections.push(formatPageIssues(page.pageIssues ?? []));
|
|
@@ -5095,7 +5625,12 @@ function buildScopedContext(page, mode) {
|
|
|
5095
5625
|
}
|
|
5096
5626
|
if (page.overlays.length > 0) {
|
|
5097
5627
|
sections.push("### Active Overlays");
|
|
5098
|
-
sections.push(formatOverlays(page
|
|
5628
|
+
sections.push(formatOverlays(page));
|
|
5629
|
+
sections.push("");
|
|
5630
|
+
}
|
|
5631
|
+
if (dialogFocus) {
|
|
5632
|
+
sections.push("### Immediate Overlay Actions");
|
|
5633
|
+
sections.push(dialogFocus);
|
|
5099
5634
|
sections.push("");
|
|
5100
5635
|
}
|
|
5101
5636
|
if (page.dormantOverlays.length > 0) {
|
|
@@ -5108,6 +5643,11 @@ function buildScopedContext(page, mode) {
|
|
|
5108
5643
|
sections.push(formatNavigation(page.navigation));
|
|
5109
5644
|
sections.push("");
|
|
5110
5645
|
}
|
|
5646
|
+
if (quantityElements.length > 0) {
|
|
5647
|
+
sections.push("### Quantity / Count Controls");
|
|
5648
|
+
sections.push(formatQuantityElements(quantityElements));
|
|
5649
|
+
sections.push("");
|
|
5650
|
+
}
|
|
5111
5651
|
if (page.interactiveElements.length > 0) {
|
|
5112
5652
|
sections.push(
|
|
5113
5653
|
`### Interactive Elements (${page.interactiveElements.length})`
|
|
@@ -5118,9 +5658,15 @@ function buildScopedContext(page, mode) {
|
|
|
5118
5658
|
}
|
|
5119
5659
|
case "forms_only": {
|
|
5120
5660
|
const sections = [];
|
|
5661
|
+
const quantityElements = getQuantityElements(page);
|
|
5662
|
+
const cartSnapshot = formatCartSnapshot(page);
|
|
5121
5663
|
sections.push(`**URL:** ${page.url}`);
|
|
5122
5664
|
sections.push(`**Title:** ${page.title}`);
|
|
5123
5665
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
5666
|
+
const visibleScrollHints = getScrollHints(page);
|
|
5667
|
+
if (visibleScrollHints.length > 0) {
|
|
5668
|
+
sections.push(`**Scroll Hint:** ${visibleScrollHints[0]}`);
|
|
5669
|
+
}
|
|
5124
5670
|
sections.push("");
|
|
5125
5671
|
const formsHighlights = getHighlightsForPage(page.url);
|
|
5126
5672
|
if (formsHighlights.length > 0) {
|
|
@@ -5128,6 +5674,11 @@ function buildScopedContext(page, mode) {
|
|
|
5128
5674
|
sections.push(formatHighlights(formsHighlights));
|
|
5129
5675
|
sections.push("");
|
|
5130
5676
|
}
|
|
5677
|
+
if (cartSnapshot) {
|
|
5678
|
+
sections.push("### Cart Snapshot");
|
|
5679
|
+
sections.push(cartSnapshot);
|
|
5680
|
+
sections.push("");
|
|
5681
|
+
}
|
|
5131
5682
|
if ((page.pageIssues?.length ?? 0) > 0) {
|
|
5132
5683
|
sections.push("### Page Access Warnings");
|
|
5133
5684
|
sections.push(formatPageIssues(page.pageIssues ?? []));
|
|
@@ -5135,7 +5686,7 @@ function buildScopedContext(page, mode) {
|
|
|
5135
5686
|
}
|
|
5136
5687
|
if (page.overlays.length > 0) {
|
|
5137
5688
|
sections.push("### Active Overlays");
|
|
5138
|
-
sections.push(formatOverlays(page
|
|
5689
|
+
sections.push(formatOverlays(page));
|
|
5139
5690
|
sections.push("");
|
|
5140
5691
|
}
|
|
5141
5692
|
if (page.dormantOverlays.length > 0) {
|
|
@@ -5143,6 +5694,11 @@ function buildScopedContext(page, mode) {
|
|
|
5143
5694
|
sections.push(formatDormantOverlays(page.dormantOverlays));
|
|
5144
5695
|
sections.push("");
|
|
5145
5696
|
}
|
|
5697
|
+
if (quantityElements.length > 0) {
|
|
5698
|
+
sections.push("### Quantity / Count Controls");
|
|
5699
|
+
sections.push(formatQuantityElements(quantityElements));
|
|
5700
|
+
sections.push("");
|
|
5701
|
+
}
|
|
5146
5702
|
if (page.forms.length > 0) {
|
|
5147
5703
|
sections.push(`### Forms (${page.forms.length})`);
|
|
5148
5704
|
sections.push(formatForms(page.forms));
|
|
@@ -5175,10 +5731,21 @@ function buildScopedContext(page, mode) {
|
|
|
5175
5731
|
case "visible_only": {
|
|
5176
5732
|
const visibleElements = page.interactiveElements.filter(isVisibleToUser);
|
|
5177
5733
|
const visibleNav = page.navigation.filter(isVisibleToUser);
|
|
5178
|
-
const
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5734
|
+
const dialogFocusedElements = getDialogFocusedElements(page);
|
|
5735
|
+
const visiblePage = {
|
|
5736
|
+
...page,
|
|
5737
|
+
interactiveElements: dialogFocusedElements.length > 0 ? dialogFocusedElements : visibleElements,
|
|
5738
|
+
forms: page.forms.map((form) => ({
|
|
5739
|
+
...form,
|
|
5740
|
+
fields: form.fields.filter(
|
|
5741
|
+
(field) => isVisibleToUser(field) && (dialogFocusedElements.length === 0 || field.context === "dialog")
|
|
5742
|
+
)
|
|
5743
|
+
})).filter((form) => form.fields.length > 0)
|
|
5744
|
+
};
|
|
5745
|
+
const quantityElements = getQuantityElements(visiblePage);
|
|
5746
|
+
const cartSnapshot = formatCartSnapshot(visiblePage);
|
|
5747
|
+
const visibleForms = visiblePage.forms;
|
|
5748
|
+
const dialogFocus = formatDialogFocus(page);
|
|
5182
5749
|
const sections = [];
|
|
5183
5750
|
sections.push(`**URL:** ${page.url}`);
|
|
5184
5751
|
sections.push(`**Title:** ${page.title}`);
|
|
@@ -5190,6 +5757,11 @@ function buildScopedContext(page, mode) {
|
|
|
5190
5757
|
sections.push(formatHighlights(visibleHighlights));
|
|
5191
5758
|
sections.push("");
|
|
5192
5759
|
}
|
|
5760
|
+
if (cartSnapshot) {
|
|
5761
|
+
sections.push("### Cart Snapshot");
|
|
5762
|
+
sections.push(cartSnapshot);
|
|
5763
|
+
sections.push("");
|
|
5764
|
+
}
|
|
5193
5765
|
if ((page.pageIssues?.length ?? 0) > 0) {
|
|
5194
5766
|
sections.push("### Page Access Warnings");
|
|
5195
5767
|
sections.push(formatPageIssues(page.pageIssues ?? []));
|
|
@@ -5197,7 +5769,18 @@ function buildScopedContext(page, mode) {
|
|
|
5197
5769
|
}
|
|
5198
5770
|
if (page.overlays.length > 0) {
|
|
5199
5771
|
sections.push("### Active Overlays");
|
|
5200
|
-
sections.push(formatOverlays(page
|
|
5772
|
+
sections.push(formatOverlays(page));
|
|
5773
|
+
sections.push("");
|
|
5774
|
+
}
|
|
5775
|
+
if (dialogFocus) {
|
|
5776
|
+
sections.push("### Immediate Overlay Actions");
|
|
5777
|
+
sections.push(dialogFocus);
|
|
5778
|
+
if (visibleElements.length > dialogFocusedElements.length) {
|
|
5779
|
+
sections.push("");
|
|
5780
|
+
sections.push(
|
|
5781
|
+
`Background controls hidden while the dialog is active: ${visibleElements.length - dialogFocusedElements.length}`
|
|
5782
|
+
);
|
|
5783
|
+
}
|
|
5201
5784
|
sections.push("");
|
|
5202
5785
|
}
|
|
5203
5786
|
if (page.dormantOverlays.length > 0) {
|
|
@@ -5210,11 +5793,18 @@ function buildScopedContext(page, mode) {
|
|
|
5210
5793
|
sections.push(formatNavigation(visibleNav));
|
|
5211
5794
|
sections.push("");
|
|
5212
5795
|
}
|
|
5213
|
-
if (
|
|
5796
|
+
if (quantityElements.length > 0) {
|
|
5797
|
+
sections.push("### Quantity / Count Controls");
|
|
5798
|
+
sections.push(formatQuantityElements(quantityElements));
|
|
5799
|
+
sections.push("");
|
|
5800
|
+
}
|
|
5801
|
+
if (visiblePage.interactiveElements.length > 0) {
|
|
5802
|
+
sections.push(
|
|
5803
|
+
`### Visible In-Viewport Interactive Elements (${visiblePage.interactiveElements.length})`
|
|
5804
|
+
);
|
|
5214
5805
|
sections.push(
|
|
5215
|
-
|
|
5806
|
+
formatInteractiveElements(visiblePage.interactiveElements)
|
|
5216
5807
|
);
|
|
5217
|
-
sections.push(formatInteractiveElements(visibleElements));
|
|
5218
5808
|
sections.push("");
|
|
5219
5809
|
}
|
|
5220
5810
|
if (visibleForms.length > 0) {
|
|
@@ -5364,6 +5954,10 @@ function buildStructuredContext(page) {
|
|
|
5364
5954
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
5365
5955
|
if (page.byline) sections.push(`**Author:** ${page.byline}`);
|
|
5366
5956
|
if (page.excerpt) sections.push(`**Summary:** ${page.excerpt}`);
|
|
5957
|
+
const structuredScrollHints = getScrollHints(page);
|
|
5958
|
+
if (structuredScrollHints.length > 0) {
|
|
5959
|
+
sections.push(`**Scroll Hint:** ${structuredScrollHints[0]}`);
|
|
5960
|
+
}
|
|
5367
5961
|
sections.push("");
|
|
5368
5962
|
const pageIntent = analyzePageIntent(page);
|
|
5369
5963
|
if (pageIntent) {
|
|
@@ -5402,7 +5996,7 @@ function buildStructuredContext(page) {
|
|
|
5402
5996
|
sections.push(formatLandmarks(page.landmarks));
|
|
5403
5997
|
sections.push("");
|
|
5404
5998
|
sections.push("### Active Overlays / Modals");
|
|
5405
|
-
sections.push(formatOverlays(page
|
|
5999
|
+
sections.push(formatOverlays(page));
|
|
5406
6000
|
sections.push("");
|
|
5407
6001
|
sections.push("### Dormant Consent / Modal UI");
|
|
5408
6002
|
sections.push(formatDormantOverlays(page.dormantOverlays));
|
|
@@ -5481,6 +6075,83 @@ function buildGeneralPrompt(query) {
|
|
|
5481
6075
|
user: query
|
|
5482
6076
|
};
|
|
5483
6077
|
}
|
|
6078
|
+
const WRAPPING_QUOTES = /* @__PURE__ */ new Set(['"', "'", "`"]);
|
|
6079
|
+
function stripWrappingQuotes(value) {
|
|
6080
|
+
const trimmed = value.trim();
|
|
6081
|
+
if (trimmed.length < 2) return trimmed;
|
|
6082
|
+
const first = trimmed[0];
|
|
6083
|
+
const last = trimmed[trimmed.length - 1];
|
|
6084
|
+
if (first === last && WRAPPING_QUOTES.has(first)) {
|
|
6085
|
+
return trimmed.slice(1, -1).trim();
|
|
6086
|
+
}
|
|
6087
|
+
return trimmed;
|
|
6088
|
+
}
|
|
6089
|
+
function normalizeArrayItem(value) {
|
|
6090
|
+
if (typeof value === "string") {
|
|
6091
|
+
return stripWrappingQuotes(value).trim();
|
|
6092
|
+
}
|
|
6093
|
+
return String(value).trim();
|
|
6094
|
+
}
|
|
6095
|
+
function normalizeLooseString(value) {
|
|
6096
|
+
if (typeof value !== "string") return void 0;
|
|
6097
|
+
const normalized = stripWrappingQuotes(value);
|
|
6098
|
+
return normalized ? normalized : void 0;
|
|
6099
|
+
}
|
|
6100
|
+
function coerceOptionalNumber(value) {
|
|
6101
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
6102
|
+
return value;
|
|
6103
|
+
}
|
|
6104
|
+
const normalized = normalizeLooseString(value);
|
|
6105
|
+
if (!normalized) return void 0;
|
|
6106
|
+
const parsed = Number(normalized);
|
|
6107
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
6108
|
+
}
|
|
6109
|
+
function coerceStringArray(value) {
|
|
6110
|
+
if (Array.isArray(value)) {
|
|
6111
|
+
return value.map(normalizeArrayItem).filter(Boolean);
|
|
6112
|
+
}
|
|
6113
|
+
const normalized = normalizeLooseString(value);
|
|
6114
|
+
if (!normalized) return [];
|
|
6115
|
+
try {
|
|
6116
|
+
const parsed = JSON.parse(normalized);
|
|
6117
|
+
if (Array.isArray(parsed)) {
|
|
6118
|
+
return parsed.map(normalizeArrayItem).filter(Boolean);
|
|
6119
|
+
}
|
|
6120
|
+
} catch {
|
|
6121
|
+
}
|
|
6122
|
+
const lines = normalized.split(/\r?\n/).map((line) => line.replace(/^\s*[-*]\s+/, "").trim()).filter(Boolean);
|
|
6123
|
+
if (lines.length > 1) {
|
|
6124
|
+
return lines;
|
|
6125
|
+
}
|
|
6126
|
+
return [normalized];
|
|
6127
|
+
}
|
|
6128
|
+
function optionalNumberLikeSchema() {
|
|
6129
|
+
return zod.z.preprocess(
|
|
6130
|
+
(value) => {
|
|
6131
|
+
if (value == null) return void 0;
|
|
6132
|
+
return coerceOptionalNumber(value) ?? value;
|
|
6133
|
+
},
|
|
6134
|
+
zod.z.number().finite().optional()
|
|
6135
|
+
);
|
|
6136
|
+
}
|
|
6137
|
+
function normalizedOptionalStringSchema() {
|
|
6138
|
+
return zod.z.preprocess(
|
|
6139
|
+
(value) => {
|
|
6140
|
+
if (value == null) return void 0;
|
|
6141
|
+
return normalizeLooseString(value) ?? value;
|
|
6142
|
+
},
|
|
6143
|
+
zod.z.string().optional()
|
|
6144
|
+
);
|
|
6145
|
+
}
|
|
6146
|
+
function stringArrayLikeSchema() {
|
|
6147
|
+
return zod.z.preprocess(
|
|
6148
|
+
(value) => {
|
|
6149
|
+
if (value == null) return value;
|
|
6150
|
+
return coerceStringArray(value) ?? value;
|
|
6151
|
+
},
|
|
6152
|
+
zod.z.array(zod.z.string().min(1)).min(1)
|
|
6153
|
+
);
|
|
6154
|
+
}
|
|
5484
6155
|
const TOOL_DEFINITIONS = [
|
|
5485
6156
|
// --- Tab Management ---
|
|
5486
6157
|
{
|
|
@@ -5609,7 +6280,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5609
6280
|
description: "Scroll the page up or down.",
|
|
5610
6281
|
inputSchema: {
|
|
5611
6282
|
direction: zod.z.enum(["up", "down"]).describe("Scroll direction"),
|
|
5612
|
-
amount:
|
|
6283
|
+
amount: optionalNumberLikeSchema().describe(
|
|
6284
|
+
"Pixels to scroll (default 500)"
|
|
6285
|
+
)
|
|
5613
6286
|
},
|
|
5614
6287
|
tier: 0,
|
|
5615
6288
|
relevance: ["ARTICLE", "SEARCH_RESULTS", "PAGINATED_LIST"]
|
|
@@ -5654,6 +6327,29 @@ const TOOL_DEFINITIONS = [
|
|
|
5654
6327
|
description: "Dismiss a modal, popup, newsletter gate, cookie banner, or overlay using common close/decline actions.",
|
|
5655
6328
|
tier: 1
|
|
5656
6329
|
},
|
|
6330
|
+
{
|
|
6331
|
+
name: "clear_overlays",
|
|
6332
|
+
title: "Clear Overlays",
|
|
6333
|
+
description: "Work through blocking overlays and modals until the page is unblocked, using overlay-specific heuristics for consent banners and radio-selection dialogs.",
|
|
6334
|
+
inputSchema: {
|
|
6335
|
+
strategy: zod.z.enum(["auto", "interactive"]).optional().describe(
|
|
6336
|
+
'How aggressively to clear overlays. "auto" uses heuristics; "interactive" stops earlier when human judgment may be needed.'
|
|
6337
|
+
)
|
|
6338
|
+
},
|
|
6339
|
+
tier: 1
|
|
6340
|
+
},
|
|
6341
|
+
{
|
|
6342
|
+
name: "inspect_element",
|
|
6343
|
+
title: "Inspect Element",
|
|
6344
|
+
description: "Inspect one element and its nearest local UI region such as a product card, result row, form section, or modal. Use this instead of reading the whole page when you only need local context.",
|
|
6345
|
+
inputSchema: {
|
|
6346
|
+
index: zod.z.number().optional().describe("Element index to inspect"),
|
|
6347
|
+
selector: zod.z.string().optional().describe("CSS selector to inspect"),
|
|
6348
|
+
limit: zod.z.number().optional().describe("Maximum nearby controls to include (default 8)")
|
|
6349
|
+
},
|
|
6350
|
+
tier: 1,
|
|
6351
|
+
relevance: ["SEARCH_RESULTS", "SHOPPING", "FORM"]
|
|
6352
|
+
},
|
|
5657
6353
|
{
|
|
5658
6354
|
name: "read_page",
|
|
5659
6355
|
title: "Read Page",
|
|
@@ -5841,7 +6537,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5841
6537
|
inputSchema: {
|
|
5842
6538
|
index: zod.z.number().optional().describe("Element index from page content to highlight"),
|
|
5843
6539
|
selector: zod.z.string().optional().describe("CSS selector of element to highlight"),
|
|
5844
|
-
text:
|
|
6540
|
+
text: normalizedOptionalStringSchema().describe(
|
|
6541
|
+
"Text to find and highlight on the page (all occurrences)"
|
|
6542
|
+
),
|
|
5845
6543
|
label: zod.z.string().optional().describe("Annotation label to display near the highlight"),
|
|
5846
6544
|
durationMs: zod.z.number().optional().describe(
|
|
5847
6545
|
"Auto-clear after this many milliseconds (omit for permanent)"
|
|
@@ -5866,11 +6564,12 @@ const TOOL_DEFINITIONS = [
|
|
|
5866
6564
|
goal: zod.z.string().describe(
|
|
5867
6565
|
"What this workflow accomplishes (e.g. 'Purchase item from Amazon')"
|
|
5868
6566
|
),
|
|
5869
|
-
steps:
|
|
6567
|
+
steps: stringArrayLikeSchema().describe(
|
|
5870
6568
|
"Ordered list of step labels (e.g. ['Log in', 'Search', 'Select item', 'Checkout'])"
|
|
5871
6569
|
)
|
|
5872
6570
|
},
|
|
5873
|
-
tier: 1
|
|
6571
|
+
tier: 1,
|
|
6572
|
+
hiddenByDefault: true
|
|
5874
6573
|
},
|
|
5875
6574
|
{
|
|
5876
6575
|
name: "flow_advance",
|
|
@@ -5879,26 +6578,30 @@ const TOOL_DEFINITIONS = [
|
|
|
5879
6578
|
inputSchema: {
|
|
5880
6579
|
detail: zod.z.string().optional().describe("Brief note about what was accomplished")
|
|
5881
6580
|
},
|
|
5882
|
-
tier: 1
|
|
6581
|
+
tier: 1,
|
|
6582
|
+
hiddenByDefault: true
|
|
5883
6583
|
},
|
|
5884
6584
|
{
|
|
5885
6585
|
name: "flow_status",
|
|
5886
6586
|
title: "Workflow Status",
|
|
5887
6587
|
description: "Check the current workflow progress.",
|
|
5888
|
-
tier: 2
|
|
6588
|
+
tier: 2,
|
|
6589
|
+
hiddenByDefault: true
|
|
5889
6590
|
},
|
|
5890
6591
|
{
|
|
5891
6592
|
name: "flow_end",
|
|
5892
6593
|
title: "End Workflow",
|
|
5893
6594
|
description: "Clear the active workflow tracker.",
|
|
5894
|
-
tier: 2
|
|
6595
|
+
tier: 2,
|
|
6596
|
+
hiddenByDefault: true
|
|
5895
6597
|
},
|
|
5896
6598
|
// --- Speedee System: Suggestion Engine ---
|
|
5897
6599
|
{
|
|
5898
6600
|
name: "suggest",
|
|
5899
6601
|
title: "What Should I Do?",
|
|
5900
6602
|
description: "Analyze the current page and return the most relevant tools and suggested next actions. Call this when unsure what to do.",
|
|
5901
|
-
tier: 1
|
|
6603
|
+
tier: 1,
|
|
6604
|
+
hiddenByDefault: true
|
|
5902
6605
|
},
|
|
5903
6606
|
// --- Speedee System: Composable Macros ---
|
|
5904
6607
|
{
|
|
@@ -5910,9 +6613,14 @@ const TOOL_DEFINITIONS = [
|
|
|
5910
6613
|
zod.z.object({
|
|
5911
6614
|
index: zod.z.number().optional().describe("Element index from page content"),
|
|
5912
6615
|
selector: zod.z.string().optional().describe("CSS selector fallback"),
|
|
6616
|
+
name: zod.z.string().optional().describe("Field name or id, such as custname"),
|
|
6617
|
+
label: zod.z.string().optional().describe("Visible label or aria-label text"),
|
|
6618
|
+
placeholder: zod.z.string().optional().describe("Placeholder text shown in the field"),
|
|
5913
6619
|
value: zod.z.string().describe("Value to enter")
|
|
5914
6620
|
})
|
|
5915
|
-
).describe(
|
|
6621
|
+
).describe(
|
|
6622
|
+
"Fields to fill, matched by index, selector, name, label, or placeholder"
|
|
6623
|
+
),
|
|
5916
6624
|
submit: zod.z.boolean().optional().describe("Submit the form after filling (default false)")
|
|
5917
6625
|
},
|
|
5918
6626
|
tier: 1,
|
|
@@ -5998,14 +6706,16 @@ const TOOL_DEFINITIONS = [
|
|
|
5998
6706
|
inputSchema: {
|
|
5999
6707
|
timeoutMs: zod.z.number().optional().describe("Maximum time to wait in milliseconds (default 10000)")
|
|
6000
6708
|
},
|
|
6001
|
-
tier: 1
|
|
6709
|
+
tier: 1,
|
|
6710
|
+
hiddenByDefault: true
|
|
6002
6711
|
},
|
|
6003
6712
|
// --- Speedee System: Metrics ---
|
|
6004
6713
|
{
|
|
6005
6714
|
name: "metrics",
|
|
6006
6715
|
title: "Session Metrics",
|
|
6007
6716
|
description: "Show performance metrics for this session: total tool calls, average duration, per-tool breakdown, and error rates.",
|
|
6008
|
-
tier: 2
|
|
6717
|
+
tier: 2,
|
|
6718
|
+
hiddenByDefault: true
|
|
6009
6719
|
}
|
|
6010
6720
|
];
|
|
6011
6721
|
function toAnthropicTools(defs) {
|
|
@@ -6047,16 +6757,19 @@ const CONTEXT_HINTS = {
|
|
|
6047
6757
|
SEARCH_RESULTS: {
|
|
6048
6758
|
paginate: "⚡ PAGINATION DETECTED — ",
|
|
6049
6759
|
search: "⚡ Refine search — ",
|
|
6760
|
+
inspect_element: "⚡ Inspect one result card without reading the whole page — ",
|
|
6050
6761
|
highlight: "💡 Mark interesting results — "
|
|
6051
6762
|
},
|
|
6052
6763
|
SHOPPING: {
|
|
6053
6764
|
fill_form: "⚡ CHECKOUT FIELDS DETECTED — ",
|
|
6054
|
-
select_option: "⚡ Payment/shipping options available — "
|
|
6765
|
+
select_option: "⚡ Payment/shipping options available — ",
|
|
6766
|
+
inspect_element: "⚡ Inspect the current product card or option group — "
|
|
6055
6767
|
},
|
|
6056
6768
|
FORM: {
|
|
6057
6769
|
fill_form: "⚡ FORM DETECTED — ",
|
|
6058
6770
|
select_option: "⚡ Dropdown fields on page — ",
|
|
6059
|
-
submit_form: "⚡ Form ready to submit — "
|
|
6771
|
+
submit_form: "⚡ Form ready to submit — ",
|
|
6772
|
+
inspect_element: "⚡ Inspect just this form section — "
|
|
6060
6773
|
},
|
|
6061
6774
|
PAGINATED_LIST: {
|
|
6062
6775
|
paginate: "⚡ PAGINATION DETECTED — ",
|
|
@@ -6079,23 +6792,122 @@ function scoreForContext(toolName, pageType) {
|
|
|
6079
6792
|
if (tier === 2 && isRelevant) return 20;
|
|
6080
6793
|
return 40;
|
|
6081
6794
|
}
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6795
|
+
const ALWAYS_FAST_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
6796
|
+
"current_tab",
|
|
6797
|
+
"navigate",
|
|
6798
|
+
"click",
|
|
6799
|
+
"type_text",
|
|
6800
|
+
"press_key",
|
|
6801
|
+
"search",
|
|
6802
|
+
"scroll",
|
|
6803
|
+
"dismiss_popup",
|
|
6804
|
+
"clear_overlays",
|
|
6805
|
+
"accept_cookies",
|
|
6806
|
+
"wait_for",
|
|
6807
|
+
"read_page",
|
|
6808
|
+
"inspect_element"
|
|
6809
|
+
]);
|
|
6810
|
+
function inferIntent(query) {
|
|
6811
|
+
const lowered = query.toLowerCase();
|
|
6812
|
+
const intents = /* @__PURE__ */ new Set();
|
|
6813
|
+
if (/\b(tab|tabs|window|windows)\b/.test(lowered)) intents.add("tabs");
|
|
6814
|
+
if (/\b(bookmark|bookmarks|save this|folder)\b/.test(lowered)) {
|
|
6815
|
+
intents.add("bookmarks");
|
|
6816
|
+
}
|
|
6817
|
+
if (/\b(session|cookies|log in|login|sign in|sign-in|resume)\b/.test(lowered)) {
|
|
6818
|
+
intents.add("sessions");
|
|
6819
|
+
}
|
|
6820
|
+
if (/\b(flow|workflow|checkpoint|step|progress|plan)\b/.test(lowered)) {
|
|
6821
|
+
intents.add("workflow");
|
|
6822
|
+
}
|
|
6823
|
+
if (/\b(metric|metrics|performance|slow|latency)\b/.test(lowered)) {
|
|
6824
|
+
intents.add("metrics");
|
|
6825
|
+
}
|
|
6826
|
+
if (/\b(highlight|mark|annotate)\b/.test(lowered)) intents.add("highlight");
|
|
6827
|
+
if (/\b(table|csv|rows|columns)\b/.test(lowered)) intents.add("table");
|
|
6828
|
+
if (/\b(overlay|modal|popup|consent|cookie|blocking ui)\b/.test(lowered)) {
|
|
6829
|
+
intents.add("debug");
|
|
6830
|
+
}
|
|
6831
|
+
if (/\b(debug|diagnose|what should i do|stuck|inspect)\b/.test(lowered)) {
|
|
6832
|
+
intents.add("debug");
|
|
6833
|
+
}
|
|
6834
|
+
return intents;
|
|
6835
|
+
}
|
|
6836
|
+
function shouldIncludeTool(toolName, pageType, intents) {
|
|
6837
|
+
if (ALWAYS_FAST_TOOL_NAMES.has(toolName)) return true;
|
|
6838
|
+
switch (toolName) {
|
|
6839
|
+
case "select_option":
|
|
6840
|
+
case "submit_form":
|
|
6841
|
+
case "fill_form":
|
|
6842
|
+
return pageType === "FORM" || pageType === "SHOPPING" || pageType === "LOGIN";
|
|
6843
|
+
case "paginate":
|
|
6844
|
+
return pageType === "SEARCH_RESULTS" || pageType === "PAGINATED_LIST";
|
|
6845
|
+
case "login":
|
|
6846
|
+
return pageType === "LOGIN" || intents.has("sessions");
|
|
6847
|
+
case "focus":
|
|
6848
|
+
return pageType === "FORM" || pageType === "LOGIN" || pageType === "SEARCH_READY";
|
|
6849
|
+
case "scroll_to_element":
|
|
6850
|
+
return pageType === "SEARCH_RESULTS" || pageType === "SHOPPING" || intents.has("debug");
|
|
6851
|
+
case "go_back":
|
|
6852
|
+
return true;
|
|
6853
|
+
case "go_forward":
|
|
6854
|
+
case "reload":
|
|
6855
|
+
case "hover":
|
|
6856
|
+
return intents.has("debug");
|
|
6857
|
+
case "highlight":
|
|
6858
|
+
case "clear_highlights":
|
|
6859
|
+
return intents.has("highlight");
|
|
6860
|
+
case "list_tabs":
|
|
6861
|
+
case "switch_tab":
|
|
6862
|
+
case "create_tab":
|
|
6863
|
+
case "set_ad_blocking":
|
|
6864
|
+
return intents.has("tabs") || intents.has("debug");
|
|
6865
|
+
case "save_session":
|
|
6866
|
+
case "load_session":
|
|
6867
|
+
case "list_sessions":
|
|
6868
|
+
case "delete_session":
|
|
6869
|
+
return intents.has("sessions");
|
|
6870
|
+
case "list_bookmarks":
|
|
6871
|
+
case "search_bookmarks":
|
|
6872
|
+
case "create_bookmark_folder":
|
|
6873
|
+
case "save_bookmark":
|
|
6874
|
+
case "organize_bookmark":
|
|
6875
|
+
case "archive_bookmark":
|
|
6876
|
+
case "open_bookmark":
|
|
6877
|
+
return intents.has("bookmarks");
|
|
6878
|
+
case "flow_start":
|
|
6879
|
+
case "flow_advance":
|
|
6880
|
+
case "flow_status":
|
|
6881
|
+
case "flow_end":
|
|
6882
|
+
return intents.has("workflow");
|
|
6883
|
+
case "suggest":
|
|
6884
|
+
case "wait_for_navigation":
|
|
6885
|
+
case "metrics":
|
|
6886
|
+
return intents.has("debug") || intents.has("metrics");
|
|
6887
|
+
case "extract_table":
|
|
6888
|
+
return intents.has("table");
|
|
6889
|
+
default:
|
|
6890
|
+
return !defByName[toolName]?.hiddenByDefault;
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
function pruneToolsForContext(tools, pageType, query = "") {
|
|
6894
|
+
const ctx = pageType ?? "GENERAL";
|
|
6895
|
+
const hints = CONTEXT_HINTS[ctx] ?? {};
|
|
6896
|
+
const intents = inferIntent(query);
|
|
6897
|
+
const scored = tools.filter((tool) => shouldIncludeTool(tool.name, ctx, intents)).map((tool) => ({
|
|
6898
|
+
tool,
|
|
6899
|
+
score: scoreForContext(tool.name, ctx)
|
|
6900
|
+
}));
|
|
6901
|
+
scored.sort((a, b) => a.score - b.score);
|
|
6902
|
+
return scored.map(({ tool, score }) => {
|
|
6903
|
+
const hint = hints[tool.name];
|
|
6904
|
+
if (hint && score <= 20) {
|
|
6905
|
+
return {
|
|
6906
|
+
...tool,
|
|
6907
|
+
description: hint + tool.description
|
|
6908
|
+
};
|
|
6909
|
+
}
|
|
6910
|
+
return tool;
|
|
6099
6911
|
});
|
|
6100
6912
|
}
|
|
6101
6913
|
function trimText(value) {
|
|
@@ -6682,7 +7494,7 @@ async function validateLinkDestination(url, timeoutMs = 3500) {
|
|
|
6682
7494
|
function formatDeadLinkMessage(label, result) {
|
|
6683
7495
|
const destination = result.finalUrl || result.checkedUrl;
|
|
6684
7496
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
6685
|
-
return `Skipped stale link "${label}" because ${destination} returned ${status}.`;
|
|
7497
|
+
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
6686
7498
|
}
|
|
6687
7499
|
const SESSION_VERSION = 1;
|
|
6688
7500
|
function getSessionsDir() {
|
|
@@ -7320,10 +8132,297 @@ async function describeElementForClick$1(wc, selector) {
|
|
|
7320
8132
|
href: "href" in result && typeof result.href === "string" ? result.href : void 0
|
|
7321
8133
|
};
|
|
7322
8134
|
}
|
|
8135
|
+
async function inspectElement(wc, selector, limit = 8) {
|
|
8136
|
+
const result = await executePageScript(
|
|
8137
|
+
wc,
|
|
8138
|
+
`
|
|
8139
|
+
(function() {
|
|
8140
|
+
function text(value) {
|
|
8141
|
+
const trimmed = value == null ? "" : String(value).trim();
|
|
8142
|
+
return trimmed || undefined;
|
|
8143
|
+
}
|
|
8144
|
+
|
|
8145
|
+
function escapeSelectorValue(value) {
|
|
8146
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
8147
|
+
return CSS.escape(value);
|
|
8148
|
+
}
|
|
8149
|
+
return String(value).replace(/["\\\\]/g, "\\\\$&");
|
|
8150
|
+
}
|
|
8151
|
+
|
|
8152
|
+
function uniqueSelector(candidate) {
|
|
8153
|
+
if (!candidate) return null;
|
|
8154
|
+
try {
|
|
8155
|
+
return document.querySelectorAll(candidate).length === 1 ? candidate : null;
|
|
8156
|
+
} catch {
|
|
8157
|
+
return null;
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
|
|
8161
|
+
function uniqueAttributeSelector(el, attribute) {
|
|
8162
|
+
const value = text(el.getAttribute && el.getAttribute(attribute));
|
|
8163
|
+
if (!value) return null;
|
|
8164
|
+
const candidate = el.tagName.toLowerCase() + "[" + attribute + "=\\"" + escapeSelectorValue(value) + "\\"]";
|
|
8165
|
+
return uniqueSelector(candidate);
|
|
8166
|
+
}
|
|
8167
|
+
|
|
8168
|
+
function selectorFor(el) {
|
|
8169
|
+
if (!el) return null;
|
|
8170
|
+
if (el.id) return "#" + escapeSelectorValue(el.id);
|
|
8171
|
+
for (const attribute of ["data-testid", "name", "form", "aria-label", "title"]) {
|
|
8172
|
+
const candidate = uniqueAttributeSelector(el, attribute);
|
|
8173
|
+
if (candidate) return candidate;
|
|
8174
|
+
}
|
|
8175
|
+
const parts = [];
|
|
8176
|
+
let current = el;
|
|
8177
|
+
while (current) {
|
|
8178
|
+
if (current.id) {
|
|
8179
|
+
parts.unshift("#" + escapeSelectorValue(current.id));
|
|
8180
|
+
break;
|
|
8181
|
+
}
|
|
8182
|
+
const tag = current.tagName.toLowerCase();
|
|
8183
|
+
const parent = current.parentElement;
|
|
8184
|
+
if (!parent) { parts.unshift(tag); break; }
|
|
8185
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === current.tagName);
|
|
8186
|
+
const index = siblings.indexOf(current) + 1;
|
|
8187
|
+
parts.unshift(siblings.length > 1 ? tag + ":nth-of-type(" + index + ")" : tag);
|
|
8188
|
+
current = parent;
|
|
8189
|
+
}
|
|
8190
|
+
const selector = parts.join(" > ");
|
|
8191
|
+
return uniqueSelector(selector) || selector;
|
|
8192
|
+
}
|
|
8193
|
+
|
|
8194
|
+
function isVisible(el) {
|
|
8195
|
+
if (!(el instanceof HTMLElement)) return true;
|
|
8196
|
+
const style = window.getComputedStyle(el);
|
|
8197
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
8198
|
+
return false;
|
|
8199
|
+
}
|
|
8200
|
+
const rect = el.getBoundingClientRect();
|
|
8201
|
+
return rect.width > 0 && rect.height > 0;
|
|
8202
|
+
}
|
|
8203
|
+
|
|
8204
|
+
function labelFor(el) {
|
|
8205
|
+
return text(
|
|
8206
|
+
el.getAttribute("aria-label") ||
|
|
8207
|
+
el.getAttribute("title") ||
|
|
8208
|
+
el.getAttribute("name") ||
|
|
8209
|
+
el.getAttribute("placeholder") ||
|
|
8210
|
+
el.textContent ||
|
|
8211
|
+
el.getAttribute("value") ||
|
|
8212
|
+
el.tagName
|
|
8213
|
+
) || "element";
|
|
8214
|
+
}
|
|
8215
|
+
|
|
8216
|
+
function chooseRegion(target) {
|
|
8217
|
+
const preferred = target.closest(
|
|
8218
|
+
"[data-testid], article, [role='article'], [role='listitem'], li, tr, form, section, aside, dialog, [role='dialog']"
|
|
8219
|
+
);
|
|
8220
|
+
if (preferred) return preferred;
|
|
8221
|
+
let current = target.parentElement;
|
|
8222
|
+
let depth = 0;
|
|
8223
|
+
while (current && depth < 5) {
|
|
8224
|
+
const count = current.querySelectorAll("a[href], button, input, select, textarea").length;
|
|
8225
|
+
if (count >= 2 && count <= 16) return current;
|
|
8226
|
+
current = current.parentElement;
|
|
8227
|
+
depth += 1;
|
|
8228
|
+
}
|
|
8229
|
+
return target.parentElement || target;
|
|
8230
|
+
}
|
|
8231
|
+
|
|
8232
|
+
const target = document.querySelector(${JSON.stringify(selector)});
|
|
8233
|
+
if (!target) return { error: "Element not found" };
|
|
8234
|
+
if (target instanceof HTMLElement) {
|
|
8235
|
+
target.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
|
|
8236
|
+
}
|
|
8237
|
+
|
|
8238
|
+
const region = chooseRegion(target);
|
|
8239
|
+
const nearby = [];
|
|
8240
|
+
const seen = new Set();
|
|
8241
|
+
region.querySelectorAll("a[href], button, input:not([type='hidden']), select, textarea").forEach((el) => {
|
|
8242
|
+
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
8243
|
+
const candidateSelector = selectorFor(el);
|
|
8244
|
+
if (!candidateSelector || seen.has(candidateSelector)) return;
|
|
8245
|
+
seen.add(candidateSelector);
|
|
8246
|
+
nearby.push({
|
|
8247
|
+
label: labelFor(el).slice(0, 100),
|
|
8248
|
+
type: el.tagName.toLowerCase(),
|
|
8249
|
+
selector: candidateSelector,
|
|
8250
|
+
href: el instanceof HTMLAnchorElement ? text(el.href) : undefined,
|
|
8251
|
+
});
|
|
8252
|
+
});
|
|
8253
|
+
|
|
8254
|
+
return {
|
|
8255
|
+
target: {
|
|
8256
|
+
label: labelFor(target).slice(0, 120),
|
|
8257
|
+
tag: target.tagName.toLowerCase(),
|
|
8258
|
+
text: text(target.textContent)?.slice(0, 240),
|
|
8259
|
+
href: target instanceof HTMLAnchorElement ? text(target.href) : undefined,
|
|
8260
|
+
value: target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement
|
|
8261
|
+
? text(target.value)?.slice(0, 120)
|
|
8262
|
+
: undefined,
|
|
8263
|
+
},
|
|
8264
|
+
region: {
|
|
8265
|
+
tag: region.tagName.toLowerCase(),
|
|
8266
|
+
label: labelFor(region).slice(0, 120),
|
|
8267
|
+
text: text(region.textContent)?.slice(0, 400),
|
|
8268
|
+
},
|
|
8269
|
+
nearby: nearby.slice(0, ${Math.max(1, Math.min(20, limit))}),
|
|
8270
|
+
};
|
|
8271
|
+
})()
|
|
8272
|
+
`,
|
|
8273
|
+
{
|
|
8274
|
+
timeoutMs: 2e3,
|
|
8275
|
+
label: "inspect element"
|
|
8276
|
+
}
|
|
8277
|
+
);
|
|
8278
|
+
if (result === PAGE_SCRIPT_TIMEOUT) {
|
|
8279
|
+
return pageBusyError("inspect_element");
|
|
8280
|
+
}
|
|
8281
|
+
if (!result || typeof result !== "object") {
|
|
8282
|
+
return "Error: Could not inspect element";
|
|
8283
|
+
}
|
|
8284
|
+
if ("error" in result && typeof result.error === "string") {
|
|
8285
|
+
return `Error: ${result.error}`;
|
|
8286
|
+
}
|
|
8287
|
+
const lines = [];
|
|
8288
|
+
if (result.target) {
|
|
8289
|
+
lines.push(`Target: ${result.target.label} <${result.target.tag}>`);
|
|
8290
|
+
if (result.target.text) lines.push(`Target text: ${result.target.text}`);
|
|
8291
|
+
if (result.target.href) lines.push(`Target href: ${result.target.href}`);
|
|
8292
|
+
if (result.target.value) lines.push(`Target value: ${result.target.value}`);
|
|
8293
|
+
}
|
|
8294
|
+
if (result.region) {
|
|
8295
|
+
lines.push(`Region: ${result.region.label} <${result.region.tag}>`);
|
|
8296
|
+
if (result.region.text) lines.push(`Region text: ${result.region.text}`);
|
|
8297
|
+
}
|
|
8298
|
+
if (Array.isArray(result.nearby) && result.nearby.length > 0) {
|
|
8299
|
+
lines.push("Nearby controls:");
|
|
8300
|
+
for (const item of result.nearby) {
|
|
8301
|
+
const hrefSuffix = item.href ? ` -> ${item.href}` : "";
|
|
8302
|
+
lines.push(
|
|
8303
|
+
`- ${item.label} [${item.type}] selector=${item.selector}${hrefSuffix}`
|
|
8304
|
+
);
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8307
|
+
return lines.join("\n");
|
|
8308
|
+
}
|
|
8309
|
+
async function getLocaleSnapshot(wc) {
|
|
8310
|
+
const snapshot = await executePageScript(
|
|
8311
|
+
wc,
|
|
8312
|
+
`
|
|
8313
|
+
(function() {
|
|
8314
|
+
return {
|
|
8315
|
+
lang:
|
|
8316
|
+
document.documentElement?.lang ||
|
|
8317
|
+
document.body?.lang ||
|
|
8318
|
+
navigator.language ||
|
|
8319
|
+
"",
|
|
8320
|
+
url: window.location.href || "",
|
|
8321
|
+
title: document.title || "",
|
|
8322
|
+
};
|
|
8323
|
+
})()
|
|
8324
|
+
`,
|
|
8325
|
+
{
|
|
8326
|
+
label: "locale snapshot"
|
|
8327
|
+
}
|
|
8328
|
+
);
|
|
8329
|
+
if (!snapshot || snapshot === PAGE_SCRIPT_TIMEOUT || typeof snapshot !== "object") {
|
|
8330
|
+
return null;
|
|
8331
|
+
}
|
|
8332
|
+
return {
|
|
8333
|
+
lang: typeof snapshot.lang === "string" ? snapshot.lang.trim() : "",
|
|
8334
|
+
url: typeof snapshot.url === "string" ? snapshot.url : wc.getURL(),
|
|
8335
|
+
title: typeof snapshot.title === "string" ? snapshot.title : wc.getTitle()
|
|
8336
|
+
};
|
|
8337
|
+
}
|
|
8338
|
+
function primaryLanguageTag(value) {
|
|
8339
|
+
return value.trim().toLowerCase().split(/[-_]/)[0] || "";
|
|
8340
|
+
}
|
|
8341
|
+
function localeChanged(before, after) {
|
|
8342
|
+
if (!before || !after) return false;
|
|
8343
|
+
const beforeLang = primaryLanguageTag(before.lang);
|
|
8344
|
+
const afterLang = primaryLanguageTag(after.lang);
|
|
8345
|
+
if (beforeLang && afterLang && beforeLang !== afterLang) {
|
|
8346
|
+
return true;
|
|
8347
|
+
}
|
|
8348
|
+
const localeHint = /[?&](lang|locale|language|hl)=|\/(ja|jp|en|fr|de|es|it|ko|zh)(\/|$)/i;
|
|
8349
|
+
return before.url !== after.url && localeHint.test(after.url);
|
|
8350
|
+
}
|
|
8351
|
+
async function restoreLocaleSnapshot(wc, snapshot) {
|
|
8352
|
+
if (!snapshot || wc.isDestroyed()) return;
|
|
8353
|
+
try {
|
|
8354
|
+
if (typeof wc.canGoBack === "function" && wc.canGoBack()) {
|
|
8355
|
+
wc.goBack();
|
|
8356
|
+
await waitForLoad$1(wc, 3e3);
|
|
8357
|
+
const reverted = await getLocaleSnapshot(wc);
|
|
8358
|
+
if (!localeChanged(snapshot, reverted)) {
|
|
8359
|
+
return;
|
|
8360
|
+
}
|
|
8361
|
+
}
|
|
8362
|
+
} catch {
|
|
8363
|
+
}
|
|
8364
|
+
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
8365
|
+
try {
|
|
8366
|
+
await wc.loadURL(snapshot.url);
|
|
8367
|
+
await waitForLoad$1(wc, 3e3);
|
|
8368
|
+
return;
|
|
8369
|
+
} catch {
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
if (snapshot.url) {
|
|
8373
|
+
try {
|
|
8374
|
+
await wc.reload();
|
|
8375
|
+
await waitForLoad$1(wc, 3e3);
|
|
8376
|
+
} catch {
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
8380
|
+
const ADD_TO_CART_PATTERNS = [
|
|
8381
|
+
"add to cart",
|
|
8382
|
+
"add to bag",
|
|
8383
|
+
"add to basket",
|
|
8384
|
+
"add to my cart",
|
|
8385
|
+
"add to my bag",
|
|
8386
|
+
"add to my basket",
|
|
8387
|
+
"add item to cart",
|
|
8388
|
+
"add item to bag",
|
|
8389
|
+
"add item to basket"
|
|
8390
|
+
];
|
|
8391
|
+
const recentCartClicks = /* @__PURE__ */ new Map();
|
|
8392
|
+
const CART_CLICK_COOLDOWN_MS = 15e3;
|
|
8393
|
+
function isAddToCartText(text) {
|
|
8394
|
+
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
8395
|
+
return ADD_TO_CART_PATTERNS.some((p) => normalized.includes(p));
|
|
8396
|
+
}
|
|
8397
|
+
function recordCartClick(url, text) {
|
|
8398
|
+
recentCartClicks.set(url, { text, ts: Date.now() });
|
|
8399
|
+
for (const [key, entry] of recentCartClicks) {
|
|
8400
|
+
if (Date.now() - entry.ts > CART_CLICK_COOLDOWN_MS) {
|
|
8401
|
+
recentCartClicks.delete(key);
|
|
8402
|
+
}
|
|
8403
|
+
}
|
|
8404
|
+
}
|
|
8405
|
+
function isDuplicateCartClick(url, text) {
|
|
8406
|
+
const recent = recentCartClicks.get(url);
|
|
8407
|
+
if (!recent) return false;
|
|
8408
|
+
if (Date.now() - recent.ts > CART_CLICK_COOLDOWN_MS) {
|
|
8409
|
+
recentCartClicks.delete(url);
|
|
8410
|
+
return false;
|
|
8411
|
+
}
|
|
8412
|
+
return isAddToCartText(text);
|
|
8413
|
+
}
|
|
7323
8414
|
async function clickResolvedSelector$1(wc, selector) {
|
|
7324
8415
|
if (selector.startsWith("__vessel_idx:")) {
|
|
7325
8416
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
7326
8417
|
const beforeUrl2 = wc.getURL();
|
|
8418
|
+
const idxLabel = await executePageScript(
|
|
8419
|
+
wc,
|
|
8420
|
+
`window.__vessel?.getElementText?.(${idx}) || ""`,
|
|
8421
|
+
{ label: "shadow element text" }
|
|
8422
|
+
);
|
|
8423
|
+
if (typeof idxLabel === "string" && isAddToCartText(idxLabel) && isDuplicateCartClick(beforeUrl2, idxLabel)) {
|
|
8424
|
+
return `Blocked: "${idxLabel}" 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).`;
|
|
8425
|
+
}
|
|
7327
8426
|
const result = await executePageScript(
|
|
7328
8427
|
wc,
|
|
7329
8428
|
`window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`,
|
|
@@ -7333,12 +8432,29 @@ async function clickResolvedSelector$1(wc, selector) {
|
|
|
7333
8432
|
);
|
|
7334
8433
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
7335
8434
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
8435
|
+
if (typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
8436
|
+
recordCartClick(beforeUrl2, idxLabel);
|
|
8437
|
+
}
|
|
7336
8438
|
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
7337
8439
|
const afterUrl2 = wc.getURL();
|
|
7338
|
-
|
|
8440
|
+
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
8441
|
+
const idxOverlay = await detectPostClickOverlay$1(wc);
|
|
8442
|
+
return idxOverlay ? `${result}
|
|
8443
|
+
${idxOverlay}` : result;
|
|
7339
8444
|
}
|
|
7340
8445
|
if (selector.includes(" >>> ")) {
|
|
7341
8446
|
const beforeUrl2 = wc.getURL();
|
|
8447
|
+
const shadowLabel = await executePageScript(
|
|
8448
|
+
wc,
|
|
8449
|
+
`(function() {
|
|
8450
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
8451
|
+
return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
|
|
8452
|
+
})()`,
|
|
8453
|
+
{ label: "shadow element text" }
|
|
8454
|
+
);
|
|
8455
|
+
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel) && isDuplicateCartClick(beforeUrl2, shadowLabel)) {
|
|
8456
|
+
return `Blocked: "${shadowLabel}" 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).`;
|
|
8457
|
+
}
|
|
7342
8458
|
const result = await executePageScript(
|
|
7343
8459
|
wc,
|
|
7344
8460
|
`
|
|
@@ -7355,19 +8471,48 @@ async function clickResolvedSelector$1(wc, selector) {
|
|
|
7355
8471
|
);
|
|
7356
8472
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
7357
8473
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
8474
|
+
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
8475
|
+
recordCartClick(beforeUrl2, shadowLabel);
|
|
8476
|
+
}
|
|
7358
8477
|
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
7359
8478
|
const afterUrl2 = wc.getURL();
|
|
7360
|
-
|
|
8479
|
+
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
8480
|
+
const shadowOverlay = await detectPostClickOverlay$1(wc);
|
|
8481
|
+
return shadowOverlay ? `${result}
|
|
8482
|
+
${shadowOverlay}` : result;
|
|
7361
8483
|
}
|
|
7362
8484
|
const beforeUrl = wc.getURL();
|
|
7363
8485
|
const elInfo = await describeElementForClick$1(wc, selector);
|
|
7364
8486
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
8487
|
+
const cartMatch = isAddToCartText(elInfo.text);
|
|
8488
|
+
console.log(
|
|
8489
|
+
`[Vessel cart-guard] text="${elInfo.text}" cartMatch=${cartMatch} url=${beforeUrl} hasPrior=${recentCartClicks.has(beforeUrl)}`
|
|
8490
|
+
);
|
|
8491
|
+
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
8492
|
+
console.log(`[Vessel cart-guard] BLOCKED duplicate add-to-cart click`);
|
|
8493
|
+
return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
8494
|
+
}
|
|
8495
|
+
if (!cartMatch && recentCartClicks.has(beforeUrl)) {
|
|
8496
|
+
const dialogActions = await getCartDialogActions$1(wc);
|
|
8497
|
+
if (dialogActions) {
|
|
8498
|
+
console.log(
|
|
8499
|
+
`[Vessel cart-guard] BLOCKED background click while cart dialog is open`
|
|
8500
|
+
);
|
|
8501
|
+
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
8502
|
+
${dialogActions}
|
|
8503
|
+
Click one of these dialog actions instead.`;
|
|
8504
|
+
}
|
|
8505
|
+
}
|
|
7365
8506
|
if (elInfo.href) {
|
|
7366
8507
|
const validation = await validateLinkDestination(elInfo.href);
|
|
7367
8508
|
if (validation.status === "dead") {
|
|
7368
8509
|
return formatDeadLinkMessage(elInfo.text, validation);
|
|
7369
8510
|
}
|
|
7370
8511
|
}
|
|
8512
|
+
if (cartMatch) {
|
|
8513
|
+
console.log(`[Vessel cart-guard] RECORDED cart click for url=${beforeUrl}`);
|
|
8514
|
+
recordCartClick(beforeUrl, elInfo.text);
|
|
8515
|
+
}
|
|
7371
8516
|
const clickText = `Clicked: ${elInfo.text}`;
|
|
7372
8517
|
const clickResult = await clickElement$1(wc, selector);
|
|
7373
8518
|
if (clickResult.startsWith("Error:")) return clickResult;
|
|
@@ -7376,6 +8521,18 @@ async function clickResolvedSelector$1(wc, selector) {
|
|
|
7376
8521
|
if (afterUrl !== beforeUrl) {
|
|
7377
8522
|
return `${clickText} -> ${afterUrl}`;
|
|
7378
8523
|
}
|
|
8524
|
+
const overlayHint = await detectPostClickOverlay$1(wc);
|
|
8525
|
+
if (overlayHint) {
|
|
8526
|
+
const dialogActions = cartMatch ? await getCartDialogActions$1(wc) : null;
|
|
8527
|
+
const actionsSuffix = dialogActions ? `
|
|
8528
|
+
${dialogActions}
|
|
8529
|
+
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
8530
|
+
return `${clickText} (${clickResult})
|
|
8531
|
+
${overlayHint}${actionsSuffix}`;
|
|
8532
|
+
}
|
|
8533
|
+
if (cartMatch) {
|
|
8534
|
+
return `${clickText} (${clickResult})`;
|
|
8535
|
+
}
|
|
7379
8536
|
const activationResult = await activateElement$1(wc, selector);
|
|
7380
8537
|
if (!activationResult.startsWith("Error:")) {
|
|
7381
8538
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
@@ -7384,14 +8541,226 @@ async function clickResolvedSelector$1(wc, selector) {
|
|
|
7384
8541
|
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
7385
8542
|
}
|
|
7386
8543
|
}
|
|
8544
|
+
const postActivationOverlayHint = await detectPostClickOverlay$1(wc);
|
|
8545
|
+
if (postActivationOverlayHint) {
|
|
8546
|
+
return `${clickText} (${clickResult})
|
|
8547
|
+
${postActivationOverlayHint}`;
|
|
8548
|
+
}
|
|
7387
8549
|
return `${clickText} (${clickResult})`;
|
|
7388
8550
|
}
|
|
8551
|
+
async function getCartDialogActions$1(wc) {
|
|
8552
|
+
const result = await executePageScript(
|
|
8553
|
+
wc,
|
|
8554
|
+
`
|
|
8555
|
+
(function() {
|
|
8556
|
+
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
8557
|
+
if (!dialog) return { found: false, actions: [] };
|
|
8558
|
+
var cs = getComputedStyle(dialog);
|
|
8559
|
+
if (cs.display === 'none' || cs.visibility === 'hidden') return { found: false, actions: [] };
|
|
8560
|
+
var text = (dialog.textContent || '').slice(0, 500).toLowerCase();
|
|
8561
|
+
var cartSignals = ['added to cart','added to bag','added to basket',
|
|
8562
|
+
'item added','your basket','your cart','your bag',
|
|
8563
|
+
'view basket','view cart','continue shopping'];
|
|
8564
|
+
var isCart = cartSignals.some(function(s) { return text.indexOf(s) !== -1; });
|
|
8565
|
+
if (!isCart) return { found: false, actions: [] };
|
|
8566
|
+
var actions = [];
|
|
8567
|
+
dialog.querySelectorAll('button, a[href], [role="button"]').forEach(function(el) {
|
|
8568
|
+
var cs2 = getComputedStyle(el);
|
|
8569
|
+
if (cs2.display === 'none' || cs2.visibility === 'hidden') return;
|
|
8570
|
+
var r = el.getBoundingClientRect();
|
|
8571
|
+
if (r.width < 20 || r.height < 10) return;
|
|
8572
|
+
var label = (el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 80);
|
|
8573
|
+
if (!label || label.length < 2) return;
|
|
8574
|
+
var href = el.getAttribute('href') || '';
|
|
8575
|
+
var sel = el.id ? '#' + el.id
|
|
8576
|
+
: el.getAttribute('data-test') ? '[data-test="' + el.getAttribute('data-test') + '"]'
|
|
8577
|
+
: el.getAttribute('aria-label') ? '[aria-label="' + el.getAttribute('aria-label') + '"]'
|
|
8578
|
+
: null;
|
|
8579
|
+
if (sel) actions.push({ label: label, selector: sel, href: href });
|
|
8580
|
+
});
|
|
8581
|
+
return {
|
|
8582
|
+
found: true,
|
|
8583
|
+
actions: actions.map(function(a) {
|
|
8584
|
+
return '- "' + a.label + '"' + (a.href ? ' → ' + a.href : '') + (a.selector ? ' (selector: ' + a.selector + ')' : '');
|
|
8585
|
+
}),
|
|
8586
|
+
};
|
|
8587
|
+
})()
|
|
8588
|
+
`,
|
|
8589
|
+
{ timeoutMs: 800, label: "get cart dialog actions" }
|
|
8590
|
+
);
|
|
8591
|
+
if (!result || result === PAGE_SCRIPT_TIMEOUT || !result.found) return null;
|
|
8592
|
+
if (result.actions.length === 0) return null;
|
|
8593
|
+
return `Available dialog actions:
|
|
8594
|
+
${result.actions.join("\n")}`;
|
|
8595
|
+
}
|
|
8596
|
+
async function detectPostClickOverlay$1(wc) {
|
|
8597
|
+
const result = await executePageScript(
|
|
8598
|
+
wc,
|
|
8599
|
+
`
|
|
8600
|
+
(function() {
|
|
8601
|
+
var vw = window.innerWidth || document.documentElement.clientWidth;
|
|
8602
|
+
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
8603
|
+
var vpArea = Math.max(1, vw * vh);
|
|
8604
|
+
|
|
8605
|
+
function isVis(el) {
|
|
8606
|
+
var cs = getComputedStyle(el);
|
|
8607
|
+
return cs.display !== 'none' && cs.visibility !== 'hidden' &&
|
|
8608
|
+
el.getBoundingClientRect().width > 0;
|
|
8609
|
+
}
|
|
8610
|
+
|
|
8611
|
+
function hasFixedAncestor(el) {
|
|
8612
|
+
var cur = el.parentElement;
|
|
8613
|
+
while (cur && cur !== document.body) {
|
|
8614
|
+
var ps = getComputedStyle(cur).position;
|
|
8615
|
+
if (ps === 'fixed' || ps === 'sticky') return true;
|
|
8616
|
+
cur = cur.parentElement;
|
|
8617
|
+
}
|
|
8618
|
+
return false;
|
|
8619
|
+
}
|
|
8620
|
+
|
|
8621
|
+
function effectiveZ(el) {
|
|
8622
|
+
var cur = el;
|
|
8623
|
+
while (cur && cur !== document.body) {
|
|
8624
|
+
var z = parseInt(getComputedStyle(cur).zIndex, 10);
|
|
8625
|
+
if (z > 0) return z;
|
|
8626
|
+
cur = cur.parentElement;
|
|
8627
|
+
}
|
|
8628
|
+
return 0;
|
|
8629
|
+
}
|
|
8630
|
+
|
|
8631
|
+
function edgePad(r) {
|
|
8632
|
+
return r.left <= 24 || r.top <= 24 ||
|
|
8633
|
+
r.right >= vw - 24 || r.bottom >= vh - 24;
|
|
8634
|
+
}
|
|
8635
|
+
|
|
8636
|
+
var cartPhrases = ['added to cart','added to bag','added to basket',
|
|
8637
|
+
'added to your cart','added to your bag','added to your basket'];
|
|
8638
|
+
var cartActions = ['view cart','go to cart','continue shopping',
|
|
8639
|
+
'keep shopping','checkout','view basket','go to basket'];
|
|
8640
|
+
|
|
8641
|
+
// Phase 1: semantic dialog elements
|
|
8642
|
+
var selectors = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"]';
|
|
8643
|
+
var candidates = document.querySelectorAll(selectors);
|
|
8644
|
+
var hit = null;
|
|
8645
|
+
for (var j = 0; j < candidates.length; j++) {
|
|
8646
|
+
if (isVis(candidates[j])) { hit = candidates[j]; break; }
|
|
8647
|
+
}
|
|
8648
|
+
|
|
8649
|
+
// Phase 2: positioned drawer-like elements
|
|
8650
|
+
if (!hit) {
|
|
8651
|
+
var els = document.querySelectorAll('*');
|
|
8652
|
+
for (var i = 0; i < els.length; i++) {
|
|
8653
|
+
var s = getComputedStyle(els[i]);
|
|
8654
|
+
if (s.display === 'none' || s.visibility === 'hidden') continue;
|
|
8655
|
+
var pos = s.position;
|
|
8656
|
+
var isFixed = pos === 'fixed' || pos === 'sticky';
|
|
8657
|
+
var isAbs = pos === 'absolute';
|
|
8658
|
+
if (!isFixed && !isAbs) continue;
|
|
8659
|
+
if (isAbs && !hasFixedAncestor(els[i])) continue;
|
|
8660
|
+
if (effectiveZ(els[i]) < 5) continue;
|
|
8661
|
+
var r = els[i].getBoundingClientRect();
|
|
8662
|
+
var area = (r.width * r.height) / vpArea;
|
|
8663
|
+
if (r.width >= 160 && r.height >= 100 && area >= 0.05 && edgePad(r)) {
|
|
8664
|
+
hit = els[i]; break;
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8668
|
+
|
|
8669
|
+
// Phase 3: text-based fallback — any positioned element with cart confirmation text
|
|
8670
|
+
if (!hit) {
|
|
8671
|
+
var els2 = document.querySelectorAll('*');
|
|
8672
|
+
for (var k = 0; k < els2.length; k++) {
|
|
8673
|
+
var s2 = getComputedStyle(els2[k]);
|
|
8674
|
+
if (s2.display === 'none' || s2.visibility === 'hidden') continue;
|
|
8675
|
+
var p2 = s2.position;
|
|
8676
|
+
if (p2 !== 'fixed' && p2 !== 'sticky' && p2 !== 'absolute') continue;
|
|
8677
|
+
var r2 = els2[k].getBoundingClientRect();
|
|
8678
|
+
if (r2.width < 120 || r2.height < 80) continue;
|
|
8679
|
+
var innerText = (els2[k].textContent || '').slice(0, 500).toLowerCase();
|
|
8680
|
+
var hasConfirm = cartPhrases.some(function(ph) { return innerText.indexOf(ph) !== -1; });
|
|
8681
|
+
if (hasConfirm) { hit = els2[k]; break; }
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8684
|
+
|
|
8685
|
+
if (!hit) return { found: false, label: '', cartLike: false };
|
|
8686
|
+
var text = (hit.textContent || '').slice(0, 500).toLowerCase();
|
|
8687
|
+
var cartLike = cartPhrases.concat(cartActions).some(function(s) { return text.indexOf(s) !== -1; });
|
|
8688
|
+
var label = (hit.getAttribute('aria-label') || (hit.querySelector('h1,h2,h3,h4') || {}).textContent || '').trim().slice(0, 80);
|
|
8689
|
+
return { found: true, label: label, cartLike: cartLike };
|
|
8690
|
+
})()
|
|
8691
|
+
`,
|
|
8692
|
+
{ timeoutMs: 800, label: "post-click overlay check" }
|
|
8693
|
+
);
|
|
8694
|
+
if (!result || result === PAGE_SCRIPT_TIMEOUT || !result.found) return null;
|
|
8695
|
+
if (result.cartLike) {
|
|
8696
|
+
const desc2 = result.label ? ` ("${result.label}")` : "";
|
|
8697
|
+
return `A cart confirmation dialog appeared${desc2}. Call read_page to see available actions — do not click Add to Cart again.`;
|
|
8698
|
+
}
|
|
8699
|
+
const desc = result.label ? ` ("${result.label}")` : "";
|
|
8700
|
+
return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
|
|
8701
|
+
}
|
|
7389
8702
|
async function dismissPopup$1(wc) {
|
|
7390
8703
|
const before = await extractContent(wc);
|
|
7391
8704
|
const initialBlocking = before.overlays.filter(
|
|
7392
8705
|
(overlay) => overlay.blocksInteraction
|
|
7393
8706
|
).length;
|
|
8707
|
+
if (initialBlocking > 0) {
|
|
8708
|
+
const overlayText = before.overlays.map((o) => [o.label, o.text].filter(Boolean).join(" ")).join(" ").toLowerCase();
|
|
8709
|
+
const cartSignals = [
|
|
8710
|
+
"added to cart",
|
|
8711
|
+
"added to bag",
|
|
8712
|
+
"added to basket",
|
|
8713
|
+
"item added",
|
|
8714
|
+
"items in your basket",
|
|
8715
|
+
"items in your cart",
|
|
8716
|
+
"items in your bag",
|
|
8717
|
+
"your basket",
|
|
8718
|
+
"your cart",
|
|
8719
|
+
"your bag",
|
|
8720
|
+
"view basket",
|
|
8721
|
+
"view cart",
|
|
8722
|
+
"continue shopping"
|
|
8723
|
+
];
|
|
8724
|
+
if (cartSignals.some((s) => overlayText.includes(s))) {
|
|
8725
|
+
const continueResult = await executePageScript(
|
|
8726
|
+
wc,
|
|
8727
|
+
`
|
|
8728
|
+
(function() {
|
|
8729
|
+
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
8730
|
+
if (!dialog) return "Error: dialog not found";
|
|
8731
|
+
var buttons = dialog.querySelectorAll('button, a[href], [role="button"]');
|
|
8732
|
+
var continueBtn = null;
|
|
8733
|
+
var viewCartBtn = null;
|
|
8734
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
8735
|
+
var label = (buttons[i].getAttribute('aria-label') || buttons[i].textContent || '').trim().toLowerCase();
|
|
8736
|
+
if (/continue shopping|keep shopping/.test(label)) { continueBtn = buttons[i]; break; }
|
|
8737
|
+
if (/view (basket|cart|bag)|checkout/.test(label) && !viewCartBtn) { viewCartBtn = buttons[i]; }
|
|
8738
|
+
}
|
|
8739
|
+
var target = continueBtn || viewCartBtn;
|
|
8740
|
+
if (!target) return "Error: no dialog action found";
|
|
8741
|
+
var actionLabel = (target.getAttribute('aria-label') || target.textContent || '').trim();
|
|
8742
|
+
if (target.tagName === 'A' && target.href) {
|
|
8743
|
+
window.location.href = target.href;
|
|
8744
|
+
return "Clicked: " + actionLabel + " -> " + target.href;
|
|
8745
|
+
}
|
|
8746
|
+
target.click();
|
|
8747
|
+
return "Clicked: " + actionLabel;
|
|
8748
|
+
})()
|
|
8749
|
+
`,
|
|
8750
|
+
{ timeoutMs: 1500, label: "cart dialog continue shopping" }
|
|
8751
|
+
);
|
|
8752
|
+
if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
8753
|
+
console.log(
|
|
8754
|
+
`[Vessel cart-guard] dismiss_popup auto-clicked dialog action: ${continueResult}`
|
|
8755
|
+
);
|
|
8756
|
+
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
8757
|
+
}
|
|
8758
|
+
const dialogActions = await getCartDialogActions$1(wc);
|
|
8759
|
+
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."}`;
|
|
8760
|
+
}
|
|
8761
|
+
}
|
|
7394
8762
|
const initialDormant = before.dormantOverlays.length;
|
|
8763
|
+
const initialLocale = await getLocaleSnapshot(wc);
|
|
7395
8764
|
const candidates = await executePageScript(
|
|
7396
8765
|
wc,
|
|
7397
8766
|
`
|
|
@@ -7503,7 +8872,8 @@ async function dismissPopup$1(wc) {
|
|
|
7503
8872
|
).toLowerCase();
|
|
7504
8873
|
const classText = text(typeof el.className === "string" ? el.className : "").toLowerCase();
|
|
7505
8874
|
const idText = text(el.id).toLowerCase();
|
|
7506
|
-
const
|
|
8875
|
+
const hrefText = text(el.getAttribute && el.getAttribute("href")).toLowerCase();
|
|
8876
|
+
const combined = label + " " + classText + " " + idText + " " + hrefText;
|
|
7507
8877
|
let score = rooted ? 30 : 0;
|
|
7508
8878
|
if (/^x$|^×$/.test(label)) score += 120;
|
|
7509
8879
|
if (/no thanks|no, thanks|not now|maybe later|dismiss|close|skip|cancel|continue without|no thank you|reject|decline/.test(label)) score += 100;
|
|
@@ -7513,6 +8883,11 @@ async function dismissPopup$1(wc) {
|
|
|
7513
8883
|
// OneTrust "Accept" is valid for dismissing the banner (user just wants it gone)
|
|
7514
8884
|
if (/onetrust-accept|cookie.*accept|consent.*accept/.test(combined)) score += 80;
|
|
7515
8885
|
if (el.getAttribute("aria-label")) score += 20;
|
|
8886
|
+
if (/(language|locale|region|country|currency)\b/.test(combined)) score -= 320;
|
|
8887
|
+
if (/\b(english|japanese|japan|francais|espanol|deutsch|italiano|portuguese|nihongo)\b/.test(label)) score -= 280;
|
|
8888
|
+
if (/日本語|中文|한국어/.test(label)) score -= 280;
|
|
8889
|
+
if (/[?&](lang|locale|language|hl)=/.test(hrefText)) score -= 260;
|
|
8890
|
+
if (//(ja|jp|en|fr|de|es|it|ko|zh)(/|$)/.test(hrefText)) score -= 220;
|
|
7516
8891
|
// Penalize general accept/subscribe buttons that aren't consent-related
|
|
7517
8892
|
if (/accept|continue|submit|sign up|subscribe|join|start|next/.test(label) && !/cookie|consent|onetrust/.test(combined)) score -= 80;
|
|
7518
8893
|
const rect = el.getBoundingClientRect();
|
|
@@ -7590,6 +8965,11 @@ async function dismissPopup$1(wc) {
|
|
|
7590
8965
|
const result = await clickElement$1(wc, candidate.selector);
|
|
7591
8966
|
if (result.startsWith("Error:")) continue;
|
|
7592
8967
|
await sleep$1(250);
|
|
8968
|
+
const postClickLocale = await getLocaleSnapshot(wc);
|
|
8969
|
+
if (localeChanged(initialLocale, postClickLocale)) {
|
|
8970
|
+
await restoreLocaleSnapshot(wc, initialLocale);
|
|
8971
|
+
continue;
|
|
8972
|
+
}
|
|
7593
8973
|
const after = await extractContent(wc);
|
|
7594
8974
|
const blocking = after.overlays.filter(
|
|
7595
8975
|
(overlay) => overlay.blocksInteraction
|
|
@@ -7613,6 +8993,93 @@ async function dismissPopup$1(wc) {
|
|
|
7613
8993
|
}
|
|
7614
8994
|
return initialBlocking > 0 ? "Could not dismiss the blocking popup automatically" : initialDormant > 0 ? `No active blocking popup detected. Found ${initialDormant} dormant consent/modal surface(s) in the DOM, likely geo-gated or inactive in this session.` : "No blocking popup detected";
|
|
7615
8995
|
}
|
|
8996
|
+
function describeOverlayState(page) {
|
|
8997
|
+
const inventory = buildOverlayInventory(page);
|
|
8998
|
+
return {
|
|
8999
|
+
inventory,
|
|
9000
|
+
blocking: inventory.filter((overlay) => overlay.blocksInteraction).length,
|
|
9001
|
+
total: inventory.length,
|
|
9002
|
+
signature: getBlockingOverlaySignature(inventory)
|
|
9003
|
+
};
|
|
9004
|
+
}
|
|
9005
|
+
async function clickOverlayCandidate(wc, action) {
|
|
9006
|
+
if (!action?.selector) return null;
|
|
9007
|
+
const result = await clickResolvedSelector$1(wc, action.selector);
|
|
9008
|
+
return `${action.label || action.selector}: ${result}`;
|
|
9009
|
+
}
|
|
9010
|
+
async function clearOverlays(wc, strategy = "auto") {
|
|
9011
|
+
const steps = [];
|
|
9012
|
+
let cleared = 0;
|
|
9013
|
+
const maxIterations = 8;
|
|
9014
|
+
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
9015
|
+
const before = await extractContent(wc);
|
|
9016
|
+
const beforeState = describeOverlayState(before);
|
|
9017
|
+
const blockingOverlays = beforeState.inventory.filter(
|
|
9018
|
+
(overlay2) => overlay2.blocksInteraction
|
|
9019
|
+
);
|
|
9020
|
+
if (blockingOverlays.length === 0) {
|
|
9021
|
+
if (cleared === 0) return "No blocking overlays detected";
|
|
9022
|
+
steps.push(`Overlays remaining: ${beforeState.total}`);
|
|
9023
|
+
steps.push("Page still blocked: false");
|
|
9024
|
+
return steps.join("\n");
|
|
9025
|
+
}
|
|
9026
|
+
const overlay = blockingOverlays[0];
|
|
9027
|
+
let actionMessage = null;
|
|
9028
|
+
if (overlay.kind === "cookie_consent") {
|
|
9029
|
+
actionMessage = await clickOverlayCandidate(
|
|
9030
|
+
wc,
|
|
9031
|
+
overlay.acceptAction || overlay.dismissAction || overlay.actions[0]
|
|
9032
|
+
);
|
|
9033
|
+
} else if (overlay.kind === "selection_modal") {
|
|
9034
|
+
if (!overlay.correctOption?.selector) {
|
|
9035
|
+
if (strategy === "interactive") {
|
|
9036
|
+
steps.push(
|
|
9037
|
+
"Stopped: selection modal needs human judgment because no likely-correct option was detected."
|
|
9038
|
+
);
|
|
9039
|
+
steps.push(`Overlays remaining: ${beforeState.total}`);
|
|
9040
|
+
steps.push("Page still blocked: true");
|
|
9041
|
+
return steps.join("\n");
|
|
9042
|
+
}
|
|
9043
|
+
} else {
|
|
9044
|
+
const optionResult = await clickOverlayCandidate(
|
|
9045
|
+
wc,
|
|
9046
|
+
overlay.correctOption
|
|
9047
|
+
);
|
|
9048
|
+
if (optionResult) {
|
|
9049
|
+
actionMessage = `Selected likely-correct option: ${optionResult}`;
|
|
9050
|
+
await sleep$1(120);
|
|
9051
|
+
const submitResult = await clickOverlayCandidate(
|
|
9052
|
+
wc,
|
|
9053
|
+
overlay.submitAction || overlay.acceptAction
|
|
9054
|
+
);
|
|
9055
|
+
if (submitResult) {
|
|
9056
|
+
actionMessage += `
|
|
9057
|
+
Submitted modal: ${submitResult}`;
|
|
9058
|
+
}
|
|
9059
|
+
}
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
if (!actionMessage) {
|
|
9063
|
+
actionMessage = `Fallback popup handling: ${await dismissPopup$1(wc)}`;
|
|
9064
|
+
}
|
|
9065
|
+
steps.push(actionMessage);
|
|
9066
|
+
await sleep$1(250);
|
|
9067
|
+
const after = await extractContent(wc);
|
|
9068
|
+
const afterState = describeOverlayState(after);
|
|
9069
|
+
steps.push(`Overlays remaining: ${afterState.total}`);
|
|
9070
|
+
steps.push(`Page still blocked: ${afterState.blocking > 0}`);
|
|
9071
|
+
if (afterState.blocking === 0) {
|
|
9072
|
+
return steps.join("\n");
|
|
9073
|
+
}
|
|
9074
|
+
const progressMade = afterState.blocking < beforeState.blocking || afterState.total !== beforeState.total || afterState.signature !== beforeState.signature;
|
|
9075
|
+
if (progressMade) {
|
|
9076
|
+
cleared += 1;
|
|
9077
|
+
continue;
|
|
9078
|
+
}
|
|
9079
|
+
return steps.join("\n");
|
|
9080
|
+
}
|
|
9081
|
+
return steps.join("\n");
|
|
9082
|
+
}
|
|
7616
9083
|
async function resolveSelector$1(wc, index, selector) {
|
|
7617
9084
|
if (selector) return selector;
|
|
7618
9085
|
if (index == null) return null;
|
|
@@ -7727,6 +9194,197 @@ async function resolveSelector$1(wc, index, selector) {
|
|
|
7727
9194
|
if (extractedSelector) return extractedSelector;
|
|
7728
9195
|
return null;
|
|
7729
9196
|
}
|
|
9197
|
+
function normalizeFieldToken(value) {
|
|
9198
|
+
return typeof value === "string" ? value.trim() : "";
|
|
9199
|
+
}
|
|
9200
|
+
function describeFillField(field) {
|
|
9201
|
+
if (field.selector) return `selector=${field.selector}`;
|
|
9202
|
+
if (field.index != null) return `index=${field.index}`;
|
|
9203
|
+
if (field.name) return `name=${field.name}`;
|
|
9204
|
+
if (field.label) return `label=${field.label}`;
|
|
9205
|
+
if (field.placeholder) return `placeholder=${field.placeholder}`;
|
|
9206
|
+
return "field";
|
|
9207
|
+
}
|
|
9208
|
+
async function resolveFieldSelector(wc, field) {
|
|
9209
|
+
const directSelector = await resolveSelector$1(wc, field.index, field.selector);
|
|
9210
|
+
if (directSelector) return directSelector;
|
|
9211
|
+
const name = normalizeFieldToken(field.name);
|
|
9212
|
+
const label = normalizeFieldToken(field.label);
|
|
9213
|
+
const placeholder = normalizeFieldToken(field.placeholder);
|
|
9214
|
+
if (!name && !label && !placeholder) return null;
|
|
9215
|
+
const selector = await executePageScript(
|
|
9216
|
+
wc,
|
|
9217
|
+
`
|
|
9218
|
+
(function() {
|
|
9219
|
+
function normalize(value) {
|
|
9220
|
+
return value == null ? "" : String(value).trim().toLowerCase();
|
|
9221
|
+
}
|
|
9222
|
+
|
|
9223
|
+
function text(value) {
|
|
9224
|
+
return value == null ? "" : String(value).trim();
|
|
9225
|
+
}
|
|
9226
|
+
|
|
9227
|
+
function isVisible(el) {
|
|
9228
|
+
if (!(el instanceof HTMLElement)) return true;
|
|
9229
|
+
const style = window.getComputedStyle(el);
|
|
9230
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
9231
|
+
return false;
|
|
9232
|
+
}
|
|
9233
|
+
if (el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true") {
|
|
9234
|
+
return false;
|
|
9235
|
+
}
|
|
9236
|
+
const rect = el.getBoundingClientRect();
|
|
9237
|
+
return rect.width > 0 && rect.height > 0;
|
|
9238
|
+
}
|
|
9239
|
+
|
|
9240
|
+
function escapeSelectorValue(value) {
|
|
9241
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
9242
|
+
return CSS.escape(value);
|
|
9243
|
+
}
|
|
9244
|
+
return String(value).replace(/["\\\\]/g, "\\\\$&");
|
|
9245
|
+
}
|
|
9246
|
+
|
|
9247
|
+
function uniqueSelector(candidate) {
|
|
9248
|
+
if (!candidate) return null;
|
|
9249
|
+
try {
|
|
9250
|
+
return document.querySelectorAll(candidate).length === 1 ? candidate : null;
|
|
9251
|
+
} catch {
|
|
9252
|
+
return null;
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
9255
|
+
|
|
9256
|
+
function uniqueAttributeSelector(el, attribute) {
|
|
9257
|
+
const value = text(el.getAttribute && el.getAttribute(attribute));
|
|
9258
|
+
if (!value) return null;
|
|
9259
|
+
const candidate = el.tagName.toLowerCase() + "[" + attribute + "=\\"" + escapeSelectorValue(value) + "\\"]";
|
|
9260
|
+
return uniqueSelector(candidate);
|
|
9261
|
+
}
|
|
9262
|
+
|
|
9263
|
+
function selectorFor(el) {
|
|
9264
|
+
if (!el) return null;
|
|
9265
|
+
if (el.id) return "#" + escapeSelectorValue(el.id);
|
|
9266
|
+
const attributes = ["data-testid", "name", "form", "aria-label", "placeholder"];
|
|
9267
|
+
for (const attribute of attributes) {
|
|
9268
|
+
const attributeCandidate = uniqueAttributeSelector(el, attribute);
|
|
9269
|
+
if (attributeCandidate) return attributeCandidate;
|
|
9270
|
+
}
|
|
9271
|
+
const parts = [];
|
|
9272
|
+
let current = el;
|
|
9273
|
+
while (current) {
|
|
9274
|
+
if (current.id) {
|
|
9275
|
+
parts.unshift("#" + escapeSelectorValue(current.id));
|
|
9276
|
+
break;
|
|
9277
|
+
}
|
|
9278
|
+
const tag = current.tagName.toLowerCase();
|
|
9279
|
+
const parent = current.parentElement;
|
|
9280
|
+
if (!parent) {
|
|
9281
|
+
parts.unshift(tag);
|
|
9282
|
+
break;
|
|
9283
|
+
}
|
|
9284
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === current.tagName);
|
|
9285
|
+
const index = siblings.indexOf(current) + 1;
|
|
9286
|
+
parts.unshift(siblings.length > 1 ? tag + ":nth-of-type(" + index + ")" : tag);
|
|
9287
|
+
current = parent;
|
|
9288
|
+
}
|
|
9289
|
+
const candidate = parts.join(" > ");
|
|
9290
|
+
return uniqueSelector(candidate) || candidate;
|
|
9291
|
+
}
|
|
9292
|
+
|
|
9293
|
+
function getLabelText(el) {
|
|
9294
|
+
const parts = [];
|
|
9295
|
+
if (el.labels) {
|
|
9296
|
+
Array.from(el.labels).forEach((labelEl) => {
|
|
9297
|
+
const value = text(labelEl.textContent);
|
|
9298
|
+
if (value) parts.push(value);
|
|
9299
|
+
});
|
|
9300
|
+
}
|
|
9301
|
+
const ariaLabel = text(el.getAttribute && el.getAttribute("aria-label"));
|
|
9302
|
+
if (ariaLabel) parts.push(ariaLabel);
|
|
9303
|
+
const labelledBy = text(el.getAttribute && el.getAttribute("aria-labelledby"));
|
|
9304
|
+
if (labelledBy) {
|
|
9305
|
+
labelledBy.split(/\\s+/).forEach((id) => {
|
|
9306
|
+
const ref = document.getElementById(id);
|
|
9307
|
+
const value = text(ref && ref.textContent);
|
|
9308
|
+
if (value) parts.push(value);
|
|
9309
|
+
});
|
|
9310
|
+
}
|
|
9311
|
+
return normalize(parts.join(" "));
|
|
9312
|
+
}
|
|
9313
|
+
|
|
9314
|
+
function scoreField(el) {
|
|
9315
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
|
|
9316
|
+
return -1;
|
|
9317
|
+
}
|
|
9318
|
+
if (!isVisible(el) || el.disabled || el.getAttribute("aria-disabled") === "true") {
|
|
9319
|
+
return -1;
|
|
9320
|
+
}
|
|
9321
|
+
|
|
9322
|
+
const normalizedName = normalize(el.getAttribute("name")) || normalize(el.id);
|
|
9323
|
+
const normalizedLabel = getLabelText(el);
|
|
9324
|
+
const normalizedPlaceholder = normalize(el.getAttribute("placeholder"));
|
|
9325
|
+
let score = 0;
|
|
9326
|
+
|
|
9327
|
+
if (${JSON.stringify(name)}) {
|
|
9328
|
+
if (normalizedName === ${JSON.stringify(name.toLowerCase())}) score += 120;
|
|
9329
|
+
else if (normalizedName.includes(${JSON.stringify(name.toLowerCase())})) score += 70;
|
|
9330
|
+
}
|
|
9331
|
+
|
|
9332
|
+
if (${JSON.stringify(label)}) {
|
|
9333
|
+
if (normalizedLabel === ${JSON.stringify(label.toLowerCase())}) score += 110;
|
|
9334
|
+
else if (normalizedLabel.includes(${JSON.stringify(label.toLowerCase())})) score += 65;
|
|
9335
|
+
}
|
|
9336
|
+
|
|
9337
|
+
if (${JSON.stringify(placeholder)}) {
|
|
9338
|
+
if (normalizedPlaceholder === ${JSON.stringify(placeholder.toLowerCase())}) score += 105;
|
|
9339
|
+
else if (normalizedPlaceholder.includes(${JSON.stringify(placeholder.toLowerCase())})) score += 60;
|
|
9340
|
+
}
|
|
9341
|
+
|
|
9342
|
+
if (score === 0) return -1;
|
|
9343
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) score += 5;
|
|
9344
|
+
return score;
|
|
9345
|
+
}
|
|
9346
|
+
|
|
9347
|
+
const candidates = Array.from(document.querySelectorAll("input, textarea, select"));
|
|
9348
|
+
let best = null;
|
|
9349
|
+
let bestScore = -1;
|
|
9350
|
+
for (const el of candidates) {
|
|
9351
|
+
const score = scoreField(el);
|
|
9352
|
+
if (score > bestScore) {
|
|
9353
|
+
best = el;
|
|
9354
|
+
bestScore = score;
|
|
9355
|
+
}
|
|
9356
|
+
}
|
|
9357
|
+
|
|
9358
|
+
return best ? selectorFor(best) : null;
|
|
9359
|
+
})()
|
|
9360
|
+
`,
|
|
9361
|
+
{
|
|
9362
|
+
label: "resolve form field"
|
|
9363
|
+
}
|
|
9364
|
+
);
|
|
9365
|
+
return typeof selector === "string" && selector ? selector : null;
|
|
9366
|
+
}
|
|
9367
|
+
async function fillFormFields(wc, fields) {
|
|
9368
|
+
const results = [];
|
|
9369
|
+
for (const field of fields) {
|
|
9370
|
+
const selector = await resolveFieldSelector(wc, field);
|
|
9371
|
+
if (!selector) {
|
|
9372
|
+
results.push({
|
|
9373
|
+
field,
|
|
9374
|
+
selector: null,
|
|
9375
|
+
result: `Skipped: no selector for ${describeFillField(field)}`
|
|
9376
|
+
});
|
|
9377
|
+
continue;
|
|
9378
|
+
}
|
|
9379
|
+
const result = await setElementValue$1(
|
|
9380
|
+
wc,
|
|
9381
|
+
selector,
|
|
9382
|
+
String(field.value || "")
|
|
9383
|
+
);
|
|
9384
|
+
results.push({ field, selector, result });
|
|
9385
|
+
}
|
|
9386
|
+
return results;
|
|
9387
|
+
}
|
|
7730
9388
|
function getTabByMatch$1(tabManager, match) {
|
|
7731
9389
|
if (!match) return null;
|
|
7732
9390
|
const lowered = match.toLowerCase();
|
|
@@ -8327,7 +9985,9 @@ async function getPostActionState$1(ctx, name) {
|
|
|
8327
9985
|
"select_option",
|
|
8328
9986
|
"hover",
|
|
8329
9987
|
"focus",
|
|
8330
|
-
"fill_form"
|
|
9988
|
+
"fill_form",
|
|
9989
|
+
"inspect_element",
|
|
9990
|
+
"clear_overlays"
|
|
8331
9991
|
];
|
|
8332
9992
|
const tabActions = [
|
|
8333
9993
|
"create_tab",
|
|
@@ -8367,6 +10027,7 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
8367
10027
|
"go_forward",
|
|
8368
10028
|
"reload",
|
|
8369
10029
|
"click",
|
|
10030
|
+
"inspect_element",
|
|
8370
10031
|
"type_text",
|
|
8371
10032
|
"select_option",
|
|
8372
10033
|
"submit_form",
|
|
@@ -8376,6 +10037,7 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
8376
10037
|
"focus",
|
|
8377
10038
|
"set_ad_blocking",
|
|
8378
10039
|
"dismiss_popup",
|
|
10040
|
+
"clear_overlays",
|
|
8379
10041
|
"read_page",
|
|
8380
10042
|
"wait_for",
|
|
8381
10043
|
"create_checkpoint",
|
|
@@ -8508,6 +10170,10 @@ async function executeAction(name, args, ctx) {
|
|
|
8508
10170
|
}
|
|
8509
10171
|
case "navigate": {
|
|
8510
10172
|
if (!wc || !tabId) return "Error: No active tab";
|
|
10173
|
+
const navValidation = await validateLinkDestination(args.url);
|
|
10174
|
+
if (navValidation.status === "dead") {
|
|
10175
|
+
return `Navigation blocked: ${args.url} returned ${navValidation.detail || "dead link"}. Try a different URL or go back and choose another link.`;
|
|
10176
|
+
}
|
|
8511
10177
|
ctx.tabManager.navigateTab(tabId, args.url);
|
|
8512
10178
|
await waitForLoad$1(wc);
|
|
8513
10179
|
return `Navigated to ${wc.getURL()}${getPostNavSummary(wc)}`;
|
|
@@ -8546,6 +10212,16 @@ async function executeAction(name, args, ctx) {
|
|
|
8546
10212
|
if (!selector) return "Error: No element index or selector provided";
|
|
8547
10213
|
return clickResolvedSelector$1(wc, selector);
|
|
8548
10214
|
}
|
|
10215
|
+
case "inspect_element": {
|
|
10216
|
+
if (!wc) return "Error: No active tab";
|
|
10217
|
+
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
10218
|
+
if (!selector) return "Error: No element index or selector provided";
|
|
10219
|
+
return inspectElement(
|
|
10220
|
+
wc,
|
|
10221
|
+
selector,
|
|
10222
|
+
typeof args.limit === "number" ? args.limit : 8
|
|
10223
|
+
);
|
|
10224
|
+
}
|
|
8549
10225
|
case "type_text": {
|
|
8550
10226
|
if (!wc) return "Error: No active tab";
|
|
8551
10227
|
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
@@ -8589,7 +10265,7 @@ async function executeAction(name, args, ctx) {
|
|
|
8589
10265
|
}
|
|
8590
10266
|
case "scroll": {
|
|
8591
10267
|
if (!wc) return "Error: No active tab";
|
|
8592
|
-
const pixels = args.amount
|
|
10268
|
+
const pixels = coerceOptionalNumber(args.amount) ?? 500;
|
|
8593
10269
|
const dir = args.direction === "up" ? -pixels : pixels;
|
|
8594
10270
|
const result2 = await scrollPage$1(wc, dir);
|
|
8595
10271
|
return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
|
|
@@ -8634,6 +10310,11 @@ async function executeAction(name, args, ctx) {
|
|
|
8634
10310
|
if (!wc) return "Error: No active tab";
|
|
8635
10311
|
return dismissPopup$1(wc);
|
|
8636
10312
|
}
|
|
10313
|
+
case "clear_overlays": {
|
|
10314
|
+
if (!wc) return "Error: No active tab";
|
|
10315
|
+
const strategy = args.strategy === "interactive" ? "interactive" : "auto";
|
|
10316
|
+
return clearOverlays(wc, strategy);
|
|
10317
|
+
}
|
|
8637
10318
|
case "read_page": {
|
|
8638
10319
|
if (!wc) return "Error: No active tab";
|
|
8639
10320
|
console.log("[Vessel read_page] starting extraction with 6s timeout");
|
|
@@ -9000,9 +10681,9 @@ ${truncated}`;
|
|
|
9000
10681
|
if (!wc) return "Error: No active tab";
|
|
9001
10682
|
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
9002
10683
|
const highlightColor = args.color || "yellow";
|
|
10684
|
+
const highlightText = normalizeLooseString(args.text);
|
|
9003
10685
|
const url = wc.getURL();
|
|
9004
10686
|
if (url && url !== "about:blank") {
|
|
9005
|
-
const highlightText = typeof args.text === "string" ? args.text : void 0;
|
|
9006
10687
|
addHighlight(
|
|
9007
10688
|
url,
|
|
9008
10689
|
typeof selector === "string" ? selector : void 0,
|
|
@@ -9015,7 +10696,7 @@ ${truncated}`;
|
|
|
9015
10696
|
return highlightOnPage(
|
|
9016
10697
|
wc,
|
|
9017
10698
|
selector,
|
|
9018
|
-
|
|
10699
|
+
highlightText,
|
|
9019
10700
|
args.label,
|
|
9020
10701
|
args.durationMs,
|
|
9021
10702
|
highlightColor
|
|
@@ -9028,7 +10709,7 @@ ${truncated}`;
|
|
|
9028
10709
|
// --- Speedee System ---
|
|
9029
10710
|
case "flow_start": {
|
|
9030
10711
|
const goal = typeof args.goal === "string" ? args.goal : "";
|
|
9031
|
-
const steps =
|
|
10712
|
+
const steps = coerceStringArray(args.steps) ?? [];
|
|
9032
10713
|
if (!goal || steps.length === 0)
|
|
9033
10714
|
return "Error: goal and steps are required";
|
|
9034
10715
|
const flow = ctx.runtime.startFlow(goal, steps, wc?.getURL());
|
|
@@ -9088,9 +10769,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
9088
10769
|
const hasOverlays = page.overlays.some((o) => o.blocksInteraction);
|
|
9089
10770
|
if (hasOverlays) {
|
|
9090
10771
|
suggestions.push("BLOCKING OVERLAY detected — dismiss it first:");
|
|
9091
|
-
suggestions.push(
|
|
9092
|
-
|
|
9093
|
-
);
|
|
10772
|
+
suggestions.push(" → clear_overlays for stacked modals");
|
|
10773
|
+
suggestions.push(" → or dismiss_popup for a single popup");
|
|
9094
10774
|
suggestions.push("");
|
|
9095
10775
|
}
|
|
9096
10776
|
if (hasPasswordField) {
|
|
@@ -9108,6 +10788,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
9108
10788
|
);
|
|
9109
10789
|
} else if (hasSearchInput && linkCount >= 10) {
|
|
9110
10790
|
suggestions.push("SEARCH RESULTS detected:");
|
|
10791
|
+
suggestions.push(
|
|
10792
|
+
" → inspect_element(index) to inspect one result card"
|
|
10793
|
+
);
|
|
9111
10794
|
suggestions.push(" → click on a result link");
|
|
9112
10795
|
if (hasPagination)
|
|
9113
10796
|
suggestions.push(" → paginate('next') for more results");
|
|
@@ -9145,26 +10828,10 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
9145
10828
|
if (!wc) return "Error: No active tab";
|
|
9146
10829
|
const fields = Array.isArray(args.fields) ? args.fields : [];
|
|
9147
10830
|
if (fields.length === 0) return "Error: No fields provided";
|
|
9148
|
-
const
|
|
9149
|
-
|
|
9150
|
-
const sel = await resolveSelector$1(wc, field.index, field.selector);
|
|
9151
|
-
if (!sel) {
|
|
9152
|
-
results.push(`Skipped: no selector for index=${field.index}`);
|
|
9153
|
-
continue;
|
|
9154
|
-
}
|
|
9155
|
-
const result2 = await setElementValue$1(
|
|
9156
|
-
wc,
|
|
9157
|
-
sel,
|
|
9158
|
-
String(field.value || "")
|
|
9159
|
-
);
|
|
9160
|
-
results.push(result2);
|
|
9161
|
-
}
|
|
10831
|
+
const fillResults = await fillFormFields(wc, fields);
|
|
10832
|
+
const results = fillResults.map((item) => item.result);
|
|
9162
10833
|
if (args.submit) {
|
|
9163
|
-
const firstSel =
|
|
9164
|
-
wc,
|
|
9165
|
-
fields[0]?.index,
|
|
9166
|
-
fields[0]?.selector
|
|
9167
|
-
);
|
|
10834
|
+
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
9168
10835
|
if (firstSel) {
|
|
9169
10836
|
const beforeUrl = wc.getURL();
|
|
9170
10837
|
const submitResult = await submitForm$1(wc, { selector: firstSel });
|
|
@@ -9264,6 +10931,36 @@ ${steps.join("\n")}`;
|
|
|
9264
10931
|
if (!wc) return "Error: No active tab";
|
|
9265
10932
|
const query = String(args.query || "");
|
|
9266
10933
|
if (!query) return "Error: No search query provided.";
|
|
10934
|
+
const queryLower = query.toLowerCase().trim();
|
|
10935
|
+
const buttonLikePatterns = [
|
|
10936
|
+
"add to cart",
|
|
10937
|
+
"add to bag",
|
|
10938
|
+
"add to basket",
|
|
10939
|
+
"buy now",
|
|
10940
|
+
"buy it now",
|
|
10941
|
+
"purchase",
|
|
10942
|
+
"continue shopping",
|
|
10943
|
+
"keep shopping",
|
|
10944
|
+
"view cart",
|
|
10945
|
+
"view bag",
|
|
10946
|
+
"view basket",
|
|
10947
|
+
"go to cart",
|
|
10948
|
+
"go to checkout",
|
|
10949
|
+
"checkout",
|
|
10950
|
+
"check out",
|
|
10951
|
+
"proceed to checkout",
|
|
10952
|
+
"place order",
|
|
10953
|
+
"submit",
|
|
10954
|
+
"subscribe",
|
|
10955
|
+
"sign up",
|
|
10956
|
+
"sign in",
|
|
10957
|
+
"log in",
|
|
10958
|
+
"register",
|
|
10959
|
+
"continue"
|
|
10960
|
+
];
|
|
10961
|
+
if (buttonLikePatterns.some((p) => queryLower.includes(p))) {
|
|
10962
|
+
return `Error: "${query}" looks like a button label, not a search query. Use the click tool to interact with this element instead.`;
|
|
10963
|
+
}
|
|
9267
10964
|
const searchInfo = args.selector ? { selector: args.selector, formAction: null, formMethod: null } : await executePageScript(
|
|
9268
10965
|
wc,
|
|
9269
10966
|
`
|
|
@@ -9634,8 +11331,11 @@ Instructions:
|
|
|
9634
11331
|
- Prefer select_option for dropdowns and submit_form for forms instead of guessing with clicks.
|
|
9635
11332
|
- After navigating to a new site, DO NOT call read_page immediately. Instead, act on what you already know: use the search tool to search the site, type_text to enter queries in search bars, or click on known navigation patterns. You know what major sites look like — use that knowledge. Only call read_page if you're genuinely stuck and need to discover unfamiliar page structure.
|
|
9636
11333
|
- The page brief you start with is intentionally sparse. It is optimized for navigation speed, not completeness.
|
|
11334
|
+
- When you only need detail on one product/result/card/form section, use inspect_element instead of reading the page.
|
|
9637
11335
|
- Escalate page reads progressively: read_page(mode="visible_only"), read_page(mode="results_only"), read_page(mode="forms_only"), read_page(mode="summary"), or read_page(mode="text_only") depending on what you need.
|
|
9638
11336
|
- Use read_page(mode="debug") only as a last resort when the narrower modes are insufficient.
|
|
11337
|
+
- VIEWPORT SYNC: Treat scrolling as a real, user-visible browser action. If you say you are going to scroll, call scroll or scroll_to_element so the human sees the page move too.
|
|
11338
|
+
- read_page inspects the page without moving the human-visible viewport. Do not describe read_page as scrolling. If you want more context without changing the user's view, say you're reading the page; if you want the user to follow along lower on the page, actually scroll first.
|
|
9639
11339
|
- After clicking or submitting a form, prefer wait_for on a specific result signal or a narrow read_page mode. Do not jump straight to read_page(mode="debug").
|
|
9640
11340
|
- If the user says they highlighted or selected text, use read_page before falling back to screenshots because it includes active selection and visible unsaved highlights.
|
|
9641
11341
|
- If a page behaves abnormally or key UI fails to load, consider disabling ad blocking for that tab and reloading before retrying.
|
|
@@ -9652,7 +11352,11 @@ Instructions:
|
|
|
9652
11352
|
- USE YOUR KNOWLEDGE: You have broad, practical knowledge about technology, products, cooking, travel, finance, and countless other domains. When the user asks for recommendations, GIVE them — don't deflect to Reddit, YouTubers, or other sources. You know enough to recommend PC parts, suggest restaurants, pick a good laptop, or advise on most consumer decisions. Make a clear recommendation, explain your reasoning briefly, and then execute. If there's genuine ambiguity (e.g. AMD vs Intel is preference-dependent), state your pick and why, then ask only the questions that would actually change your recommendation. Never refuse a recommendation by claiming you're "not an expert" — the user chose to ask you, so help them.
|
|
9653
11353
|
- NEVER USE EMOJIS unless the user uses them first.`;
|
|
9654
11354
|
const actionCtx = { tabManager, runtime };
|
|
9655
|
-
const contextualTools = pruneToolsForContext(
|
|
11355
|
+
const contextualTools = pruneToolsForContext(
|
|
11356
|
+
AGENT_TOOLS,
|
|
11357
|
+
pageType,
|
|
11358
|
+
query
|
|
11359
|
+
);
|
|
9656
11360
|
await provider.streamAgentQuery(
|
|
9657
11361
|
systemPrompt,
|
|
9658
11362
|
query,
|
|
@@ -10777,16 +12481,42 @@ async function clickResolvedSelector(wc, selector) {
|
|
|
10777
12481
|
if (selector.startsWith("__vessel_idx:")) {
|
|
10778
12482
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
10779
12483
|
const beforeUrl2 = wc.getURL();
|
|
12484
|
+
const idxLabel = await wc.executeJavaScript(
|
|
12485
|
+
`window.__vessel?.getElementText?.(${idx}) || ""`
|
|
12486
|
+
);
|
|
12487
|
+
if (typeof idxLabel === "string" && isAddToCartText(idxLabel) && isDuplicateCartClick(beforeUrl2, idxLabel)) {
|
|
12488
|
+
return `Blocked: "${idxLabel}" 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).`;
|
|
12489
|
+
}
|
|
10780
12490
|
const result = await wc.executeJavaScript(
|
|
10781
12491
|
`window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`
|
|
10782
12492
|
);
|
|
10783
12493
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
12494
|
+
if (typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
12495
|
+
recordCartClick(beforeUrl2, idxLabel);
|
|
12496
|
+
}
|
|
10784
12497
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
10785
12498
|
const afterUrl2 = wc.getURL();
|
|
10786
|
-
|
|
12499
|
+
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
12500
|
+
const overlayHint2 = await detectPostClickOverlay(wc);
|
|
12501
|
+
if (!overlayHint2) return result;
|
|
12502
|
+
const dialogActions = typeof idxLabel === "string" && isAddToCartText(idxLabel) ? await getCartDialogActions(wc) : null;
|
|
12503
|
+
const actionsSuffix = dialogActions ? `
|
|
12504
|
+
${dialogActions}
|
|
12505
|
+
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
12506
|
+
return `${result}
|
|
12507
|
+
${overlayHint2}${actionsSuffix}`;
|
|
10787
12508
|
}
|
|
10788
12509
|
if (selector.includes(" >>> ")) {
|
|
10789
12510
|
const beforeUrl2 = wc.getURL();
|
|
12511
|
+
const shadowLabel = await wc.executeJavaScript(`
|
|
12512
|
+
(function() {
|
|
12513
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
12514
|
+
return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
|
|
12515
|
+
})()
|
|
12516
|
+
`);
|
|
12517
|
+
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel) && isDuplicateCartClick(beforeUrl2, shadowLabel)) {
|
|
12518
|
+
return `Blocked: "${shadowLabel}" 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).`;
|
|
12519
|
+
}
|
|
10790
12520
|
const result = await wc.executeJavaScript(`
|
|
10791
12521
|
(function() {
|
|
10792
12522
|
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
@@ -10796,19 +12526,45 @@ async function clickResolvedSelector(wc, selector) {
|
|
|
10796
12526
|
})()
|
|
10797
12527
|
`);
|
|
10798
12528
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
12529
|
+
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
12530
|
+
recordCartClick(beforeUrl2, shadowLabel);
|
|
12531
|
+
}
|
|
10799
12532
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
10800
12533
|
const afterUrl2 = wc.getURL();
|
|
10801
|
-
|
|
12534
|
+
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
12535
|
+
const overlayHint2 = await detectPostClickOverlay(wc);
|
|
12536
|
+
if (!overlayHint2) return result;
|
|
12537
|
+
const dialogActions = typeof shadowLabel === "string" && isAddToCartText(shadowLabel) ? await getCartDialogActions(wc) : null;
|
|
12538
|
+
const actionsSuffix = dialogActions ? `
|
|
12539
|
+
${dialogActions}
|
|
12540
|
+
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
12541
|
+
return `${result}
|
|
12542
|
+
${overlayHint2}${actionsSuffix}`;
|
|
10802
12543
|
}
|
|
10803
12544
|
const beforeUrl = wc.getURL();
|
|
10804
12545
|
const elInfo = await describeElementForClick(wc, selector);
|
|
10805
12546
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
12547
|
+
const cartMatch = isAddToCartText(elInfo.text);
|
|
12548
|
+
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
12549
|
+
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).`;
|
|
12550
|
+
}
|
|
12551
|
+
if (!cartMatch) {
|
|
12552
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
12553
|
+
if (dialogActions) {
|
|
12554
|
+
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
12555
|
+
${dialogActions}
|
|
12556
|
+
Click one of these dialog actions instead.`;
|
|
12557
|
+
}
|
|
12558
|
+
}
|
|
10806
12559
|
if (elInfo.href) {
|
|
10807
12560
|
const validation = await validateLinkDestination(elInfo.href);
|
|
10808
12561
|
if (validation.status === "dead") {
|
|
10809
12562
|
return formatDeadLinkMessage(elInfo.text, validation);
|
|
10810
12563
|
}
|
|
10811
12564
|
}
|
|
12565
|
+
if (cartMatch) {
|
|
12566
|
+
recordCartClick(beforeUrl, elInfo.text);
|
|
12567
|
+
}
|
|
10812
12568
|
const clickText = `Clicked: ${elInfo.text}`;
|
|
10813
12569
|
const clickResult = await clickElement(wc, selector);
|
|
10814
12570
|
if (clickResult.startsWith("Error:")) return clickResult;
|
|
@@ -10817,6 +12573,18 @@ async function clickResolvedSelector(wc, selector) {
|
|
|
10817
12573
|
if (afterUrl !== beforeUrl) {
|
|
10818
12574
|
return `${clickText} -> ${afterUrl}`;
|
|
10819
12575
|
}
|
|
12576
|
+
const overlayHint = await detectPostClickOverlay(wc);
|
|
12577
|
+
if (overlayHint) {
|
|
12578
|
+
const dialogActions = cartMatch ? await getCartDialogActions(wc) : null;
|
|
12579
|
+
const actionsSuffix = dialogActions ? `
|
|
12580
|
+
${dialogActions}
|
|
12581
|
+
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
12582
|
+
return `${clickText} (${clickResult})
|
|
12583
|
+
${overlayHint}${actionsSuffix}`;
|
|
12584
|
+
}
|
|
12585
|
+
if (cartMatch) {
|
|
12586
|
+
return `${clickText} (${clickResult})`;
|
|
12587
|
+
}
|
|
10820
12588
|
const activationResult = await activateElement(wc, selector);
|
|
10821
12589
|
if (!activationResult.startsWith("Error:")) {
|
|
10822
12590
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
@@ -10825,13 +12593,239 @@ async function clickResolvedSelector(wc, selector) {
|
|
|
10825
12593
|
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
10826
12594
|
}
|
|
10827
12595
|
}
|
|
12596
|
+
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
12597
|
+
if (postActivationOverlayHint) {
|
|
12598
|
+
return `${clickText} (${clickResult})
|
|
12599
|
+
${postActivationOverlayHint}`;
|
|
12600
|
+
}
|
|
10828
12601
|
return `${clickText} (${clickResult})`;
|
|
10829
12602
|
}
|
|
12603
|
+
async function getCartDialogActions(wc) {
|
|
12604
|
+
const result = await wc.executeJavaScript(`
|
|
12605
|
+
(function() {
|
|
12606
|
+
function isVisible(el) {
|
|
12607
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
12608
|
+
const style = getComputedStyle(el);
|
|
12609
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
12610
|
+
const rect = el.getBoundingClientRect();
|
|
12611
|
+
return rect.width >= 20 && rect.height >= 10;
|
|
12612
|
+
}
|
|
12613
|
+
|
|
12614
|
+
function findDialogRoot() {
|
|
12615
|
+
const selectors = [
|
|
12616
|
+
'[data-test="basket-flyout"]',
|
|
12617
|
+
'[role="dialog"]',
|
|
12618
|
+
'dialog[open]',
|
|
12619
|
+
'[role="alertdialog"]',
|
|
12620
|
+
'[aria-modal="true"]',
|
|
12621
|
+
];
|
|
12622
|
+
for (const selector of selectors) {
|
|
12623
|
+
const nodes = document.querySelectorAll(selector);
|
|
12624
|
+
for (const node of nodes) {
|
|
12625
|
+
if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
|
|
12626
|
+
const text = (node.textContent || "").slice(0, 800).toLowerCase();
|
|
12627
|
+
const cartSignals = [
|
|
12628
|
+
"added to cart", "added to bag", "added to basket",
|
|
12629
|
+
"item added", "your basket", "your cart", "your bag",
|
|
12630
|
+
"view basket", "view cart", "continue shopping",
|
|
12631
|
+
];
|
|
12632
|
+
if (cartSignals.some((signal) => text.includes(signal))) {
|
|
12633
|
+
return node;
|
|
12634
|
+
}
|
|
12635
|
+
}
|
|
12636
|
+
}
|
|
12637
|
+
return null;
|
|
12638
|
+
}
|
|
12639
|
+
|
|
12640
|
+
const dialog = findDialogRoot();
|
|
12641
|
+
if (!dialog) return { found: false, actions: [] };
|
|
12642
|
+
|
|
12643
|
+
const actions = [];
|
|
12644
|
+
dialog.querySelectorAll('button, a[href], [role="button"]').forEach((el) => {
|
|
12645
|
+
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
12646
|
+
const label = (el.getAttribute("aria-label") || el.textContent || "").trim().slice(0, 80);
|
|
12647
|
+
if (!label || label.length < 2) return;
|
|
12648
|
+
const href = el.getAttribute("href") || "";
|
|
12649
|
+
const selector = el.id ? "#" + el.id
|
|
12650
|
+
: el.getAttribute("data-test") ? '[data-test="' + el.getAttribute("data-test") + '"]'
|
|
12651
|
+
: el.getAttribute("aria-label") ? '[aria-label="' + el.getAttribute("aria-label") + '"]'
|
|
12652
|
+
: null;
|
|
12653
|
+
if (selector) {
|
|
12654
|
+
actions.push({ label: label, href: href, selector: selector });
|
|
12655
|
+
}
|
|
12656
|
+
});
|
|
12657
|
+
|
|
12658
|
+
return {
|
|
12659
|
+
found: true,
|
|
12660
|
+
actions: actions.map((action) =>
|
|
12661
|
+
'- "' + action.label + '"' +
|
|
12662
|
+
(action.href ? ' -> ' + action.href : "") +
|
|
12663
|
+
(action.selector ? ' (selector: ' + action.selector + ')' : "")
|
|
12664
|
+
),
|
|
12665
|
+
};
|
|
12666
|
+
})()
|
|
12667
|
+
`);
|
|
12668
|
+
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
12669
|
+
return null;
|
|
12670
|
+
}
|
|
12671
|
+
if (!("actions" in result) || !Array.isArray(result.actions) || result.actions.length === 0) {
|
|
12672
|
+
return null;
|
|
12673
|
+
}
|
|
12674
|
+
return `Available dialog actions:
|
|
12675
|
+
${result.actions.join("\n")}`;
|
|
12676
|
+
}
|
|
12677
|
+
async function detectPostClickOverlay(wc) {
|
|
12678
|
+
const result = await wc.executeJavaScript(`
|
|
12679
|
+
(function() {
|
|
12680
|
+
var vw = window.innerWidth || document.documentElement.clientWidth;
|
|
12681
|
+
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
12682
|
+
var vpArea = Math.max(1, vw * vh);
|
|
12683
|
+
|
|
12684
|
+
function isVisible(el) {
|
|
12685
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
12686
|
+
var style = getComputedStyle(el);
|
|
12687
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
12688
|
+
return el.getBoundingClientRect().width > 0;
|
|
12689
|
+
}
|
|
12690
|
+
|
|
12691
|
+
function hasFixedAncestor(el) {
|
|
12692
|
+
var current = el.parentElement;
|
|
12693
|
+
while (current && current !== document.body) {
|
|
12694
|
+
var position = getComputedStyle(current).position;
|
|
12695
|
+
if (position === "fixed" || position === "sticky") return true;
|
|
12696
|
+
current = current.parentElement;
|
|
12697
|
+
}
|
|
12698
|
+
return false;
|
|
12699
|
+
}
|
|
12700
|
+
|
|
12701
|
+
function effectiveZ(el) {
|
|
12702
|
+
var current = el;
|
|
12703
|
+
while (current && current !== document.body) {
|
|
12704
|
+
var z = parseInt(getComputedStyle(current).zIndex, 10);
|
|
12705
|
+
if (z > 0) return z;
|
|
12706
|
+
current = current.parentElement;
|
|
12707
|
+
}
|
|
12708
|
+
return 0;
|
|
12709
|
+
}
|
|
12710
|
+
|
|
12711
|
+
function touchesViewportEdge(rect) {
|
|
12712
|
+
return rect.left <= 24 || rect.top <= 24 ||
|
|
12713
|
+
rect.right >= vw - 24 || rect.bottom >= vh - 24;
|
|
12714
|
+
}
|
|
12715
|
+
|
|
12716
|
+
var cartPhrases = [
|
|
12717
|
+
"added to cart", "added to bag", "added to basket",
|
|
12718
|
+
"added to your cart", "added to your bag", "added to your basket",
|
|
12719
|
+
];
|
|
12720
|
+
var cartActions = [
|
|
12721
|
+
"view cart", "go to cart", "view basket", "go to basket",
|
|
12722
|
+
"continue shopping", "keep shopping", "checkout",
|
|
12723
|
+
];
|
|
12724
|
+
|
|
12725
|
+
var selectors = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"], [data-test="basket-flyout"]';
|
|
12726
|
+
var candidates = document.querySelectorAll(selectors);
|
|
12727
|
+
var hit = null;
|
|
12728
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
12729
|
+
if (isVisible(candidates[i])) {
|
|
12730
|
+
hit = candidates[i];
|
|
12731
|
+
break;
|
|
12732
|
+
}
|
|
12733
|
+
}
|
|
12734
|
+
|
|
12735
|
+
if (!hit) {
|
|
12736
|
+
var elements = document.querySelectorAll("*");
|
|
12737
|
+
for (var j = 0; j < elements.length; j++) {
|
|
12738
|
+
var el = elements[j];
|
|
12739
|
+
if (!(el instanceof HTMLElement) || !isVisible(el)) continue;
|
|
12740
|
+
var style = getComputedStyle(el);
|
|
12741
|
+
var position = style.position;
|
|
12742
|
+
var isFixed = position === "fixed" || position === "sticky";
|
|
12743
|
+
var isAbsolute = position === "absolute";
|
|
12744
|
+
if (!isFixed && !isAbsolute) continue;
|
|
12745
|
+
if (isAbsolute && !hasFixedAncestor(el)) continue;
|
|
12746
|
+
if (effectiveZ(el) < 5) continue;
|
|
12747
|
+
var rect = el.getBoundingClientRect();
|
|
12748
|
+
var areaRatio = (rect.width * rect.height) / vpArea;
|
|
12749
|
+
if (rect.width >= 160 && rect.height >= 100 && areaRatio >= 0.05 && touchesViewportEdge(rect)) {
|
|
12750
|
+
hit = el;
|
|
12751
|
+
break;
|
|
12752
|
+
}
|
|
12753
|
+
}
|
|
12754
|
+
}
|
|
12755
|
+
|
|
12756
|
+
if (!hit) return { found: false, label: "", cartLike: false };
|
|
12757
|
+
var text = (hit.textContent || "").slice(0, 800).toLowerCase();
|
|
12758
|
+
var cartLike = cartPhrases.concat(cartActions).some(function(signal) {
|
|
12759
|
+
return text.indexOf(signal) !== -1;
|
|
12760
|
+
});
|
|
12761
|
+
var heading = hit.querySelector("h1,h2,h3,h4");
|
|
12762
|
+
var label = (hit.getAttribute("aria-label") || (heading && heading.textContent) || "").trim().slice(0, 80);
|
|
12763
|
+
return { found: true, label: label, cartLike: cartLike };
|
|
12764
|
+
})()
|
|
12765
|
+
`);
|
|
12766
|
+
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
12767
|
+
return null;
|
|
12768
|
+
}
|
|
12769
|
+
const label = typeof result.label === "string" && result.label ? ` ("${result.label}")` : "";
|
|
12770
|
+
if ("cartLike" in result && result.cartLike) {
|
|
12771
|
+
return `A cart confirmation dialog appeared${label}. Call read_page to see available actions — do not click Add to Cart again.`;
|
|
12772
|
+
}
|
|
12773
|
+
return `A dialog or overlay appeared${label}. Call read_page to see available actions.`;
|
|
12774
|
+
}
|
|
10830
12775
|
async function dismissPopup(wc) {
|
|
10831
12776
|
const before = await extractContent(wc);
|
|
10832
12777
|
const initialBlocking = before.overlays.filter(
|
|
10833
12778
|
(overlay) => overlay.blocksInteraction
|
|
10834
12779
|
).length;
|
|
12780
|
+
if (initialBlocking > 0) {
|
|
12781
|
+
const overlayText = before.overlays.map(
|
|
12782
|
+
(o) => [o.label, o.text].filter(Boolean).join(" ")
|
|
12783
|
+
).join(" ").toLowerCase();
|
|
12784
|
+
const cartSignals = [
|
|
12785
|
+
"added to cart",
|
|
12786
|
+
"added to bag",
|
|
12787
|
+
"added to basket",
|
|
12788
|
+
"item added",
|
|
12789
|
+
"items in your basket",
|
|
12790
|
+
"items in your cart",
|
|
12791
|
+
"items in your bag",
|
|
12792
|
+
"your basket",
|
|
12793
|
+
"your cart",
|
|
12794
|
+
"your bag",
|
|
12795
|
+
"view basket",
|
|
12796
|
+
"view cart",
|
|
12797
|
+
"continue shopping"
|
|
12798
|
+
];
|
|
12799
|
+
if (cartSignals.some((s) => overlayText.includes(s))) {
|
|
12800
|
+
const continueResult = await wc.executeJavaScript(`
|
|
12801
|
+
(function() {
|
|
12802
|
+
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
12803
|
+
if (!dialog) return "Error: dialog not found";
|
|
12804
|
+
var buttons = dialog.querySelectorAll('button, a[href], [role="button"]');
|
|
12805
|
+
var continueBtn = null;
|
|
12806
|
+
var viewCartBtn = null;
|
|
12807
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
12808
|
+
var label = (buttons[i].getAttribute('aria-label') || buttons[i].textContent || '').trim().toLowerCase();
|
|
12809
|
+
if (/continue shopping|keep shopping/.test(label)) { continueBtn = buttons[i]; break; }
|
|
12810
|
+
if (/view (basket|cart|bag)|checkout/.test(label) && !viewCartBtn) { viewCartBtn = buttons[i]; }
|
|
12811
|
+
}
|
|
12812
|
+
var target = continueBtn || viewCartBtn;
|
|
12813
|
+
if (!target) return "Error: no dialog action found";
|
|
12814
|
+
var actionLabel = (target.getAttribute('aria-label') || target.textContent || '').trim();
|
|
12815
|
+
if (target.tagName === 'A' && target.href) {
|
|
12816
|
+
window.location.href = target.href;
|
|
12817
|
+
return "Clicked: " + actionLabel + " -> " + target.href;
|
|
12818
|
+
}
|
|
12819
|
+
target.click();
|
|
12820
|
+
return "Clicked: " + actionLabel;
|
|
12821
|
+
})()
|
|
12822
|
+
`);
|
|
12823
|
+
if (typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
12824
|
+
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
12825
|
+
}
|
|
12826
|
+
return "Cannot dismiss: this is a cart confirmation dialog. Item is in your cart. Use read_page to see dialog actions (e.g. View Basket, Continue Shopping) and click one of them instead.";
|
|
12827
|
+
}
|
|
12828
|
+
}
|
|
10835
12829
|
const initialDormant = before.dormantOverlays.length;
|
|
10836
12830
|
const candidates = await wc.executeJavaScript(`
|
|
10837
12831
|
(function() {
|
|
@@ -11077,7 +13071,13 @@ async function getPostActionState(tabManager, name) {
|
|
|
11077
13071
|
"reload",
|
|
11078
13072
|
"press_key"
|
|
11079
13073
|
];
|
|
11080
|
-
const interactActions = [
|
|
13074
|
+
const interactActions = [
|
|
13075
|
+
"type",
|
|
13076
|
+
"type_text",
|
|
13077
|
+
"select_option",
|
|
13078
|
+
"hover",
|
|
13079
|
+
"focus"
|
|
13080
|
+
];
|
|
11081
13081
|
const tabActions = ["create_tab", "switch_tab", "close_tab"];
|
|
11082
13082
|
if (navActions.includes(name)) {
|
|
11083
13083
|
let warning = "";
|
|
@@ -11260,7 +13260,8 @@ async function hoverElement(wc, selector) {
|
|
|
11260
13260
|
if ("error" in pos && typeof pos.error === "string") return pos.error;
|
|
11261
13261
|
const x = typeof pos.x === "number" ? pos.x : null;
|
|
11262
13262
|
const y = typeof pos.y === "number" ? pos.y : null;
|
|
11263
|
-
if (x == null || y == null)
|
|
13263
|
+
if (x == null || y == null)
|
|
13264
|
+
return "Error: Could not resolve hover coordinates";
|
|
11264
13265
|
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
11265
13266
|
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
11266
13267
|
return `Hovered: ${label}`;
|
|
@@ -11479,7 +13480,10 @@ async function waitForCondition(wc, text, selector, timeoutMs) {
|
|
|
11479
13480
|
const expectedText = (text || "").trim();
|
|
11480
13481
|
const expectedSelector = (selector || "").trim();
|
|
11481
13482
|
if (!expectedText && !expectedSelector) {
|
|
11482
|
-
return JSON.stringify({
|
|
13483
|
+
return JSON.stringify({
|
|
13484
|
+
matched: false,
|
|
13485
|
+
error: "wait_for requires text or selector"
|
|
13486
|
+
});
|
|
11483
13487
|
}
|
|
11484
13488
|
if (wc.isLoading()) {
|
|
11485
13489
|
await waitForLoad(wc, Math.min(effectiveTimeout, 5e3));
|
|
@@ -11503,13 +13507,26 @@ async function waitForCondition(wc, text, selector, timeoutMs) {
|
|
|
11503
13507
|
`);
|
|
11504
13508
|
const elapsedMs2 = Date.now() - startedAt;
|
|
11505
13509
|
if (result === "selector") {
|
|
11506
|
-
return JSON.stringify({
|
|
13510
|
+
return JSON.stringify({
|
|
13511
|
+
matched: true,
|
|
13512
|
+
type: "selector",
|
|
13513
|
+
value: expectedSelector,
|
|
13514
|
+
elapsed_ms: elapsedMs2
|
|
13515
|
+
});
|
|
11507
13516
|
}
|
|
11508
13517
|
if (result === "text") {
|
|
11509
|
-
return JSON.stringify({
|
|
13518
|
+
return JSON.stringify({
|
|
13519
|
+
matched: true,
|
|
13520
|
+
type: "text",
|
|
13521
|
+
value: expectedText.slice(0, 80),
|
|
13522
|
+
elapsed_ms: elapsedMs2
|
|
13523
|
+
});
|
|
11510
13524
|
}
|
|
11511
13525
|
if (typeof result === "string" && result.startsWith("invalid_selector:")) {
|
|
11512
|
-
return JSON.stringify({
|
|
13526
|
+
return JSON.stringify({
|
|
13527
|
+
matched: false,
|
|
13528
|
+
error: `Invalid selector "${expectedSelector}" — ${result.slice(17)}`
|
|
13529
|
+
});
|
|
11513
13530
|
}
|
|
11514
13531
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
11515
13532
|
}
|
|
@@ -11653,7 +13670,12 @@ function registerTools(server, tabManager, runtime) {
|
|
|
11653
13670
|
pageType,
|
|
11654
13671
|
pageUrl,
|
|
11655
13672
|
pageTitle,
|
|
11656
|
-
recommended: scored.filter((t) => t.score <= 20).map(({ name, title, description, relevance }) => ({
|
|
13673
|
+
recommended: scored.filter((t) => t.score <= 20).map(({ name, title, description, relevance }) => ({
|
|
13674
|
+
name,
|
|
13675
|
+
title,
|
|
13676
|
+
description,
|
|
13677
|
+
relevance
|
|
13678
|
+
})),
|
|
11657
13679
|
available: scored.filter((t) => t.score > 20).map(({ name, title, relevance }) => ({ name, title, relevance }))
|
|
11658
13680
|
};
|
|
11659
13681
|
return {
|
|
@@ -11685,8 +13707,12 @@ function registerTools(server, tabManager, runtime) {
|
|
|
11685
13707
|
description: "Publish or stream agent reasoning/status text into Vessel's in-browser transcript monitor. Intended for external harnesses that want to mirror live thinking into the browser UI.",
|
|
11686
13708
|
inputSchema: {
|
|
11687
13709
|
text: zod.z.string().describe("Transcript text chunk to publish"),
|
|
11688
|
-
stream_id: zod.z.string().optional().describe(
|
|
11689
|
-
|
|
13710
|
+
stream_id: zod.z.string().optional().describe(
|
|
13711
|
+
"Stable stream ID for incremental updates to the same entry"
|
|
13712
|
+
),
|
|
13713
|
+
mode: zod.z.enum(["append", "replace", "final"]).optional().describe(
|
|
13714
|
+
"append (default), replace current stream text, or mark the stream final"
|
|
13715
|
+
),
|
|
11690
13716
|
kind: zod.z.enum(["thinking", "message", "status"]).optional().describe("Visual style for the transcript entry"),
|
|
11691
13717
|
title: zod.z.string().optional().describe("Optional short label such as Plan, Search, or Summary")
|
|
11692
13718
|
}
|
|
@@ -11737,7 +13763,9 @@ function registerTools(server, tabManager, runtime) {
|
|
|
11737
13763
|
];
|
|
11738
13764
|
async function buildExtractResponse(pageContent, mode, adBlockingEnabled, wc) {
|
|
11739
13765
|
const adBlockLine = `**Ad Blocking:** ${adBlockingEnabled ? "On" : "Off"}`;
|
|
11740
|
-
const savedHighlights = getHighlightsForUrl(
|
|
13766
|
+
const savedHighlights = getHighlightsForUrl(
|
|
13767
|
+
pageContent.url
|
|
13768
|
+
);
|
|
11741
13769
|
const liveSelectionSection = wc ? formatLiveSelectionSection(
|
|
11742
13770
|
await captureLiveHighlightSnapshot(wc, savedHighlights)
|
|
11743
13771
|
) : null;
|
|
@@ -11854,10 +13882,18 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
11854
13882
|
async ({ url }) => {
|
|
11855
13883
|
const tab = tabManager.getActiveTab();
|
|
11856
13884
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13885
|
+
const preCheck = await validateLinkDestination(url);
|
|
13886
|
+
if (preCheck.status === "dead") {
|
|
13887
|
+
return asTextResponse(
|
|
13888
|
+
`Navigation blocked: ${url} returned ${preCheck.detail || "dead link"}. Try a different URL or go back and choose another link.`
|
|
13889
|
+
);
|
|
13890
|
+
}
|
|
11857
13891
|
return withAction(runtime, tabManager, "navigate", { url }, async () => {
|
|
11858
13892
|
const id = tabManager.getActiveTabId();
|
|
11859
13893
|
tabManager.navigateTab(id, url);
|
|
11860
|
-
const { httpStatus } = await waitForLoadWithStatus(
|
|
13894
|
+
const { httpStatus } = await waitForLoadWithStatus(
|
|
13895
|
+
tab.view.webContents
|
|
13896
|
+
);
|
|
11861
13897
|
const finalUrl = tab.view.webContents.getURL();
|
|
11862
13898
|
const statusNote = httpStatus !== null && httpStatus >= 400 ? ` [HTTP ${httpStatus} — page may be missing or unavailable, consider navigating back and trying a different link]` : "";
|
|
11863
13899
|
return `Navigated to ${finalUrl}${statusNote}`;
|
|
@@ -11927,7 +13963,9 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
11927
13963
|
const pageContent = await extractContent(tab.view.webContents);
|
|
11928
13964
|
const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
|
|
11929
13965
|
const entities = (pageContent.structuredData ?? []).filter(
|
|
11930
|
-
(entity) => requestedType ? entity.types.some(
|
|
13966
|
+
(entity) => requestedType ? entity.types.some(
|
|
13967
|
+
(entry) => entry.toLowerCase() === requestedType
|
|
13968
|
+
) : true
|
|
11931
13969
|
);
|
|
11932
13970
|
const sourceCounts = {
|
|
11933
13971
|
json_ld: pageContent.jsonLd?.length ?? 0,
|
|
@@ -12166,7 +14204,9 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
12166
14204
|
})()
|
|
12167
14205
|
`);
|
|
12168
14206
|
if (!result || typeof result !== "object") {
|
|
12169
|
-
return asTextResponse(
|
|
14207
|
+
return asTextResponse(
|
|
14208
|
+
"Error: Element text extraction returned no result"
|
|
14209
|
+
);
|
|
12170
14210
|
}
|
|
12171
14211
|
if ("error" in result && typeof result.error === "string") {
|
|
12172
14212
|
return asTextResponse(`Error: ${result.error}`);
|
|
@@ -12216,11 +14256,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
12216
14256
|
return "Error: No index or selector provided";
|
|
12217
14257
|
}
|
|
12218
14258
|
if (mode === "keystroke") {
|
|
12219
|
-
return typeKeystroke(
|
|
12220
|
-
tab.view.webContents,
|
|
12221
|
-
resolvedSelector,
|
|
12222
|
-
text
|
|
12223
|
-
);
|
|
14259
|
+
return typeKeystroke(tab.view.webContents, resolvedSelector, text);
|
|
12224
14260
|
}
|
|
12225
14261
|
return setElementValue(tab.view.webContents, resolvedSelector, text);
|
|
12226
14262
|
}
|
|
@@ -12259,11 +14295,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
12259
14295
|
return "Error: No index or selector provided";
|
|
12260
14296
|
}
|
|
12261
14297
|
if (mode === "keystroke") {
|
|
12262
|
-
return typeKeystroke(
|
|
12263
|
-
tab.view.webContents,
|
|
12264
|
-
resolvedSelector,
|
|
12265
|
-
text
|
|
12266
|
-
);
|
|
14298
|
+
return typeKeystroke(tab.view.webContents, resolvedSelector, text);
|
|
12267
14299
|
}
|
|
12268
14300
|
return setElementValue(tab.view.webContents, resolvedSelector, text);
|
|
12269
14301
|
}
|
|
@@ -12368,7 +14400,9 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
12368
14400
|
description: "Scroll the page up or down.",
|
|
12369
14401
|
inputSchema: {
|
|
12370
14402
|
direction: zod.z.enum(["up", "down"]).describe("Scroll direction"),
|
|
12371
|
-
amount:
|
|
14403
|
+
amount: optionalNumberLikeSchema().describe(
|
|
14404
|
+
"Pixels to scroll (default 500)"
|
|
14405
|
+
)
|
|
12372
14406
|
}
|
|
12373
14407
|
},
|
|
12374
14408
|
async ({ direction, amount }) => {
|
|
@@ -12380,7 +14414,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
12380
14414
|
"scroll",
|
|
12381
14415
|
{ direction, amount },
|
|
12382
14416
|
async () => {
|
|
12383
|
-
const pixels = amount
|
|
14417
|
+
const pixels = coerceOptionalNumber(amount) ?? 500;
|
|
12384
14418
|
const dir = direction === "up" ? -pixels : pixels;
|
|
12385
14419
|
const result = await scrollPage(tab.view.webContents, dir);
|
|
12386
14420
|
return `Scrolled ${direction} by ${pixels}px (moved ${Math.abs(result.movedY)}px, now at y=${Math.round(result.afterY)})`;
|
|
@@ -12406,6 +14440,32 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
12406
14440
|
);
|
|
12407
14441
|
}
|
|
12408
14442
|
);
|
|
14443
|
+
server.registerTool(
|
|
14444
|
+
"vessel_clear_overlays",
|
|
14445
|
+
{
|
|
14446
|
+
title: "Clear Overlays",
|
|
14447
|
+
description: "Work through blocking overlays and modals until the page is unblocked, using overlay-specific heuristics for consent banners and radio-selection dialogs.",
|
|
14448
|
+
inputSchema: {
|
|
14449
|
+
strategy: zod.z.enum(["auto", "interactive"]).optional().describe(
|
|
14450
|
+
'How aggressively to clear overlays. "auto" uses heuristics; "interactive" stops earlier when human judgment may be needed.'
|
|
14451
|
+
)
|
|
14452
|
+
}
|
|
14453
|
+
},
|
|
14454
|
+
async ({ strategy }) => {
|
|
14455
|
+
const tab = tabManager.getActiveTab();
|
|
14456
|
+
if (!tab) return asTextResponse("Error: No active tab");
|
|
14457
|
+
return withAction(
|
|
14458
|
+
runtime,
|
|
14459
|
+
tabManager,
|
|
14460
|
+
"clear_overlays",
|
|
14461
|
+
{ strategy: strategy || "auto" },
|
|
14462
|
+
async () => clearOverlays(
|
|
14463
|
+
tab.view.webContents,
|
|
14464
|
+
strategy === "interactive" ? "interactive" : "auto"
|
|
14465
|
+
)
|
|
14466
|
+
);
|
|
14467
|
+
}
|
|
14468
|
+
);
|
|
12409
14469
|
server.registerTool(
|
|
12410
14470
|
"vessel_wait_for",
|
|
12411
14471
|
{
|
|
@@ -12667,8 +14727,14 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
12667
14727
|
`Error capturing screenshot: ${screenshot.error}`
|
|
12668
14728
|
);
|
|
12669
14729
|
}
|
|
12670
|
-
const screenshotPath = path$1.join(
|
|
12671
|
-
|
|
14730
|
+
const screenshotPath = path$1.join(
|
|
14731
|
+
os.tmpdir(),
|
|
14732
|
+
`vessel_screenshot_${Date.now()}.png`
|
|
14733
|
+
);
|
|
14734
|
+
fs$1.writeFileSync(
|
|
14735
|
+
screenshotPath,
|
|
14736
|
+
Buffer.from(screenshot.base64, "base64")
|
|
14737
|
+
);
|
|
12672
14738
|
return {
|
|
12673
14739
|
content: [
|
|
12674
14740
|
{
|
|
@@ -12699,7 +14765,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
12699
14765
|
inputSchema: {
|
|
12700
14766
|
index: zod.z.number().optional().describe("Element index from extracted content to highlight"),
|
|
12701
14767
|
selector: zod.z.string().optional().describe("CSS selector of element to highlight"),
|
|
12702
|
-
text:
|
|
14768
|
+
text: normalizedOptionalStringSchema().describe(
|
|
12703
14769
|
"Text to find and highlight on the page (highlights all occurrences)"
|
|
12704
14770
|
),
|
|
12705
14771
|
label: zod.z.string().optional().describe("Optional annotation label to display near the highlight"),
|
|
@@ -12717,18 +14783,27 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
12717
14783
|
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
12718
14784
|
const tab = tabManager.getActiveTab();
|
|
12719
14785
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
14786
|
+
const normalizedText = normalizeLooseString(text);
|
|
12720
14787
|
return withAction(
|
|
12721
14788
|
runtime,
|
|
12722
14789
|
tabManager,
|
|
12723
14790
|
"highlight",
|
|
12724
|
-
{
|
|
14791
|
+
{
|
|
14792
|
+
index,
|
|
14793
|
+
selector,
|
|
14794
|
+
text: normalizedText,
|
|
14795
|
+
label,
|
|
14796
|
+
durationMs,
|
|
14797
|
+
persist,
|
|
14798
|
+
color
|
|
14799
|
+
},
|
|
12725
14800
|
async () => {
|
|
12726
14801
|
const wc = tab.view.webContents;
|
|
12727
14802
|
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
12728
14803
|
const result = await highlightOnPage(
|
|
12729
14804
|
wc,
|
|
12730
14805
|
resolvedSelector,
|
|
12731
|
-
|
|
14806
|
+
normalizedText,
|
|
12732
14807
|
label,
|
|
12733
14808
|
durationMs,
|
|
12734
14809
|
color
|
|
@@ -12738,7 +14813,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
12738
14813
|
addHighlight(
|
|
12739
14814
|
url,
|
|
12740
14815
|
resolvedSelector ?? void 0,
|
|
12741
|
-
|
|
14816
|
+
normalizedText,
|
|
12742
14817
|
label,
|
|
12743
14818
|
color,
|
|
12744
14819
|
"agent"
|
|
@@ -12758,12 +14833,18 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
12758
14833
|
async () => {
|
|
12759
14834
|
const tab = tabManager.getActiveTab();
|
|
12760
14835
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
12761
|
-
return withAction(
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
14836
|
+
return withAction(
|
|
14837
|
+
runtime,
|
|
14838
|
+
tabManager,
|
|
14839
|
+
"clear_highlights",
|
|
14840
|
+
{},
|
|
14841
|
+
async () => {
|
|
14842
|
+
const wc = tab.view.webContents;
|
|
14843
|
+
const url = normalizeUrl(wc.getURL());
|
|
14844
|
+
clearHighlightsForUrl(url);
|
|
14845
|
+
return clearHighlights(wc);
|
|
14846
|
+
}
|
|
14847
|
+
);
|
|
12767
14848
|
}
|
|
12768
14849
|
);
|
|
12769
14850
|
server.registerTool(
|
|
@@ -12934,12 +15015,22 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
12934
15015
|
title: "Save Bookmark",
|
|
12935
15016
|
description: "Save the current page, a specific URL, or a link target from the current page into a bookmark folder. You can provide folder_id or folder_name; missing folder names can be created automatically.",
|
|
12936
15017
|
inputSchema: {
|
|
12937
|
-
url: zod.z.string().optional().describe(
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
15018
|
+
url: zod.z.string().optional().describe(
|
|
15019
|
+
"URL to bookmark. Omit to use the current page or provide index/selector to bookmark a link target from the page"
|
|
15020
|
+
),
|
|
15021
|
+
title: zod.z.string().optional().describe(
|
|
15022
|
+
"Human-readable title for the bookmark. Omit to use the page or link text"
|
|
15023
|
+
),
|
|
15024
|
+
index: zod.z.number().optional().describe(
|
|
15025
|
+
"Element index of a link on the current page to bookmark without opening it"
|
|
15026
|
+
),
|
|
15027
|
+
selector: zod.z.string().optional().describe(
|
|
15028
|
+
"CSS selector of a link on the current page to bookmark without opening it"
|
|
15029
|
+
),
|
|
12941
15030
|
folder_id: zod.z.string().optional().describe("Folder ID to save into (omit for Unsorted)"),
|
|
12942
|
-
folder_name: zod.z.string().optional().describe(
|
|
15031
|
+
folder_name: zod.z.string().optional().describe(
|
|
15032
|
+
"Folder name to save into. Created automatically if missing"
|
|
15033
|
+
),
|
|
12943
15034
|
folder_summary: zod.z.string().optional().describe("Optional one-sentence summary if a new folder is created"),
|
|
12944
15035
|
create_folder_if_missing: zod.z.boolean().optional().describe("Create folder_name automatically when it does not exist"),
|
|
12945
15036
|
note: zod.z.string().optional().describe("Optional note about why this was bookmarked"),
|
|
@@ -13085,10 +15176,16 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
13085
15176
|
description: "Organize a bookmark by intent: save or move a bookmark into a folder, creating the folder if needed. Works with bookmark_id, url, a link target from the current page, or the current page itself.",
|
|
13086
15177
|
inputSchema: {
|
|
13087
15178
|
bookmark_id: zod.z.string().optional().describe("Existing bookmark ID to move or update"),
|
|
13088
|
-
url: zod.z.string().optional().describe(
|
|
15179
|
+
url: zod.z.string().optional().describe(
|
|
15180
|
+
"URL to organize. Omit to use the current page or provide index/selector to target a link"
|
|
15181
|
+
),
|
|
13089
15182
|
title: zod.z.string().optional().describe("Optional title when saving a new bookmark"),
|
|
13090
|
-
index: zod.z.number().optional().describe(
|
|
13091
|
-
|
|
15183
|
+
index: zod.z.number().optional().describe(
|
|
15184
|
+
"Element index of a link on the current page to organize without opening it"
|
|
15185
|
+
),
|
|
15186
|
+
selector: zod.z.string().optional().describe(
|
|
15187
|
+
"CSS selector of a link on the current page to organize without opening it"
|
|
15188
|
+
),
|
|
13092
15189
|
folder_id: zod.z.string().optional().describe("Folder ID to organize into"),
|
|
13093
15190
|
folder_name: zod.z.string().optional().describe("Folder name to organize into"),
|
|
13094
15191
|
folder_summary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
|
|
@@ -13215,10 +15312,16 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
13215
15312
|
description: 'Archive the current page, a URL, a link target from the current page, or an existing bookmark into the default "Archive" folder.',
|
|
13216
15313
|
inputSchema: {
|
|
13217
15314
|
bookmark_id: zod.z.string().optional().describe("Existing bookmark ID to archive"),
|
|
13218
|
-
url: zod.z.string().optional().describe(
|
|
15315
|
+
url: zod.z.string().optional().describe(
|
|
15316
|
+
"URL to archive. Omit to use the current page or provide index/selector to target a link"
|
|
15317
|
+
),
|
|
13219
15318
|
title: zod.z.string().optional().describe("Optional title when saving a new archived bookmark"),
|
|
13220
|
-
index: zod.z.number().optional().describe(
|
|
13221
|
-
|
|
15319
|
+
index: zod.z.number().optional().describe(
|
|
15320
|
+
"Element index of a link on the current page to archive without opening it"
|
|
15321
|
+
),
|
|
15322
|
+
selector: zod.z.string().optional().describe(
|
|
15323
|
+
"CSS selector of a link on the current page to archive without opening it"
|
|
15324
|
+
),
|
|
13222
15325
|
note: zod.z.string().optional().describe("Optional note to store with the archived bookmark")
|
|
13223
15326
|
}
|
|
13224
15327
|
},
|
|
@@ -13374,7 +15477,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
13374
15477
|
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
13375
15478
|
);
|
|
13376
15479
|
}
|
|
13377
|
-
const folder = renameFolder(
|
|
15480
|
+
const folder = renameFolder(
|
|
15481
|
+
folder_id,
|
|
15482
|
+
new_name,
|
|
15483
|
+
summary
|
|
15484
|
+
);
|
|
13378
15485
|
return folder ? composeFolderAwareResponse(`Renamed folder to "${folder.name}"`) : `Folder ${folder_id} not found`;
|
|
13379
15486
|
}
|
|
13380
15487
|
);
|
|
@@ -13575,13 +15682,22 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
13575
15682
|
title: "Start Workflow",
|
|
13576
15683
|
description: "Begin tracking a multi-step web workflow. Vessel will show progress after every action so you always know where you are in the flow.",
|
|
13577
15684
|
inputSchema: {
|
|
13578
|
-
goal: zod.z.string().describe(
|
|
13579
|
-
|
|
15685
|
+
goal: zod.z.string().describe(
|
|
15686
|
+
"What this workflow accomplishes (e.g. 'Purchase item from Amazon')"
|
|
15687
|
+
),
|
|
15688
|
+
steps: stringArrayLikeSchema().describe(
|
|
15689
|
+
"Ordered list of step labels (e.g. ['Log in', 'Search', 'Select item', 'Checkout'])"
|
|
15690
|
+
)
|
|
13580
15691
|
}
|
|
13581
15692
|
},
|
|
13582
15693
|
async ({ goal, steps }) => {
|
|
15694
|
+
const normalizedSteps = coerceStringArray(steps) ?? [];
|
|
13583
15695
|
const tab = tabManager.getActiveTab();
|
|
13584
|
-
const flow = runtime.startFlow(
|
|
15696
|
+
const flow = runtime.startFlow(
|
|
15697
|
+
goal,
|
|
15698
|
+
normalizedSteps,
|
|
15699
|
+
tab?.view.webContents.getURL()
|
|
15700
|
+
);
|
|
13585
15701
|
return asTextResponse(
|
|
13586
15702
|
`Flow started: ${flow.goal}
|
|
13587
15703
|
${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
@@ -13635,13 +15751,18 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
13635
15751
|
},
|
|
13636
15752
|
async () => {
|
|
13637
15753
|
const tab = tabManager.getActiveTab();
|
|
13638
|
-
if (!tab)
|
|
15754
|
+
if (!tab)
|
|
15755
|
+
return asTextResponse(
|
|
15756
|
+
"No active tab. Use vessel_navigate to open a page."
|
|
15757
|
+
);
|
|
13639
15758
|
const wc = tab.view.webContents;
|
|
13640
15759
|
let page;
|
|
13641
15760
|
try {
|
|
13642
15761
|
page = await extractContent(wc);
|
|
13643
15762
|
} catch {
|
|
13644
|
-
return asTextResponse(
|
|
15763
|
+
return asTextResponse(
|
|
15764
|
+
"Could not read page. Try vessel_navigate to a working URL."
|
|
15765
|
+
);
|
|
13645
15766
|
}
|
|
13646
15767
|
const suggestions = [];
|
|
13647
15768
|
suggestions.push(`Page: ${page.title || "(untitled)"}`);
|
|
@@ -13661,23 +15782,32 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
13661
15782
|
);
|
|
13662
15783
|
const formCount = page.forms.length;
|
|
13663
15784
|
const totalFields = page.forms.reduce((n, f) => n + f.fields.length, 0);
|
|
13664
|
-
const linkCount = page.interactiveElements.filter(
|
|
15785
|
+
const linkCount = page.interactiveElements.filter(
|
|
15786
|
+
(el) => el.type === "link"
|
|
15787
|
+
).length;
|
|
13665
15788
|
const hasPagination = page.interactiveElements.some(
|
|
13666
15789
|
(el) => (el.text || "").toLowerCase() === "next" || el.text === "›" || el.text === "»"
|
|
13667
15790
|
);
|
|
13668
15791
|
const hasOverlays = page.overlays.some((o) => o.blocksInteraction);
|
|
13669
15792
|
if (hasOverlays) {
|
|
13670
15793
|
suggestions.push("⚠ BLOCKING OVERLAY detected — dismiss it first:");
|
|
13671
|
-
suggestions.push(" →
|
|
15794
|
+
suggestions.push(" → vessel_clear_overlays for stacked modals");
|
|
15795
|
+
suggestions.push(" → or vessel_dismiss_popup for a single popup");
|
|
13672
15796
|
suggestions.push("");
|
|
13673
15797
|
}
|
|
13674
15798
|
if (hasPasswordField) {
|
|
13675
15799
|
suggestions.push("🔑 LOGIN PAGE detected:");
|
|
13676
|
-
suggestions.push(
|
|
13677
|
-
|
|
15800
|
+
suggestions.push(
|
|
15801
|
+
" → vessel_login(username, password) — handles the full flow"
|
|
15802
|
+
);
|
|
15803
|
+
suggestions.push(
|
|
15804
|
+
" → Or vessel_fill_form + vessel_submit_form for manual control"
|
|
15805
|
+
);
|
|
13678
15806
|
} else if (hasSearchInput && linkCount < 10) {
|
|
13679
15807
|
suggestions.push("🔍 SEARCH PAGE detected:");
|
|
13680
|
-
suggestions.push(
|
|
15808
|
+
suggestions.push(
|
|
15809
|
+
" → vessel_search(query) — finds the box, types, submits"
|
|
15810
|
+
);
|
|
13681
15811
|
} else if (hasSearchInput && linkCount >= 10) {
|
|
13682
15812
|
suggestions.push("📋 SEARCH RESULTS detected:");
|
|
13683
15813
|
suggestions.push(" → vessel_click on a result link");
|
|
@@ -13686,7 +15816,9 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
13686
15816
|
}
|
|
13687
15817
|
} else if (formCount > 0) {
|
|
13688
15818
|
suggestions.push(`📝 FORM detected (${totalFields} fields):`);
|
|
13689
|
-
suggestions.push(
|
|
15819
|
+
suggestions.push(
|
|
15820
|
+
" → vessel_fill_form(fields) — fill all fields at once"
|
|
15821
|
+
);
|
|
13690
15822
|
suggestions.push(" → Or vessel_type for individual fields");
|
|
13691
15823
|
} else if (hasPagination) {
|
|
13692
15824
|
suggestions.push("📄 PAGINATED CONTENT:");
|
|
@@ -13698,12 +15830,16 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
13698
15830
|
suggestions.push(" → vessel_scroll to see more");
|
|
13699
15831
|
} else {
|
|
13700
15832
|
suggestions.push("🌐 GENERAL PAGE:");
|
|
13701
|
-
suggestions.push(
|
|
15833
|
+
suggestions.push(
|
|
15834
|
+
" → vessel_extract_content to understand the page structure"
|
|
15835
|
+
);
|
|
13702
15836
|
suggestions.push(" → vessel_click on any element by index");
|
|
13703
15837
|
suggestions.push(" → vessel_navigate to go somewhere new");
|
|
13704
15838
|
}
|
|
13705
15839
|
suggestions.push("");
|
|
13706
|
-
suggestions.push(
|
|
15840
|
+
suggestions.push(
|
|
15841
|
+
`Available: ${page.interactiveElements.length} interactive elements, ${formCount} forms, ${linkCount} links`
|
|
15842
|
+
);
|
|
13707
15843
|
return asTextResponse(suggestions.join("\n"));
|
|
13708
15844
|
}
|
|
13709
15845
|
);
|
|
@@ -13717,9 +15853,14 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
13717
15853
|
zod.z.object({
|
|
13718
15854
|
index: zod.z.number().optional().describe("Element index from page content"),
|
|
13719
15855
|
selector: zod.z.string().optional().describe("CSS selector fallback"),
|
|
15856
|
+
name: zod.z.string().optional().describe("Field name or id, such as custname"),
|
|
15857
|
+
label: zod.z.string().optional().describe("Visible label or aria-label text"),
|
|
15858
|
+
placeholder: zod.z.string().optional().describe("Placeholder text shown in the field"),
|
|
13720
15859
|
value: zod.z.string().describe("Value to enter")
|
|
13721
15860
|
})
|
|
13722
|
-
).describe(
|
|
15861
|
+
).describe(
|
|
15862
|
+
"Fields to fill, matched by index, selector, name, label, or placeholder"
|
|
15863
|
+
),
|
|
13723
15864
|
submit: zod.z.boolean().optional().describe("Submit the form after filling (default false)")
|
|
13724
15865
|
}
|
|
13725
15866
|
},
|
|
@@ -13733,18 +15874,10 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
13733
15874
|
{ fieldCount: fields.length, submit },
|
|
13734
15875
|
async () => {
|
|
13735
15876
|
const wc = tab.view.webContents;
|
|
13736
|
-
const
|
|
13737
|
-
|
|
13738
|
-
const sel = await resolveSelector(wc, field.index, field.selector);
|
|
13739
|
-
if (!sel) {
|
|
13740
|
-
results.push(`Skipped: no selector for index=${field.index}`);
|
|
13741
|
-
continue;
|
|
13742
|
-
}
|
|
13743
|
-
const result = await setElementValue(wc, sel, field.value);
|
|
13744
|
-
results.push(result);
|
|
13745
|
-
}
|
|
15877
|
+
const fillResults = await fillFormFields(wc, fields);
|
|
15878
|
+
const results = fillResults.map((item) => item.result);
|
|
13746
15879
|
if (submit) {
|
|
13747
|
-
const firstSel =
|
|
15880
|
+
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
13748
15881
|
if (firstSel) {
|
|
13749
15882
|
const beforeUrl = wc.getURL();
|
|
13750
15883
|
const submitResult = await submitForm(wc, void 0, firstSel);
|
|
@@ -13770,12 +15903,25 @@ ${results.join("\n")}`;
|
|
|
13770
15903
|
url: zod.z.string().optional().describe("Login page URL (skip if already on login page)"),
|
|
13771
15904
|
username: zod.z.string().describe("Username or email"),
|
|
13772
15905
|
password: zod.z.string().describe("Password"),
|
|
13773
|
-
username_selector: zod.z.string().optional().describe(
|
|
13774
|
-
|
|
13775
|
-
|
|
15906
|
+
username_selector: zod.z.string().optional().describe(
|
|
15907
|
+
"CSS selector for username field (auto-detected if omitted)"
|
|
15908
|
+
),
|
|
15909
|
+
password_selector: zod.z.string().optional().describe(
|
|
15910
|
+
"CSS selector for password field (auto-detected if omitted)"
|
|
15911
|
+
),
|
|
15912
|
+
submit_selector: zod.z.string().optional().describe(
|
|
15913
|
+
"CSS selector for submit button (auto-detected if omitted)"
|
|
15914
|
+
)
|
|
13776
15915
|
}
|
|
13777
15916
|
},
|
|
13778
|
-
async ({
|
|
15917
|
+
async ({
|
|
15918
|
+
url,
|
|
15919
|
+
username,
|
|
15920
|
+
password,
|
|
15921
|
+
username_selector,
|
|
15922
|
+
password_selector,
|
|
15923
|
+
submit_selector
|
|
15924
|
+
}) => {
|
|
13779
15925
|
const tab = tabManager.getActiveTab();
|
|
13780
15926
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13781
15927
|
return withAction(
|
|
@@ -13798,14 +15944,16 @@ ${results.join("\n")}`;
|
|
|
13798
15944
|
return el ? (el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null) : null;
|
|
13799
15945
|
})()
|
|
13800
15946
|
`);
|
|
13801
|
-
if (!userSel)
|
|
15947
|
+
if (!userSel)
|
|
15948
|
+
return "Error: Could not find username/email field. Try providing username_selector.";
|
|
13802
15949
|
const passSel = password_selector || await wc.executeJavaScript(`
|
|
13803
15950
|
(function() {
|
|
13804
15951
|
var el = document.querySelector('input[type="password"]');
|
|
13805
15952
|
return el ? (el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null) : null;
|
|
13806
15953
|
})()
|
|
13807
15954
|
`);
|
|
13808
|
-
if (!passSel)
|
|
15955
|
+
if (!passSel)
|
|
15956
|
+
return "Error: Could not find password field. Try providing password_selector.";
|
|
13809
15957
|
const userResult = await setElementValue(wc, userSel, username);
|
|
13810
15958
|
steps.push(userResult);
|
|
13811
15959
|
const passResult = await setElementValue(wc, passSel, password);
|
|
@@ -13823,7 +15971,8 @@ ${results.join("\n")}`;
|
|
|
13823
15971
|
return false;
|
|
13824
15972
|
})()
|
|
13825
15973
|
`);
|
|
13826
|
-
if (!clicked)
|
|
15974
|
+
if (!clicked)
|
|
15975
|
+
return steps.join("\n") + "\nWarning: Could not find submit button. Credentials filled but form not submitted.";
|
|
13827
15976
|
}
|
|
13828
15977
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13829
15978
|
const afterUrl = wc.getURL();
|
|
@@ -13849,14 +15998,41 @@ ${steps.join("\n")}`;
|
|
|
13849
15998
|
async ({ query, selector }) => {
|
|
13850
15999
|
const tab = tabManager.getActiveTab();
|
|
13851
16000
|
if (!tab) return asTextResponse("Error: No active tab");
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
"
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
16001
|
+
const qLower = query.toLowerCase().trim();
|
|
16002
|
+
const buttonLabels = [
|
|
16003
|
+
"add to cart",
|
|
16004
|
+
"add to bag",
|
|
16005
|
+
"add to basket",
|
|
16006
|
+
"buy now",
|
|
16007
|
+
"buy it now",
|
|
16008
|
+
"purchase",
|
|
16009
|
+
"continue shopping",
|
|
16010
|
+
"keep shopping",
|
|
16011
|
+
"view cart",
|
|
16012
|
+
"view bag",
|
|
16013
|
+
"view basket",
|
|
16014
|
+
"go to cart",
|
|
16015
|
+
"go to checkout",
|
|
16016
|
+
"checkout",
|
|
16017
|
+
"check out",
|
|
16018
|
+
"proceed to checkout",
|
|
16019
|
+
"place order",
|
|
16020
|
+
"submit",
|
|
16021
|
+
"subscribe",
|
|
16022
|
+
"sign up",
|
|
16023
|
+
"sign in",
|
|
16024
|
+
"log in",
|
|
16025
|
+
"register",
|
|
16026
|
+
"continue"
|
|
16027
|
+
];
|
|
16028
|
+
if (buttonLabels.some((p) => qLower.includes(p))) {
|
|
16029
|
+
return asTextResponse(
|
|
16030
|
+
`Error: "${query}" looks like a button label, not a search query. Use the click tool to interact with this element instead.`
|
|
16031
|
+
);
|
|
16032
|
+
}
|
|
16033
|
+
return withAction(runtime, tabManager, "search", { query }, async () => {
|
|
16034
|
+
const wc = tab.view.webContents;
|
|
16035
|
+
const searchSel = selector || await wc.executeJavaScript(`
|
|
13860
16036
|
(function() {
|
|
13861
16037
|
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]');
|
|
13862
16038
|
if (!el) {
|
|
@@ -13872,24 +16048,24 @@ ${steps.join("\n")}`;
|
|
|
13872
16048
|
return el ? (el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null) : null;
|
|
13873
16049
|
})()
|
|
13874
16050
|
`);
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
16051
|
+
if (!searchSel)
|
|
16052
|
+
return "Error: Could not find search input. Try providing a selector.";
|
|
16053
|
+
await setElementValue(wc, searchSel, query);
|
|
16054
|
+
await wc.executeJavaScript(`
|
|
13878
16055
|
(function() {
|
|
13879
16056
|
var el = document.querySelector(${JSON.stringify(searchSel)});
|
|
13880
16057
|
if (el) el.focus();
|
|
13881
16058
|
})()
|
|
13882
16059
|
`);
|
|
13883
|
-
|
|
13884
|
-
|
|
13885
|
-
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
13891
|
-
|
|
13892
|
-
);
|
|
16060
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
16061
|
+
const beforeUrl = wc.getURL();
|
|
16062
|
+
wc.sendInputEvent({ type: "keyDown", keyCode: "Return" });
|
|
16063
|
+
await new Promise((r) => setTimeout(r, 16));
|
|
16064
|
+
wc.sendInputEvent({ type: "keyUp", keyCode: "Return" });
|
|
16065
|
+
await waitForPotentialNavigation(wc, beforeUrl);
|
|
16066
|
+
const afterUrl = wc.getURL();
|
|
16067
|
+
return afterUrl !== beforeUrl ? `Searched "${query}" → ${afterUrl}` : `Searched "${query}" (same page — results may have loaded dynamically)`;
|
|
16068
|
+
});
|
|
13893
16069
|
}
|
|
13894
16070
|
);
|
|
13895
16071
|
server.registerTool(
|
|
@@ -13899,7 +16075,9 @@ ${steps.join("\n")}`;
|
|
|
13899
16075
|
description: "Navigate to the next or previous page of results. Auto-detects pagination controls.",
|
|
13900
16076
|
inputSchema: {
|
|
13901
16077
|
direction: zod.z.enum(["next", "prev"]).describe("Pagination direction"),
|
|
13902
|
-
selector: zod.z.string().optional().describe(
|
|
16078
|
+
selector: zod.z.string().optional().describe(
|
|
16079
|
+
"CSS selector for the pagination link (auto-detected if omitted)"
|
|
16080
|
+
)
|
|
13903
16081
|
}
|
|
13904
16082
|
},
|
|
13905
16083
|
async ({ direction, selector }) => {
|
|
@@ -13937,7 +16115,8 @@ ${steps.join("\n")}`;
|
|
|
13937
16115
|
return false;
|
|
13938
16116
|
})()
|
|
13939
16117
|
`);
|
|
13940
|
-
if (!clicked)
|
|
16118
|
+
if (!clicked)
|
|
16119
|
+
return `Error: Could not find ${direction} pagination control. Try providing a selector.`;
|
|
13941
16120
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13942
16121
|
const afterUrl = wc.getURL();
|
|
13943
16122
|
return afterUrl !== beforeUrl ? `Paginated ${direction} → ${afterUrl}` : `Clicked ${direction} (page may have updated dynamically)`;
|
|
@@ -13953,12 +16132,15 @@ ${steps.join("\n")}`;
|
|
|
13953
16132
|
inputSchema: zod.z.object({})
|
|
13954
16133
|
},
|
|
13955
16134
|
async () => {
|
|
16135
|
+
const tab = tabManager.getActiveTab();
|
|
16136
|
+
if (!tab) return asTextResponse("Error: No active tab");
|
|
13956
16137
|
return withAction(
|
|
13957
|
-
tabManager,
|
|
13958
16138
|
runtime,
|
|
16139
|
+
tabManager,
|
|
13959
16140
|
"vessel_accept_cookies",
|
|
13960
16141
|
{},
|
|
13961
|
-
async (
|
|
16142
|
+
async () => {
|
|
16143
|
+
const wc = tab.view.webContents;
|
|
13962
16144
|
const dismissed = await wc.executeJavaScript(`
|
|
13963
16145
|
(function() {
|
|
13964
16146
|
var selectors = [
|
|
@@ -14008,12 +16190,15 @@ ${steps.join("\n")}`;
|
|
|
14008
16190
|
})
|
|
14009
16191
|
},
|
|
14010
16192
|
async ({ index, selector: rawSelector }) => {
|
|
16193
|
+
const tab = tabManager.getActiveTab();
|
|
16194
|
+
if (!tab) return asTextResponse("Error: No active tab");
|
|
14011
16195
|
return withAction(
|
|
14012
|
-
tabManager,
|
|
14013
16196
|
runtime,
|
|
16197
|
+
tabManager,
|
|
14014
16198
|
"vessel_extract_table",
|
|
14015
16199
|
{ index, selector: rawSelector },
|
|
14016
|
-
async (
|
|
16200
|
+
async () => {
|
|
16201
|
+
const wc = tab.view.webContents;
|
|
14017
16202
|
const sel = rawSelector || (index != null ? await resolveSelector(wc, index) : null);
|
|
14018
16203
|
const tableJson = await wc.executeJavaScript(`
|
|
14019
16204
|
(function() {
|
|
@@ -14060,12 +16245,15 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
14060
16245
|
})
|
|
14061
16246
|
},
|
|
14062
16247
|
async ({ index, selector: rawSelector, position }) => {
|
|
16248
|
+
const tab = tabManager.getActiveTab();
|
|
16249
|
+
if (!tab) return asTextResponse("Error: No active tab");
|
|
14063
16250
|
return withAction(
|
|
14064
|
-
tabManager,
|
|
14065
16251
|
runtime,
|
|
16252
|
+
tabManager,
|
|
14066
16253
|
"vessel_scroll_to_element",
|
|
14067
16254
|
{ index, selector: rawSelector, position },
|
|
14068
|
-
async (
|
|
16255
|
+
async () => {
|
|
16256
|
+
const wc = tab.view.webContents;
|
|
14069
16257
|
const sel = rawSelector || (index != null ? await resolveSelector(wc, index) : null);
|
|
14070
16258
|
if (!sel) return "Error: Provide an index or selector.";
|
|
14071
16259
|
const block = position === "top" ? "start" : position === "bottom" ? "end" : "center";
|
|
@@ -14115,12 +16303,15 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
14115
16303
|
})
|
|
14116
16304
|
},
|
|
14117
16305
|
async ({ timeoutMs }) => {
|
|
16306
|
+
const tab = tabManager.getActiveTab();
|
|
16307
|
+
if (!tab) return asTextResponse("Error: No active tab");
|
|
14118
16308
|
return withAction(
|
|
14119
|
-
tabManager,
|
|
14120
16309
|
runtime,
|
|
16310
|
+
tabManager,
|
|
14121
16311
|
"vessel_wait_for_navigation",
|
|
14122
16312
|
{ timeoutMs },
|
|
14123
|
-
async (
|
|
16313
|
+
async () => {
|
|
16314
|
+
const wc = tab.view.webContents;
|
|
14124
16315
|
const timeout = timeoutMs || 1e4;
|
|
14125
16316
|
const beforeUrl = wc.getURL();
|
|
14126
16317
|
if (wc.isLoading()) {
|
|
@@ -14134,9 +16325,12 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
14134
16325
|
} else {
|
|
14135
16326
|
await new Promise((resolve) => {
|
|
14136
16327
|
let navigated = false;
|
|
14137
|
-
const timer = setTimeout(
|
|
14138
|
-
|
|
14139
|
-
|
|
16328
|
+
const timer = setTimeout(
|
|
16329
|
+
() => {
|
|
16330
|
+
if (!navigated) resolve();
|
|
16331
|
+
},
|
|
16332
|
+
Math.min(timeout, 2e3)
|
|
16333
|
+
);
|
|
14140
16334
|
wc.once("did-start-loading", () => {
|
|
14141
16335
|
navigated = true;
|
|
14142
16336
|
clearTimeout(timer);
|
|
@@ -14174,7 +16368,9 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
14174
16368
|
`Tool breakdown:`
|
|
14175
16369
|
];
|
|
14176
16370
|
for (const [name, stats] of Object.entries(m.toolBreakdown)) {
|
|
14177
|
-
lines.push(
|
|
16371
|
+
lines.push(
|
|
16372
|
+
` ${name}: ${stats.count} calls, avg ${stats.avgMs}ms${stats.errors > 0 ? `, ${stats.errors} errors` : ""}`
|
|
16373
|
+
);
|
|
14178
16374
|
}
|
|
14179
16375
|
return asTextResponse(lines.join("\n"));
|
|
14180
16376
|
}
|