@quanta-intellect/vessel-browser 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/out/main/index.js +2214 -431
- package/out/preload/content-script.js +61 -17
- package/out/preload/index.js +20 -0
- package/out/renderer/assets/{index-DOCQcMR5.css → index-DMd-y6tm.css} +95 -0
- package/out/renderer/assets/{index-BUYEjb3N.js → index-Do3B3G1W.js} +234 -122
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -62,25 +62,15 @@ class Tab {
|
|
|
62
62
|
isReaderMode: false,
|
|
63
63
|
adBlockingEnabled: options?.adBlockingEnabled ?? true
|
|
64
64
|
};
|
|
65
|
-
this.view.webContents.on("before-input-event", (
|
|
65
|
+
this.view.webContents.on("before-input-event", (_event, input) => {
|
|
66
66
|
if (!input.control && !input.meta) return;
|
|
67
|
+
if (input.type !== "keyDown") return;
|
|
67
68
|
const key = input.key.toLowerCase();
|
|
68
69
|
const wc = this.view.webContents;
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} else if (key === "v") {
|
|
74
|
-
wc.paste();
|
|
75
|
-
event.preventDefault();
|
|
76
|
-
} else if (key === "x") {
|
|
77
|
-
wc.cut();
|
|
78
|
-
event.preventDefault();
|
|
79
|
-
} else if (key === "a") {
|
|
80
|
-
wc.selectAll();
|
|
81
|
-
event.preventDefault();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
70
|
+
if (key === "c") wc.copy();
|
|
71
|
+
else if (key === "v") wc.paste();
|
|
72
|
+
else if (key === "x") wc.cut();
|
|
73
|
+
else if (key === "a") wc.selectAll();
|
|
84
74
|
});
|
|
85
75
|
this.setupListeners();
|
|
86
76
|
if (url) {
|
|
@@ -732,26 +722,46 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
732
722
|
var SKIP_TAGS = {SCRIPT:1,STYLE:1,NOSCRIPT:1,TEMPLATE:1,IFRAME:1,SVG:1};
|
|
733
723
|
// Collect matching text nodes first, then wrap — avoids TreeWalker
|
|
734
724
|
// seeing newly created nodes from surroundContents and re-matching.
|
|
735
|
-
|
|
736
|
-
var
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
725
|
+
// Prioritize main content area over nav/sidebar/captions.
|
|
726
|
+
var contentRoots = ['main', 'article', '[role="main"]', '#mw-content-text', '.mw-parser-output', '#content', '.post-content', '.entry-content', '.article-body'];
|
|
727
|
+
var contentRoot = null;
|
|
728
|
+
for (var cr = 0; cr < contentRoots.length; cr++) {
|
|
729
|
+
contentRoot = document.querySelector(contentRoots[cr]);
|
|
730
|
+
if (contentRoot) break;
|
|
731
|
+
}
|
|
732
|
+
var NAV_ANCESTORS = 'nav, aside, footer, header, [role="navigation"], [role="complementary"], .sidebar, .navbox, .infobox, figcaption, .thumbcaption, .mw-jump-link';
|
|
733
|
+
|
|
734
|
+
function collectMatches(root, limit) {
|
|
735
|
+
var matches = [];
|
|
736
|
+
var w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
737
|
+
acceptNode: function(n) {
|
|
738
|
+
var p = n.parentElement;
|
|
739
|
+
if (!p) return NodeFilter.FILTER_REJECT;
|
|
740
|
+
if (SKIP_TAGS[p.tagName]) return NodeFilter.FILTER_REJECT;
|
|
741
|
+
if (p.closest('[data-vessel-highlight]')) return NodeFilter.FILTER_REJECT;
|
|
742
|
+
if (p.closest(NAV_ANCESTORS)) return NodeFilter.FILTER_REJECT;
|
|
743
|
+
var style = window.getComputedStyle(p);
|
|
744
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return NodeFilter.FILTER_REJECT;
|
|
745
|
+
if (p.offsetWidth === 0 && p.offsetHeight === 0) return NodeFilter.FILTER_REJECT;
|
|
746
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
var n;
|
|
750
|
+
while ((n = w.nextNode())) {
|
|
751
|
+
var idx = n.textContent.indexOf(searchText);
|
|
752
|
+
if (idx !== -1) {
|
|
753
|
+
matches.push({ node: n, idx: idx });
|
|
754
|
+
if (matches.length >= limit) break;
|
|
755
|
+
}
|
|
754
756
|
}
|
|
757
|
+
return matches;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// First try: match inside main content area (skip nav/sidebar/captions)
|
|
761
|
+
var textNodes = contentRoot ? collectMatches(contentRoot, 20) : [];
|
|
762
|
+
// Fallback: if no matches in content area, search the whole body
|
|
763
|
+
if (textNodes.length === 0) {
|
|
764
|
+
textNodes = collectMatches(document.body, 20);
|
|
755
765
|
}
|
|
756
766
|
var count = 0;
|
|
757
767
|
var firstMark = null;
|
|
@@ -1946,6 +1956,74 @@ function setSetting(key, value) {
|
|
|
1946
1956
|
saveSettings();
|
|
1947
1957
|
return { ...settings };
|
|
1948
1958
|
}
|
|
1959
|
+
const Channels = {
|
|
1960
|
+
// Tab management
|
|
1961
|
+
TAB_CREATE: "tab:create",
|
|
1962
|
+
TAB_CLOSE: "tab:close",
|
|
1963
|
+
TAB_SWITCH: "tab:switch",
|
|
1964
|
+
TAB_NAVIGATE: "tab:navigate",
|
|
1965
|
+
TAB_BACK: "tab:back",
|
|
1966
|
+
TAB_FORWARD: "tab:forward",
|
|
1967
|
+
TAB_RELOAD: "tab:reload",
|
|
1968
|
+
TAB_STATE_UPDATE: "tab:state-update",
|
|
1969
|
+
// AI
|
|
1970
|
+
AI_QUERY: "ai:query",
|
|
1971
|
+
AI_STREAM_START: "ai:stream-start",
|
|
1972
|
+
AI_STREAM_CHUNK: "ai:stream-chunk",
|
|
1973
|
+
AI_STREAM_END: "ai:stream-end",
|
|
1974
|
+
AI_CANCEL: "ai:cancel",
|
|
1975
|
+
AI_FETCH_MODELS: "ai:fetch-models",
|
|
1976
|
+
AGENT_RUNTIME_GET: "agent-runtime:get",
|
|
1977
|
+
AGENT_RUNTIME_UPDATE: "agent-runtime:update",
|
|
1978
|
+
AGENT_PAUSE: "agent:pause",
|
|
1979
|
+
AGENT_RESUME: "agent:resume",
|
|
1980
|
+
AGENT_SET_APPROVAL_MODE: "agent:set-approval-mode",
|
|
1981
|
+
AGENT_APPROVAL_RESOLVE: "agent:approval-resolve",
|
|
1982
|
+
AGENT_CHECKPOINT_CREATE: "agent:checkpoint-create",
|
|
1983
|
+
AGENT_CHECKPOINT_RESTORE: "agent:checkpoint-restore",
|
|
1984
|
+
AGENT_SESSION_CAPTURE: "agent:session-capture",
|
|
1985
|
+
AGENT_SESSION_RESTORE: "agent:session-restore",
|
|
1986
|
+
// Content
|
|
1987
|
+
CONTENT_EXTRACT: "content:extract",
|
|
1988
|
+
READER_MODE_TOGGLE: "reader:toggle",
|
|
1989
|
+
// UI state
|
|
1990
|
+
SIDEBAR_TOGGLE: "ui:sidebar-toggle",
|
|
1991
|
+
SIDEBAR_RESIZE: "ui:sidebar-resize",
|
|
1992
|
+
SIDEBAR_RESIZE_START: "ui:sidebar-resize-start",
|
|
1993
|
+
SIDEBAR_RESIZE_COMMIT: "ui:sidebar-resize-commit",
|
|
1994
|
+
SIDEBAR_CONTEXT_MENU: "ui:sidebar-context-menu",
|
|
1995
|
+
FOCUS_MODE_TOGGLE: "ui:focus-mode-toggle",
|
|
1996
|
+
SETTINGS_VISIBILITY: "ui:settings-visibility",
|
|
1997
|
+
// Settings
|
|
1998
|
+
SETTINGS_GET: "settings:get",
|
|
1999
|
+
SETTINGS_SET: "settings:set",
|
|
2000
|
+
SETTINGS_UPDATE: "settings:update",
|
|
2001
|
+
SETTINGS_HEALTH_GET: "settings:health:get",
|
|
2002
|
+
// Bookmarks
|
|
2003
|
+
BOOKMARKS_GET: "bookmarks:get",
|
|
2004
|
+
BOOKMARKS_UPDATE: "bookmarks:update",
|
|
2005
|
+
BOOKMARK_SAVE: "bookmarks:save",
|
|
2006
|
+
BOOKMARK_REMOVE: "bookmarks:remove",
|
|
2007
|
+
FOLDER_CREATE: "bookmarks:folder-create",
|
|
2008
|
+
FOLDER_REMOVE: "bookmarks:folder-remove",
|
|
2009
|
+
FOLDER_RENAME: "bookmarks:folder-rename",
|
|
2010
|
+
// Highlights
|
|
2011
|
+
HIGHLIGHT_CAPTURE: "highlights:capture",
|
|
2012
|
+
HIGHLIGHT_CAPTURE_RESULT: "highlights:capture-result",
|
|
2013
|
+
HIGHLIGHT_SELECTION: "vessel:highlight-selection",
|
|
2014
|
+
HIGHLIGHT_NAV_COUNT: "highlights:nav-count",
|
|
2015
|
+
HIGHLIGHT_NAV_SCROLL: "highlights:nav-scroll",
|
|
2016
|
+
HIGHLIGHT_NAV_REMOVE: "highlights:nav-remove",
|
|
2017
|
+
HIGHLIGHT_NAV_CLEAR: "highlights:nav-clear",
|
|
2018
|
+
SIDEBAR_HIGHLIGHT_ACTION: "highlights:sidebar-action",
|
|
2019
|
+
// DevTools panel
|
|
2020
|
+
DEVTOOLS_PANEL_TOGGLE: "devtools-panel:toggle",
|
|
2021
|
+
DEVTOOLS_PANEL_STATE: "devtools-panel:state",
|
|
2022
|
+
// Window controls
|
|
2023
|
+
WINDOW_MINIMIZE: "window:minimize",
|
|
2024
|
+
WINDOW_MAXIMIZE: "window:maximize",
|
|
2025
|
+
WINDOW_CLOSE: "window:close"
|
|
2026
|
+
};
|
|
1949
2027
|
function enableClipboardShortcuts(view) {
|
|
1950
2028
|
view.webContents.on("before-input-event", (event, input) => {
|
|
1951
2029
|
if (!input.control && !input.meta) return;
|
|
@@ -1972,6 +2050,102 @@ const CHROME_HEIGHT = 110;
|
|
|
1972
2050
|
const DEFAULT_DEVTOOLS_PANEL_HEIGHT = 250;
|
|
1973
2051
|
const MIN_DEVTOOLS_PANEL = 120;
|
|
1974
2052
|
const MAX_DEVTOOLS_PANEL = 600;
|
|
2053
|
+
async function getSidebarContextTarget(sidebarView, x, y) {
|
|
2054
|
+
try {
|
|
2055
|
+
return await sidebarView.webContents.executeJavaScript(
|
|
2056
|
+
`(() => {
|
|
2057
|
+
const el = document.elementFromPoint(${x}, ${y});
|
|
2058
|
+
const nav = el && typeof el.closest === "function"
|
|
2059
|
+
? el.closest(".highlight-nav")
|
|
2060
|
+
: null;
|
|
2061
|
+
const label = nav?.querySelector(".highlight-nav-label")?.textContent?.trim() || "";
|
|
2062
|
+
return {
|
|
2063
|
+
inHighlightNav: !!nav,
|
|
2064
|
+
canRemoveCurrent: /\\d+\\s*\\/\\s*\\d+/.test(label),
|
|
2065
|
+
};
|
|
2066
|
+
})()`,
|
|
2067
|
+
true
|
|
2068
|
+
);
|
|
2069
|
+
} catch {
|
|
2070
|
+
return { inHighlightNav: false, canRemoveCurrent: false };
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
async function showSidebarContextMenu(mainWindow, sidebarView, params) {
|
|
2074
|
+
const target = await getSidebarContextTarget(sidebarView, params.x, params.y);
|
|
2075
|
+
const menu = new electron.Menu();
|
|
2076
|
+
if (target.inHighlightNav) {
|
|
2077
|
+
if (target.canRemoveCurrent) {
|
|
2078
|
+
menu.append(
|
|
2079
|
+
new electron.MenuItem({
|
|
2080
|
+
label: "Remove Current Highlight",
|
|
2081
|
+
click: () => sidebarView.webContents.send(
|
|
2082
|
+
Channels.SIDEBAR_HIGHLIGHT_ACTION,
|
|
2083
|
+
"remove-current"
|
|
2084
|
+
)
|
|
2085
|
+
})
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
menu.append(
|
|
2089
|
+
new electron.MenuItem({
|
|
2090
|
+
label: "Clear All Highlights",
|
|
2091
|
+
click: () => sidebarView.webContents.send(
|
|
2092
|
+
Channels.SIDEBAR_HIGHLIGHT_ACTION,
|
|
2093
|
+
"clear-all"
|
|
2094
|
+
)
|
|
2095
|
+
})
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
if (params.isEditable) {
|
|
2099
|
+
if (menu.items.length > 0) {
|
|
2100
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
2101
|
+
}
|
|
2102
|
+
menu.append(
|
|
2103
|
+
new electron.MenuItem({
|
|
2104
|
+
role: "undo",
|
|
2105
|
+
enabled: params.editFlags.canUndo
|
|
2106
|
+
})
|
|
2107
|
+
);
|
|
2108
|
+
menu.append(
|
|
2109
|
+
new electron.MenuItem({
|
|
2110
|
+
role: "redo",
|
|
2111
|
+
enabled: params.editFlags.canRedo
|
|
2112
|
+
})
|
|
2113
|
+
);
|
|
2114
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
2115
|
+
menu.append(
|
|
2116
|
+
new electron.MenuItem({
|
|
2117
|
+
role: "cut",
|
|
2118
|
+
enabled: params.editFlags.canCut
|
|
2119
|
+
})
|
|
2120
|
+
);
|
|
2121
|
+
menu.append(
|
|
2122
|
+
new electron.MenuItem({
|
|
2123
|
+
role: "copy",
|
|
2124
|
+
enabled: params.editFlags.canCopy
|
|
2125
|
+
})
|
|
2126
|
+
);
|
|
2127
|
+
menu.append(
|
|
2128
|
+
new electron.MenuItem({
|
|
2129
|
+
role: "paste",
|
|
2130
|
+
enabled: params.editFlags.canPaste
|
|
2131
|
+
})
|
|
2132
|
+
);
|
|
2133
|
+
menu.append(
|
|
2134
|
+
new electron.MenuItem({
|
|
2135
|
+
role: "selectAll",
|
|
2136
|
+
enabled: params.editFlags.canSelectAll
|
|
2137
|
+
})
|
|
2138
|
+
);
|
|
2139
|
+
} else if (params.selectionText?.trim()) {
|
|
2140
|
+
if (menu.items.length > 0) {
|
|
2141
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
2142
|
+
}
|
|
2143
|
+
menu.append(new electron.MenuItem({ role: "copy" }));
|
|
2144
|
+
}
|
|
2145
|
+
if (menu.items.length === 0) return;
|
|
2146
|
+
sidebarView.webContents.focus();
|
|
2147
|
+
menu.popup({ window: mainWindow });
|
|
2148
|
+
}
|
|
1975
2149
|
function getWindowIconPath() {
|
|
1976
2150
|
const candidates = [
|
|
1977
2151
|
path.join(electron.app.getAppPath(), "resources", "vessel-icon.png"),
|
|
@@ -2009,6 +2183,10 @@ function createMainWindow(onTabStateChange) {
|
|
|
2009
2183
|
}
|
|
2010
2184
|
});
|
|
2011
2185
|
sidebarView.setBackgroundColor("#00000000");
|
|
2186
|
+
sidebarView.webContents.on("context-menu", (event, params) => {
|
|
2187
|
+
event.preventDefault();
|
|
2188
|
+
void showSidebarContextMenu(mainWindow, sidebarView, params);
|
|
2189
|
+
});
|
|
2012
2190
|
mainWindow.contentView.addChildView(sidebarView);
|
|
2013
2191
|
const devtoolsPanelView = new electron.WebContentsView({
|
|
2014
2192
|
webPreferences: {
|
|
@@ -2097,68 +2275,6 @@ function layoutViews(state2) {
|
|
|
2097
2275
|
});
|
|
2098
2276
|
}
|
|
2099
2277
|
}
|
|
2100
|
-
const Channels = {
|
|
2101
|
-
// Tab management
|
|
2102
|
-
TAB_CREATE: "tab:create",
|
|
2103
|
-
TAB_CLOSE: "tab:close",
|
|
2104
|
-
TAB_SWITCH: "tab:switch",
|
|
2105
|
-
TAB_NAVIGATE: "tab:navigate",
|
|
2106
|
-
TAB_BACK: "tab:back",
|
|
2107
|
-
TAB_FORWARD: "tab:forward",
|
|
2108
|
-
TAB_RELOAD: "tab:reload",
|
|
2109
|
-
TAB_STATE_UPDATE: "tab:state-update",
|
|
2110
|
-
// AI
|
|
2111
|
-
AI_QUERY: "ai:query",
|
|
2112
|
-
AI_STREAM_START: "ai:stream-start",
|
|
2113
|
-
AI_STREAM_CHUNK: "ai:stream-chunk",
|
|
2114
|
-
AI_STREAM_END: "ai:stream-end",
|
|
2115
|
-
AI_CANCEL: "ai:cancel",
|
|
2116
|
-
AI_FETCH_MODELS: "ai:fetch-models",
|
|
2117
|
-
AGENT_RUNTIME_GET: "agent-runtime:get",
|
|
2118
|
-
AGENT_RUNTIME_UPDATE: "agent-runtime:update",
|
|
2119
|
-
AGENT_PAUSE: "agent:pause",
|
|
2120
|
-
AGENT_RESUME: "agent:resume",
|
|
2121
|
-
AGENT_SET_APPROVAL_MODE: "agent:set-approval-mode",
|
|
2122
|
-
AGENT_APPROVAL_RESOLVE: "agent:approval-resolve",
|
|
2123
|
-
AGENT_CHECKPOINT_CREATE: "agent:checkpoint-create",
|
|
2124
|
-
AGENT_CHECKPOINT_RESTORE: "agent:checkpoint-restore",
|
|
2125
|
-
AGENT_SESSION_CAPTURE: "agent:session-capture",
|
|
2126
|
-
AGENT_SESSION_RESTORE: "agent:session-restore",
|
|
2127
|
-
// Content
|
|
2128
|
-
CONTENT_EXTRACT: "content:extract",
|
|
2129
|
-
READER_MODE_TOGGLE: "reader:toggle",
|
|
2130
|
-
// UI state
|
|
2131
|
-
SIDEBAR_TOGGLE: "ui:sidebar-toggle",
|
|
2132
|
-
SIDEBAR_RESIZE: "ui:sidebar-resize",
|
|
2133
|
-
SIDEBAR_RESIZE_START: "ui:sidebar-resize-start",
|
|
2134
|
-
SIDEBAR_RESIZE_COMMIT: "ui:sidebar-resize-commit",
|
|
2135
|
-
FOCUS_MODE_TOGGLE: "ui:focus-mode-toggle",
|
|
2136
|
-
SETTINGS_VISIBILITY: "ui:settings-visibility",
|
|
2137
|
-
// Settings
|
|
2138
|
-
SETTINGS_GET: "settings:get",
|
|
2139
|
-
SETTINGS_SET: "settings:set",
|
|
2140
|
-
SETTINGS_UPDATE: "settings:update",
|
|
2141
|
-
SETTINGS_HEALTH_GET: "settings:health:get",
|
|
2142
|
-
// Bookmarks
|
|
2143
|
-
BOOKMARKS_GET: "bookmarks:get",
|
|
2144
|
-
BOOKMARKS_UPDATE: "bookmarks:update",
|
|
2145
|
-
BOOKMARK_SAVE: "bookmarks:save",
|
|
2146
|
-
BOOKMARK_REMOVE: "bookmarks:remove",
|
|
2147
|
-
FOLDER_CREATE: "bookmarks:folder-create",
|
|
2148
|
-
FOLDER_REMOVE: "bookmarks:folder-remove",
|
|
2149
|
-
FOLDER_RENAME: "bookmarks:folder-rename",
|
|
2150
|
-
// Highlights
|
|
2151
|
-
HIGHLIGHT_CAPTURE: "highlights:capture",
|
|
2152
|
-
HIGHLIGHT_CAPTURE_RESULT: "highlights:capture-result",
|
|
2153
|
-
HIGHLIGHT_SELECTION: "vessel:highlight-selection",
|
|
2154
|
-
// DevTools panel
|
|
2155
|
-
DEVTOOLS_PANEL_TOGGLE: "devtools-panel:toggle",
|
|
2156
|
-
DEVTOOLS_PANEL_STATE: "devtools-panel:state",
|
|
2157
|
-
// Window controls
|
|
2158
|
-
WINDOW_MINIMIZE: "window:minimize",
|
|
2159
|
-
WINDOW_MAXIMIZE: "window:maximize",
|
|
2160
|
-
WINDOW_CLOSE: "window:close"
|
|
2161
|
-
};
|
|
2162
2278
|
const SEARCH_ENGINE_HOSTS = [
|
|
2163
2279
|
"google.",
|
|
2164
2280
|
"bing.com",
|
|
@@ -2756,6 +2872,14 @@ const PRELOAD_EXTRACTION_SCRIPT = String.raw`
|
|
|
2756
2872
|
`;
|
|
2757
2873
|
const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
2758
2874
|
(function() {
|
|
2875
|
+
// Time budget: stop expensive DOM traversals after this many ms so heavy
|
|
2876
|
+
// pages (Newegg, Wikipedia, etc.) don't stall the agent for 30-60s+.
|
|
2877
|
+
var BUDGET_MS = 5000;
|
|
2878
|
+
var _budgetStart = performance.now();
|
|
2879
|
+
function withinBudget() {
|
|
2880
|
+
return (performance.now() - _budgetStart) < BUDGET_MS;
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2759
2883
|
function getCleanBodyText() {
|
|
2760
2884
|
var removed = [];
|
|
2761
2885
|
document
|
|
@@ -2913,20 +3037,40 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
2913
3037
|
const viewportArea = Math.max(1, viewportWidth() * viewportHeight());
|
|
2914
3038
|
const overlays = [];
|
|
2915
3039
|
|
|
2916
|
-
|
|
3040
|
+
// Use targeted selectors instead of querySelectorAll("*") to avoid
|
|
3041
|
+
// expensive getComputedStyle/getBoundingClientRect on every DOM element.
|
|
3042
|
+
// On heavy SPAs (e.g. Newegg) the wildcard could hit 10,000+ elements.
|
|
3043
|
+
var candidates = new Set();
|
|
3044
|
+
|
|
3045
|
+
// Semantic overlays: dialogs, modals, aria-modal
|
|
3046
|
+
document.body.querySelectorAll(
|
|
3047
|
+
"dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']"
|
|
3048
|
+
).forEach(function(el) { candidates.add(el); });
|
|
3049
|
+
|
|
3050
|
+
// Fixed/sticky elements are the other overlay category — walk only
|
|
3051
|
+
// direct children of body and high-level containers (depth ≤ 3)
|
|
3052
|
+
// since real overlays are almost always near the top of the DOM tree.
|
|
3053
|
+
var MAX_CANDIDATES = 2000;
|
|
3054
|
+
var allElements = document.body.querySelectorAll("*");
|
|
3055
|
+
for (var ci = 0; ci < allElements.length && candidates.size < MAX_CANDIDATES; ci++) {
|
|
3056
|
+
candidates.add(allElements[ci]);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
candidates.forEach(function(node) {
|
|
3060
|
+
if (!withinBudget()) return;
|
|
2917
3061
|
if (!(node instanceof HTMLElement)) return;
|
|
2918
3062
|
if (!visible(node)) return;
|
|
2919
3063
|
|
|
2920
|
-
|
|
3064
|
+
var style = window.getComputedStyle(node);
|
|
2921
3065
|
if (style.pointerEvents === "none") return;
|
|
2922
3066
|
|
|
2923
|
-
|
|
3067
|
+
var rect = node.getBoundingClientRect();
|
|
2924
3068
|
if (!inViewport(rect)) return;
|
|
2925
3069
|
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
3070
|
+
var type = overlayType(node);
|
|
3071
|
+
var dialogLike = type === "dialog" || type === "modal";
|
|
3072
|
+
var areaRatio = (rect.width * rect.height) / viewportArea;
|
|
3073
|
+
var blocksInteraction = dialogLike ||
|
|
2930
3074
|
((style.position === "fixed" || style.position === "sticky") &&
|
|
2931
3075
|
parseZIndex(style) >= 10 &&
|
|
2932
3076
|
areaRatio >= 0.3 &&
|
|
@@ -2936,17 +3080,17 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
2936
3080
|
|
|
2937
3081
|
overlays.push({
|
|
2938
3082
|
element: node,
|
|
2939
|
-
type,
|
|
3083
|
+
type: type,
|
|
2940
3084
|
role: text(node.getAttribute("role")),
|
|
2941
3085
|
label: overlayLabel(node),
|
|
2942
3086
|
selector: selectorFor(node),
|
|
2943
3087
|
text: text(node.textContent)?.slice(0, 160),
|
|
2944
|
-
blocksInteraction,
|
|
3088
|
+
blocksInteraction: blocksInteraction,
|
|
2945
3089
|
zIndex: parseZIndex(style),
|
|
2946
3090
|
});
|
|
2947
3091
|
});
|
|
2948
3092
|
|
|
2949
|
-
return overlays.sort((a, b)
|
|
3093
|
+
return overlays.sort(function(a, b) {
|
|
2950
3094
|
if ((a.blocksInteraction ? 1 : 0) !== (b.blocksInteraction ? 1 : 0)) {
|
|
2951
3095
|
return (b.blocksInteraction ? 1 : 0) - (a.blocksInteraction ? 1 : 0);
|
|
2952
3096
|
}
|
|
@@ -3203,6 +3347,7 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3203
3347
|
|
|
3204
3348
|
const navigation = [];
|
|
3205
3349
|
document.querySelectorAll("nav a[href], [role='navigation'] a[href], header nav a[href]").forEach((el) => {
|
|
3350
|
+
if (!withinBudget()) return;
|
|
3206
3351
|
const item = serializeInteractive(el, "link");
|
|
3207
3352
|
if (!item.text || !item.href || item.href.startsWith("#")) return;
|
|
3208
3353
|
item.context = "nav";
|
|
@@ -3218,14 +3363,17 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3218
3363
|
|
|
3219
3364
|
const interactiveElements = [];
|
|
3220
3365
|
document.querySelectorAll("button, [role='button'], input[type='submit'], input[type='button']").forEach((el) => {
|
|
3366
|
+
if (!withinBudget()) return;
|
|
3221
3367
|
interactiveElements.push(serializeInteractive(el, "button"));
|
|
3222
3368
|
});
|
|
3223
3369
|
document.querySelectorAll("a[href]").forEach((el) => {
|
|
3370
|
+
if (!withinBudget()) return;
|
|
3224
3371
|
const item = serializeInteractive(el, "link");
|
|
3225
3372
|
if (!item.text || !item.href || item.href.startsWith("#") || item.context === "nav") return;
|
|
3226
3373
|
interactiveElements.push(item);
|
|
3227
3374
|
});
|
|
3228
3375
|
document.querySelectorAll("input:not([type='hidden']):not([type='submit']):not([type='button']), select, textarea").forEach((el) => {
|
|
3376
|
+
if (!withinBudget()) return;
|
|
3229
3377
|
const tag = el.tagName.toLowerCase();
|
|
3230
3378
|
interactiveElements.push(
|
|
3231
3379
|
serializeInteractive(el, tag === "select" ? "select" : tag === "textarea" ? "textarea" : "input"),
|
|
@@ -3250,7 +3398,7 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
3250
3398
|
serializeInteractive(el, tag === "select" ? "select" : tag === "textarea" ? "textarea" : "input"),
|
|
3251
3399
|
);
|
|
3252
3400
|
});
|
|
3253
|
-
Array.from(
|
|
3401
|
+
Array.from(form.querySelectorAll("button, input[type='submit'], input[type='image']"))
|
|
3254
3402
|
.filter((el) => isSubmitControlForForm(el, form))
|
|
3255
3403
|
.forEach((el) => {
|
|
3256
3404
|
fields.push(serializeInteractive(el, "button"));
|
|
@@ -3486,6 +3634,7 @@ const SAFE_EXTRACTION_SCRIPT = String.raw`
|
|
|
3486
3634
|
function delay(ms) {
|
|
3487
3635
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3488
3636
|
}
|
|
3637
|
+
const EXECUTE_SCRIPT_TIMEOUT_MS = 1500;
|
|
3489
3638
|
async function waitForDomReady(webContents, timeoutMs = 1500) {
|
|
3490
3639
|
const deadline = Date.now() + timeoutMs;
|
|
3491
3640
|
while (Date.now() < deadline) {
|
|
@@ -3507,10 +3656,20 @@ async function executeScript(webContents, script) {
|
|
|
3507
3656
|
if (webContents.isDestroyed()) {
|
|
3508
3657
|
return null;
|
|
3509
3658
|
}
|
|
3659
|
+
let timer = null;
|
|
3510
3660
|
try {
|
|
3511
|
-
return await
|
|
3661
|
+
return await Promise.race([
|
|
3662
|
+
webContents.executeJavaScript(script),
|
|
3663
|
+
new Promise((resolve) => {
|
|
3664
|
+
timer = setTimeout(() => resolve(null), EXECUTE_SCRIPT_TIMEOUT_MS);
|
|
3665
|
+
})
|
|
3666
|
+
]);
|
|
3512
3667
|
} catch {
|
|
3513
3668
|
return null;
|
|
3669
|
+
} finally {
|
|
3670
|
+
if (typeof timer !== "undefined" && timer) {
|
|
3671
|
+
clearTimeout(timer);
|
|
3672
|
+
}
|
|
3514
3673
|
}
|
|
3515
3674
|
}
|
|
3516
3675
|
function bestString(values) {
|
|
@@ -3591,18 +3750,30 @@ function mergePageContent(candidates, webContents) {
|
|
|
3591
3750
|
url: mergedBase.url || webContents.getURL() || ""
|
|
3592
3751
|
};
|
|
3593
3752
|
}
|
|
3753
|
+
const EXTRACT_TIMEOUT_MS = 8e3;
|
|
3754
|
+
async function extractContentInner(webContents) {
|
|
3755
|
+
await waitForDomReady(webContents);
|
|
3756
|
+
const [preloadResult, directResult, safeResult] = await Promise.all([
|
|
3757
|
+
executeScript(webContents, PRELOAD_EXTRACTION_SCRIPT),
|
|
3758
|
+
executeScript(webContents, DIRECT_EXTRACTION_SCRIPT),
|
|
3759
|
+
executeScript(webContents, SAFE_EXTRACTION_SCRIPT)
|
|
3760
|
+
]);
|
|
3761
|
+
return mergePageContent(
|
|
3762
|
+
[preloadResult, directResult, safeResult],
|
|
3763
|
+
webContents
|
|
3764
|
+
);
|
|
3765
|
+
}
|
|
3594
3766
|
async function extractContent(webContents) {
|
|
3595
3767
|
try {
|
|
3596
|
-
await
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3768
|
+
return await Promise.race([
|
|
3769
|
+
extractContentInner(webContents),
|
|
3770
|
+
new Promise(
|
|
3771
|
+
(_, reject) => setTimeout(
|
|
3772
|
+
() => reject(new Error("extractContent timeout")),
|
|
3773
|
+
EXTRACT_TIMEOUT_MS
|
|
3774
|
+
)
|
|
3775
|
+
)
|
|
3601
3776
|
]);
|
|
3602
|
-
return mergePageContent(
|
|
3603
|
-
[preloadResult, directResult, safeResult],
|
|
3604
|
-
webContents
|
|
3605
|
-
);
|
|
3606
3777
|
} catch {
|
|
3607
3778
|
return {
|
|
3608
3779
|
...EMPTY_PAGE_CONTENT,
|
|
@@ -3833,6 +4004,10 @@ class AnthropicProvider {
|
|
|
3833
4004
|
let iterationsUsed = 0;
|
|
3834
4005
|
for (let i = 0; i < maxIterations; i++) {
|
|
3835
4006
|
iterationsUsed = i + 1;
|
|
4007
|
+
const msgTokenEstimate = JSON.stringify(messages).length;
|
|
4008
|
+
const sysTokenEstimate = systemPrompt.length;
|
|
4009
|
+
console.log(`[Vessel Agent] iteration=${i} messages=${messages.length} msgChars=${msgTokenEstimate} sysChars=${sysTokenEstimate} tools=${tools.length}`);
|
|
4010
|
+
const streamStartTime = Date.now();
|
|
3836
4011
|
const stream = this.client.messages.stream(
|
|
3837
4012
|
{
|
|
3838
4013
|
model: this.model,
|
|
@@ -3846,40 +4021,56 @@ class AnthropicProvider {
|
|
|
3846
4021
|
let textContent = "";
|
|
3847
4022
|
const toolUseBlocks = [];
|
|
3848
4023
|
let currentToolUse = null;
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
4024
|
+
const STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
4025
|
+
let idleTimer = null;
|
|
4026
|
+
const resetIdleTimer = () => {
|
|
4027
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
4028
|
+
idleTimer = setTimeout(() => {
|
|
4029
|
+
this.abortController?.abort();
|
|
4030
|
+
}, STREAM_IDLE_TIMEOUT_MS);
|
|
4031
|
+
};
|
|
4032
|
+
resetIdleTimer();
|
|
4033
|
+
try {
|
|
4034
|
+
for await (const event of stream) {
|
|
4035
|
+
resetIdleTimer();
|
|
4036
|
+
if (event.type === "content_block_start") {
|
|
4037
|
+
if (event.content_block.type === "tool_use") {
|
|
4038
|
+
currentToolUse = {
|
|
4039
|
+
id: event.content_block.id,
|
|
4040
|
+
name: event.content_block.name,
|
|
4041
|
+
inputJson: ""
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
4044
|
+
} else if (event.type === "content_block_delta") {
|
|
4045
|
+
if (event.delta.type === "text_delta") {
|
|
4046
|
+
textContent += event.delta.text;
|
|
4047
|
+
onChunk(event.delta.text);
|
|
4048
|
+
} else if (event.delta.type === "input_json_delta" && currentToolUse) {
|
|
4049
|
+
currentToolUse.inputJson += event.delta.partial_json;
|
|
4050
|
+
}
|
|
4051
|
+
} else if (event.type === "content_block_stop" && currentToolUse) {
|
|
4052
|
+
try {
|
|
4053
|
+
toolUseBlocks.push({
|
|
4054
|
+
id: currentToolUse.id,
|
|
4055
|
+
name: currentToolUse.name,
|
|
4056
|
+
input: JSON.parse(currentToolUse.inputJson || "{}")
|
|
4057
|
+
});
|
|
4058
|
+
} catch {
|
|
4059
|
+
toolUseBlocks.push({
|
|
4060
|
+
id: currentToolUse.id,
|
|
4061
|
+
name: currentToolUse.name,
|
|
4062
|
+
input: {}
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
currentToolUse = null;
|
|
3878
4066
|
}
|
|
3879
|
-
currentToolUse = null;
|
|
3880
4067
|
}
|
|
4068
|
+
} finally {
|
|
4069
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
3881
4070
|
}
|
|
4071
|
+
console.log(`[Vessel Agent] stream complete in ${Date.now() - streamStartTime}ms, toolCalls=${toolUseBlocks.length} textLen=${textContent.length}`);
|
|
3882
4072
|
const finalMessage = await stream.finalMessage();
|
|
4073
|
+
console.log(`[Vessel Agent] finalMessage received, stop_reason=${finalMessage.stop_reason}`);
|
|
3883
4074
|
const assistantContent = [];
|
|
3884
4075
|
if (textContent) {
|
|
3885
4076
|
assistantContent.push({ type: "text", text: textContent });
|
|
@@ -3902,7 +4093,15 @@ class AnthropicProvider {
|
|
|
3902
4093
|
onChunk(`
|
|
3903
4094
|
<<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
|
|
3904
4095
|
`);
|
|
3905
|
-
|
|
4096
|
+
let result;
|
|
4097
|
+
const toolStartTime = Date.now();
|
|
4098
|
+
console.log(`[Vessel Agent] executing tool: ${tb.name}`);
|
|
4099
|
+
try {
|
|
4100
|
+
result = await onToolCall(tb.name, tb.input);
|
|
4101
|
+
} catch (toolErr) {
|
|
4102
|
+
result = `Error: Tool execution failed — ${toolErr.message || toolErr}. Try a different approach or call read_page to refresh context.`;
|
|
4103
|
+
}
|
|
4104
|
+
console.log(`[Vessel Agent] tool ${tb.name} completed in ${Date.now() - toolStartTime}ms, resultLen=${result.length}`);
|
|
3906
4105
|
toolResults.push({
|
|
3907
4106
|
type: "tool_result",
|
|
3908
4107
|
tool_use_id: tb.id,
|
|
@@ -4091,6 +4290,9 @@ class OpenAICompatProvider {
|
|
|
4091
4290
|
let iterationsUsed = 0;
|
|
4092
4291
|
for (let i = 0; i < maxIterations; i++) {
|
|
4093
4292
|
iterationsUsed = i + 1;
|
|
4293
|
+
const msgTokenEstimate = JSON.stringify(messages).length;
|
|
4294
|
+
console.log(`[Vessel Agent OpenAI] iteration=${i} messages=${messages.length} msgChars=${msgTokenEstimate} tools=${openAITools.length}`);
|
|
4295
|
+
const streamStartTime = Date.now();
|
|
4094
4296
|
let textAccum = "";
|
|
4095
4297
|
const toolCallAccums = {};
|
|
4096
4298
|
let finishReason = null;
|
|
@@ -4125,10 +4327,21 @@ class OpenAICompatProvider {
|
|
|
4125
4327
|
}
|
|
4126
4328
|
}
|
|
4127
4329
|
}
|
|
4330
|
+
console.log(`[Vessel Agent OpenAI] stream complete in ${Date.now() - streamStartTime}ms, toolCalls=${Object.keys(toolCallAccums).length} textLen=${textAccum.length} finishReason=${finishReason}`);
|
|
4128
4331
|
const toolCalls = Object.values(toolCallAccums);
|
|
4332
|
+
for (const tc of Object.values(toolCallAccums)) {
|
|
4333
|
+
if (!tc.id) tc.id = `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
4334
|
+
}
|
|
4335
|
+
for (const tc of toolCalls) {
|
|
4336
|
+
try {
|
|
4337
|
+
JSON.parse(tc.argsJson || "{}");
|
|
4338
|
+
} catch {
|
|
4339
|
+
tc.argsJson = "{}";
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4129
4342
|
const assistantMsg = {
|
|
4130
4343
|
role: "assistant",
|
|
4131
|
-
content: textAccum ||
|
|
4344
|
+
content: textAccum || "",
|
|
4132
4345
|
...toolCalls.length > 0 && {
|
|
4133
4346
|
tool_calls: toolCalls.map((tc) => ({
|
|
4134
4347
|
id: tc.id,
|
|
@@ -4144,12 +4357,29 @@ class OpenAICompatProvider {
|
|
|
4144
4357
|
try {
|
|
4145
4358
|
args = JSON.parse(tc.argsJson || "{}");
|
|
4146
4359
|
} catch {
|
|
4360
|
+
onChunk(`
|
|
4361
|
+
<<tool:${tc.name}:⚠ invalid args>>
|
|
4362
|
+
`);
|
|
4363
|
+
messages.push({
|
|
4364
|
+
role: "tool",
|
|
4365
|
+
tool_call_id: tc.id,
|
|
4366
|
+
content: `Error: Invalid JSON in tool arguments. Please retry with valid JSON. Raw: ${tc.argsJson}`
|
|
4367
|
+
});
|
|
4368
|
+
continue;
|
|
4147
4369
|
}
|
|
4148
4370
|
const argSummary = args.url || args.text || args.direction || "";
|
|
4149
4371
|
onChunk(`
|
|
4150
4372
|
<<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
|
|
4151
4373
|
`);
|
|
4152
|
-
|
|
4374
|
+
let result;
|
|
4375
|
+
const toolStartTime = Date.now();
|
|
4376
|
+
console.log(`[Vessel Agent OpenAI] executing tool: ${tc.name}`);
|
|
4377
|
+
try {
|
|
4378
|
+
result = await onToolCall(tc.name, args);
|
|
4379
|
+
} catch (toolErr) {
|
|
4380
|
+
result = `Error: Tool execution failed — ${toolErr.message || toolErr}. Try a different approach or call read_page to refresh context.`;
|
|
4381
|
+
}
|
|
4382
|
+
console.log(`[Vessel Agent OpenAI] tool ${tc.name} completed in ${Date.now() - toolStartTime}ms, resultLen=${result.length}`);
|
|
4153
4383
|
messages.push({
|
|
4154
4384
|
role: "tool",
|
|
4155
4385
|
tool_call_id: tc.id,
|
|
@@ -4320,7 +4550,20 @@ function isVisibleToUser(el) {
|
|
|
4320
4550
|
}
|
|
4321
4551
|
function formatInteractiveElements(elements) {
|
|
4322
4552
|
if (elements.length === 0) return "None";
|
|
4323
|
-
const
|
|
4553
|
+
const sorted = [...elements].sort((a, b) => {
|
|
4554
|
+
const scoreEl = (el) => {
|
|
4555
|
+
let s = 0;
|
|
4556
|
+
if (el.visible === false) s += 100;
|
|
4557
|
+
if (el.inViewport === false) s += 50;
|
|
4558
|
+
if (el.context === "nav" || el.context === "footer" || el.context === "sidebar")
|
|
4559
|
+
s += 30;
|
|
4560
|
+
if (el.obscured) s += 20;
|
|
4561
|
+
if (el.type === "link") s += 5;
|
|
4562
|
+
return s;
|
|
4563
|
+
};
|
|
4564
|
+
return scoreEl(a) - scoreEl(b);
|
|
4565
|
+
});
|
|
4566
|
+
const items = limitItems(sorted, 50);
|
|
4324
4567
|
return items.map((el) => {
|
|
4325
4568
|
const prefix = el.index ? `[#${el.index}]` : "-";
|
|
4326
4569
|
const parts = [prefix];
|
|
@@ -4486,7 +4729,10 @@ function formatStructuredEntities(entities) {
|
|
|
4486
4729
|
if (entity.url && entity.url !== entity.name) {
|
|
4487
4730
|
lines.push(` url: ${entity.url}`);
|
|
4488
4731
|
}
|
|
4489
|
-
for (const [key, value] of Object.entries(entity.attributes).slice(
|
|
4732
|
+
for (const [key, value] of Object.entries(entity.attributes).slice(
|
|
4733
|
+
0,
|
|
4734
|
+
8
|
|
4735
|
+
)) {
|
|
4490
4736
|
const rendered = formatStructuredValue(value);
|
|
4491
4737
|
if (rendered) {
|
|
4492
4738
|
lines.push(` ${key}: ${rendered}`);
|
|
@@ -4529,10 +4775,34 @@ function getHighlightsForPage(url) {
|
|
|
4529
4775
|
function formatJsonLd(items) {
|
|
4530
4776
|
if (!items || items.length === 0) return "";
|
|
4531
4777
|
const lines = [];
|
|
4532
|
-
const SKIP = /* @__PURE__ */ new Set([
|
|
4778
|
+
const SKIP = /* @__PURE__ */ new Set([
|
|
4779
|
+
"@context",
|
|
4780
|
+
"image",
|
|
4781
|
+
"logo",
|
|
4782
|
+
"thumbnail",
|
|
4783
|
+
"potentialAction"
|
|
4784
|
+
]);
|
|
4533
4785
|
const TYPE_FIELDS = {
|
|
4534
|
-
Recipe: [
|
|
4535
|
-
|
|
4786
|
+
Recipe: [
|
|
4787
|
+
"name",
|
|
4788
|
+
"url",
|
|
4789
|
+
"description",
|
|
4790
|
+
"recipeYield",
|
|
4791
|
+
"totalTime",
|
|
4792
|
+
"cookTime",
|
|
4793
|
+
"prepTime",
|
|
4794
|
+
"recipeIngredient",
|
|
4795
|
+
"recipeInstructions"
|
|
4796
|
+
],
|
|
4797
|
+
Article: [
|
|
4798
|
+
"headline",
|
|
4799
|
+
"name",
|
|
4800
|
+
"url",
|
|
4801
|
+
"datePublished",
|
|
4802
|
+
"dateModified",
|
|
4803
|
+
"author",
|
|
4804
|
+
"description"
|
|
4805
|
+
],
|
|
4536
4806
|
Product: ["name", "url", "description", "offers"],
|
|
4537
4807
|
BreadcrumbList: ["itemListElement"],
|
|
4538
4808
|
Organization: ["name", "url", "description"]
|
|
@@ -4545,9 +4815,11 @@ function formatJsonLd(items) {
|
|
|
4545
4815
|
const renderValue = (val, depth = 0) => {
|
|
4546
4816
|
if (val === null || val === void 0) return "";
|
|
4547
4817
|
if (typeof val === "string") return val;
|
|
4548
|
-
if (typeof val === "number" || typeof val === "boolean")
|
|
4818
|
+
if (typeof val === "number" || typeof val === "boolean")
|
|
4819
|
+
return String(val);
|
|
4549
4820
|
if (Array.isArray(val)) {
|
|
4550
|
-
if (depth > 0)
|
|
4821
|
+
if (depth > 0)
|
|
4822
|
+
return val.map((v) => renderValue(v, depth + 1)).filter(Boolean).join(", ");
|
|
4551
4823
|
return val.map((v, i) => {
|
|
4552
4824
|
const s = renderValue(v, depth + 1);
|
|
4553
4825
|
return s ? ` ${i + 1}. ${s}` : "";
|
|
@@ -4577,6 +4849,24 @@ function formatJsonLd(items) {
|
|
|
4577
4849
|
}
|
|
4578
4850
|
return lines.join("\n");
|
|
4579
4851
|
}
|
|
4852
|
+
function chooseAgentReadMode(page) {
|
|
4853
|
+
const pageType = detectPageType(page);
|
|
4854
|
+
switch (pageType) {
|
|
4855
|
+
case "SEARCH_RESULTS":
|
|
4856
|
+
case "PAGINATED_LIST":
|
|
4857
|
+
return "results_only";
|
|
4858
|
+
case "LOGIN":
|
|
4859
|
+
case "SEARCH_READY":
|
|
4860
|
+
case "SHOPPING":
|
|
4861
|
+
case "FORM":
|
|
4862
|
+
return "visible_only";
|
|
4863
|
+
case "ARTICLE":
|
|
4864
|
+
return "summary";
|
|
4865
|
+
case "GENERAL":
|
|
4866
|
+
default:
|
|
4867
|
+
return "visible_only";
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4580
4870
|
function normalizeComparable(value) {
|
|
4581
4871
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().replace(/\s+/g, " ");
|
|
4582
4872
|
}
|
|
@@ -4621,7 +4911,9 @@ function collectJsonLdEntityItems(input, results = []) {
|
|
|
4621
4911
|
const item = input;
|
|
4622
4912
|
const type = item["@type"];
|
|
4623
4913
|
const types = Array.isArray(type) ? type : [type];
|
|
4624
|
-
const typeNames = types.filter(
|
|
4914
|
+
const typeNames = types.filter(
|
|
4915
|
+
(entry) => typeof entry === "string"
|
|
4916
|
+
);
|
|
4625
4917
|
if ((typeof item.name === "string" || typeof item.url === "string") && !typeNames.some(
|
|
4626
4918
|
(entry) => ["BreadcrumbList", "Organization", "WebSite", "WebPage"].includes(entry)
|
|
4627
4919
|
)) {
|
|
@@ -4666,7 +4958,8 @@ function getResultCandidates(page) {
|
|
|
4666
4958
|
score += 4;
|
|
4667
4959
|
}
|
|
4668
4960
|
if (element.context === "article") score += 3;
|
|
4669
|
-
else if (element.context === "main" || element.context === "content")
|
|
4961
|
+
else if (element.context === "main" || element.context === "content")
|
|
4962
|
+
score += 1;
|
|
4670
4963
|
if (href && pageHost) {
|
|
4671
4964
|
try {
|
|
4672
4965
|
if (new URL(href).origin === new URL(pageHost).origin) score += 1;
|
|
@@ -4680,7 +4973,9 @@ function getResultCandidates(page) {
|
|
|
4680
4973
|
score += 2;
|
|
4681
4974
|
}
|
|
4682
4975
|
if (/\b(card|tile|result|rating|review)\b/.test(haystack)) score += 1;
|
|
4683
|
-
if (/\b(item|list|row|repo|repository|issue|pull request|event)\b/.test(
|
|
4976
|
+
if (/\b(item|list|row|repo|repository|issue|pull request|event)\b/.test(
|
|
4977
|
+
haystack
|
|
4978
|
+
)) {
|
|
4684
4979
|
score += 1;
|
|
4685
4980
|
}
|
|
4686
4981
|
if (text.length >= 12 && text.split(/\s+/).length >= 2) score += 1;
|
|
@@ -4699,7 +4994,9 @@ function getResultCandidates(page) {
|
|
|
4699
4994
|
return score >= 4 || score >= 3 && (element.context === "article" || element.context === "main" || element.context === "content");
|
|
4700
4995
|
}
|
|
4701
4996
|
return score >= 4 || score >= 3 && element.context === "article";
|
|
4702
|
-
}).sort(
|
|
4997
|
+
}).sort(
|
|
4998
|
+
(a, b) => b.score - a.score || (a.element.index ?? 0) - (b.element.index ?? 0)
|
|
4999
|
+
);
|
|
4703
5000
|
const seen = /* @__PURE__ */ new Set();
|
|
4704
5001
|
return scored.map(({ element }) => element).filter((element) => {
|
|
4705
5002
|
const key = `${normalizeComparable(element.text || "")}|${normalizeUrlForMatch(element.href) || ""}`;
|
|
@@ -4952,7 +5249,9 @@ function buildScopedContext(page, mode) {
|
|
|
4952
5249
|
sections.push(`### Likely Search Results (${resultElements.length})`);
|
|
4953
5250
|
sections.push(formatInteractiveElements(resultElements));
|
|
4954
5251
|
} else {
|
|
4955
|
-
sections.push(
|
|
5252
|
+
sections.push(
|
|
5253
|
+
"No likely primary result links were detected on this page."
|
|
5254
|
+
);
|
|
4956
5255
|
}
|
|
4957
5256
|
return sections.join("\n");
|
|
4958
5257
|
}
|
|
@@ -4961,10 +5260,8 @@ function buildScopedContext(page, mode) {
|
|
|
4961
5260
|
return buildStructuredContext(page);
|
|
4962
5261
|
}
|
|
4963
5262
|
}
|
|
4964
|
-
function
|
|
4965
|
-
const hints = [];
|
|
5263
|
+
function detectPageType(page) {
|
|
4966
5264
|
const url = page.url.toLowerCase();
|
|
4967
|
-
(page.title || "").toLowerCase();
|
|
4968
5265
|
const hasPasswordField = page.forms.some(
|
|
4969
5266
|
(f) => f.fields.some((el) => el.inputType === "password")
|
|
4970
5267
|
);
|
|
@@ -4983,47 +5280,85 @@ function analyzePageIntent(page) {
|
|
|
4983
5280
|
const hasPagination = page.interactiveElements.some(
|
|
4984
5281
|
(el) => (el.text || "").toLowerCase() === "next" || el.text === "›" || el.text === "»" || (el.label || "").toLowerCase().includes("next page")
|
|
4985
5282
|
);
|
|
4986
|
-
if (hasPasswordField)
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
} else if (hasSearchInput && !hasResults) {
|
|
4996
|
-
hints.push("Page type: SEARCH READY");
|
|
4997
|
-
hints.push("Suggested: vessel_search → auto-finds search box, types query, and submits");
|
|
4998
|
-
} else if (hasResults && hasSearchInput) {
|
|
4999
|
-
hints.push("Page type: SEARCH RESULTS");
|
|
5000
|
-
hints.push("Suggested: click a result link, or vessel_paginate for more results");
|
|
5001
|
-
if (hasPagination) hints.push("Pagination detected — vessel_paginate available");
|
|
5002
|
-
} else if (hasCart) {
|
|
5003
|
-
hints.push("Page type: SHOPPING/CHECKOUT");
|
|
5004
|
-
hints.push("Suggested: vessel_fill_form for payment/address fields");
|
|
5005
|
-
} else if (formCount > 0 && !hasPasswordField) {
|
|
5006
|
-
const totalFields = page.forms.reduce((n, f) => n + f.fields.length, 0);
|
|
5007
|
-
hints.push(`Page type: FORM (${formCount} form${formCount > 1 ? "s" : ""}, ${totalFields} fields)`);
|
|
5008
|
-
hints.push("Suggested: vessel_fill_form → fill all fields in one call");
|
|
5009
|
-
} else if (hasPagination) {
|
|
5010
|
-
hints.push("Page type: PAGINATED LIST");
|
|
5011
|
-
hints.push("Suggested: vessel_paginate to navigate between pages");
|
|
5012
|
-
} else if (page.content.length > 3e3 && page.interactiveElements.length < 10) {
|
|
5013
|
-
hints.push("Page type: ARTICLE/CONTENT");
|
|
5014
|
-
hints.push("Suggested: vessel_extract_content for readable text");
|
|
5015
|
-
}
|
|
5016
|
-
if (hints.length === 0) return "";
|
|
5017
|
-
return `### Page Intent (Speedee)
|
|
5018
|
-
${hints.join("\n")}`;
|
|
5283
|
+
if (hasPasswordField) return "LOGIN";
|
|
5284
|
+
if (hasSearchInput && !hasResults) return "SEARCH_READY";
|
|
5285
|
+
if (hasResults && hasSearchInput) return "SEARCH_RESULTS";
|
|
5286
|
+
if (hasCart) return "SHOPPING";
|
|
5287
|
+
if (formCount > 0 && !hasPasswordField) return "FORM";
|
|
5288
|
+
if (hasPagination) return "PAGINATED_LIST";
|
|
5289
|
+
if (page.content.length > 3e3 && page.interactiveElements.length < 10)
|
|
5290
|
+
return "ARTICLE";
|
|
5291
|
+
return "GENERAL";
|
|
5019
5292
|
}
|
|
5020
|
-
function
|
|
5021
|
-
const
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
)
|
|
5293
|
+
function analyzePageIntent(page) {
|
|
5294
|
+
const hints = [];
|
|
5295
|
+
const pageType = detectPageType(page);
|
|
5296
|
+
const hasPagination = page.interactiveElements.some(
|
|
5297
|
+
(el) => (el.text || "").toLowerCase() === "next" || el.text === "›" || el.text === "»" || (el.label || "").toLowerCase().includes("next page")
|
|
5298
|
+
);
|
|
5299
|
+
switch (pageType) {
|
|
5300
|
+
case "LOGIN": {
|
|
5301
|
+
hints.push("Page type: LOGIN/SIGNUP");
|
|
5302
|
+
hints.push(
|
|
5303
|
+
"Suggested: vessel_login or vessel_fill_form → auto-fills credentials and submits"
|
|
5304
|
+
);
|
|
5305
|
+
const userField = page.forms.flatMap((f) => f.fields).find(
|
|
5306
|
+
(el) => el.inputType === "email" || el.name === "email" || el.name === "username" || el.autocomplete === "username"
|
|
5307
|
+
);
|
|
5308
|
+
if (userField) {
|
|
5309
|
+
hints.push(
|
|
5310
|
+
`Username field: #${userField.index} [${userField.label || userField.name || userField.placeholder || "input"}]`
|
|
5311
|
+
);
|
|
5312
|
+
}
|
|
5313
|
+
break;
|
|
5314
|
+
}
|
|
5315
|
+
case "SEARCH_READY":
|
|
5316
|
+
hints.push("Page type: SEARCH READY");
|
|
5317
|
+
hints.push(
|
|
5318
|
+
"Suggested: vessel_search → auto-finds search box, types query, and submits"
|
|
5319
|
+
);
|
|
5320
|
+
break;
|
|
5321
|
+
case "SEARCH_RESULTS":
|
|
5322
|
+
hints.push("Page type: SEARCH RESULTS");
|
|
5323
|
+
hints.push(
|
|
5324
|
+
"Suggested: click a result link, or vessel_paginate for more results"
|
|
5325
|
+
);
|
|
5326
|
+
if (hasPagination)
|
|
5327
|
+
hints.push("Pagination detected — vessel_paginate available");
|
|
5328
|
+
break;
|
|
5329
|
+
case "SHOPPING":
|
|
5330
|
+
hints.push("Page type: SHOPPING/CHECKOUT");
|
|
5331
|
+
hints.push("Suggested: vessel_fill_form for payment/address fields");
|
|
5332
|
+
break;
|
|
5333
|
+
case "FORM": {
|
|
5334
|
+
const formCount = page.forms.length;
|
|
5335
|
+
const totalFields = page.forms.reduce((n, f) => n + f.fields.length, 0);
|
|
5336
|
+
hints.push(
|
|
5337
|
+
`Page type: FORM (${formCount} form${formCount > 1 ? "s" : ""}, ${totalFields} fields)`
|
|
5338
|
+
);
|
|
5339
|
+
hints.push("Suggested: vessel_fill_form → fill all fields in one call");
|
|
5340
|
+
break;
|
|
5341
|
+
}
|
|
5342
|
+
case "PAGINATED_LIST":
|
|
5343
|
+
hints.push("Page type: PAGINATED LIST");
|
|
5344
|
+
hints.push("Suggested: vessel_paginate to navigate between pages");
|
|
5345
|
+
break;
|
|
5346
|
+
case "ARTICLE":
|
|
5347
|
+
hints.push("Page type: ARTICLE/CONTENT");
|
|
5348
|
+
hints.push("Suggested: vessel_extract_content for readable text");
|
|
5349
|
+
break;
|
|
5350
|
+
}
|
|
5351
|
+
if (hints.length === 0) return "";
|
|
5352
|
+
return `### Page Intent (Speedee)
|
|
5353
|
+
${hints.join("\n")}`;
|
|
5354
|
+
}
|
|
5355
|
+
function buildStructuredContext(page) {
|
|
5356
|
+
const sections = [];
|
|
5357
|
+
sections.push("## PAGE STRUCTURE");
|
|
5358
|
+
sections.push("");
|
|
5359
|
+
sections.push(
|
|
5360
|
+
"**User Focus:** This page is from the active tab currently visible to the human user."
|
|
5361
|
+
);
|
|
5027
5362
|
sections.push(`**URL:** ${page.url}`);
|
|
5028
5363
|
sections.push(`**Title:** ${page.title}`);
|
|
5029
5364
|
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
@@ -5151,12 +5486,14 @@ const TOOL_DEFINITIONS = [
|
|
|
5151
5486
|
{
|
|
5152
5487
|
name: "current_tab",
|
|
5153
5488
|
title: "Get Active Tab",
|
|
5154
|
-
description: "Get the browser tab the human is actively looking at right now. Use this instead of list_tabs when you only need the focused tab."
|
|
5489
|
+
description: "Get the browser tab the human is actively looking at right now. Use this instead of list_tabs when you only need the focused tab.",
|
|
5490
|
+
tier: 0
|
|
5155
5491
|
},
|
|
5156
5492
|
{
|
|
5157
5493
|
name: "list_tabs",
|
|
5158
5494
|
title: "List Tabs",
|
|
5159
|
-
description: "List all open browser tabs with their IDs, titles, and URLs."
|
|
5495
|
+
description: "List all open browser tabs with their IDs, titles, and URLs.",
|
|
5496
|
+
tier: 2
|
|
5160
5497
|
},
|
|
5161
5498
|
{
|
|
5162
5499
|
name: "switch_tab",
|
|
@@ -5164,10 +5501,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5164
5501
|
description: "Switch to a browser tab by tab ID, or by matching part of the title or URL.",
|
|
5165
5502
|
inputSchema: {
|
|
5166
5503
|
tabId: zod.z.string().optional().describe("Exact tab ID to switch to"),
|
|
5167
|
-
match: zod.z.string().optional().describe(
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
}
|
|
5504
|
+
match: zod.z.string().optional().describe("Case-insensitive partial match against tab title or URL")
|
|
5505
|
+
},
|
|
5506
|
+
tier: 2
|
|
5171
5507
|
},
|
|
5172
5508
|
{
|
|
5173
5509
|
name: "create_tab",
|
|
@@ -5175,7 +5511,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5175
5511
|
description: "Open a new browser tab, optionally navigating to a URL.",
|
|
5176
5512
|
inputSchema: {
|
|
5177
5513
|
url: zod.z.string().optional().describe("Optional URL to open")
|
|
5178
|
-
}
|
|
5514
|
+
},
|
|
5515
|
+
tier: 2
|
|
5179
5516
|
},
|
|
5180
5517
|
// --- Navigation ---
|
|
5181
5518
|
{
|
|
@@ -5184,22 +5521,26 @@ const TOOL_DEFINITIONS = [
|
|
|
5184
5521
|
description: "Navigate the browser to a URL.",
|
|
5185
5522
|
inputSchema: {
|
|
5186
5523
|
url: zod.z.string().describe("The URL to navigate to")
|
|
5187
|
-
}
|
|
5524
|
+
},
|
|
5525
|
+
tier: 0
|
|
5188
5526
|
},
|
|
5189
5527
|
{
|
|
5190
5528
|
name: "go_back",
|
|
5191
5529
|
title: "Go Back",
|
|
5192
|
-
description: "Go back to the previous page in browser history."
|
|
5530
|
+
description: "Go back to the previous page in browser history.",
|
|
5531
|
+
tier: 1
|
|
5193
5532
|
},
|
|
5194
5533
|
{
|
|
5195
5534
|
name: "go_forward",
|
|
5196
5535
|
title: "Go Forward",
|
|
5197
|
-
description: "Go forward in browser history."
|
|
5536
|
+
description: "Go forward in browser history.",
|
|
5537
|
+
tier: 2
|
|
5198
5538
|
},
|
|
5199
5539
|
{
|
|
5200
5540
|
name: "reload",
|
|
5201
5541
|
title: "Reload",
|
|
5202
|
-
description: "Reload the current page."
|
|
5542
|
+
description: "Reload the current page.",
|
|
5543
|
+
tier: 2
|
|
5203
5544
|
},
|
|
5204
5545
|
// --- Interaction ---
|
|
5205
5546
|
{
|
|
@@ -5209,7 +5550,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5209
5550
|
inputSchema: {
|
|
5210
5551
|
index: zod.z.number().optional().describe("Element index from the page content listing"),
|
|
5211
5552
|
selector: zod.z.string().optional().describe("CSS selector as fallback")
|
|
5212
|
-
}
|
|
5553
|
+
},
|
|
5554
|
+
tier: 0
|
|
5213
5555
|
},
|
|
5214
5556
|
{
|
|
5215
5557
|
name: "type_text",
|
|
@@ -5222,7 +5564,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5222
5564
|
mode: zod.z.enum(["default", "keystroke"]).optional().describe(
|
|
5223
5565
|
'"default" sets value directly. "keystroke" simulates character-by-character key events.'
|
|
5224
5566
|
)
|
|
5225
|
-
}
|
|
5567
|
+
},
|
|
5568
|
+
tier: 0,
|
|
5569
|
+
relevance: ["LOGIN", "FORM", "SEARCH_READY"]
|
|
5226
5570
|
},
|
|
5227
5571
|
{
|
|
5228
5572
|
name: "select_option",
|
|
@@ -5233,7 +5577,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5233
5577
|
selector: zod.z.string().optional().describe("CSS selector as fallback"),
|
|
5234
5578
|
label: zod.z.string().optional().describe("Visible option label to match"),
|
|
5235
5579
|
value: zod.z.string().optional().describe("Option value attribute to match")
|
|
5236
|
-
}
|
|
5580
|
+
},
|
|
5581
|
+
tier: 1,
|
|
5582
|
+
relevance: ["FORM", "SHOPPING"]
|
|
5237
5583
|
},
|
|
5238
5584
|
{
|
|
5239
5585
|
name: "submit_form",
|
|
@@ -5242,7 +5588,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5242
5588
|
inputSchema: {
|
|
5243
5589
|
index: zod.z.number().optional().describe("Index of a form field or submit button"),
|
|
5244
5590
|
selector: zod.z.string().optional().describe("Form or submit button selector")
|
|
5245
|
-
}
|
|
5591
|
+
},
|
|
5592
|
+
tier: 1,
|
|
5593
|
+
relevance: ["LOGIN", "FORM", "SEARCH_READY", "SHOPPING"]
|
|
5246
5594
|
},
|
|
5247
5595
|
{
|
|
5248
5596
|
name: "press_key",
|
|
@@ -5252,7 +5600,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5252
5600
|
key: zod.z.string().describe("Keyboard key such as Enter or Escape"),
|
|
5253
5601
|
index: zod.z.number().optional().describe("Element index to focus first"),
|
|
5254
5602
|
selector: zod.z.string().optional().describe("CSS selector to focus first")
|
|
5255
|
-
}
|
|
5603
|
+
},
|
|
5604
|
+
tier: 1
|
|
5256
5605
|
},
|
|
5257
5606
|
{
|
|
5258
5607
|
name: "scroll",
|
|
@@ -5261,7 +5610,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5261
5610
|
inputSchema: {
|
|
5262
5611
|
direction: zod.z.enum(["up", "down"]).describe("Scroll direction"),
|
|
5263
5612
|
amount: zod.z.number().optional().describe("Pixels to scroll (default 500)")
|
|
5264
|
-
}
|
|
5613
|
+
},
|
|
5614
|
+
tier: 0,
|
|
5615
|
+
relevance: ["ARTICLE", "SEARCH_RESULTS", "PAGINATED_LIST"]
|
|
5265
5616
|
},
|
|
5266
5617
|
{
|
|
5267
5618
|
name: "hover",
|
|
@@ -5270,7 +5621,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5270
5621
|
inputSchema: {
|
|
5271
5622
|
index: zod.z.number().optional().describe("Element index number"),
|
|
5272
5623
|
selector: zod.z.string().optional().describe("CSS selector as fallback")
|
|
5273
|
-
}
|
|
5624
|
+
},
|
|
5625
|
+
tier: 2
|
|
5274
5626
|
},
|
|
5275
5627
|
{
|
|
5276
5628
|
name: "focus",
|
|
@@ -5279,7 +5631,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5279
5631
|
inputSchema: {
|
|
5280
5632
|
index: zod.z.number().optional().describe("Element index number"),
|
|
5281
5633
|
selector: zod.z.string().optional().describe("CSS selector as fallback")
|
|
5282
|
-
}
|
|
5634
|
+
},
|
|
5635
|
+
tier: 2,
|
|
5636
|
+
relevance: ["FORM", "LOGIN"]
|
|
5283
5637
|
},
|
|
5284
5638
|
// --- Page & Content ---
|
|
5285
5639
|
{
|
|
@@ -5289,21 +5643,36 @@ const TOOL_DEFINITIONS = [
|
|
|
5289
5643
|
inputSchema: {
|
|
5290
5644
|
enabled: zod.z.boolean().describe("Whether ad blocking should be enabled for the tab"),
|
|
5291
5645
|
tabId: zod.z.string().optional().describe("Exact tab ID to target instead of the active tab"),
|
|
5292
|
-
match: zod.z.string().optional().describe(
|
|
5293
|
-
"Case-insensitive partial match against tab title or URL"
|
|
5294
|
-
),
|
|
5646
|
+
match: zod.z.string().optional().describe("Case-insensitive partial match against tab title or URL"),
|
|
5295
5647
|
reload: zod.z.boolean().optional().describe("Reload the tab after changing (default true)")
|
|
5296
|
-
}
|
|
5648
|
+
},
|
|
5649
|
+
tier: 2
|
|
5297
5650
|
},
|
|
5298
5651
|
{
|
|
5299
5652
|
name: "dismiss_popup",
|
|
5300
5653
|
title: "Dismiss Popup",
|
|
5301
|
-
description: "Dismiss a modal, popup, newsletter gate, cookie banner, or overlay using common close/decline actions."
|
|
5654
|
+
description: "Dismiss a modal, popup, newsletter gate, cookie banner, or overlay using common close/decline actions.",
|
|
5655
|
+
tier: 1
|
|
5302
5656
|
},
|
|
5303
5657
|
{
|
|
5304
5658
|
name: "read_page",
|
|
5305
5659
|
title: "Read Page",
|
|
5306
|
-
description: "
|
|
5660
|
+
description: "Read the current page using a scoped mode. Defaults to a minimal navigation-focused brief; use mode='debug' only when narrower modes are insufficient.",
|
|
5661
|
+
inputSchema: {
|
|
5662
|
+
mode: zod.z.enum([
|
|
5663
|
+
"summary",
|
|
5664
|
+
"interactives_only",
|
|
5665
|
+
"forms_only",
|
|
5666
|
+
"text_only",
|
|
5667
|
+
"visible_only",
|
|
5668
|
+
"results_only",
|
|
5669
|
+
"full",
|
|
5670
|
+
"debug"
|
|
5671
|
+
]).optional().describe(
|
|
5672
|
+
"Read mode: visible_only/results_only/forms_only/summary/text_only for narrow reads, full/debug for the complete page dump"
|
|
5673
|
+
)
|
|
5674
|
+
},
|
|
5675
|
+
tier: 0
|
|
5307
5676
|
},
|
|
5308
5677
|
{
|
|
5309
5678
|
name: "wait_for",
|
|
@@ -5313,7 +5682,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5313
5682
|
text: zod.z.string().optional().describe("Text that should appear in the page body"),
|
|
5314
5683
|
selector: zod.z.string().optional().describe("CSS selector that should match an element"),
|
|
5315
5684
|
timeoutMs: zod.z.number().optional().describe("Maximum time to wait in milliseconds (default 5000)")
|
|
5316
|
-
}
|
|
5685
|
+
},
|
|
5686
|
+
tier: 2
|
|
5317
5687
|
},
|
|
5318
5688
|
// --- Checkpoints & Sessions ---
|
|
5319
5689
|
{
|
|
@@ -5323,7 +5693,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5323
5693
|
inputSchema: {
|
|
5324
5694
|
name: zod.z.string().optional().describe("Short checkpoint name"),
|
|
5325
5695
|
note: zod.z.string().optional().describe("Optional note about why this checkpoint matters")
|
|
5326
|
-
}
|
|
5696
|
+
},
|
|
5697
|
+
tier: 2
|
|
5327
5698
|
},
|
|
5328
5699
|
{
|
|
5329
5700
|
name: "restore_checkpoint",
|
|
@@ -5332,7 +5703,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5332
5703
|
inputSchema: {
|
|
5333
5704
|
checkpointId: zod.z.string().optional().describe("Exact checkpoint ID"),
|
|
5334
5705
|
name: zod.z.string().optional().describe("Checkpoint name to match if ID is unknown")
|
|
5335
|
-
}
|
|
5706
|
+
},
|
|
5707
|
+
tier: 2
|
|
5336
5708
|
},
|
|
5337
5709
|
{
|
|
5338
5710
|
name: "save_session",
|
|
@@ -5340,7 +5712,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5340
5712
|
description: "Persist the current browser cookies, localStorage, and tab layout under a reusable session name.",
|
|
5341
5713
|
inputSchema: {
|
|
5342
5714
|
name: zod.z.string().describe("Session name such as github-logged-in")
|
|
5343
|
-
}
|
|
5715
|
+
},
|
|
5716
|
+
tier: 2,
|
|
5717
|
+
relevance: ["LOGIN"]
|
|
5344
5718
|
},
|
|
5345
5719
|
{
|
|
5346
5720
|
name: "load_session",
|
|
@@ -5348,12 +5722,14 @@ const TOOL_DEFINITIONS = [
|
|
|
5348
5722
|
description: "Load a previously saved named session, restoring cookies, localStorage, and saved tabs.",
|
|
5349
5723
|
inputSchema: {
|
|
5350
5724
|
name: zod.z.string().describe("Previously saved session name")
|
|
5351
|
-
}
|
|
5725
|
+
},
|
|
5726
|
+
tier: 2
|
|
5352
5727
|
},
|
|
5353
5728
|
{
|
|
5354
5729
|
name: "list_sessions",
|
|
5355
5730
|
title: "List Sessions",
|
|
5356
|
-
description: "List previously saved named browser sessions with cookie and storage counts."
|
|
5731
|
+
description: "List previously saved named browser sessions with cookie and storage counts.",
|
|
5732
|
+
tier: 2
|
|
5357
5733
|
},
|
|
5358
5734
|
{
|
|
5359
5735
|
name: "delete_session",
|
|
@@ -5361,7 +5737,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5361
5737
|
description: "Delete a previously saved named browser session.",
|
|
5362
5738
|
inputSchema: {
|
|
5363
5739
|
name: zod.z.string().describe("Saved session name to delete")
|
|
5364
|
-
}
|
|
5740
|
+
},
|
|
5741
|
+
tier: 2
|
|
5365
5742
|
},
|
|
5366
5743
|
// --- Bookmarks ---
|
|
5367
5744
|
{
|
|
@@ -5371,7 +5748,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5371
5748
|
inputSchema: {
|
|
5372
5749
|
folderId: zod.z.string().optional().describe("Exact bookmark folder ID to filter by"),
|
|
5373
5750
|
folderName: zod.z.string().optional().describe("Exact bookmark folder name to filter by")
|
|
5374
|
-
}
|
|
5751
|
+
},
|
|
5752
|
+
tier: 2
|
|
5375
5753
|
},
|
|
5376
5754
|
{
|
|
5377
5755
|
name: "search_bookmarks",
|
|
@@ -5379,7 +5757,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5379
5757
|
description: "Search bookmarks by title, URL, note, folder name, or folder summary.",
|
|
5380
5758
|
inputSchema: {
|
|
5381
5759
|
query: zod.z.string().describe("Search term to match against saved bookmarks")
|
|
5382
|
-
}
|
|
5760
|
+
},
|
|
5761
|
+
tier: 2
|
|
5383
5762
|
},
|
|
5384
5763
|
{
|
|
5385
5764
|
name: "create_bookmark_folder",
|
|
@@ -5388,7 +5767,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5388
5767
|
inputSchema: {
|
|
5389
5768
|
name: zod.z.string().describe("Folder name to create"),
|
|
5390
5769
|
summary: zod.z.string().optional().describe("Optional one-sentence summary for this folder")
|
|
5391
|
-
}
|
|
5770
|
+
},
|
|
5771
|
+
tier: 2
|
|
5392
5772
|
},
|
|
5393
5773
|
{
|
|
5394
5774
|
name: "save_bookmark",
|
|
@@ -5400,12 +5780,15 @@ const TOOL_DEFINITIONS = [
|
|
|
5400
5780
|
index: zod.z.number().optional().describe("Element index of a link to bookmark without opening"),
|
|
5401
5781
|
selector: zod.z.string().optional().describe("CSS selector of a link to bookmark without opening"),
|
|
5402
5782
|
folderId: zod.z.string().optional().describe("Folder ID to save into"),
|
|
5403
|
-
folderName: zod.z.string().optional().describe(
|
|
5783
|
+
folderName: zod.z.string().optional().describe(
|
|
5784
|
+
"Folder name to save into. Created automatically if missing."
|
|
5785
|
+
),
|
|
5404
5786
|
folderSummary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
|
|
5405
5787
|
createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
|
|
5406
5788
|
note: zod.z.string().optional().describe("Optional note about why the page was saved"),
|
|
5407
5789
|
onDuplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe("How to handle duplicate URLs in the same folder")
|
|
5408
|
-
}
|
|
5790
|
+
},
|
|
5791
|
+
tier: 1
|
|
5409
5792
|
},
|
|
5410
5793
|
{
|
|
5411
5794
|
name: "organize_bookmark",
|
|
@@ -5423,7 +5806,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5423
5806
|
createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
|
|
5424
5807
|
note: zod.z.string().optional().describe("Optional note"),
|
|
5425
5808
|
archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder')
|
|
5426
|
-
}
|
|
5809
|
+
},
|
|
5810
|
+
tier: 2
|
|
5427
5811
|
},
|
|
5428
5812
|
{
|
|
5429
5813
|
name: "archive_bookmark",
|
|
@@ -5436,7 +5820,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5436
5820
|
index: zod.z.number().optional().describe("Element index of a link to archive"),
|
|
5437
5821
|
selector: zod.z.string().optional().describe("CSS selector of a link to archive"),
|
|
5438
5822
|
note: zod.z.string().optional().describe("Optional note")
|
|
5439
|
-
}
|
|
5823
|
+
},
|
|
5824
|
+
tier: 2
|
|
5440
5825
|
},
|
|
5441
5826
|
{
|
|
5442
5827
|
name: "open_bookmark",
|
|
@@ -5445,7 +5830,8 @@ const TOOL_DEFINITIONS = [
|
|
|
5445
5830
|
inputSchema: {
|
|
5446
5831
|
bookmarkId: zod.z.string().describe("Exact bookmark ID to open"),
|
|
5447
5832
|
newTab: zod.z.boolean().optional().describe("Open in a new tab instead of the current tab")
|
|
5448
|
-
}
|
|
5833
|
+
},
|
|
5834
|
+
tier: 2
|
|
5449
5835
|
},
|
|
5450
5836
|
// --- Highlights ---
|
|
5451
5837
|
{
|
|
@@ -5457,14 +5843,19 @@ const TOOL_DEFINITIONS = [
|
|
|
5457
5843
|
selector: zod.z.string().optional().describe("CSS selector of element to highlight"),
|
|
5458
5844
|
text: zod.z.string().optional().describe("Text to find and highlight on the page (all occurrences)"),
|
|
5459
5845
|
label: zod.z.string().optional().describe("Annotation label to display near the highlight"),
|
|
5460
|
-
durationMs: zod.z.number().optional().describe(
|
|
5846
|
+
durationMs: zod.z.number().optional().describe(
|
|
5847
|
+
"Auto-clear after this many milliseconds (omit for permanent)"
|
|
5848
|
+
),
|
|
5461
5849
|
color: zod.z.enum(["yellow", "red", "green", "blue", "purple", "orange"]).optional().describe("Highlight color (default yellow)")
|
|
5462
|
-
}
|
|
5850
|
+
},
|
|
5851
|
+
tier: 1,
|
|
5852
|
+
relevance: ["ARTICLE", "SEARCH_RESULTS"]
|
|
5463
5853
|
},
|
|
5464
5854
|
{
|
|
5465
5855
|
name: "clear_highlights",
|
|
5466
5856
|
title: "Clear Highlights",
|
|
5467
|
-
description: "Remove all visual highlights from the current page."
|
|
5857
|
+
description: "Remove all visual highlights from the current page.",
|
|
5858
|
+
tier: 2
|
|
5468
5859
|
},
|
|
5469
5860
|
// --- Speedee System: Flow State ---
|
|
5470
5861
|
{
|
|
@@ -5472,11 +5863,14 @@ const TOOL_DEFINITIONS = [
|
|
|
5472
5863
|
title: "Start Workflow",
|
|
5473
5864
|
description: "Begin tracking a multi-step web workflow. Vessel will show progress after every action so you always know where you are.",
|
|
5474
5865
|
inputSchema: {
|
|
5475
|
-
goal: zod.z.string().describe(
|
|
5866
|
+
goal: zod.z.string().describe(
|
|
5867
|
+
"What this workflow accomplishes (e.g. 'Purchase item from Amazon')"
|
|
5868
|
+
),
|
|
5476
5869
|
steps: zod.z.array(zod.z.string()).describe(
|
|
5477
5870
|
"Ordered list of step labels (e.g. ['Log in', 'Search', 'Select item', 'Checkout'])"
|
|
5478
5871
|
)
|
|
5479
|
-
}
|
|
5872
|
+
},
|
|
5873
|
+
tier: 1
|
|
5480
5874
|
},
|
|
5481
5875
|
{
|
|
5482
5876
|
name: "flow_advance",
|
|
@@ -5484,23 +5878,27 @@ const TOOL_DEFINITIONS = [
|
|
|
5484
5878
|
description: "Mark the current workflow step as done and move to the next one.",
|
|
5485
5879
|
inputSchema: {
|
|
5486
5880
|
detail: zod.z.string().optional().describe("Brief note about what was accomplished")
|
|
5487
|
-
}
|
|
5881
|
+
},
|
|
5882
|
+
tier: 1
|
|
5488
5883
|
},
|
|
5489
5884
|
{
|
|
5490
5885
|
name: "flow_status",
|
|
5491
5886
|
title: "Workflow Status",
|
|
5492
|
-
description: "Check the current workflow progress."
|
|
5887
|
+
description: "Check the current workflow progress.",
|
|
5888
|
+
tier: 2
|
|
5493
5889
|
},
|
|
5494
5890
|
{
|
|
5495
5891
|
name: "flow_end",
|
|
5496
5892
|
title: "End Workflow",
|
|
5497
|
-
description: "Clear the active workflow tracker."
|
|
5893
|
+
description: "Clear the active workflow tracker.",
|
|
5894
|
+
tier: 2
|
|
5498
5895
|
},
|
|
5499
5896
|
// --- Speedee System: Suggestion Engine ---
|
|
5500
5897
|
{
|
|
5501
5898
|
name: "suggest",
|
|
5502
5899
|
title: "What Should I Do?",
|
|
5503
|
-
description: "Analyze the current page and return the most relevant tools and suggested next actions. Call this when unsure what to do."
|
|
5900
|
+
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
|
|
5504
5902
|
},
|
|
5505
5903
|
// --- Speedee System: Composable Macros ---
|
|
5506
5904
|
{
|
|
@@ -5516,7 +5914,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5516
5914
|
})
|
|
5517
5915
|
).describe("Fields to fill"),
|
|
5518
5916
|
submit: zod.z.boolean().optional().describe("Submit the form after filling (default false)")
|
|
5519
|
-
}
|
|
5917
|
+
},
|
|
5918
|
+
tier: 1,
|
|
5919
|
+
relevance: ["FORM", "LOGIN", "SHOPPING"]
|
|
5520
5920
|
},
|
|
5521
5921
|
{
|
|
5522
5922
|
name: "login",
|
|
@@ -5529,7 +5929,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5529
5929
|
username_selector: zod.z.string().optional().describe("CSS selector for username field (auto-detected if omitted)"),
|
|
5530
5930
|
password_selector: zod.z.string().optional().describe("CSS selector for password field (auto-detected if omitted)"),
|
|
5531
5931
|
submit_selector: zod.z.string().optional().describe("CSS selector for submit button (auto-detected if omitted)")
|
|
5532
|
-
}
|
|
5932
|
+
},
|
|
5933
|
+
tier: 1,
|
|
5934
|
+
relevance: ["LOGIN"]
|
|
5533
5935
|
},
|
|
5534
5936
|
{
|
|
5535
5937
|
name: "search",
|
|
@@ -5538,7 +5940,9 @@ const TOOL_DEFINITIONS = [
|
|
|
5538
5940
|
inputSchema: {
|
|
5539
5941
|
query: zod.z.string().describe("Search query text"),
|
|
5540
5942
|
selector: zod.z.string().optional().describe("CSS selector for search input (auto-detected if omitted)")
|
|
5541
|
-
}
|
|
5943
|
+
},
|
|
5944
|
+
tier: 1,
|
|
5945
|
+
relevance: ["SEARCH_READY", "SEARCH_RESULTS"]
|
|
5542
5946
|
},
|
|
5543
5947
|
{
|
|
5544
5948
|
name: "paginate",
|
|
@@ -5546,8 +5950,62 @@ const TOOL_DEFINITIONS = [
|
|
|
5546
5950
|
description: "Navigate to the next or previous page of results. Auto-detects pagination controls.",
|
|
5547
5951
|
inputSchema: {
|
|
5548
5952
|
direction: zod.z.enum(["next", "prev"]).describe("Pagination direction"),
|
|
5549
|
-
selector: zod.z.string().optional().describe(
|
|
5550
|
-
|
|
5953
|
+
selector: zod.z.string().optional().describe(
|
|
5954
|
+
"CSS selector for pagination link (auto-detected if omitted)"
|
|
5955
|
+
)
|
|
5956
|
+
},
|
|
5957
|
+
tier: 1,
|
|
5958
|
+
relevance: ["SEARCH_RESULTS", "PAGINATED_LIST"]
|
|
5959
|
+
},
|
|
5960
|
+
// --- Speedee System: Expanded Macros ---
|
|
5961
|
+
{
|
|
5962
|
+
name: "accept_cookies",
|
|
5963
|
+
title: "Accept Cookies",
|
|
5964
|
+
description: "Dismiss cookie consent banners (OneTrust, CookieBot, GDPR popups, etc.). More targeted than dismiss_popup for consent-specific overlays.",
|
|
5965
|
+
tier: 1
|
|
5966
|
+
},
|
|
5967
|
+
{
|
|
5968
|
+
name: "extract_table",
|
|
5969
|
+
title: "Extract Table",
|
|
5970
|
+
description: "Extract a table from the page as structured JSON rows. Returns column headers and cell values.",
|
|
5971
|
+
inputSchema: {
|
|
5972
|
+
index: zod.z.number().optional().describe("Element index of the table to extract"),
|
|
5973
|
+
selector: zod.z.string().optional().describe(
|
|
5974
|
+
"CSS selector for the table (auto-detected if omitted — uses first table)"
|
|
5975
|
+
)
|
|
5976
|
+
},
|
|
5977
|
+
tier: 1,
|
|
5978
|
+
relevance: ["SEARCH_RESULTS", "ARTICLE"]
|
|
5979
|
+
},
|
|
5980
|
+
{
|
|
5981
|
+
name: "scroll_to_element",
|
|
5982
|
+
title: "Scroll To Element",
|
|
5983
|
+
description: "Scroll a specific element into view by index or selector. Useful for navigating to off-screen content.",
|
|
5984
|
+
inputSchema: {
|
|
5985
|
+
index: zod.z.number().optional().describe("Element index to scroll to"),
|
|
5986
|
+
selector: zod.z.string().optional().describe("CSS selector to scroll to"),
|
|
5987
|
+
position: zod.z.enum(["center", "top", "bottom"]).optional().describe(
|
|
5988
|
+
"Where to position the element in the viewport (default center)"
|
|
5989
|
+
)
|
|
5990
|
+
},
|
|
5991
|
+
tier: 1
|
|
5992
|
+
},
|
|
5993
|
+
// --- Navigation Primitives ---
|
|
5994
|
+
{
|
|
5995
|
+
name: "wait_for_navigation",
|
|
5996
|
+
title: "Wait For Navigation",
|
|
5997
|
+
description: "Wait for the current page to finish loading after a click or form submission. Use when you clicked a link and need to wait for the new page before reading it.",
|
|
5998
|
+
inputSchema: {
|
|
5999
|
+
timeoutMs: zod.z.number().optional().describe("Maximum time to wait in milliseconds (default 10000)")
|
|
6000
|
+
},
|
|
6001
|
+
tier: 1
|
|
6002
|
+
},
|
|
6003
|
+
// --- Speedee System: Metrics ---
|
|
6004
|
+
{
|
|
6005
|
+
name: "metrics",
|
|
6006
|
+
title: "Session Metrics",
|
|
6007
|
+
description: "Show performance metrics for this session: total tool calls, average duration, per-tool breakdown, and error rates.",
|
|
6008
|
+
tier: 2
|
|
5551
6009
|
}
|
|
5552
6010
|
];
|
|
5553
6011
|
function toAnthropicTools(defs) {
|
|
@@ -5572,6 +6030,74 @@ function toAnthropicTools(defs) {
|
|
|
5572
6030
|
});
|
|
5573
6031
|
}
|
|
5574
6032
|
const AGENT_TOOLS = toAnthropicTools(TOOL_DEFINITIONS);
|
|
6033
|
+
const defByName = Object.fromEntries(
|
|
6034
|
+
TOOL_DEFINITIONS.map((d) => [d.name, d])
|
|
6035
|
+
);
|
|
6036
|
+
const CONTEXT_HINTS = {
|
|
6037
|
+
LOGIN: {
|
|
6038
|
+
login: "⚡ LOGIN PAGE DETECTED — ",
|
|
6039
|
+
fill_form: "⚡ Login fields detected — ",
|
|
6040
|
+
type_text: "⚡ Credential fields on page — ",
|
|
6041
|
+
save_session: "💡 Save session after successful login — "
|
|
6042
|
+
},
|
|
6043
|
+
SEARCH_READY: {
|
|
6044
|
+
search: "⚡ SEARCH BOX DETECTED — ",
|
|
6045
|
+
type_text: "⚡ Search input available — "
|
|
6046
|
+
},
|
|
6047
|
+
SEARCH_RESULTS: {
|
|
6048
|
+
paginate: "⚡ PAGINATION DETECTED — ",
|
|
6049
|
+
search: "⚡ Refine search — ",
|
|
6050
|
+
highlight: "💡 Mark interesting results — "
|
|
6051
|
+
},
|
|
6052
|
+
SHOPPING: {
|
|
6053
|
+
fill_form: "⚡ CHECKOUT FIELDS DETECTED — ",
|
|
6054
|
+
select_option: "⚡ Payment/shipping options available — "
|
|
6055
|
+
},
|
|
6056
|
+
FORM: {
|
|
6057
|
+
fill_form: "⚡ FORM DETECTED — ",
|
|
6058
|
+
select_option: "⚡ Dropdown fields on page — ",
|
|
6059
|
+
submit_form: "⚡ Form ready to submit — "
|
|
6060
|
+
},
|
|
6061
|
+
PAGINATED_LIST: {
|
|
6062
|
+
paginate: "⚡ PAGINATION DETECTED — ",
|
|
6063
|
+
scroll: "💡 Scroll to see more — "
|
|
6064
|
+
},
|
|
6065
|
+
ARTICLE: {
|
|
6066
|
+
highlight: "💡 Mark interesting passages — ",
|
|
6067
|
+
save_bookmark: "💡 Save for later — ",
|
|
6068
|
+
scroll: "💡 Long content — scroll to continue — "
|
|
6069
|
+
}
|
|
6070
|
+
};
|
|
6071
|
+
function scoreForContext(toolName, pageType) {
|
|
6072
|
+
const def = defByName[toolName];
|
|
6073
|
+
if (!def) return 500;
|
|
6074
|
+
const tier = def.tier ?? 1;
|
|
6075
|
+
if (tier === 0) return 0;
|
|
6076
|
+
const isRelevant = !def.relevance || def.relevance.includes(pageType);
|
|
6077
|
+
if (tier === 1 && isRelevant) return 10;
|
|
6078
|
+
if (tier === 1 && !isRelevant) return 30;
|
|
6079
|
+
if (tier === 2 && isRelevant) return 20;
|
|
6080
|
+
return 40;
|
|
6081
|
+
}
|
|
6082
|
+
function pruneToolsForContext(tools, pageType) {
|
|
6083
|
+
const ctx = pageType ?? "GENERAL";
|
|
6084
|
+
const hints = CONTEXT_HINTS[ctx] ?? {};
|
|
6085
|
+
const scored = tools.map((tool) => ({
|
|
6086
|
+
tool,
|
|
6087
|
+
score: scoreForContext(tool.name, ctx)
|
|
6088
|
+
}));
|
|
6089
|
+
scored.sort((a, b) => a.score - b.score);
|
|
6090
|
+
return scored.map(({ tool, score }) => {
|
|
6091
|
+
const hint = hints[tool.name];
|
|
6092
|
+
if (hint && score <= 20) {
|
|
6093
|
+
return {
|
|
6094
|
+
...tool,
|
|
6095
|
+
description: hint + tool.description
|
|
6096
|
+
};
|
|
6097
|
+
}
|
|
6098
|
+
return tool;
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
5575
6101
|
function trimText(value) {
|
|
5576
6102
|
return typeof value === "string" ? value.trim() : "";
|
|
5577
6103
|
}
|
|
@@ -6436,29 +6962,82 @@ function deleteNamedSession(name) {
|
|
|
6436
6962
|
fs$1.unlinkSync(filePath);
|
|
6437
6963
|
return true;
|
|
6438
6964
|
}
|
|
6965
|
+
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
|
|
6966
|
+
const QUIET_NAVIGATION_WINDOW_MS = 1200;
|
|
6967
|
+
const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
6968
|
+
function pageBusyError(action) {
|
|
6969
|
+
return `Error: Page is still busy; ${action} timed out waiting for page scripts. Retry in a moment.`;
|
|
6970
|
+
}
|
|
6971
|
+
function normalizeReadPageMode(mode, pageContent) {
|
|
6972
|
+
if (typeof mode === "string") {
|
|
6973
|
+
const normalized = mode.trim().toLowerCase();
|
|
6974
|
+
if (normalized === "debug") return "debug";
|
|
6975
|
+
if (normalized === "full" || normalized === "summary" || normalized === "interactives_only" || normalized === "forms_only" || normalized === "text_only" || normalized === "visible_only" || normalized === "results_only") {
|
|
6976
|
+
return normalized;
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6979
|
+
return pageContent ? chooseAgentReadMode(pageContent) : "visible_only";
|
|
6980
|
+
}
|
|
6981
|
+
async function executePageScript(wc, script, options) {
|
|
6982
|
+
if (wc.isDestroyed()) return null;
|
|
6983
|
+
const timeoutMs = Math.max(
|
|
6984
|
+
150,
|
|
6985
|
+
options?.timeoutMs ?? DEFAULT_PAGE_SCRIPT_TIMEOUT_MS
|
|
6986
|
+
);
|
|
6987
|
+
let timer = null;
|
|
6988
|
+
try {
|
|
6989
|
+
const result = await Promise.race([
|
|
6990
|
+
wc.executeJavaScript(script, options?.userGesture ?? false),
|
|
6991
|
+
new Promise((resolve) => {
|
|
6992
|
+
timer = setTimeout(() => resolve(PAGE_SCRIPT_TIMEOUT), timeoutMs);
|
|
6993
|
+
})
|
|
6994
|
+
]);
|
|
6995
|
+
if (result === PAGE_SCRIPT_TIMEOUT) {
|
|
6996
|
+
console.log(
|
|
6997
|
+
`[Vessel pageScript] timed out after ${timeoutMs}ms (${options?.label || "page-script"})`
|
|
6998
|
+
);
|
|
6999
|
+
return PAGE_SCRIPT_TIMEOUT;
|
|
7000
|
+
}
|
|
7001
|
+
return result;
|
|
7002
|
+
} catch {
|
|
7003
|
+
return null;
|
|
7004
|
+
} finally {
|
|
7005
|
+
if (timer) {
|
|
7006
|
+
clearTimeout(timer);
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
6439
7010
|
function waitForLoad$1(wc, timeout = 5e3) {
|
|
6440
7011
|
return new Promise((resolve) => {
|
|
6441
7012
|
let finished = false;
|
|
7013
|
+
console.log(
|
|
7014
|
+
`[Vessel waitForLoad] started, isLoading=${wc.isLoading()}, timeout=${timeout}`
|
|
7015
|
+
);
|
|
6442
7016
|
const cleanup = () => {
|
|
6443
7017
|
wc.removeListener("did-finish-load", onLoadEvent);
|
|
6444
7018
|
wc.removeListener("did-stop-loading", onLoadEvent);
|
|
6445
7019
|
wc.removeListener("did-fail-load", onLoadEvent);
|
|
6446
7020
|
};
|
|
6447
|
-
const finish = () => {
|
|
7021
|
+
const finish = (reason) => {
|
|
6448
7022
|
if (finished) return;
|
|
6449
7023
|
finished = true;
|
|
7024
|
+
console.log(`[Vessel waitForLoad] finished: ${reason}`);
|
|
6450
7025
|
clearTimeout(timer);
|
|
6451
7026
|
cleanup();
|
|
6452
7027
|
resolve();
|
|
6453
7028
|
};
|
|
6454
7029
|
const onLoadEvent = () => {
|
|
6455
|
-
|
|
6456
|
-
|
|
7030
|
+
const loading = wc.isLoading();
|
|
7031
|
+
console.log(
|
|
7032
|
+
`[Vessel waitForLoad] load event fired, isLoading=${loading}`
|
|
7033
|
+
);
|
|
7034
|
+
if (!loading) {
|
|
7035
|
+
finish("load event");
|
|
6457
7036
|
}
|
|
6458
7037
|
};
|
|
6459
|
-
const timer = setTimeout(finish, timeout);
|
|
7038
|
+
const timer = setTimeout(() => finish("timeout"), timeout);
|
|
6460
7039
|
if (!wc.isLoading()) {
|
|
6461
|
-
finish();
|
|
7040
|
+
finish("already loaded");
|
|
6462
7041
|
return;
|
|
6463
7042
|
}
|
|
6464
7043
|
wc.on("did-finish-load", onLoadEvent);
|
|
@@ -6466,41 +7045,70 @@ function waitForLoad$1(wc, timeout = 5e3) {
|
|
|
6466
7045
|
wc.on("did-fail-load", onLoadEvent);
|
|
6467
7046
|
});
|
|
6468
7047
|
}
|
|
6469
|
-
function waitForPotentialNavigation$1(wc, beforeUrl, timeout =
|
|
7048
|
+
function waitForPotentialNavigation$1(wc, beforeUrl, timeout = 2500) {
|
|
6470
7049
|
return new Promise((resolve) => {
|
|
6471
7050
|
let done = false;
|
|
7051
|
+
let waitingForLoad = false;
|
|
7052
|
+
const beforeTitle = wc.getTitle();
|
|
6472
7053
|
const finish = () => {
|
|
6473
7054
|
if (done) return;
|
|
6474
7055
|
done = true;
|
|
6475
7056
|
clearTimeout(timer);
|
|
7057
|
+
clearInterval(poller);
|
|
6476
7058
|
wc.removeListener("did-start-loading", onStart);
|
|
6477
7059
|
wc.removeListener("did-navigate", onNavigate);
|
|
6478
7060
|
wc.removeListener("did-navigate-in-page", onNavigateInPage);
|
|
7061
|
+
wc.removeListener("did-stop-loading", onNativeChange);
|
|
7062
|
+
wc.removeListener("page-title-updated", onNativeChange);
|
|
6479
7063
|
resolve();
|
|
6480
7064
|
};
|
|
6481
|
-
const
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
void waitForLoad$1(wc, timeout).then(finish);
|
|
6485
|
-
});
|
|
7065
|
+
const finishAfterLoad = () => {
|
|
7066
|
+
if (waitingForLoad) return;
|
|
7067
|
+
waitingForLoad = true;
|
|
6486
7068
|
void waitForLoad$1(wc, timeout).then(finish);
|
|
6487
7069
|
};
|
|
7070
|
+
const onNativeChange = () => {
|
|
7071
|
+
if (wc.isLoading()) {
|
|
7072
|
+
finishAfterLoad();
|
|
7073
|
+
return;
|
|
7074
|
+
}
|
|
7075
|
+
if (wc.getURL() !== beforeUrl || wc.getTitle() !== beforeTitle) {
|
|
7076
|
+
finish();
|
|
7077
|
+
}
|
|
7078
|
+
};
|
|
7079
|
+
const onStart = () => {
|
|
7080
|
+
finishAfterLoad();
|
|
7081
|
+
};
|
|
6488
7082
|
const onNavigate = () => {
|
|
6489
|
-
|
|
7083
|
+
finishAfterLoad();
|
|
6490
7084
|
};
|
|
6491
7085
|
const onNavigateInPage = () => finish();
|
|
6492
|
-
const timer = setTimeout(
|
|
6493
|
-
|
|
6494
|
-
|
|
7086
|
+
const timer = setTimeout(
|
|
7087
|
+
finish,
|
|
7088
|
+
Math.min(timeout, QUIET_NAVIGATION_WINDOW_MS)
|
|
7089
|
+
);
|
|
7090
|
+
const poller = setInterval(onNativeChange, 100);
|
|
7091
|
+
if (wc.getURL() !== beforeUrl || wc.getTitle() !== beforeTitle || wc.isLoading()) {
|
|
7092
|
+
onNativeChange();
|
|
6495
7093
|
return;
|
|
6496
7094
|
}
|
|
6497
7095
|
wc.once("did-start-loading", onStart);
|
|
6498
7096
|
wc.once("did-navigate", onNavigate);
|
|
6499
7097
|
wc.once("did-navigate-in-page", onNavigateInPage);
|
|
7098
|
+
wc.on("did-stop-loading", onNativeChange);
|
|
7099
|
+
wc.on("page-title-updated", onNativeChange);
|
|
6500
7100
|
});
|
|
6501
7101
|
}
|
|
7102
|
+
function getPostNavSummary(wc) {
|
|
7103
|
+
const title = wc.getTitle();
|
|
7104
|
+
return title ? `
|
|
7105
|
+
Page title: ${title}` : "";
|
|
7106
|
+
}
|
|
6502
7107
|
async function scrollPage$1(wc, deltaY) {
|
|
6503
|
-
const getScrollY = () =>
|
|
7108
|
+
const getScrollY = async () => {
|
|
7109
|
+
const scrollY = await executePageScript(
|
|
7110
|
+
wc,
|
|
7111
|
+
`
|
|
6504
7112
|
(function() {
|
|
6505
7113
|
return Math.max(
|
|
6506
7114
|
window.scrollY || 0,
|
|
@@ -6510,9 +7118,28 @@ async function scrollPage$1(wc, deltaY) {
|
|
|
6510
7118
|
document.body?.scrollTop || 0,
|
|
6511
7119
|
);
|
|
6512
7120
|
})()
|
|
6513
|
-
|
|
7121
|
+
`,
|
|
7122
|
+
{
|
|
7123
|
+
label: "read scroll position"
|
|
7124
|
+
}
|
|
7125
|
+
);
|
|
7126
|
+
return typeof scrollY === "number" ? scrollY : 0;
|
|
7127
|
+
};
|
|
6514
7128
|
const beforeY = await getScrollY();
|
|
6515
|
-
await
|
|
7129
|
+
const scrolled = await executePageScript(
|
|
7130
|
+
wc,
|
|
7131
|
+
`window.scrollBy(0, ${deltaY})`,
|
|
7132
|
+
{
|
|
7133
|
+
label: "scroll page"
|
|
7134
|
+
}
|
|
7135
|
+
);
|
|
7136
|
+
if (scrolled === PAGE_SCRIPT_TIMEOUT) {
|
|
7137
|
+
return {
|
|
7138
|
+
beforeY,
|
|
7139
|
+
afterY: beforeY,
|
|
7140
|
+
movedY: 0
|
|
7141
|
+
};
|
|
7142
|
+
}
|
|
6516
7143
|
await sleep$1(100);
|
|
6517
7144
|
const afterY = await getScrollY();
|
|
6518
7145
|
return {
|
|
@@ -6525,7 +7152,9 @@ function sleep$1(ms) {
|
|
|
6525
7152
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6526
7153
|
}
|
|
6527
7154
|
async function clickElement$1(wc, selector) {
|
|
6528
|
-
const target = await
|
|
7155
|
+
const target = await executePageScript(
|
|
7156
|
+
wc,
|
|
7157
|
+
`
|
|
6529
7158
|
(async function() {
|
|
6530
7159
|
function matchesTarget(candidate, el) {
|
|
6531
7160
|
return !!candidate && (candidate === el || el.contains(candidate) || candidate.contains(el));
|
|
@@ -6589,7 +7218,15 @@ async function clickElement$1(wc, selector) {
|
|
|
6589
7218
|
hiddenWindow: document.visibilityState !== "visible",
|
|
6590
7219
|
};
|
|
6591
7220
|
})()
|
|
6592
|
-
|
|
7221
|
+
`,
|
|
7222
|
+
{
|
|
7223
|
+
timeoutMs: 2e3,
|
|
7224
|
+
label: "resolve click target"
|
|
7225
|
+
}
|
|
7226
|
+
);
|
|
7227
|
+
if (target === PAGE_SCRIPT_TIMEOUT) {
|
|
7228
|
+
return pageBusyError("click");
|
|
7229
|
+
}
|
|
6593
7230
|
if (!target || typeof target !== "object") {
|
|
6594
7231
|
return "Error: Could not resolve click target";
|
|
6595
7232
|
}
|
|
@@ -6619,7 +7256,9 @@ async function clickElement$1(wc, selector) {
|
|
|
6619
7256
|
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
6620
7257
|
}
|
|
6621
7258
|
async function activateElement$1(wc, selector) {
|
|
6622
|
-
const activated = await
|
|
7259
|
+
const activated = await executePageScript(
|
|
7260
|
+
wc,
|
|
7261
|
+
`
|
|
6623
7262
|
(function() {
|
|
6624
7263
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
6625
7264
|
if (!el) return { error: "Element not found" };
|
|
@@ -6632,7 +7271,14 @@ async function activateElement$1(wc, selector) {
|
|
|
6632
7271
|
}
|
|
6633
7272
|
return { error: "Element is not clickable" };
|
|
6634
7273
|
})()
|
|
6635
|
-
|
|
7274
|
+
`,
|
|
7275
|
+
{
|
|
7276
|
+
label: "activate element"
|
|
7277
|
+
}
|
|
7278
|
+
);
|
|
7279
|
+
if (activated === PAGE_SCRIPT_TIMEOUT) {
|
|
7280
|
+
return pageBusyError("activate");
|
|
7281
|
+
}
|
|
6636
7282
|
if (!activated || typeof activated !== "object") {
|
|
6637
7283
|
return "Error: Could not activate element";
|
|
6638
7284
|
}
|
|
@@ -6642,7 +7288,9 @@ async function activateElement$1(wc, selector) {
|
|
|
6642
7288
|
return "Activated element via DOM click";
|
|
6643
7289
|
}
|
|
6644
7290
|
async function describeElementForClick$1(wc, selector) {
|
|
6645
|
-
const result = await
|
|
7291
|
+
const result = await executePageScript(
|
|
7292
|
+
wc,
|
|
7293
|
+
`
|
|
6646
7294
|
(function() {
|
|
6647
7295
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
6648
7296
|
if (!el) return { error: "Element not found" };
|
|
@@ -6653,7 +7301,14 @@ async function describeElementForClick$1(wc, selector) {
|
|
|
6653
7301
|
href: anchor instanceof HTMLAnchorElement ? anchor.href : undefined,
|
|
6654
7302
|
};
|
|
6655
7303
|
})()
|
|
6656
|
-
|
|
7304
|
+
`,
|
|
7305
|
+
{
|
|
7306
|
+
label: "describe element"
|
|
7307
|
+
}
|
|
7308
|
+
);
|
|
7309
|
+
if (result === PAGE_SCRIPT_TIMEOUT) {
|
|
7310
|
+
return { error: "Page is still busy" };
|
|
7311
|
+
}
|
|
6657
7312
|
if (!result || typeof result !== "object") {
|
|
6658
7313
|
return { error: "Element not found" };
|
|
6659
7314
|
}
|
|
@@ -6669,9 +7324,36 @@ async function clickResolvedSelector$1(wc, selector) {
|
|
|
6669
7324
|
if (selector.startsWith("__vessel_idx:")) {
|
|
6670
7325
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
6671
7326
|
const beforeUrl2 = wc.getURL();
|
|
6672
|
-
const result = await
|
|
6673
|
-
|
|
7327
|
+
const result = await executePageScript(
|
|
7328
|
+
wc,
|
|
7329
|
+
`window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`,
|
|
7330
|
+
{
|
|
7331
|
+
label: "shadow click by index"
|
|
7332
|
+
}
|
|
7333
|
+
);
|
|
7334
|
+
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
7335
|
+
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
7336
|
+
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
7337
|
+
const afterUrl2 = wc.getURL();
|
|
7338
|
+
return afterUrl2 !== beforeUrl2 ? `${result} -> ${afterUrl2}` : result;
|
|
7339
|
+
}
|
|
7340
|
+
if (selector.includes(" >>> ")) {
|
|
7341
|
+
const beforeUrl2 = wc.getURL();
|
|
7342
|
+
const result = await executePageScript(
|
|
7343
|
+
wc,
|
|
7344
|
+
`
|
|
7345
|
+
(function() {
|
|
7346
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
7347
|
+
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
7348
|
+
if (el instanceof HTMLElement) { el.focus(); el.click(); }
|
|
7349
|
+
return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
7350
|
+
})()
|
|
7351
|
+
`,
|
|
7352
|
+
{
|
|
7353
|
+
label: "shadow click selector"
|
|
7354
|
+
}
|
|
6674
7355
|
);
|
|
7356
|
+
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
6675
7357
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
6676
7358
|
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
6677
7359
|
const afterUrl2 = wc.getURL();
|
|
@@ -6710,7 +7392,9 @@ async function dismissPopup$1(wc) {
|
|
|
6710
7392
|
(overlay) => overlay.blocksInteraction
|
|
6711
7393
|
).length;
|
|
6712
7394
|
const initialDormant = before.dormantOverlays.length;
|
|
6713
|
-
const candidates = await
|
|
7395
|
+
const candidates = await executePageScript(
|
|
7396
|
+
wc,
|
|
7397
|
+
`
|
|
6714
7398
|
(function() {
|
|
6715
7399
|
function text(value) {
|
|
6716
7400
|
const trimmed = value == null ? "" : String(value).trim();
|
|
@@ -6889,7 +7573,15 @@ async function dismissPopup$1(wc) {
|
|
|
6889
7573
|
.sort((a, b) => b.score - a.score)
|
|
6890
7574
|
.slice(0, 8);
|
|
6891
7575
|
})()
|
|
6892
|
-
|
|
7576
|
+
`,
|
|
7577
|
+
{
|
|
7578
|
+
timeoutMs: 2e3,
|
|
7579
|
+
label: "inspect popup candidates"
|
|
7580
|
+
}
|
|
7581
|
+
);
|
|
7582
|
+
if (candidates === PAGE_SCRIPT_TIMEOUT) {
|
|
7583
|
+
return pageBusyError("dismiss_popup");
|
|
7584
|
+
}
|
|
6893
7585
|
if (Array.isArray(candidates)) {
|
|
6894
7586
|
for (const candidate of candidates) {
|
|
6895
7587
|
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
@@ -6924,7 +7616,8 @@ async function dismissPopup$1(wc) {
|
|
|
6924
7616
|
async function resolveSelector$1(wc, index, selector) {
|
|
6925
7617
|
if (selector) return selector;
|
|
6926
7618
|
if (index == null) return null;
|
|
6927
|
-
const authoritativeSelector = await
|
|
7619
|
+
const authoritativeSelector = await executePageScript(
|
|
7620
|
+
wc,
|
|
6928
7621
|
`
|
|
6929
7622
|
(function() {
|
|
6930
7623
|
return window.__vessel?.getElementSelector
|
|
@@ -6934,16 +7627,23 @@ async function resolveSelector$1(wc, index, selector) {
|
|
|
6934
7627
|
`
|
|
6935
7628
|
);
|
|
6936
7629
|
if (typeof authoritativeSelector === "string" && authoritativeSelector) {
|
|
6937
|
-
|
|
7630
|
+
if (authoritativeSelector.includes(" >>> ")) {
|
|
7631
|
+
const resolves2 = await executePageScript(
|
|
7632
|
+
wc,
|
|
7633
|
+
`!!window.__vessel?.resolveShadowSelector?.(${JSON.stringify(authoritativeSelector)})`
|
|
7634
|
+
);
|
|
7635
|
+
if (resolves2) return authoritativeSelector;
|
|
7636
|
+
return `__vessel_idx:${index}`;
|
|
7637
|
+
}
|
|
7638
|
+
const resolves = await executePageScript(
|
|
7639
|
+
wc,
|
|
6938
7640
|
`!!document.querySelector(${JSON.stringify(authoritativeSelector)})`
|
|
6939
7641
|
);
|
|
6940
7642
|
if (resolves) return authoritativeSelector;
|
|
6941
7643
|
return `__vessel_idx:${index}`;
|
|
6942
7644
|
}
|
|
6943
|
-
const
|
|
6944
|
-
|
|
6945
|
-
if (extractedSelector) return extractedSelector;
|
|
6946
|
-
return wc.executeJavaScript(
|
|
7645
|
+
const fallbackSelector = await executePageScript(
|
|
7646
|
+
wc,
|
|
6947
7647
|
`
|
|
6948
7648
|
(function() {
|
|
6949
7649
|
// Final fallback: replicate the legacy extraction order.
|
|
@@ -7019,6 +7719,13 @@ async function resolveSelector$1(wc, index, selector) {
|
|
|
7019
7719
|
})()
|
|
7020
7720
|
`
|
|
7021
7721
|
);
|
|
7722
|
+
if (typeof fallbackSelector === "string" && fallbackSelector) {
|
|
7723
|
+
return fallbackSelector;
|
|
7724
|
+
}
|
|
7725
|
+
const page = await extractContent(wc);
|
|
7726
|
+
const extractedSelector = findSelectorByIndex(page, index);
|
|
7727
|
+
if (extractedSelector) return extractedSelector;
|
|
7728
|
+
return null;
|
|
7022
7729
|
}
|
|
7023
7730
|
function getTabByMatch$1(tabManager, match) {
|
|
7024
7731
|
if (!match) return null;
|
|
@@ -7049,11 +7756,38 @@ function isDangerousAction$1(name) {
|
|
|
7049
7756
|
async function setElementValue$1(wc, selector, value) {
|
|
7050
7757
|
if (selector.startsWith("__vessel_idx:")) {
|
|
7051
7758
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
7052
|
-
|
|
7759
|
+
const result2 = await executePageScript(
|
|
7760
|
+
wc,
|
|
7053
7761
|
`window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
|
|
7054
7762
|
);
|
|
7763
|
+
return result2 === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result2 || "Error: interactByIndex not available";
|
|
7055
7764
|
}
|
|
7056
|
-
|
|
7765
|
+
if (selector.includes(" >>> ")) {
|
|
7766
|
+
const result2 = await executePageScript(
|
|
7767
|
+
wc,
|
|
7768
|
+
`
|
|
7769
|
+
(function() {
|
|
7770
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
7771
|
+
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
7772
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a text input";
|
|
7773
|
+
var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
7774
|
+
var desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
7775
|
+
if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
|
|
7776
|
+
el.focus();
|
|
7777
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
7778
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
7779
|
+
return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
|
|
7780
|
+
})()
|
|
7781
|
+
`,
|
|
7782
|
+
{
|
|
7783
|
+
label: "type text in shadow input"
|
|
7784
|
+
}
|
|
7785
|
+
);
|
|
7786
|
+
return result2 === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result2 || "Error: Could not type into element";
|
|
7787
|
+
}
|
|
7788
|
+
const result = await executePageScript(
|
|
7789
|
+
wc,
|
|
7790
|
+
`
|
|
7057
7791
|
(function() {
|
|
7058
7792
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7059
7793
|
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
@@ -7086,10 +7820,17 @@ async function setElementValue$1(wc, selector, value) {
|
|
|
7086
7820
|
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
7087
7821
|
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
7088
7822
|
})()
|
|
7089
|
-
|
|
7823
|
+
`,
|
|
7824
|
+
{
|
|
7825
|
+
label: "type text"
|
|
7826
|
+
}
|
|
7827
|
+
);
|
|
7828
|
+
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
7090
7829
|
}
|
|
7091
7830
|
async function typeKeystroke$1(wc, selector, value) {
|
|
7092
|
-
|
|
7831
|
+
const result = await executePageScript(
|
|
7832
|
+
wc,
|
|
7833
|
+
`
|
|
7093
7834
|
(async function() {
|
|
7094
7835
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7095
7836
|
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
@@ -7126,7 +7867,13 @@ async function typeKeystroke$1(wc, selector, value) {
|
|
|
7126
7867
|
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
7127
7868
|
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
7128
7869
|
})()
|
|
7129
|
-
|
|
7870
|
+
`,
|
|
7871
|
+
{
|
|
7872
|
+
timeoutMs: 2e3,
|
|
7873
|
+
label: "type keystrokes"
|
|
7874
|
+
}
|
|
7875
|
+
);
|
|
7876
|
+
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
7130
7877
|
}
|
|
7131
7878
|
async function hoverElement$1(wc, selector) {
|
|
7132
7879
|
const pos = await wc.executeJavaScript(`
|
|
@@ -7152,7 +7899,8 @@ async function hoverElement$1(wc, selector) {
|
|
|
7152
7899
|
if ("error" in pos && typeof pos.error === "string") return pos.error;
|
|
7153
7900
|
const x = typeof pos.x === "number" ? pos.x : null;
|
|
7154
7901
|
const y = typeof pos.y === "number" ? pos.y : null;
|
|
7155
|
-
if (x == null || y == null)
|
|
7902
|
+
if (x == null || y == null)
|
|
7903
|
+
return "Error: Could not resolve hover coordinates";
|
|
7156
7904
|
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
7157
7905
|
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
7158
7906
|
return `Hovered: ${label}`;
|
|
@@ -7183,7 +7931,9 @@ async function waitForCondition$1(wc, args) {
|
|
|
7183
7931
|
}
|
|
7184
7932
|
const startedAt = Date.now();
|
|
7185
7933
|
while (Date.now() - startedAt < timeoutMs) {
|
|
7186
|
-
const result = await
|
|
7934
|
+
const result = await executePageScript(
|
|
7935
|
+
wc,
|
|
7936
|
+
`
|
|
7187
7937
|
(function() {
|
|
7188
7938
|
var selector = ${JSON.stringify(selector)};
|
|
7189
7939
|
var text = ${JSON.stringify(text)};
|
|
@@ -7197,7 +7947,14 @@ async function waitForCondition$1(wc, args) {
|
|
|
7197
7947
|
if (text && document.body && document.body.innerText && document.body.innerText.includes(text)) return 'text';
|
|
7198
7948
|
return '';
|
|
7199
7949
|
})()
|
|
7200
|
-
|
|
7950
|
+
`,
|
|
7951
|
+
{
|
|
7952
|
+
label: "wait_for probe"
|
|
7953
|
+
}
|
|
7954
|
+
);
|
|
7955
|
+
if (result === PAGE_SCRIPT_TIMEOUT) {
|
|
7956
|
+
return pageBusyError("wait_for");
|
|
7957
|
+
}
|
|
7201
7958
|
if (result === "selector") {
|
|
7202
7959
|
return `Matched selector ${selector}`;
|
|
7203
7960
|
}
|
|
@@ -7281,7 +8038,9 @@ ${formatFolderStatus$1()}`;
|
|
|
7281
8038
|
async function selectOption$1(wc, args) {
|
|
7282
8039
|
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
7283
8040
|
if (!selector) return "Error: No select element index or selector provided";
|
|
7284
|
-
|
|
8041
|
+
const result = await executePageScript(
|
|
8042
|
+
wc,
|
|
8043
|
+
`
|
|
7285
8044
|
(function() {
|
|
7286
8045
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
7287
8046
|
if (!(el instanceof HTMLSelectElement)) {
|
|
@@ -7305,13 +8064,20 @@ async function selectOption$1(wc, args) {
|
|
|
7305
8064
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
7306
8065
|
return 'Selected: ' + ((option.textContent || option.value).trim().slice(0, 100));
|
|
7307
8066
|
})()
|
|
7308
|
-
|
|
8067
|
+
`,
|
|
8068
|
+
{
|
|
8069
|
+
label: "select option"
|
|
8070
|
+
}
|
|
8071
|
+
);
|
|
8072
|
+
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
|
|
7309
8073
|
}
|
|
7310
8074
|
async function submitForm$1(wc, args) {
|
|
7311
8075
|
const beforeUrl = wc.getURL();
|
|
7312
8076
|
let selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
7313
8077
|
if (!selector) {
|
|
7314
|
-
|
|
8078
|
+
const discoveredSelector = await executePageScript(
|
|
8079
|
+
wc,
|
|
8080
|
+
`
|
|
7315
8081
|
(function() {
|
|
7316
8082
|
var forms = document.querySelectorAll('form');
|
|
7317
8083
|
for (var i = 0; i < forms.length; i++) {
|
|
@@ -7321,10 +8087,20 @@ async function submitForm$1(wc, args) {
|
|
|
7321
8087
|
}
|
|
7322
8088
|
return forms.length > 0 ? 'form' : null;
|
|
7323
8089
|
})()
|
|
7324
|
-
|
|
8090
|
+
`,
|
|
8091
|
+
{
|
|
8092
|
+
label: "discover form"
|
|
8093
|
+
}
|
|
8094
|
+
);
|
|
8095
|
+
if (discoveredSelector === PAGE_SCRIPT_TIMEOUT) {
|
|
8096
|
+
return pageBusyError("submit_form");
|
|
8097
|
+
}
|
|
8098
|
+
selector = discoveredSelector || null;
|
|
7325
8099
|
if (!selector) return "Error: No form found on the page";
|
|
7326
8100
|
}
|
|
7327
|
-
const formInfo = await
|
|
8101
|
+
const formInfo = await executePageScript(
|
|
8102
|
+
wc,
|
|
8103
|
+
`
|
|
7328
8104
|
(function() {
|
|
7329
8105
|
const target = document.querySelector(${JSON.stringify(selector)});
|
|
7330
8106
|
if (!target) return { error: 'Target not found' };
|
|
@@ -7418,7 +8194,18 @@ async function submitForm$1(wc, args) {
|
|
|
7418
8194
|
form.submit();
|
|
7419
8195
|
return { submitted: true, method };
|
|
7420
8196
|
})()
|
|
7421
|
-
|
|
8197
|
+
`,
|
|
8198
|
+
{
|
|
8199
|
+
timeoutMs: 2e3,
|
|
8200
|
+
label: "submit form"
|
|
8201
|
+
}
|
|
8202
|
+
);
|
|
8203
|
+
if (formInfo === PAGE_SCRIPT_TIMEOUT) {
|
|
8204
|
+
return pageBusyError("submit_form");
|
|
8205
|
+
}
|
|
8206
|
+
if (!formInfo || typeof formInfo !== "object") {
|
|
8207
|
+
return "Error: Could not inspect form";
|
|
8208
|
+
}
|
|
7422
8209
|
if (formInfo.error) return formInfo.error;
|
|
7423
8210
|
if (formInfo.found && formInfo.method === "GET") {
|
|
7424
8211
|
const url = new URL(formInfo.action);
|
|
@@ -7433,7 +8220,37 @@ async function submitForm$1(wc, args) {
|
|
|
7433
8220
|
if (formInfo.submitted) {
|
|
7434
8221
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
7435
8222
|
const afterUrl = wc.getURL();
|
|
7436
|
-
|
|
8223
|
+
if (afterUrl !== beforeUrl) {
|
|
8224
|
+
return `Submitted form via ${formInfo.method} -> ${afterUrl}`;
|
|
8225
|
+
}
|
|
8226
|
+
await executePageScript(
|
|
8227
|
+
wc,
|
|
8228
|
+
`
|
|
8229
|
+
(function() {
|
|
8230
|
+
var active = document.activeElement;
|
|
8231
|
+
if (!active || active === document.body) {
|
|
8232
|
+
var inputs = document.querySelectorAll('input[type="text"], input[type="search"], input:not([type])');
|
|
8233
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
8234
|
+
if (inputs[i].value) { active = inputs[i]; active.focus(); break; }
|
|
8235
|
+
}
|
|
8236
|
+
}
|
|
8237
|
+
if (active && active !== document.body) {
|
|
8238
|
+
active.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true, cancelable: true }));
|
|
8239
|
+
active.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true, cancelable: true }));
|
|
8240
|
+
active.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true, cancelable: true }));
|
|
8241
|
+
}
|
|
8242
|
+
})()
|
|
8243
|
+
`,
|
|
8244
|
+
{
|
|
8245
|
+
label: "submit form enter fallback"
|
|
8246
|
+
}
|
|
8247
|
+
);
|
|
8248
|
+
wc.sendInputEvent({ type: "keyDown", keyCode: "Return" });
|
|
8249
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
8250
|
+
wc.sendInputEvent({ type: "keyUp", keyCode: "Return" });
|
|
8251
|
+
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
8252
|
+
const finalUrl = wc.getURL();
|
|
8253
|
+
return finalUrl !== beforeUrl ? `Submitted form (Enter fallback) -> ${finalUrl}` : `Submitted form via ${formInfo.method} (page may have updated dynamically)`;
|
|
7437
8254
|
}
|
|
7438
8255
|
return "Submitted form";
|
|
7439
8256
|
}
|
|
@@ -7441,30 +8258,52 @@ async function pressKey$1(wc, args) {
|
|
|
7441
8258
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
7442
8259
|
if (!key) return "Error: No key provided";
|
|
7443
8260
|
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
7444
|
-
|
|
8261
|
+
const focusResult = await executePageScript(
|
|
8262
|
+
wc,
|
|
8263
|
+
`
|
|
7445
8264
|
(function() {
|
|
7446
|
-
const key = ${JSON.stringify(key)};
|
|
7447
8265
|
const selector = ${JSON.stringify(selector)};
|
|
7448
8266
|
const target =
|
|
7449
8267
|
selector ? document.querySelector(selector) : document.activeElement;
|
|
7450
8268
|
if (!target || !(target instanceof HTMLElement)) {
|
|
7451
|
-
return selector ? 'Target not found' : 'No focused element';
|
|
7452
|
-
}
|
|
7453
|
-
target.focus();
|
|
7454
|
-
const eventInit = { key, bubbles: true, cancelable: true };
|
|
7455
|
-
target.dispatchEvent(new KeyboardEvent('keydown', eventInit));
|
|
7456
|
-
target.dispatchEvent(new KeyboardEvent('keypress', eventInit));
|
|
7457
|
-
const tag = target.tagName;
|
|
7458
|
-
const type = target instanceof HTMLInputElement ? target.type : '';
|
|
7459
|
-
if (key === 'Enter' &&
|
|
7460
|
-
typeof target.click === 'function' &&
|
|
7461
|
-
(tag === 'BUTTON' || (tag === 'INPUT' && (type === 'submit' || type === 'button')))) {
|
|
7462
|
-
target.click();
|
|
8269
|
+
return { error: selector ? 'Target not found' : 'No focused element' };
|
|
7463
8270
|
}
|
|
7464
|
-
target.
|
|
7465
|
-
return
|
|
8271
|
+
target.focus({ preventScroll: false });
|
|
8272
|
+
return {
|
|
8273
|
+
ok: true,
|
|
8274
|
+
label:
|
|
8275
|
+
target.getAttribute('aria-label') ||
|
|
8276
|
+
target.getAttribute('name') ||
|
|
8277
|
+
target.getAttribute('placeholder') ||
|
|
8278
|
+
target.textContent?.trim().slice(0, 60) ||
|
|
8279
|
+
target.tagName.toLowerCase(),
|
|
8280
|
+
};
|
|
7466
8281
|
})()
|
|
7467
|
-
|
|
8282
|
+
`,
|
|
8283
|
+
{
|
|
8284
|
+
label: "focus before key press"
|
|
8285
|
+
}
|
|
8286
|
+
);
|
|
8287
|
+
if (focusResult === PAGE_SCRIPT_TIMEOUT) {
|
|
8288
|
+
return pageBusyError("press_key");
|
|
8289
|
+
}
|
|
8290
|
+
if (!focusResult || typeof focusResult !== "object") {
|
|
8291
|
+
return "Error: Could not prepare key press";
|
|
8292
|
+
}
|
|
8293
|
+
if ("error" in focusResult && typeof focusResult.error === "string") {
|
|
8294
|
+
return focusResult.error;
|
|
8295
|
+
}
|
|
8296
|
+
wc.focus();
|
|
8297
|
+
const normalizedKey = key.length === 1 ? key : key[0].toUpperCase() + key.slice(1);
|
|
8298
|
+
const electronKeyCode = normalizedKey === "Enter" ? "Return" : normalizedKey === "ArrowUp" ? "Up" : normalizedKey === "ArrowDown" ? "Down" : normalizedKey === "ArrowLeft" ? "Left" : normalizedKey === "ArrowRight" ? "Right" : normalizedKey;
|
|
8299
|
+
wc.sendInputEvent({ type: "keyDown", keyCode: electronKeyCode });
|
|
8300
|
+
if (key.length === 1) {
|
|
8301
|
+
wc.sendInputEvent({ type: "char", keyCode: key });
|
|
8302
|
+
}
|
|
8303
|
+
await sleep$1(16);
|
|
8304
|
+
wc.sendInputEvent({ type: "keyUp", keyCode: electronKeyCode });
|
|
8305
|
+
const label = "label" in focusResult && typeof focusResult.label === "string" ? focusResult.label : null;
|
|
8306
|
+
return label ? `Pressed key: ${key} on ${label}` : `Pressed key: ${key}`;
|
|
7468
8307
|
}
|
|
7469
8308
|
async function getPostActionState$1(ctx, name) {
|
|
7470
8309
|
const tab = ctx.tabManager.getActiveTab();
|
|
@@ -7483,7 +8322,13 @@ async function getPostActionState$1(ctx, name) {
|
|
|
7483
8322
|
"search",
|
|
7484
8323
|
"paginate"
|
|
7485
8324
|
];
|
|
7486
|
-
const interactActions = [
|
|
8325
|
+
const interactActions = [
|
|
8326
|
+
"type_text",
|
|
8327
|
+
"select_option",
|
|
8328
|
+
"hover",
|
|
8329
|
+
"focus",
|
|
8330
|
+
"fill_form"
|
|
8331
|
+
];
|
|
7487
8332
|
const tabActions = [
|
|
7488
8333
|
"create_tab",
|
|
7489
8334
|
"switch_tab",
|
|
@@ -7491,33 +8336,11 @@ async function getPostActionState$1(ctx, name) {
|
|
|
7491
8336
|
"load_session"
|
|
7492
8337
|
];
|
|
7493
8338
|
if (navActions.includes(name)) {
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
const page = await extractContent(wc);
|
|
7497
|
-
const issue = getRecoverableAccessIssue(page);
|
|
7498
|
-
if (issue) {
|
|
7499
|
-
const blockedUrl = wc.getURL();
|
|
7500
|
-
const canRecover = [
|
|
7501
|
-
"navigate",
|
|
7502
|
-
"open_bookmark",
|
|
7503
|
-
"click",
|
|
7504
|
-
"submit_form",
|
|
7505
|
-
"reload",
|
|
7506
|
-
"press_key"
|
|
7507
|
-
].includes(name) && tab.canGoBack();
|
|
7508
|
-
if (canRecover && tab.goBack()) {
|
|
7509
|
-
await waitForLoad$1(wc);
|
|
7510
|
-
warning = `
|
|
7511
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
|
|
7512
|
-
} else {
|
|
7513
|
-
warning = `
|
|
7514
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
|
|
7515
|
-
}
|
|
7516
|
-
}
|
|
7517
|
-
} catch {
|
|
8339
|
+
if (wc.isLoading()) {
|
|
8340
|
+
await waitForLoad$1(wc);
|
|
7518
8341
|
}
|
|
7519
|
-
return
|
|
7520
|
-
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
8342
|
+
return `
|
|
8343
|
+
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
7521
8344
|
}
|
|
7522
8345
|
if (interactActions.includes(name)) {
|
|
7523
8346
|
return `
|
|
@@ -7534,7 +8357,69 @@ async function getPostActionState$1(ctx, name) {
|
|
|
7534
8357
|
}
|
|
7535
8358
|
return "";
|
|
7536
8359
|
}
|
|
8360
|
+
const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
8361
|
+
"current_tab",
|
|
8362
|
+
"list_tabs",
|
|
8363
|
+
"switch_tab",
|
|
8364
|
+
"create_tab",
|
|
8365
|
+
"navigate",
|
|
8366
|
+
"go_back",
|
|
8367
|
+
"go_forward",
|
|
8368
|
+
"reload",
|
|
8369
|
+
"click",
|
|
8370
|
+
"type_text",
|
|
8371
|
+
"select_option",
|
|
8372
|
+
"submit_form",
|
|
8373
|
+
"press_key",
|
|
8374
|
+
"scroll",
|
|
8375
|
+
"hover",
|
|
8376
|
+
"focus",
|
|
8377
|
+
"set_ad_blocking",
|
|
8378
|
+
"dismiss_popup",
|
|
8379
|
+
"read_page",
|
|
8380
|
+
"wait_for",
|
|
8381
|
+
"create_checkpoint",
|
|
8382
|
+
"restore_checkpoint",
|
|
8383
|
+
"save_session",
|
|
8384
|
+
"load_session",
|
|
8385
|
+
"list_sessions",
|
|
8386
|
+
"delete_session",
|
|
8387
|
+
"list_bookmarks",
|
|
8388
|
+
"search_bookmarks",
|
|
8389
|
+
"create_bookmark_folder",
|
|
8390
|
+
"save_bookmark",
|
|
8391
|
+
"organize_bookmark",
|
|
8392
|
+
"archive_bookmark",
|
|
8393
|
+
"open_bookmark",
|
|
8394
|
+
"highlight",
|
|
8395
|
+
"clear_highlights",
|
|
8396
|
+
"flow_start",
|
|
8397
|
+
"flow_advance",
|
|
8398
|
+
"flow_status",
|
|
8399
|
+
"flow_end",
|
|
8400
|
+
"suggest",
|
|
8401
|
+
"fill_form",
|
|
8402
|
+
"login",
|
|
8403
|
+
"search",
|
|
8404
|
+
"paginate",
|
|
8405
|
+
"accept_cookies",
|
|
8406
|
+
"extract_table",
|
|
8407
|
+
"scroll_to_element",
|
|
8408
|
+
"metrics",
|
|
8409
|
+
"wait_for_navigation"
|
|
8410
|
+
]);
|
|
7537
8411
|
async function executeAction(name, args, ctx) {
|
|
8412
|
+
if (!KNOWN_TOOLS.has(name)) {
|
|
8413
|
+
for (const known of KNOWN_TOOLS) {
|
|
8414
|
+
if (name.startsWith(known) && name.length > known.length) {
|
|
8415
|
+
const remaining = name.slice(known.length);
|
|
8416
|
+
const otherTools = [...KNOWN_TOOLS].filter(
|
|
8417
|
+
(t) => remaining.includes(t)
|
|
8418
|
+
);
|
|
8419
|
+
return `Error: It looks like you tried to call multiple tools at once (${known}, ${otherTools.join(", ")}). Please call them one at a time — send one tool call per message.`;
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8422
|
+
}
|
|
7538
8423
|
const tab = ctx.tabManager.getActiveTab();
|
|
7539
8424
|
const tabId = ctx.tabManager.getActiveTabId();
|
|
7540
8425
|
if (!tab && ![
|
|
@@ -7617,6 +8502,7 @@ async function executeAction(name, args, ctx) {
|
|
|
7617
8502
|
const created = ctx.tabManager.getActiveTab();
|
|
7618
8503
|
if (created) {
|
|
7619
8504
|
await waitForLoad$1(created.view.webContents);
|
|
8505
|
+
return `Created tab ${createdId}${getPostNavSummary(created.view.webContents)}`;
|
|
7620
8506
|
}
|
|
7621
8507
|
return `Created tab ${createdId}`;
|
|
7622
8508
|
}
|
|
@@ -7624,7 +8510,7 @@ async function executeAction(name, args, ctx) {
|
|
|
7624
8510
|
if (!wc || !tabId) return "Error: No active tab";
|
|
7625
8511
|
ctx.tabManager.navigateTab(tabId, args.url);
|
|
7626
8512
|
await waitForLoad$1(wc);
|
|
7627
|
-
return `Navigated to ${wc.getURL()}`;
|
|
8513
|
+
return `Navigated to ${wc.getURL()}${getPostNavSummary(wc)}`;
|
|
7628
8514
|
}
|
|
7629
8515
|
case "go_back": {
|
|
7630
8516
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -7635,7 +8521,7 @@ async function executeAction(name, args, ctx) {
|
|
|
7635
8521
|
ctx.tabManager.goBack(tabId);
|
|
7636
8522
|
await waitForLoad$1(wc);
|
|
7637
8523
|
const afterUrl = wc.getURL();
|
|
7638
|
-
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
8524
|
+
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}${getPostNavSummary(wc)}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
7639
8525
|
}
|
|
7640
8526
|
case "go_forward": {
|
|
7641
8527
|
if (!tab || !wc || !tabId) return "Error: No active tab";
|
|
@@ -7646,7 +8532,7 @@ async function executeAction(name, args, ctx) {
|
|
|
7646
8532
|
ctx.tabManager.goForward(tabId);
|
|
7647
8533
|
await waitForLoad$1(wc);
|
|
7648
8534
|
const afterUrl = wc.getURL();
|
|
7649
|
-
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
8535
|
+
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}${getPostNavSummary(wc)}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
7650
8536
|
}
|
|
7651
8537
|
case "reload": {
|
|
7652
8538
|
if (!wc || !tabId) return "Error: No active tab";
|
|
@@ -7750,28 +8636,110 @@ async function executeAction(name, args, ctx) {
|
|
|
7750
8636
|
}
|
|
7751
8637
|
case "read_page": {
|
|
7752
8638
|
if (!wc) return "Error: No active tab";
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
8639
|
+
console.log("[Vessel read_page] starting extraction with 6s timeout");
|
|
8640
|
+
let content = null;
|
|
8641
|
+
try {
|
|
8642
|
+
content = await Promise.race([
|
|
8643
|
+
extractContent(wc),
|
|
8644
|
+
new Promise(
|
|
8645
|
+
(resolve) => setTimeout(() => {
|
|
8646
|
+
console.log("[Vessel read_page] timeout fired, falling back");
|
|
8647
|
+
resolve(null);
|
|
8648
|
+
}, 6e3)
|
|
8649
|
+
)
|
|
8650
|
+
]);
|
|
8651
|
+
} catch {
|
|
8652
|
+
content = null;
|
|
8653
|
+
}
|
|
8654
|
+
console.log(
|
|
8655
|
+
`[Vessel read_page] extraction result: ${content ? `content=${content.content.length}` : "null (timeout)"}`
|
|
7759
8656
|
);
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
8657
|
+
if (content) {
|
|
8658
|
+
const liveSelectionSection = formatLiveSelectionSection(
|
|
8659
|
+
await captureLiveHighlightSnapshot(
|
|
8660
|
+
wc,
|
|
8661
|
+
getHighlightsForUrl(content.url)
|
|
8662
|
+
)
|
|
8663
|
+
);
|
|
8664
|
+
const livePrefix = liveSelectionSection ? `${liveSelectionSection}
|
|
7763
8665
|
|
|
7764
8666
|
` : "";
|
|
7765
|
-
|
|
8667
|
+
const requestedMode = normalizeReadPageMode(args.mode, content);
|
|
8668
|
+
if (requestedMode === "debug" || requestedMode === "full") {
|
|
8669
|
+
const structured = buildStructuredContext(content);
|
|
8670
|
+
const truncated = content.content.length > 2e4 ? content.content.slice(0, 2e4) + "\n[Content truncated...]" : content.content;
|
|
8671
|
+
return `${livePrefix}[read_page mode=debug]
|
|
8672
|
+
|
|
8673
|
+
${structured}
|
|
7766
8674
|
|
|
7767
8675
|
## PAGE CONTENT
|
|
7768
8676
|
|
|
7769
8677
|
${truncated}`;
|
|
8678
|
+
}
|
|
8679
|
+
const scoped = buildScopedContext(content, requestedMode);
|
|
8680
|
+
return [
|
|
8681
|
+
livePrefix ? livePrefix.trimEnd() : "",
|
|
8682
|
+
`[read_page mode=${requestedMode}]`,
|
|
8683
|
+
"",
|
|
8684
|
+
scoped,
|
|
8685
|
+
"",
|
|
8686
|
+
`Need more detail? Escalate with read_page(mode="debug") only if the narrow modes are insufficient.`
|
|
8687
|
+
].filter(Boolean).join("\n\n");
|
|
8688
|
+
}
|
|
8689
|
+
const title = wc.getTitle() || "(untitled)";
|
|
8690
|
+
const url = wc.getURL();
|
|
8691
|
+
return [
|
|
8692
|
+
`# ${title}`,
|
|
8693
|
+
`URL: ${url}`,
|
|
8694
|
+
"",
|
|
8695
|
+
"[Page content extraction timed out — the page JS thread is busy.]",
|
|
8696
|
+
"[Use the search tool to search the site, or type_text/click to interact directly.]",
|
|
8697
|
+
"[You can retry read_page in a few seconds once the page finishes loading.]"
|
|
8698
|
+
].join("\n");
|
|
7770
8699
|
}
|
|
7771
8700
|
case "wait_for": {
|
|
7772
8701
|
if (!wc) return "Error: No active tab";
|
|
7773
8702
|
return waitForCondition$1(wc, args);
|
|
7774
8703
|
}
|
|
8704
|
+
case "wait_for_navigation": {
|
|
8705
|
+
if (!wc) return "Error: No active tab";
|
|
8706
|
+
const timeout = typeof args.timeoutMs === "number" ? args.timeoutMs : 1e4;
|
|
8707
|
+
const beforeUrl = wc.getURL();
|
|
8708
|
+
if (wc.isLoading()) {
|
|
8709
|
+
await new Promise((resolve) => {
|
|
8710
|
+
const timer = setTimeout(resolve, timeout);
|
|
8711
|
+
wc.once("did-stop-loading", () => {
|
|
8712
|
+
clearTimeout(timer);
|
|
8713
|
+
resolve();
|
|
8714
|
+
});
|
|
8715
|
+
});
|
|
8716
|
+
} else {
|
|
8717
|
+
await new Promise((resolve) => {
|
|
8718
|
+
let navigated = false;
|
|
8719
|
+
const timer = setTimeout(
|
|
8720
|
+
() => {
|
|
8721
|
+
if (!navigated) resolve();
|
|
8722
|
+
},
|
|
8723
|
+
Math.min(timeout, 2e3)
|
|
8724
|
+
);
|
|
8725
|
+
wc.once("did-start-loading", () => {
|
|
8726
|
+
navigated = true;
|
|
8727
|
+
clearTimeout(timer);
|
|
8728
|
+
const loadTimer = setTimeout(resolve, timeout);
|
|
8729
|
+
wc.once("did-stop-loading", () => {
|
|
8730
|
+
clearTimeout(loadTimer);
|
|
8731
|
+
resolve();
|
|
8732
|
+
});
|
|
8733
|
+
});
|
|
8734
|
+
});
|
|
8735
|
+
}
|
|
8736
|
+
const afterUrl = wc.getURL();
|
|
8737
|
+
const title = wc.getTitle();
|
|
8738
|
+
if (afterUrl !== beforeUrl) {
|
|
8739
|
+
return `Navigation complete: ${title} (${afterUrl})`;
|
|
8740
|
+
}
|
|
8741
|
+
return `Page loaded: ${title} (${afterUrl})`;
|
|
8742
|
+
}
|
|
7775
8743
|
case "create_checkpoint": {
|
|
7776
8744
|
const checkpoint = ctx.runtime.createCheckpoint(args.name, args.note);
|
|
7777
8745
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
@@ -8031,12 +8999,26 @@ ${truncated}`;
|
|
|
8031
8999
|
case "highlight": {
|
|
8032
9000
|
if (!wc) return "Error: No active tab";
|
|
8033
9001
|
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
9002
|
+
const highlightColor = args.color || "yellow";
|
|
9003
|
+
const url = wc.getURL();
|
|
9004
|
+
if (url && url !== "about:blank") {
|
|
9005
|
+
const highlightText = typeof args.text === "string" ? args.text : void 0;
|
|
9006
|
+
addHighlight(
|
|
9007
|
+
url,
|
|
9008
|
+
typeof selector === "string" ? selector : void 0,
|
|
9009
|
+
highlightText,
|
|
9010
|
+
typeof args.label === "string" ? args.label : void 0,
|
|
9011
|
+
highlightColor,
|
|
9012
|
+
"agent"
|
|
9013
|
+
);
|
|
9014
|
+
}
|
|
8034
9015
|
return highlightOnPage(
|
|
8035
9016
|
wc,
|
|
8036
9017
|
selector,
|
|
8037
9018
|
args.text,
|
|
8038
9019
|
args.label,
|
|
8039
|
-
args.durationMs
|
|
9020
|
+
args.durationMs,
|
|
9021
|
+
highlightColor
|
|
8040
9022
|
);
|
|
8041
9023
|
}
|
|
8042
9024
|
case "clear_highlights": {
|
|
@@ -8047,7 +9029,8 @@ ${truncated}`;
|
|
|
8047
9029
|
case "flow_start": {
|
|
8048
9030
|
const goal = typeof args.goal === "string" ? args.goal : "";
|
|
8049
9031
|
const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
|
|
8050
|
-
if (!goal || steps.length === 0)
|
|
9032
|
+
if (!goal || steps.length === 0)
|
|
9033
|
+
return "Error: goal and steps are required";
|
|
8051
9034
|
const flow = ctx.runtime.startFlow(goal, steps, wc?.getURL());
|
|
8052
9035
|
return `Flow started: ${flow.goal}
|
|
8053
9036
|
${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`;
|
|
@@ -8092,47 +9075,70 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
8092
9075
|
(el) => el.inputType === "search" || el.name === "q" || el.name === "query" || (el.placeholder || "").toLowerCase().includes("search")
|
|
8093
9076
|
);
|
|
8094
9077
|
const formCount = page.forms.length;
|
|
8095
|
-
const totalFields = page.forms.reduce(
|
|
8096
|
-
|
|
9078
|
+
const totalFields = page.forms.reduce(
|
|
9079
|
+
(n, f) => n + f.fields.length,
|
|
9080
|
+
0
|
|
9081
|
+
);
|
|
9082
|
+
const linkCount = page.interactiveElements.filter(
|
|
9083
|
+
(el) => el.type === "link"
|
|
9084
|
+
).length;
|
|
8097
9085
|
const hasPagination = page.interactiveElements.some(
|
|
8098
9086
|
(el) => (el.text || "").toLowerCase() === "next" || el.text === "›" || el.text === "»"
|
|
8099
9087
|
);
|
|
8100
9088
|
const hasOverlays = page.overlays.some((o) => o.blocksInteraction);
|
|
8101
9089
|
if (hasOverlays) {
|
|
8102
9090
|
suggestions.push("BLOCKING OVERLAY detected — dismiss it first:");
|
|
8103
|
-
suggestions.push(
|
|
9091
|
+
suggestions.push(
|
|
9092
|
+
" → dismiss_popup or click on close/accept button"
|
|
9093
|
+
);
|
|
8104
9094
|
suggestions.push("");
|
|
8105
9095
|
}
|
|
8106
9096
|
if (hasPasswordField) {
|
|
8107
9097
|
suggestions.push("LOGIN PAGE detected:");
|
|
8108
|
-
suggestions.push(
|
|
8109
|
-
|
|
9098
|
+
suggestions.push(
|
|
9099
|
+
" → login(username, password) — handles the full flow"
|
|
9100
|
+
);
|
|
9101
|
+
suggestions.push(
|
|
9102
|
+
" → Or fill_form + submit_form for manual control"
|
|
9103
|
+
);
|
|
8110
9104
|
} else if (hasSearchInput && linkCount < 10) {
|
|
8111
9105
|
suggestions.push("SEARCH PAGE detected:");
|
|
8112
|
-
suggestions.push(
|
|
9106
|
+
suggestions.push(
|
|
9107
|
+
" → search(query) — finds the box, types, submits"
|
|
9108
|
+
);
|
|
8113
9109
|
} else if (hasSearchInput && linkCount >= 10) {
|
|
8114
9110
|
suggestions.push("SEARCH RESULTS detected:");
|
|
8115
9111
|
suggestions.push(" → click on a result link");
|
|
8116
|
-
if (hasPagination)
|
|
9112
|
+
if (hasPagination)
|
|
9113
|
+
suggestions.push(" → paginate('next') for more results");
|
|
8117
9114
|
} else if (formCount > 0) {
|
|
8118
9115
|
suggestions.push(`FORM detected (${totalFields} fields):`);
|
|
8119
9116
|
suggestions.push(" → fill_form(fields) — fill all fields at once");
|
|
8120
9117
|
} else if (hasPagination) {
|
|
8121
9118
|
suggestions.push("PAGINATED CONTENT:");
|
|
8122
|
-
suggestions.push(
|
|
9119
|
+
suggestions.push(
|
|
9120
|
+
" → read_page(mode='results_only') to inspect likely results"
|
|
9121
|
+
);
|
|
8123
9122
|
suggestions.push(" → paginate('next') for the next page");
|
|
8124
9123
|
} else if (page.content.length > 3e3 && page.interactiveElements.length < 10) {
|
|
8125
9124
|
suggestions.push("ARTICLE/CONTENT page:");
|
|
8126
|
-
suggestions.push(" → read_page for
|
|
9125
|
+
suggestions.push(" → read_page(mode='summary') for a fast brief");
|
|
9126
|
+
suggestions.push(
|
|
9127
|
+
" → read_page(mode='text_only') for readable text"
|
|
9128
|
+
);
|
|
8127
9129
|
suggestions.push(" → scroll to see more");
|
|
8128
9130
|
} else {
|
|
8129
9131
|
suggestions.push("GENERAL PAGE:");
|
|
8130
|
-
suggestions.push(
|
|
9132
|
+
suggestions.push(
|
|
9133
|
+
" → read_page(mode='visible_only') to inspect active controls"
|
|
9134
|
+
);
|
|
8131
9135
|
suggestions.push(" → click on any element by index");
|
|
8132
9136
|
suggestions.push(" → navigate to go somewhere new");
|
|
8133
9137
|
}
|
|
8134
9138
|
suggestions.push("");
|
|
8135
|
-
suggestions.push(
|
|
9139
|
+
suggestions.push(
|
|
9140
|
+
`Available: ${page.interactiveElements.length} interactive elements, ${formCount} forms, ${linkCount} links`
|
|
9141
|
+
);
|
|
8136
9142
|
return suggestions.join("\n");
|
|
8137
9143
|
}
|
|
8138
9144
|
case "fill_form": {
|
|
@@ -8146,11 +9152,19 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
8146
9152
|
results.push(`Skipped: no selector for index=${field.index}`);
|
|
8147
9153
|
continue;
|
|
8148
9154
|
}
|
|
8149
|
-
const result2 = await setElementValue$1(
|
|
9155
|
+
const result2 = await setElementValue$1(
|
|
9156
|
+
wc,
|
|
9157
|
+
sel,
|
|
9158
|
+
String(field.value || "")
|
|
9159
|
+
);
|
|
8150
9160
|
results.push(result2);
|
|
8151
9161
|
}
|
|
8152
9162
|
if (args.submit) {
|
|
8153
|
-
const firstSel = await resolveSelector$1(
|
|
9163
|
+
const firstSel = await resolveSelector$1(
|
|
9164
|
+
wc,
|
|
9165
|
+
fields[0]?.index,
|
|
9166
|
+
fields[0]?.selector
|
|
9167
|
+
);
|
|
8154
9168
|
if (firstSel) {
|
|
8155
9169
|
const beforeUrl = wc.getURL();
|
|
8156
9170
|
const submitResult = await submitForm$1(wc, { selector: firstSel });
|
|
@@ -8173,29 +9187,53 @@ ${results.join("\n")}`;
|
|
|
8173
9187
|
await waitForLoad$1(wc);
|
|
8174
9188
|
steps.push(`Navigated to ${wc.getURL()}`);
|
|
8175
9189
|
}
|
|
8176
|
-
const userSel = args.username_selector || await
|
|
9190
|
+
const userSel = args.username_selector || await executePageScript(
|
|
9191
|
+
wc,
|
|
9192
|
+
`
|
|
8177
9193
|
(function() {
|
|
8178
9194
|
var el = document.querySelector('input[type="email"], input[name="email"], input[name="username"], input[name="user"], input[autocomplete="username"], input[autocomplete="email"], input[type="text"]:not([name="search"]):not([name="q"])');
|
|
8179
9195
|
return el ? (el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null) : null;
|
|
8180
9196
|
})()
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
9197
|
+
`,
|
|
9198
|
+
{
|
|
9199
|
+
label: "find username field"
|
|
9200
|
+
}
|
|
9201
|
+
);
|
|
9202
|
+
if (!userSel)
|
|
9203
|
+
return "Error: Could not find username/email field. Try providing username_selector.";
|
|
9204
|
+
const passSel = args.password_selector || await executePageScript(
|
|
9205
|
+
wc,
|
|
9206
|
+
`
|
|
8184
9207
|
(function() {
|
|
8185
9208
|
var el = document.querySelector('input[type="password"]');
|
|
8186
9209
|
return el ? (el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null) : null;
|
|
8187
9210
|
})()
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
9211
|
+
`,
|
|
9212
|
+
{
|
|
9213
|
+
label: "find password field"
|
|
9214
|
+
}
|
|
9215
|
+
);
|
|
9216
|
+
if (!passSel)
|
|
9217
|
+
return "Error: Could not find password field. Try providing password_selector.";
|
|
9218
|
+
const userResult = await setElementValue$1(
|
|
9219
|
+
wc,
|
|
9220
|
+
userSel,
|
|
9221
|
+
String(args.username || "")
|
|
9222
|
+
);
|
|
8191
9223
|
steps.push(userResult);
|
|
8192
|
-
const passResult = await setElementValue$1(
|
|
9224
|
+
const passResult = await setElementValue$1(
|
|
9225
|
+
wc,
|
|
9226
|
+
passSel,
|
|
9227
|
+
String(args.password || "")
|
|
9228
|
+
);
|
|
8193
9229
|
steps.push(passResult);
|
|
8194
9230
|
const beforeUrl = wc.getURL();
|
|
8195
9231
|
if (args.submit_selector) {
|
|
8196
9232
|
await clickResolvedSelector$1(wc, args.submit_selector);
|
|
8197
9233
|
} else {
|
|
8198
|
-
const clicked = await
|
|
9234
|
+
const clicked = await executePageScript(
|
|
9235
|
+
wc,
|
|
9236
|
+
`
|
|
8199
9237
|
(function() {
|
|
8200
9238
|
var btn = document.querySelector('button[type="submit"], input[type="submit"], form button:not([type="button"])');
|
|
8201
9239
|
if (btn) { btn.click(); return true; }
|
|
@@ -8203,8 +9241,16 @@ ${results.join("\n")}`;
|
|
|
8203
9241
|
if (form) { form.requestSubmit ? form.requestSubmit() : form.submit(); return true; }
|
|
8204
9242
|
return false;
|
|
8205
9243
|
})()
|
|
8206
|
-
|
|
8207
|
-
|
|
9244
|
+
`,
|
|
9245
|
+
{
|
|
9246
|
+
label: "submit login form"
|
|
9247
|
+
}
|
|
9248
|
+
);
|
|
9249
|
+
if (clicked === PAGE_SCRIPT_TIMEOUT) {
|
|
9250
|
+
return pageBusyError("login");
|
|
9251
|
+
}
|
|
9252
|
+
if (!clicked)
|
|
9253
|
+
return steps.join("\n") + "\nWarning: Could not find submit button. Credentials filled but form not submitted.";
|
|
8208
9254
|
}
|
|
8209
9255
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
8210
9256
|
const afterUrl = wc.getURL();
|
|
@@ -8212,42 +9258,137 @@ ${results.join("\n")}`;
|
|
|
8212
9258
|
afterUrl !== beforeUrl ? `Submitted → ${afterUrl}` : "Form submitted (same page)"
|
|
8213
9259
|
);
|
|
8214
9260
|
return `Login flow complete:
|
|
8215
|
-
${steps.join("\n")}`;
|
|
8216
|
-
}
|
|
8217
|
-
case "search": {
|
|
8218
|
-
if (!wc) return "Error: No active tab";
|
|
8219
|
-
const
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
9261
|
+
${steps.join("\n")}`;
|
|
9262
|
+
}
|
|
9263
|
+
case "search": {
|
|
9264
|
+
if (!wc) return "Error: No active tab";
|
|
9265
|
+
const query = String(args.query || "");
|
|
9266
|
+
if (!query) return "Error: No search query provided.";
|
|
9267
|
+
const searchInfo = args.selector ? { selector: args.selector, formAction: null, formMethod: null } : await executePageScript(
|
|
9268
|
+
wc,
|
|
9269
|
+
`
|
|
9270
|
+
(function() {
|
|
9271
|
+
var SELECTORS = 'input[type="search"], input[name="q"], input[name="query"], input[name="search"], input[role="searchbox"], input[aria-label*="search" i], input[placeholder*="search" i]';
|
|
9272
|
+
function find() {
|
|
9273
|
+
var el = document.querySelector(SELECTORS);
|
|
9274
|
+
if (!el) {
|
|
9275
|
+
var inputs = document.querySelectorAll('input[type="text"]');
|
|
9276
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
9277
|
+
var form = inputs[i].closest('form');
|
|
9278
|
+
if (form && (form.getAttribute('role') === 'search' || (form.action && form.action.includes('search')))) {
|
|
9279
|
+
el = inputs[i];
|
|
9280
|
+
break;
|
|
9281
|
+
}
|
|
8229
9282
|
}
|
|
8230
9283
|
}
|
|
9284
|
+
if (!el) return null;
|
|
9285
|
+
var sel = el.id ? '#' + CSS.escape(el.id) : el.name ? 'input[name="' + el.name + '"]' : null;
|
|
9286
|
+
var form = el.closest('form');
|
|
9287
|
+
return {
|
|
9288
|
+
selector: sel,
|
|
9289
|
+
formAction: form ? form.action : null,
|
|
9290
|
+
formMethod: form ? (form.method || 'GET').toUpperCase() : null,
|
|
9291
|
+
inputName: el.name || 'q',
|
|
9292
|
+
};
|
|
8231
9293
|
}
|
|
8232
|
-
return
|
|
9294
|
+
return new Promise(function(resolve) {
|
|
9295
|
+
var result = find();
|
|
9296
|
+
if (result) { resolve(result); return; }
|
|
9297
|
+
var attempts = 0;
|
|
9298
|
+
var timer = setInterval(function() {
|
|
9299
|
+
result = find();
|
|
9300
|
+
if (result || ++attempts >= 20) {
|
|
9301
|
+
clearInterval(timer);
|
|
9302
|
+
resolve(result);
|
|
9303
|
+
}
|
|
9304
|
+
}, 250);
|
|
9305
|
+
});
|
|
8233
9306
|
})()
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
9307
|
+
`,
|
|
9308
|
+
{
|
|
9309
|
+
timeoutMs: 1800,
|
|
9310
|
+
label: "find search input"
|
|
9311
|
+
}
|
|
9312
|
+
);
|
|
9313
|
+
if (searchInfo === PAGE_SCRIPT_TIMEOUT) {
|
|
9314
|
+
return pageBusyError("search");
|
|
9315
|
+
}
|
|
9316
|
+
if (!searchInfo?.selector)
|
|
9317
|
+
return "Error: Could not find search input. Try providing a selector.";
|
|
9318
|
+
const fillResult = await setElementValue$1(
|
|
9319
|
+
wc,
|
|
9320
|
+
searchInfo.selector,
|
|
9321
|
+
query
|
|
9322
|
+
);
|
|
9323
|
+
if (fillResult.startsWith("Error:")) {
|
|
9324
|
+
return fillResult;
|
|
9325
|
+
}
|
|
9326
|
+
await sleep$1(100);
|
|
9327
|
+
const focusResult = await executePageScript(
|
|
9328
|
+
wc,
|
|
9329
|
+
`
|
|
8238
9330
|
(function() {
|
|
8239
|
-
var el = document.querySelector(${JSON.stringify(
|
|
9331
|
+
var el = document.querySelector(${JSON.stringify(searchInfo.selector)});
|
|
8240
9332
|
if (el) el.focus();
|
|
8241
9333
|
})()
|
|
8242
|
-
|
|
9334
|
+
`,
|
|
9335
|
+
{
|
|
9336
|
+
label: "focus search input"
|
|
9337
|
+
}
|
|
9338
|
+
);
|
|
9339
|
+
if (focusResult === PAGE_SCRIPT_TIMEOUT) {
|
|
9340
|
+
return pageBusyError("search");
|
|
9341
|
+
}
|
|
8243
9342
|
await sleep$1(50);
|
|
8244
9343
|
const beforeUrl = wc.getURL();
|
|
8245
9344
|
wc.sendInputEvent({ type: "keyDown", keyCode: "Return" });
|
|
8246
9345
|
await sleep$1(16);
|
|
8247
9346
|
wc.sendInputEvent({ type: "keyUp", keyCode: "Return" });
|
|
8248
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
8249
|
-
|
|
8250
|
-
|
|
9347
|
+
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
9348
|
+
let afterUrl = wc.getURL();
|
|
9349
|
+
if (afterUrl !== beforeUrl) {
|
|
9350
|
+
return `Searched "${query}" → ${afterUrl}`;
|
|
9351
|
+
}
|
|
9352
|
+
const clickedSubmit = await executePageScript(
|
|
9353
|
+
wc,
|
|
9354
|
+
`
|
|
9355
|
+
(function() {
|
|
9356
|
+
var form = document.querySelector(${JSON.stringify(searchInfo.selector)})?.closest('form');
|
|
9357
|
+
if (!form) return false;
|
|
9358
|
+
var btn = form.querySelector('button[type="submit"], input[type="submit"], button:not([type])');
|
|
9359
|
+
if (btn) { btn.click(); return true; }
|
|
9360
|
+
// Try any button in the form
|
|
9361
|
+
var anyBtn = form.querySelector('button');
|
|
9362
|
+
if (anyBtn) { anyBtn.click(); return true; }
|
|
9363
|
+
return false;
|
|
9364
|
+
})()
|
|
9365
|
+
`,
|
|
9366
|
+
{
|
|
9367
|
+
label: "click search submit"
|
|
9368
|
+
}
|
|
9369
|
+
);
|
|
9370
|
+
if (clickedSubmit === PAGE_SCRIPT_TIMEOUT) {
|
|
9371
|
+
return pageBusyError("search");
|
|
9372
|
+
}
|
|
9373
|
+
if (clickedSubmit) {
|
|
9374
|
+
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
9375
|
+
afterUrl = wc.getURL();
|
|
9376
|
+
if (afterUrl !== beforeUrl) {
|
|
9377
|
+
return `Searched "${query}" (via submit button) → ${afterUrl}`;
|
|
9378
|
+
}
|
|
9379
|
+
}
|
|
9380
|
+
if (searchInfo.formAction && searchInfo.formMethod === "GET") {
|
|
9381
|
+
try {
|
|
9382
|
+
const url = new URL(searchInfo.formAction);
|
|
9383
|
+
url.searchParams.set(searchInfo.inputName || "q", query);
|
|
9384
|
+
wc.loadURL(url.toString());
|
|
9385
|
+
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
9386
|
+
afterUrl = wc.getURL();
|
|
9387
|
+
return `Searched "${query}" (via direct URL) → ${afterUrl}`;
|
|
9388
|
+
} catch {
|
|
9389
|
+
}
|
|
9390
|
+
}
|
|
9391
|
+
return `Searched "${query}" (same page — results may have loaded dynamically)`;
|
|
8251
9392
|
}
|
|
8252
9393
|
case "paginate": {
|
|
8253
9394
|
if (!wc) return "Error: No active tab";
|
|
@@ -8256,7 +9397,9 @@ ${steps.join("\n")}`;
|
|
|
8256
9397
|
return clickResolvedSelector$1(wc, args.selector);
|
|
8257
9398
|
}
|
|
8258
9399
|
const isNext = args.direction === "next";
|
|
8259
|
-
const clicked = await
|
|
9400
|
+
const clicked = await executePageScript(
|
|
9401
|
+
wc,
|
|
9402
|
+
`
|
|
8260
9403
|
(function() {
|
|
8261
9404
|
var patterns = ${isNext ? '["next", "Next", "›", "»", "→", ">", "Next Page", "Load More"]' : '["prev", "Prev", "Previous", "‹", "«", "←", "<", "Previous Page"]'};
|
|
8262
9405
|
var links = document.querySelectorAll('a, button');
|
|
@@ -8275,12 +9418,156 @@ ${steps.join("\n")}`;
|
|
|
8275
9418
|
}
|
|
8276
9419
|
return false;
|
|
8277
9420
|
})()
|
|
8278
|
-
|
|
8279
|
-
|
|
9421
|
+
`,
|
|
9422
|
+
{
|
|
9423
|
+
label: "paginate"
|
|
9424
|
+
}
|
|
9425
|
+
);
|
|
9426
|
+
if (clicked === PAGE_SCRIPT_TIMEOUT) {
|
|
9427
|
+
return pageBusyError("paginate");
|
|
9428
|
+
}
|
|
9429
|
+
if (!clicked)
|
|
9430
|
+
return `Error: Could not find ${args.direction} pagination control. Try providing a selector.`;
|
|
8280
9431
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
8281
9432
|
const afterUrl = wc.getURL();
|
|
8282
9433
|
return afterUrl !== beforeUrl ? `Paginated ${args.direction} → ${afterUrl}` : `Clicked ${args.direction} (page may have updated dynamically)`;
|
|
8283
9434
|
}
|
|
9435
|
+
case "accept_cookies": {
|
|
9436
|
+
if (!wc) return "Error: No active tab";
|
|
9437
|
+
const dismissed = await executePageScript(
|
|
9438
|
+
wc,
|
|
9439
|
+
`
|
|
9440
|
+
(function() {
|
|
9441
|
+
// Common cookie consent selectors — OneTrust, CookieBot, GDPR banners
|
|
9442
|
+
var selectors = [
|
|
9443
|
+
'#onetrust-accept-btn-handler',
|
|
9444
|
+
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
|
|
9445
|
+
'[data-cookiefirst-action="accept"]',
|
|
9446
|
+
'.cookie-consent-accept-all',
|
|
9447
|
+
'#accept-cookies',
|
|
9448
|
+
'.cc-accept',
|
|
9449
|
+
'.cc-btn.cc-allow',
|
|
9450
|
+
'[aria-label="Accept cookies"]',
|
|
9451
|
+
'[aria-label="Accept all cookies"]',
|
|
9452
|
+
'[data-testid="cookie-accept"]',
|
|
9453
|
+
];
|
|
9454
|
+
// Also try text-matching on buttons
|
|
9455
|
+
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'consent'];
|
|
9456
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
9457
|
+
var el = document.querySelector(selectors[i]);
|
|
9458
|
+
if (el && el instanceof HTMLElement) { el.click(); return "Dismissed cookie banner via: " + selectors[i]; }
|
|
9459
|
+
}
|
|
9460
|
+
var buttons = document.querySelectorAll('button, a[role="button"], [type="submit"]');
|
|
9461
|
+
for (var j = 0; j < buttons.length; j++) {
|
|
9462
|
+
var btn = buttons[j];
|
|
9463
|
+
var text = (btn.textContent || '').trim().toLowerCase();
|
|
9464
|
+
for (var k = 0; k < textPatterns.length; k++) {
|
|
9465
|
+
if (text === textPatterns[k] || text.startsWith(textPatterns[k])) {
|
|
9466
|
+
btn.click();
|
|
9467
|
+
return "Dismissed cookie banner via text match: " + text;
|
|
9468
|
+
}
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
return null;
|
|
9472
|
+
})()
|
|
9473
|
+
`,
|
|
9474
|
+
{
|
|
9475
|
+
label: "accept cookies"
|
|
9476
|
+
}
|
|
9477
|
+
);
|
|
9478
|
+
if (dismissed === PAGE_SCRIPT_TIMEOUT) {
|
|
9479
|
+
return pageBusyError("accept_cookies");
|
|
9480
|
+
}
|
|
9481
|
+
return dismissed || "No cookie consent banner detected. Try dismiss_popup for other overlays.";
|
|
9482
|
+
}
|
|
9483
|
+
case "extract_table": {
|
|
9484
|
+
if (!wc) return "Error: No active tab";
|
|
9485
|
+
const selector = args.selector ? args.selector : args.index != null ? await resolveSelector$1(wc, args.index, void 0) : null;
|
|
9486
|
+
const tableJson = await wc.executeJavaScript(`
|
|
9487
|
+
(function() {
|
|
9488
|
+
var table = ${selector ? `document.querySelector(${JSON.stringify(selector)})` : "document.querySelector('table')"};
|
|
9489
|
+
if (!table) return null;
|
|
9490
|
+
var headers = [];
|
|
9491
|
+
var headerRow = table.querySelector('thead tr') || table.querySelector('tr');
|
|
9492
|
+
if (headerRow) {
|
|
9493
|
+
headerRow.querySelectorAll('th, td').forEach(function(cell) {
|
|
9494
|
+
headers.push(cell.textContent.trim());
|
|
9495
|
+
});
|
|
9496
|
+
}
|
|
9497
|
+
var rows = [];
|
|
9498
|
+
var bodyRows = table.querySelectorAll('tbody tr');
|
|
9499
|
+
if (bodyRows.length === 0) bodyRows = table.querySelectorAll('tr');
|
|
9500
|
+
bodyRows.forEach(function(tr, idx) {
|
|
9501
|
+
if (idx === 0 && headers.length > 0 && !table.querySelector('thead')) return;
|
|
9502
|
+
var row = {};
|
|
9503
|
+
tr.querySelectorAll('td, th').forEach(function(cell, ci) {
|
|
9504
|
+
var key = headers[ci] || ("col_" + ci);
|
|
9505
|
+
row[key] = cell.textContent.trim();
|
|
9506
|
+
});
|
|
9507
|
+
if (Object.keys(row).length > 0) rows.push(row);
|
|
9508
|
+
});
|
|
9509
|
+
return { headers: headers, rows: rows, rowCount: rows.length };
|
|
9510
|
+
})()
|
|
9511
|
+
`);
|
|
9512
|
+
if (!tableJson) return "Error: No table found on the page.";
|
|
9513
|
+
return `Extracted table (${tableJson.rowCount} rows):
|
|
9514
|
+
${JSON.stringify(tableJson, null, 2)}`;
|
|
9515
|
+
}
|
|
9516
|
+
case "metrics": {
|
|
9517
|
+
const m = ctx.runtime.getMetrics();
|
|
9518
|
+
const lines = [
|
|
9519
|
+
`Session Metrics:`,
|
|
9520
|
+
` Total actions: ${m.totalActions}`,
|
|
9521
|
+
` Completed: ${m.completedActions}`,
|
|
9522
|
+
` Failed: ${m.failedActions}`,
|
|
9523
|
+
` Average duration: ${m.averageDurationMs}ms`,
|
|
9524
|
+
``,
|
|
9525
|
+
`Tool breakdown:`
|
|
9526
|
+
];
|
|
9527
|
+
for (const [name2, stats] of Object.entries(m.toolBreakdown)) {
|
|
9528
|
+
lines.push(
|
|
9529
|
+
` ${name2}: ${stats.count} calls, avg ${stats.avgMs}ms${stats.errors > 0 ? `, ${stats.errors} errors` : ""}`
|
|
9530
|
+
);
|
|
9531
|
+
}
|
|
9532
|
+
return lines.join("\n");
|
|
9533
|
+
}
|
|
9534
|
+
case "scroll_to_element": {
|
|
9535
|
+
if (!wc) return "Error: No active tab";
|
|
9536
|
+
const sel = await resolveSelector$1(wc, args.index, args.selector);
|
|
9537
|
+
if (!sel)
|
|
9538
|
+
return "Error: Provide an index or selector for the element to scroll to.";
|
|
9539
|
+
const block = args.position === "top" ? "start" : args.position === "bottom" ? "end" : "center";
|
|
9540
|
+
if (sel.startsWith("__vessel_idx:")) {
|
|
9541
|
+
const idx = Number(sel.slice("__vessel_idx:".length));
|
|
9542
|
+
return wc.executeJavaScript(`
|
|
9543
|
+
(function() {
|
|
9544
|
+
var el = window.__vessel?.interactByIndex && Object.values(window.__vessel)[2];
|
|
9545
|
+
var ref = (function() { try { return document.querySelector('[data-vessel-idx="${idx}"]'); } catch(e) { return null; } })();
|
|
9546
|
+
if (!ref) return "Error: Element not found";
|
|
9547
|
+
ref.scrollIntoView({ behavior: "smooth", block: "${block}" });
|
|
9548
|
+
return "Scrolled to element #${idx}";
|
|
9549
|
+
})()
|
|
9550
|
+
`);
|
|
9551
|
+
}
|
|
9552
|
+
if (sel.includes(" >>> ")) {
|
|
9553
|
+
return wc.executeJavaScript(`
|
|
9554
|
+
(function() {
|
|
9555
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(sel)});
|
|
9556
|
+
if (!el) return "Error: Shadow DOM element not found";
|
|
9557
|
+
el.scrollIntoView({ behavior: "smooth", block: "${block}" });
|
|
9558
|
+
return "Scrolled to shadow DOM element";
|
|
9559
|
+
})()
|
|
9560
|
+
`);
|
|
9561
|
+
}
|
|
9562
|
+
return wc.executeJavaScript(`
|
|
9563
|
+
(function() {
|
|
9564
|
+
var el = document.querySelector(${JSON.stringify(sel)});
|
|
9565
|
+
if (!el) return "Error: Element not found";
|
|
9566
|
+
el.scrollIntoView({ behavior: "smooth", block: "${block}" });
|
|
9567
|
+
return "Scrolled to element";
|
|
9568
|
+
})()
|
|
9569
|
+
`);
|
|
9570
|
+
}
|
|
8284
9571
|
default:
|
|
8285
9572
|
return `Unknown tool: ${name}`;
|
|
8286
9573
|
}
|
|
@@ -8294,9 +9581,17 @@ async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd,
|
|
|
8294
9581
|
const isSummarize = lowerQuery.startsWith("summarize") || lowerQuery.startsWith("tldr") || lowerQuery === "summary";
|
|
8295
9582
|
if (provider.streamAgentQuery && tabManager && activeWebContents && runtime) {
|
|
8296
9583
|
try {
|
|
9584
|
+
const extractStart = Date.now();
|
|
8297
9585
|
const pageContent = await extractContent(activeWebContents);
|
|
8298
|
-
|
|
8299
|
-
|
|
9586
|
+
console.log(
|
|
9587
|
+
`[Vessel Agent] initial extractContent completed in ${Date.now() - extractStart}ms, contentLen=${pageContent.content.length}`
|
|
9588
|
+
);
|
|
9589
|
+
const pageType = detectPageType(pageContent);
|
|
9590
|
+
const defaultReadMode = chooseAgentReadMode(pageContent);
|
|
9591
|
+
const structuredContext = buildScopedContext(
|
|
9592
|
+
pageContent,
|
|
9593
|
+
defaultReadMode
|
|
9594
|
+
);
|
|
8300
9595
|
const runtimeState = runtime.getState();
|
|
8301
9596
|
const recentCheckpoints = runtimeState.checkpoints.slice(-3).map((item) => `- ${item.name} (${item.id})`).join("\n");
|
|
8302
9597
|
const activeTabTitle = pageContent.title || "(untitled)";
|
|
@@ -8311,13 +9606,14 @@ THE USER IS CURRENTLY LOOKING AT:
|
|
|
8311
9606
|
Title: ${activeTabTitle}
|
|
8312
9607
|
URL: ${activeTabUrl}${tabSummary}
|
|
8313
9608
|
|
|
8314
|
-
When the user says "this page", "this article", "this site", or asks about what they're viewing, they mean the page above. The
|
|
9609
|
+
When the user says "this page", "this article", "this site", or asks about what they're viewing, they mean the page above. The context below is from that page — answer directly without needing to call read_page or current_tab first.
|
|
8315
9610
|
|
|
8316
9611
|
Current page context:
|
|
8317
|
-
|
|
9612
|
+
This brief is intentionally minimal and filtered for speed. It omits most page text and low-value chrome unless you explicitly ask for more.
|
|
9613
|
+
Default brief mode: ${defaultReadMode}
|
|
9614
|
+
Detected page type: ${pageType}
|
|
8318
9615
|
|
|
8319
|
-
|
|
8320
|
-
${truncated}
|
|
9616
|
+
${structuredContext}
|
|
8321
9617
|
|
|
8322
9618
|
Supervisor state:
|
|
8323
9619
|
- paused: ${runtimeState.supervisor.paused ? "yes" : "no"}
|
|
@@ -8336,7 +9632,11 @@ Instructions:
|
|
|
8336
9632
|
- Create a checkpoint before risky multi-step flows or before leaving an important state.
|
|
8337
9633
|
- Use save_session after completing a login flow you may need again later, and load_session to resume that authenticated state in future runs.
|
|
8338
9634
|
- Prefer select_option for dropdowns and submit_form for forms instead of guessing with clicks.
|
|
8339
|
-
- After
|
|
9635
|
+
- 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
|
+
- The page brief you start with is intentionally sparse. It is optimized for navigation speed, not completeness.
|
|
9637
|
+
- 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
|
+
- Use read_page(mode="debug") only as a last resort when the narrower modes are insufficient.
|
|
9639
|
+
- 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").
|
|
8340
9640
|
- 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.
|
|
8341
9641
|
- If a page behaves abnormally or key UI fails to load, consider disabling ad blocking for that tab and reloading before retrying.
|
|
8342
9642
|
- For broad discovery tasks, prefer direct sources, official sites, venue directories, and site-specific search over generic search engines, which often rate-limit automated browser traffic.
|
|
@@ -8344,13 +9644,19 @@ Instructions:
|
|
|
8344
9644
|
- Reference interactive elements by their index number (shown as [#N] in the listings above).
|
|
8345
9645
|
- Be concise. Explain what you're doing as you go.
|
|
8346
9646
|
- For simple questions about the page, just answer directly without using tools.
|
|
8347
|
-
-
|
|
8348
|
-
- After completing a task or answering a question, offer 1-2 brief, natural follow-up suggestions that make sense in context (e.g. "Want me to highlight any of these?" or "I can save these to a bookmark folder if you'd like"). Keep suggestions short and conversational — don't list every possible action
|
|
9647
|
+
- VISUAL AWARENESS: The human is watching the browser alongside this chat. Highlights are your pointing finger — they show the user exactly what you're looking at on the page. Use them proactively: highlight key findings, important elements, errors, or anything you're referencing. Don't wait to be asked. If you mention something specific on the page, highlight it. Colors: yellow (default/attention), red (errors/warnings), green (success/good), blue (info/neutral), purple (important/notable), orange (caution). Clear highlights when moving to a new topic or page.
|
|
9648
|
+
- After completing a task or answering a question, offer 1-2 brief, natural follow-up suggestions that make sense in context (e.g. "Want me to highlight any of these?" or "I can save these to a bookmark folder if you'd like"). Keep suggestions short and conversational — don't list every possible action.
|
|
9649
|
+
- Call one tool at a time unless you are certain your provider supports parallel tool calls. Sequential calls are more reliable.
|
|
9650
|
+
- MINIMIZE TOOL CALLS: Every tool call takes time and costs a round trip. Be efficient. Don't use flow_start/flow_advance for simple multi-step tasks — just do the work. Don't call read_page after navigating — use search or type_text directly. Don't retry failed tools with slight variations — if search fails, go straight to type_text + press_key Enter, don't try read_page in between. The fastest path is usually: navigate → search → wait_for or read_page(mode="results_only") → click.
|
|
9651
|
+
- ACT, DON'T HEDGE: You have a full browser — you can navigate to any website, see live content, search, click, add to cart, fill forms, and interact with real pages in real time. Never claim you "don't have access" to a website's inventory, pricing, or content. If the user asks you to go somewhere and do something, start doing it immediately. Don't ask for permission to do what the user just asked you to do — that's redundant and frustrating. Jump straight into action.
|
|
9652
|
+
- 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
|
+
- NEVER USE EMOJIS unless the user uses them first.`;
|
|
8349
9654
|
const actionCtx = { tabManager, runtime };
|
|
9655
|
+
const contextualTools = pruneToolsForContext(AGENT_TOOLS, pageType);
|
|
8350
9656
|
await provider.streamAgentQuery(
|
|
8351
9657
|
systemPrompt,
|
|
8352
9658
|
query,
|
|
8353
|
-
|
|
9659
|
+
contextualTools,
|
|
8354
9660
|
onChunk,
|
|
8355
9661
|
(name, args) => executeAction(name, args, actionCtx),
|
|
8356
9662
|
onEnd,
|
|
@@ -8375,7 +9681,13 @@ Instructions:
|
|
|
8375
9681
|
} else {
|
|
8376
9682
|
prompt = buildGeneralPrompt(query);
|
|
8377
9683
|
}
|
|
8378
|
-
await provider.streamQuery(
|
|
9684
|
+
await provider.streamQuery(
|
|
9685
|
+
prompt.system,
|
|
9686
|
+
prompt.user,
|
|
9687
|
+
onChunk,
|
|
9688
|
+
onEnd,
|
|
9689
|
+
history
|
|
9690
|
+
);
|
|
8379
9691
|
}
|
|
8380
9692
|
const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
|
|
8381
9693
|
const DEFAULT_NOTE_FOLDER = "Vessel/Research";
|
|
@@ -9473,6 +10785,21 @@ async function clickResolvedSelector(wc, selector) {
|
|
|
9473
10785
|
const afterUrl2 = wc.getURL();
|
|
9474
10786
|
return afterUrl2 !== beforeUrl2 ? `${result} -> ${afterUrl2}` : result;
|
|
9475
10787
|
}
|
|
10788
|
+
if (selector.includes(" >>> ")) {
|
|
10789
|
+
const beforeUrl2 = wc.getURL();
|
|
10790
|
+
const result = await wc.executeJavaScript(`
|
|
10791
|
+
(function() {
|
|
10792
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
10793
|
+
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
10794
|
+
if (el instanceof HTMLElement) { el.focus(); el.click(); }
|
|
10795
|
+
return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
10796
|
+
})()
|
|
10797
|
+
`);
|
|
10798
|
+
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
10799
|
+
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
10800
|
+
const afterUrl2 = wc.getURL();
|
|
10801
|
+
return afterUrl2 !== beforeUrl2 ? `${result} -> ${afterUrl2}` : result;
|
|
10802
|
+
}
|
|
9476
10803
|
const beforeUrl = wc.getURL();
|
|
9477
10804
|
const elInfo = await describeElementForClick(wc, selector);
|
|
9478
10805
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
@@ -9820,6 +11147,22 @@ async function setElementValue(wc, selector, value) {
|
|
|
9820
11147
|
`window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
|
|
9821
11148
|
);
|
|
9822
11149
|
}
|
|
11150
|
+
if (selector.includes(" >>> ")) {
|
|
11151
|
+
return wc.executeJavaScript(`
|
|
11152
|
+
(function() {
|
|
11153
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
11154
|
+
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
11155
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a text input";
|
|
11156
|
+
var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
11157
|
+
var desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
11158
|
+
if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
|
|
11159
|
+
el.focus();
|
|
11160
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
11161
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
11162
|
+
return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
|
|
11163
|
+
})()
|
|
11164
|
+
`);
|
|
11165
|
+
}
|
|
9823
11166
|
return wc.executeJavaScript(`
|
|
9824
11167
|
(function() {
|
|
9825
11168
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -10264,6 +11607,65 @@ function registerTools(server, tabManager, runtime) {
|
|
|
10264
11607
|
]
|
|
10265
11608
|
})
|
|
10266
11609
|
);
|
|
11610
|
+
server.registerResource(
|
|
11611
|
+
"vessel-recommended-tools",
|
|
11612
|
+
"vessel://context/recommended-tools",
|
|
11613
|
+
{
|
|
11614
|
+
title: "Recommended Tools for Current Page",
|
|
11615
|
+
description: "Context-aware tool recommendations based on the current page type (login, search, form, article, etc.). Returns tools sorted by relevance with contextual hints.",
|
|
11616
|
+
mimeType: "application/json"
|
|
11617
|
+
},
|
|
11618
|
+
async () => {
|
|
11619
|
+
const activeTab = tabManager.getActiveTab();
|
|
11620
|
+
let pageType = "GENERAL";
|
|
11621
|
+
let pageUrl = "";
|
|
11622
|
+
let pageTitle = "";
|
|
11623
|
+
if (activeTab) {
|
|
11624
|
+
try {
|
|
11625
|
+
const wc = activeTab.view.webContents;
|
|
11626
|
+
pageUrl = wc.getURL();
|
|
11627
|
+
pageTitle = wc.getTitle();
|
|
11628
|
+
const page = await extractContent(wc);
|
|
11629
|
+
pageType = detectPageType(page);
|
|
11630
|
+
} catch {
|
|
11631
|
+
}
|
|
11632
|
+
}
|
|
11633
|
+
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
11634
|
+
const tier = def.tier ?? 1;
|
|
11635
|
+
const isRelevant = !def.relevance || def.relevance.includes(pageType);
|
|
11636
|
+
let score;
|
|
11637
|
+
if (tier === 0) score = 0;
|
|
11638
|
+
else if (tier === 1 && isRelevant) score = 10;
|
|
11639
|
+
else if (tier === 2 && isRelevant) score = 20;
|
|
11640
|
+
else if (tier === 1) score = 30;
|
|
11641
|
+
else score = 40;
|
|
11642
|
+
return {
|
|
11643
|
+
name: `vessel_${def.name}`,
|
|
11644
|
+
title: def.title,
|
|
11645
|
+
description: def.description,
|
|
11646
|
+
tier,
|
|
11647
|
+
relevance: isRelevant ? "high" : "low",
|
|
11648
|
+
score
|
|
11649
|
+
};
|
|
11650
|
+
});
|
|
11651
|
+
scored.sort((a, b) => a.score - b.score);
|
|
11652
|
+
const result = {
|
|
11653
|
+
pageType,
|
|
11654
|
+
pageUrl,
|
|
11655
|
+
pageTitle,
|
|
11656
|
+
recommended: scored.filter((t) => t.score <= 20).map(({ name, title, description, relevance }) => ({ name, title, description, relevance })),
|
|
11657
|
+
available: scored.filter((t) => t.score > 20).map(({ name, title, relevance }) => ({ name, title, relevance }))
|
|
11658
|
+
};
|
|
11659
|
+
return {
|
|
11660
|
+
contents: [
|
|
11661
|
+
{
|
|
11662
|
+
uri: "vessel://context/recommended-tools",
|
|
11663
|
+
text: JSON.stringify(result, null, 2)
|
|
11664
|
+
}
|
|
11665
|
+
]
|
|
11666
|
+
};
|
|
11667
|
+
}
|
|
11668
|
+
);
|
|
10267
11669
|
server.registerTool(
|
|
10268
11670
|
"vessel_current_tab",
|
|
10269
11671
|
{
|
|
@@ -12543,6 +13945,240 @@ ${steps.join("\n")}`;
|
|
|
12543
13945
|
);
|
|
12544
13946
|
}
|
|
12545
13947
|
);
|
|
13948
|
+
server.registerTool(
|
|
13949
|
+
"vessel_accept_cookies",
|
|
13950
|
+
{
|
|
13951
|
+
title: "Accept Cookies",
|
|
13952
|
+
description: "Dismiss cookie consent banners (OneTrust, CookieBot, GDPR popups, etc.).",
|
|
13953
|
+
inputSchema: zod.z.object({})
|
|
13954
|
+
},
|
|
13955
|
+
async () => {
|
|
13956
|
+
return withAction(
|
|
13957
|
+
tabManager,
|
|
13958
|
+
runtime,
|
|
13959
|
+
"vessel_accept_cookies",
|
|
13960
|
+
{},
|
|
13961
|
+
async (wc) => {
|
|
13962
|
+
const dismissed = await wc.executeJavaScript(`
|
|
13963
|
+
(function() {
|
|
13964
|
+
var selectors = [
|
|
13965
|
+
'#onetrust-accept-btn-handler',
|
|
13966
|
+
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
|
|
13967
|
+
'[data-cookiefirst-action="accept"]',
|
|
13968
|
+
'.cookie-consent-accept-all',
|
|
13969
|
+
'#accept-cookies',
|
|
13970
|
+
'.cc-accept',
|
|
13971
|
+
'.cc-btn.cc-allow',
|
|
13972
|
+
'[aria-label="Accept cookies"]',
|
|
13973
|
+
'[aria-label="Accept all cookies"]',
|
|
13974
|
+
'[data-testid="cookie-accept"]',
|
|
13975
|
+
];
|
|
13976
|
+
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'consent'];
|
|
13977
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
13978
|
+
var el = document.querySelector(selectors[i]);
|
|
13979
|
+
if (el && el instanceof HTMLElement) { el.click(); return "Dismissed cookie banner via: " + selectors[i]; }
|
|
13980
|
+
}
|
|
13981
|
+
var buttons = document.querySelectorAll('button, a[role="button"], [type="submit"]');
|
|
13982
|
+
for (var j = 0; j < buttons.length; j++) {
|
|
13983
|
+
var btn = buttons[j];
|
|
13984
|
+
var text = (btn.textContent || '').trim().toLowerCase();
|
|
13985
|
+
for (var k = 0; k < textPatterns.length; k++) {
|
|
13986
|
+
if (text === textPatterns[k] || text.startsWith(textPatterns[k])) {
|
|
13987
|
+
btn.click();
|
|
13988
|
+
return "Dismissed cookie banner via text match: " + text;
|
|
13989
|
+
}
|
|
13990
|
+
}
|
|
13991
|
+
}
|
|
13992
|
+
return null;
|
|
13993
|
+
})()
|
|
13994
|
+
`);
|
|
13995
|
+
return dismissed || "No cookie consent banner detected. Try vessel_dismiss_popup for other overlays.";
|
|
13996
|
+
}
|
|
13997
|
+
);
|
|
13998
|
+
}
|
|
13999
|
+
);
|
|
14000
|
+
server.registerTool(
|
|
14001
|
+
"vessel_extract_table",
|
|
14002
|
+
{
|
|
14003
|
+
title: "Extract Table",
|
|
14004
|
+
description: "Extract a table from the page as structured JSON rows with headers.",
|
|
14005
|
+
inputSchema: zod.z.object({
|
|
14006
|
+
index: zod.z.number().optional().describe("Element index of the table"),
|
|
14007
|
+
selector: zod.z.string().optional().describe("CSS selector for the table")
|
|
14008
|
+
})
|
|
14009
|
+
},
|
|
14010
|
+
async ({ index, selector: rawSelector }) => {
|
|
14011
|
+
return withAction(
|
|
14012
|
+
tabManager,
|
|
14013
|
+
runtime,
|
|
14014
|
+
"vessel_extract_table",
|
|
14015
|
+
{ index, selector: rawSelector },
|
|
14016
|
+
async (wc) => {
|
|
14017
|
+
const sel = rawSelector || (index != null ? await resolveSelector(wc, index) : null);
|
|
14018
|
+
const tableJson = await wc.executeJavaScript(`
|
|
14019
|
+
(function() {
|
|
14020
|
+
var table = ${sel ? `document.querySelector(${JSON.stringify(sel)})` : "document.querySelector('table')"};
|
|
14021
|
+
if (!table) return null;
|
|
14022
|
+
var headers = [];
|
|
14023
|
+
var headerRow = table.querySelector('thead tr') || table.querySelector('tr');
|
|
14024
|
+
if (headerRow) {
|
|
14025
|
+
headerRow.querySelectorAll('th, td').forEach(function(cell) {
|
|
14026
|
+
headers.push(cell.textContent.trim());
|
|
14027
|
+
});
|
|
14028
|
+
}
|
|
14029
|
+
var rows = [];
|
|
14030
|
+
var bodyRows = table.querySelectorAll('tbody tr');
|
|
14031
|
+
if (bodyRows.length === 0) bodyRows = table.querySelectorAll('tr');
|
|
14032
|
+
bodyRows.forEach(function(tr, idx) {
|
|
14033
|
+
if (idx === 0 && headers.length > 0 && !table.querySelector('thead')) return;
|
|
14034
|
+
var row = {};
|
|
14035
|
+
tr.querySelectorAll('td, th').forEach(function(cell, ci) {
|
|
14036
|
+
var key = headers[ci] || ("col_" + ci);
|
|
14037
|
+
row[key] = cell.textContent.trim();
|
|
14038
|
+
});
|
|
14039
|
+
if (Object.keys(row).length > 0) rows.push(row);
|
|
14040
|
+
});
|
|
14041
|
+
return { headers: headers, rows: rows, rowCount: rows.length };
|
|
14042
|
+
})()
|
|
14043
|
+
`);
|
|
14044
|
+
if (!tableJson) return "Error: No table found on the page.";
|
|
14045
|
+
return `Extracted table (${tableJson.rowCount} rows):
|
|
14046
|
+
${JSON.stringify(tableJson, null, 2)}`;
|
|
14047
|
+
}
|
|
14048
|
+
);
|
|
14049
|
+
}
|
|
14050
|
+
);
|
|
14051
|
+
server.registerTool(
|
|
14052
|
+
"vessel_scroll_to_element",
|
|
14053
|
+
{
|
|
14054
|
+
title: "Scroll To Element",
|
|
14055
|
+
description: "Scroll a specific element into view by index or selector.",
|
|
14056
|
+
inputSchema: zod.z.object({
|
|
14057
|
+
index: zod.z.number().optional().describe("Element index to scroll to"),
|
|
14058
|
+
selector: zod.z.string().optional().describe("CSS selector to scroll to"),
|
|
14059
|
+
position: zod.z.enum(["center", "top", "bottom"]).optional().describe("Viewport position (default center)")
|
|
14060
|
+
})
|
|
14061
|
+
},
|
|
14062
|
+
async ({ index, selector: rawSelector, position }) => {
|
|
14063
|
+
return withAction(
|
|
14064
|
+
tabManager,
|
|
14065
|
+
runtime,
|
|
14066
|
+
"vessel_scroll_to_element",
|
|
14067
|
+
{ index, selector: rawSelector, position },
|
|
14068
|
+
async (wc) => {
|
|
14069
|
+
const sel = rawSelector || (index != null ? await resolveSelector(wc, index) : null);
|
|
14070
|
+
if (!sel) return "Error: Provide an index or selector.";
|
|
14071
|
+
const block = position === "top" ? "start" : position === "bottom" ? "end" : "center";
|
|
14072
|
+
if (sel.startsWith("__vessel_idx:")) {
|
|
14073
|
+
const idx = Number(sel.slice("__vessel_idx:".length));
|
|
14074
|
+
return wc.executeJavaScript(`
|
|
14075
|
+
(function() {
|
|
14076
|
+
var refs = window.__vessel;
|
|
14077
|
+
if (!refs || !refs.interactByIndex) return "Error: __vessel not available";
|
|
14078
|
+
// Use stored ref directly
|
|
14079
|
+
var el = document.querySelector('[data-vessel-idx="${idx}"]');
|
|
14080
|
+
if (!el) return "Error: Element #${idx} not found";
|
|
14081
|
+
el.scrollIntoView({ behavior: "smooth", block: "${block}" });
|
|
14082
|
+
return "Scrolled to element #${idx}";
|
|
14083
|
+
})()
|
|
14084
|
+
`);
|
|
14085
|
+
}
|
|
14086
|
+
if (sel.includes(" >>> ")) {
|
|
14087
|
+
return wc.executeJavaScript(`
|
|
14088
|
+
(function() {
|
|
14089
|
+
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(sel)});
|
|
14090
|
+
if (!el) return "Error: Shadow DOM element not found";
|
|
14091
|
+
el.scrollIntoView({ behavior: "smooth", block: "${block}" });
|
|
14092
|
+
return "Scrolled to shadow DOM element";
|
|
14093
|
+
})()
|
|
14094
|
+
`);
|
|
14095
|
+
}
|
|
14096
|
+
return wc.executeJavaScript(`
|
|
14097
|
+
(function() {
|
|
14098
|
+
var el = document.querySelector(${JSON.stringify(sel)});
|
|
14099
|
+
if (!el) return "Error: Element not found";
|
|
14100
|
+
el.scrollIntoView({ behavior: "smooth", block: "${block}" });
|
|
14101
|
+
return "Scrolled to element";
|
|
14102
|
+
})()
|
|
14103
|
+
`);
|
|
14104
|
+
}
|
|
14105
|
+
);
|
|
14106
|
+
}
|
|
14107
|
+
);
|
|
14108
|
+
server.registerTool(
|
|
14109
|
+
"vessel_wait_for_navigation",
|
|
14110
|
+
{
|
|
14111
|
+
title: "Wait For Navigation",
|
|
14112
|
+
description: "Wait for the current page to finish loading after a click or form submission.",
|
|
14113
|
+
inputSchema: zod.z.object({
|
|
14114
|
+
timeoutMs: zod.z.number().optional().describe("Max wait in milliseconds (default 10000)")
|
|
14115
|
+
})
|
|
14116
|
+
},
|
|
14117
|
+
async ({ timeoutMs }) => {
|
|
14118
|
+
return withAction(
|
|
14119
|
+
tabManager,
|
|
14120
|
+
runtime,
|
|
14121
|
+
"vessel_wait_for_navigation",
|
|
14122
|
+
{ timeoutMs },
|
|
14123
|
+
async (wc) => {
|
|
14124
|
+
const timeout = timeoutMs || 1e4;
|
|
14125
|
+
const beforeUrl = wc.getURL();
|
|
14126
|
+
if (wc.isLoading()) {
|
|
14127
|
+
await new Promise((resolve) => {
|
|
14128
|
+
const timer = setTimeout(resolve, timeout);
|
|
14129
|
+
wc.once("did-stop-loading", () => {
|
|
14130
|
+
clearTimeout(timer);
|
|
14131
|
+
resolve();
|
|
14132
|
+
});
|
|
14133
|
+
});
|
|
14134
|
+
} else {
|
|
14135
|
+
await new Promise((resolve) => {
|
|
14136
|
+
let navigated = false;
|
|
14137
|
+
const timer = setTimeout(() => {
|
|
14138
|
+
if (!navigated) resolve();
|
|
14139
|
+
}, Math.min(timeout, 2e3));
|
|
14140
|
+
wc.once("did-start-loading", () => {
|
|
14141
|
+
navigated = true;
|
|
14142
|
+
clearTimeout(timer);
|
|
14143
|
+
const loadTimer = setTimeout(resolve, timeout);
|
|
14144
|
+
wc.once("did-stop-loading", () => {
|
|
14145
|
+
clearTimeout(loadTimer);
|
|
14146
|
+
resolve();
|
|
14147
|
+
});
|
|
14148
|
+
});
|
|
14149
|
+
});
|
|
14150
|
+
}
|
|
14151
|
+
const afterUrl = wc.getURL();
|
|
14152
|
+
const title = wc.getTitle();
|
|
14153
|
+
return afterUrl !== beforeUrl ? `Navigation complete: ${title} (${afterUrl})` : `Page loaded: ${title} (${afterUrl})`;
|
|
14154
|
+
}
|
|
14155
|
+
);
|
|
14156
|
+
}
|
|
14157
|
+
);
|
|
14158
|
+
server.registerTool(
|
|
14159
|
+
"vessel_metrics",
|
|
14160
|
+
{
|
|
14161
|
+
title: "Session Metrics",
|
|
14162
|
+
description: "Show performance metrics: total tool calls, average duration, per-tool breakdown.",
|
|
14163
|
+
inputSchema: zod.z.object({})
|
|
14164
|
+
},
|
|
14165
|
+
async () => {
|
|
14166
|
+
const m = runtime.getMetrics();
|
|
14167
|
+
const lines = [
|
|
14168
|
+
`Session Metrics:`,
|
|
14169
|
+
` Total actions: ${m.totalActions}`,
|
|
14170
|
+
` Completed: ${m.completedActions}`,
|
|
14171
|
+
` Failed: ${m.failedActions}`,
|
|
14172
|
+
` Average duration: ${m.averageDurationMs}ms`,
|
|
14173
|
+
``,
|
|
14174
|
+
`Tool breakdown:`
|
|
14175
|
+
];
|
|
14176
|
+
for (const [name, stats] of Object.entries(m.toolBreakdown)) {
|
|
14177
|
+
lines.push(` ${name}: ${stats.count} calls, avg ${stats.avgMs}ms${stats.errors > 0 ? `, ${stats.errors} errors` : ""}`);
|
|
14178
|
+
}
|
|
14179
|
+
return asTextResponse(lines.join("\n"));
|
|
14180
|
+
}
|
|
14181
|
+
);
|
|
12546
14182
|
}
|
|
12547
14183
|
function waitForLoad(wc, timeout = 1e4) {
|
|
12548
14184
|
return new Promise((resolve) => {
|
|
@@ -12592,6 +14228,13 @@ async function resolveSelector(wc, index, selector) {
|
|
|
12592
14228
|
`
|
|
12593
14229
|
);
|
|
12594
14230
|
if (typeof authoritativeSelector === "string" && authoritativeSelector) {
|
|
14231
|
+
if (authoritativeSelector.includes(" >>> ")) {
|
|
14232
|
+
const resolves2 = await wc.executeJavaScript(
|
|
14233
|
+
`!!window.__vessel?.resolveShadowSelector?.(${JSON.stringify(authoritativeSelector)})`
|
|
14234
|
+
);
|
|
14235
|
+
if (resolves2) return authoritativeSelector;
|
|
14236
|
+
return `__vessel_idx:${index}`;
|
|
14237
|
+
}
|
|
12595
14238
|
const resolves = await wc.executeJavaScript(
|
|
12596
14239
|
`!!document.querySelector(${JSON.stringify(authoritativeSelector)})`
|
|
12597
14240
|
);
|
|
@@ -13091,6 +14734,112 @@ function registerIpcHandlers(windowState, runtime) {
|
|
|
13091
14734
|
} catch {
|
|
13092
14735
|
}
|
|
13093
14736
|
});
|
|
14737
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
|
|
14738
|
+
const tab = tabManager.getActiveTab();
|
|
14739
|
+
if (!tab) return 0;
|
|
14740
|
+
const wc = tab.view.webContents;
|
|
14741
|
+
if (wc.isDestroyed()) return 0;
|
|
14742
|
+
try {
|
|
14743
|
+
return wc.executeJavaScript(
|
|
14744
|
+
`document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text').length`
|
|
14745
|
+
);
|
|
14746
|
+
} catch {
|
|
14747
|
+
return 0;
|
|
14748
|
+
}
|
|
14749
|
+
});
|
|
14750
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_SCROLL, (_, index) => {
|
|
14751
|
+
const tab = tabManager.getActiveTab();
|
|
14752
|
+
if (!tab) return false;
|
|
14753
|
+
const wc = tab.view.webContents;
|
|
14754
|
+
if (wc.isDestroyed()) return false;
|
|
14755
|
+
try {
|
|
14756
|
+
return wc.executeJavaScript(`
|
|
14757
|
+
(function() {
|
|
14758
|
+
var highlights = document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text');
|
|
14759
|
+
if (${index} < 0 || ${index} >= highlights.length) return false;
|
|
14760
|
+
// Remove focus ring from all highlights
|
|
14761
|
+
highlights.forEach(function(h) { h.style.removeProperty('outline'); h.style.removeProperty('outline-offset'); });
|
|
14762
|
+
var target = highlights[${index}];
|
|
14763
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
14764
|
+
// Add focus ring to current highlight
|
|
14765
|
+
target.style.setProperty('outline', '2px solid rgba(255, 255, 255, 0.9)', 'important');
|
|
14766
|
+
target.style.setProperty('outline-offset', '2px', 'important');
|
|
14767
|
+
return true;
|
|
14768
|
+
})()
|
|
14769
|
+
`);
|
|
14770
|
+
} catch {
|
|
14771
|
+
return false;
|
|
14772
|
+
}
|
|
14773
|
+
});
|
|
14774
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_REMOVE, (_, index) => {
|
|
14775
|
+
const tab = tabManager.getActiveTab();
|
|
14776
|
+
if (!tab) return false;
|
|
14777
|
+
const wc = tab.view.webContents;
|
|
14778
|
+
if (wc.isDestroyed()) return false;
|
|
14779
|
+
try {
|
|
14780
|
+
return wc.executeJavaScript(`
|
|
14781
|
+
(function() {
|
|
14782
|
+
var highlights = document.querySelectorAll('.__vessel-highlight, .__vessel-highlight-text');
|
|
14783
|
+
if (${index} < 0 || ${index} >= highlights.length) return false;
|
|
14784
|
+
var el = highlights[${index}];
|
|
14785
|
+
// Remove associated label if any
|
|
14786
|
+
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) {
|
|
14787
|
+
if (b.__vesselAnchor === el) b.remove();
|
|
14788
|
+
});
|
|
14789
|
+
// Unwrap text highlights, remove class from element highlights
|
|
14790
|
+
if (el.tagName === 'MARK' && el.classList.contains('__vessel-highlight-text')) {
|
|
14791
|
+
var parent = el.parentNode;
|
|
14792
|
+
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
14793
|
+
parent.removeChild(el);
|
|
14794
|
+
parent.normalize();
|
|
14795
|
+
} else {
|
|
14796
|
+
el.classList.remove('__vessel-highlight');
|
|
14797
|
+
el.style.removeProperty('background');
|
|
14798
|
+
el.style.removeProperty('outline-color');
|
|
14799
|
+
el.style.removeProperty('box-shadow');
|
|
14800
|
+
el.style.removeProperty('outline');
|
|
14801
|
+
el.style.removeProperty('outline-offset');
|
|
14802
|
+
}
|
|
14803
|
+
return true;
|
|
14804
|
+
})()
|
|
14805
|
+
`);
|
|
14806
|
+
} catch {
|
|
14807
|
+
return false;
|
|
14808
|
+
}
|
|
14809
|
+
});
|
|
14810
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_CLEAR, () => {
|
|
14811
|
+
const tab = tabManager.getActiveTab();
|
|
14812
|
+
if (!tab) return false;
|
|
14813
|
+
const wc = tab.view.webContents;
|
|
14814
|
+
if (wc.isDestroyed()) return false;
|
|
14815
|
+
try {
|
|
14816
|
+
return wc.executeJavaScript(`
|
|
14817
|
+
(function() {
|
|
14818
|
+
// Remove all labels
|
|
14819
|
+
document.querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]').forEach(function(b) { b.remove(); });
|
|
14820
|
+
// Unwrap text highlights
|
|
14821
|
+
document.querySelectorAll('.__vessel-highlight-text').forEach(function(mark) {
|
|
14822
|
+
var parent = mark.parentNode;
|
|
14823
|
+
while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
|
|
14824
|
+
parent.removeChild(mark);
|
|
14825
|
+
parent.normalize();
|
|
14826
|
+
});
|
|
14827
|
+
// Remove element highlights
|
|
14828
|
+
document.querySelectorAll('.__vessel-highlight').forEach(function(el) {
|
|
14829
|
+
el.classList.remove('__vessel-highlight');
|
|
14830
|
+
el.style.removeProperty('background');
|
|
14831
|
+
el.style.removeProperty('outline-color');
|
|
14832
|
+
el.style.removeProperty('box-shadow');
|
|
14833
|
+
el.style.removeProperty('outline');
|
|
14834
|
+
el.style.removeProperty('outline-offset');
|
|
14835
|
+
});
|
|
14836
|
+
return true;
|
|
14837
|
+
})()
|
|
14838
|
+
`);
|
|
14839
|
+
} catch {
|
|
14840
|
+
return false;
|
|
14841
|
+
}
|
|
14842
|
+
});
|
|
13094
14843
|
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => {
|
|
13095
14844
|
windowState.uiState.devtoolsPanelOpen = !windowState.uiState.devtoolsPanelOpen;
|
|
13096
14845
|
layoutViews(windowState);
|
|
@@ -13355,6 +15104,7 @@ ${progress}
|
|
|
13355
15104
|
mode: "replace"
|
|
13356
15105
|
});
|
|
13357
15106
|
const approvalReason = this.getApprovalReason(dangerous);
|
|
15107
|
+
console.log(`[Vessel Runtime] action=${name} dangerous=${dangerous} approvalReason=${approvalReason} mode=${this.state.supervisor.approvalMode}`);
|
|
13358
15108
|
if (approvalReason) {
|
|
13359
15109
|
this.publishTranscript({
|
|
13360
15110
|
source,
|
|
@@ -13364,7 +15114,9 @@ ${progress}
|
|
|
13364
15114
|
streamId: transcriptStreamId,
|
|
13365
15115
|
mode: "replace"
|
|
13366
15116
|
});
|
|
15117
|
+
console.log(`[Vessel Runtime] awaiting approval for ${name}...`);
|
|
13367
15118
|
const approved = await this.awaitApproval(action, approvalReason);
|
|
15119
|
+
console.log(`[Vessel Runtime] approval result for ${name}: ${approved}`);
|
|
13368
15120
|
if (!approved) {
|
|
13369
15121
|
this.publishTranscript({
|
|
13370
15122
|
source,
|
|
@@ -13499,13 +15251,44 @@ ${progress}
|
|
|
13499
15251
|
this.emit();
|
|
13500
15252
|
}
|
|
13501
15253
|
finishAction(actionId, status, resultSummary, error) {
|
|
15254
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15255
|
+
const action = this.state.actions.find((a) => a.id === actionId);
|
|
15256
|
+
const durationMs = action ? new Date(finishedAt).getTime() - new Date(action.startedAt).getTime() : void 0;
|
|
13502
15257
|
this.updateAction(actionId, {
|
|
13503
15258
|
status,
|
|
13504
|
-
finishedAt
|
|
15259
|
+
finishedAt,
|
|
15260
|
+
durationMs,
|
|
13505
15261
|
resultSummary,
|
|
13506
15262
|
error
|
|
13507
15263
|
});
|
|
13508
15264
|
}
|
|
15265
|
+
/** Aggregate metrics for all completed actions in this session. */
|
|
15266
|
+
getMetrics() {
|
|
15267
|
+
const completed = this.state.actions.filter((a) => a.status === "completed");
|
|
15268
|
+
const failed = this.state.actions.filter((a) => a.status === "error");
|
|
15269
|
+
const durations = completed.filter((a) => a.durationMs != null).map((a) => a.durationMs);
|
|
15270
|
+
const avgDuration = durations.length > 0 ? durations.reduce((s, d) => s + d, 0) / durations.length : 0;
|
|
15271
|
+
const toolBreakdown = {};
|
|
15272
|
+
for (const action of this.state.actions) {
|
|
15273
|
+
const name = action.name;
|
|
15274
|
+
if (!toolBreakdown[name]) toolBreakdown[name] = { count: 0, totalMs: 0, avgMs: 0, errors: 0 };
|
|
15275
|
+
toolBreakdown[name].count++;
|
|
15276
|
+
if (action.durationMs != null) toolBreakdown[name].totalMs += action.durationMs;
|
|
15277
|
+
if (action.status === "error") toolBreakdown[name].errors++;
|
|
15278
|
+
}
|
|
15279
|
+
for (const entry of Object.values(toolBreakdown)) {
|
|
15280
|
+
entry.avgMs = entry.count > 0 ? Math.round(entry.totalMs / entry.count) : 0;
|
|
15281
|
+
}
|
|
15282
|
+
return {
|
|
15283
|
+
totalActions: this.state.actions.length,
|
|
15284
|
+
completedActions: completed.length,
|
|
15285
|
+
failedActions: failed.length,
|
|
15286
|
+
averageDurationMs: Math.round(avgDuration),
|
|
15287
|
+
toolBreakdown: Object.fromEntries(
|
|
15288
|
+
Object.entries(toolBreakdown).map(([k, v]) => [k, { count: v.count, avgMs: v.avgMs, errors: v.errors }])
|
|
15289
|
+
)
|
|
15290
|
+
};
|
|
15291
|
+
}
|
|
13509
15292
|
getApprovalReason(dangerous) {
|
|
13510
15293
|
if (this.state.supervisor.paused) {
|
|
13511
15294
|
return "Agent execution is paused";
|