@quanta-intellect/vessel-browser 0.1.141 → 0.1.144
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/main/index.js
CHANGED
|
@@ -2221,6 +2221,19 @@ async function scrollToHighlight(wc, index) {
|
|
|
2221
2221
|
})()
|
|
2222
2222
|
`);
|
|
2223
2223
|
}
|
|
2224
|
+
async function getHighlightTextAtIndex(wc, index) {
|
|
2225
|
+
const safeIndex = Math.floor(Number(index));
|
|
2226
|
+
return wc.executeJavaScript(`
|
|
2227
|
+
(function() {
|
|
2228
|
+
var highlights = document.querySelectorAll(${HIGHLIGHT_SELECTOR});
|
|
2229
|
+
if (${safeIndex} < 0 || ${safeIndex} >= highlights.length) return null;
|
|
2230
|
+
var el = highlights[${safeIndex}];
|
|
2231
|
+
var text = el.getAttribute && el.getAttribute('data-vessel-highlight-text');
|
|
2232
|
+
if (!text && el.textContent) text = el.textContent;
|
|
2233
|
+
return text ? text.trim() : null;
|
|
2234
|
+
})()
|
|
2235
|
+
`);
|
|
2236
|
+
}
|
|
2224
2237
|
async function removeHighlightAtIndex(wc, index) {
|
|
2225
2238
|
const safeIndex = Math.floor(Number(index));
|
|
2226
2239
|
return wc.executeJavaScript(`
|
|
@@ -8751,7 +8764,24 @@ function recoverNarratedActionToolCalls(text, availableToolNames) {
|
|
|
8751
8764
|
}
|
|
8752
8765
|
return recovered;
|
|
8753
8766
|
}
|
|
8754
|
-
|
|
8767
|
+
function shouldRetryUnexecutedHighlightCompletion(userMessage, assistantText, successfulToolNames) {
|
|
8768
|
+
const userAskedForHighlights = /\b(?:highlight|highlights|mark|annotate)\b/i.test(
|
|
8769
|
+
userMessage
|
|
8770
|
+
);
|
|
8771
|
+
if (!userAskedForHighlights || successfulToolNames.includes("highlight")) {
|
|
8772
|
+
return false;
|
|
8773
|
+
}
|
|
8774
|
+
const normalizedAssistant = assistantText.toLowerCase();
|
|
8775
|
+
return /\b(?:highlighted|marked|annotated)\b/.test(normalizedAssistant) || /\b(?:green|yellow|red|blue|purple|orange)\s+highlights?\b/.test(
|
|
8776
|
+
normalizedAssistant
|
|
8777
|
+
) || /\bhighlights?\s+(?:added|shown|applied|visible|on the page)\b/.test(
|
|
8778
|
+
normalizedAssistant
|
|
8779
|
+
);
|
|
8780
|
+
}
|
|
8781
|
+
function buildHighlightToolCompletionPrompt() {
|
|
8782
|
+
return `The user asked you to highlight items on the page, but no highlight tool call succeeded. Do not claim visual highlights are present until you call the supported highlight tool. Use read_page only if you need current page text, then call highlight with {"text":"exact visible title or passage"} for each item you want to mark. Use an element index only when the latest read_page result gives the exact current index for that same item.`;
|
|
8783
|
+
}
|
|
8784
|
+
const DOLLAR_PRICE_RE = /\$\s?(?:\d{2,4}|\d{1,3}(?:,\d{3})+)(?:\.\d{2})?\b/;
|
|
8755
8785
|
const FLIGHT_TASK_RE = /\b(?:flight|flights|airfare|air fare|plane ticket|airline|airport|google flights|pdx|sfo|san francisco|portland)\b/i;
|
|
8756
8786
|
const SHOPPING_INTENT_RE = /\b(?:cheap|cheapest|price|prices|fare|fares|one[- ]?way|round[- ]?trip|depart|departure|arrive|arrival|from|to)\b/i;
|
|
8757
8787
|
const FLIGHT_CLAIM_CONTEXT_RE = /\b(?:flight|flights|airline|airlines|departure|arrival|depart|arrive|nonstop|non-stop|stops?|duration|alaska|united|delta|american|southwest|frontier|spirit|jetblue|hawaiian)\b/i;
|
|
@@ -8787,6 +8817,106 @@ function buildFlightPriceEvidenceRecoveryPrompt(userMessage, assistantText, late
|
|
|
8787
8817
|
`Last unsupported answer: ${assistantText.replace(/\s+/g, " ").trim().slice(0, 500) || "(empty)"}`
|
|
8788
8818
|
].join("\n");
|
|
8789
8819
|
}
|
|
8820
|
+
const SEARCH_HISTORY_LIMIT = 4;
|
|
8821
|
+
function normalizeSearchToolQuery(name, args) {
|
|
8822
|
+
if (name !== "search" && name !== "web_search") return null;
|
|
8823
|
+
const raw = typeof args.query === "string" ? args.query : typeof args.text === "string" ? args.text : typeof args.term === "string" ? args.term : "";
|
|
8824
|
+
const normalized = raw.replace(/\s+/g, " ").trim().toLowerCase();
|
|
8825
|
+
return normalized || null;
|
|
8826
|
+
}
|
|
8827
|
+
function buildLatestStateReminder(toolResultPreview) {
|
|
8828
|
+
const text = (toolResultPreview || "").trim();
|
|
8829
|
+
if (!text) return "";
|
|
8830
|
+
const existingReminder = text.match(
|
|
8831
|
+
/\bLatest browser state:\s*URL\s+.+?(?:Trust the latest tool result over the initial page context\.|$)/i
|
|
8832
|
+
)?.[0]?.trim();
|
|
8833
|
+
if (existingReminder) return existingReminder;
|
|
8834
|
+
const stateMatch = text.match(
|
|
8835
|
+
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
8836
|
+
);
|
|
8837
|
+
if (stateMatch) {
|
|
8838
|
+
const url = stateMatch[1]?.trim();
|
|
8839
|
+
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
8840
|
+
if (url) {
|
|
8841
|
+
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8844
|
+
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8845
|
+
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8846
|
+
if (structuredUrl) {
|
|
8847
|
+
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8848
|
+
}
|
|
8849
|
+
const navigatedUrl = text.match(
|
|
8850
|
+
/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i
|
|
8851
|
+
)?.[1]?.trim() ?? text.match(
|
|
8852
|
+
/\b(?:web\s+)?searched "[^"]+"[^\n]*?(?:->|→)\s+([^\s\n]+)/i
|
|
8853
|
+
)?.[1]?.trim();
|
|
8854
|
+
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
8855
|
+
if (navigatedUrl) {
|
|
8856
|
+
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8857
|
+
}
|
|
8858
|
+
return "";
|
|
8859
|
+
}
|
|
8860
|
+
function buildRepeatedSearchError(previousTool, previousQuery, latestToolResultPreview, mode) {
|
|
8861
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8862
|
+
const lines = [
|
|
8863
|
+
mode === "drifted" ? `Error: You already performed ${previousTool} successfully for this task.` : `Error: You already searched for "${previousQuery}" successfully with ${previousTool}.`,
|
|
8864
|
+
mode === "drifted" ? `Do not rewrite or broaden the query with another ${previousTool}; use the current search results instead.` : `Do not search the same query again with ${previousTool} or its search/web_search alias; use the current search results instead.`,
|
|
8865
|
+
`For named venues, businesses, organizations, schools, or local places, prefer opening the official site or clearly direct result from the current results page before answering. Do not switch to a site: restricted web_search when an official or direct result is already available.`,
|
|
8866
|
+
`Take the next action from the results you already have: click a result, inspect a specific item, or provide the final answer to the user. Do not call any search tool again as preparation, and do not call read_page as preparation for another search.`
|
|
8867
|
+
];
|
|
8868
|
+
if (stateReminder) {
|
|
8869
|
+
lines.push(stateReminder);
|
|
8870
|
+
}
|
|
8871
|
+
return lines.join(" ");
|
|
8872
|
+
}
|
|
8873
|
+
class SearchLoopGuard {
|
|
8874
|
+
recentSuccessfulSearchQueries = [];
|
|
8875
|
+
recentSuccessfulSearchToolByQuery = /* @__PURE__ */ new Map();
|
|
8876
|
+
lastSuccessfulWebSearchQuery = null;
|
|
8877
|
+
isContextResettingTool;
|
|
8878
|
+
constructor(isContextResettingTool) {
|
|
8879
|
+
this.isContextResettingTool = isContextResettingTool;
|
|
8880
|
+
}
|
|
8881
|
+
/**
|
|
8882
|
+
* Check whether a search/web_search call should be suppressed. Returns the
|
|
8883
|
+
* details needed to build the corrective error, or null if the call is OK.
|
|
8884
|
+
*/
|
|
8885
|
+
check(toolName, query) {
|
|
8886
|
+
const isRepeatedSearchAcrossTools = query !== null && this.recentSuccessfulSearchQueries.includes(query);
|
|
8887
|
+
const isQueryDriftedWebSearch = toolName === "web_search" && this.lastSuccessfulWebSearchQuery !== null && query !== null && query !== this.lastSuccessfulWebSearchQuery;
|
|
8888
|
+
if (!isRepeatedSearchAcrossTools && !isQueryDriftedWebSearch) return null;
|
|
8889
|
+
const mode = isRepeatedSearchAcrossTools ? "repeated" : "drifted";
|
|
8890
|
+
const previousTool = isRepeatedSearchAcrossTools ? this.recentSuccessfulSearchToolByQuery.get(query ?? "") ?? (toolName === "web_search" ? "search" : "web_search") : "web_search";
|
|
8891
|
+
const previousQuery = isRepeatedSearchAcrossTools ? query ?? "" : this.lastSuccessfulWebSearchQuery ?? "";
|
|
8892
|
+
return { mode, previousTool, previousQuery };
|
|
8893
|
+
}
|
|
8894
|
+
/**
|
|
8895
|
+
* Record a successfully executed tool. Search queries are added to the
|
|
8896
|
+
* recent-history ring buffer, and real-progress tools clear the drift anchor
|
|
8897
|
+
* so a later distinct search is not flagged as drift.
|
|
8898
|
+
*/
|
|
8899
|
+
recordSuccess(toolName, query, wasSuccessful) {
|
|
8900
|
+
if (wasSuccessful && this.isContextResettingTool(toolName)) {
|
|
8901
|
+
this.lastSuccessfulWebSearchQuery = null;
|
|
8902
|
+
}
|
|
8903
|
+
if (wasSuccessful && query) {
|
|
8904
|
+
if (!this.recentSuccessfulSearchQueries.includes(query)) {
|
|
8905
|
+
this.recentSuccessfulSearchQueries.push(query);
|
|
8906
|
+
this.recentSuccessfulSearchToolByQuery.set(query, toolName);
|
|
8907
|
+
if (this.recentSuccessfulSearchQueries.length > SEARCH_HISTORY_LIMIT) {
|
|
8908
|
+
const dropped = this.recentSuccessfulSearchQueries.shift();
|
|
8909
|
+
if (dropped) {
|
|
8910
|
+
this.recentSuccessfulSearchToolByQuery.delete(dropped);
|
|
8911
|
+
}
|
|
8912
|
+
}
|
|
8913
|
+
}
|
|
8914
|
+
}
|
|
8915
|
+
if (wasSuccessful && toolName === "web_search" && query) {
|
|
8916
|
+
this.lastSuccessfulWebSearchQuery = query;
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
}
|
|
8790
8920
|
const logger$v = createLogger("OpenAIProvider");
|
|
8791
8921
|
function shouldDebugAgentLoop() {
|
|
8792
8922
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
@@ -8837,10 +8967,26 @@ function toOpenAIReasoningEffort(effort, providerId, model) {
|
|
|
8837
8967
|
return void 0;
|
|
8838
8968
|
}
|
|
8839
8969
|
}
|
|
8970
|
+
function openRouterRoutingOptions(providerId) {
|
|
8971
|
+
if (providerId !== "openrouter") return {};
|
|
8972
|
+
return {
|
|
8973
|
+
provider: {
|
|
8974
|
+
require_parameters: true,
|
|
8975
|
+
sort: "latency"
|
|
8976
|
+
}
|
|
8977
|
+
};
|
|
8978
|
+
}
|
|
8979
|
+
function buildOpenRouterAttributionHeaders() {
|
|
8980
|
+
return {
|
|
8981
|
+
"HTTP-Referer": "https://github.com/unmodeled-tyler/vessel-browser",
|
|
8982
|
+
"X-OpenRouter-Title": "Vessel Browser",
|
|
8983
|
+
"X-OpenRouter-Categories": "personal-agent,general-chat"
|
|
8984
|
+
};
|
|
8985
|
+
}
|
|
8840
8986
|
function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
|
|
8841
8987
|
if (profile !== "compact") return null;
|
|
8842
8988
|
const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
|
|
8843
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview
|
|
8989
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8844
8990
|
return {
|
|
8845
8991
|
role: "user",
|
|
8846
8992
|
content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
|
|
@@ -8850,13 +8996,13 @@ ${phaseReminder}` : "")
|
|
|
8850
8996
|
};
|
|
8851
8997
|
}
|
|
8852
8998
|
function extractSingleGoalDomain(goal) {
|
|
8853
|
-
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.
|
|
8999
|
+
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.[a-z]{2,})\b/g);
|
|
8854
9000
|
if (!matches || matches.length !== 1) return null;
|
|
8855
9001
|
return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
|
|
8856
9002
|
}
|
|
8857
9003
|
function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
|
|
8858
9004
|
const phaseReminder = buildPhaseReminder(userMessage, assistantText);
|
|
8859
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview
|
|
9005
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8860
9006
|
const goalDomain = extractSingleGoalDomain(userMessage);
|
|
8861
9007
|
const latest = (latestToolResultPreview || "").toLowerCase();
|
|
8862
9008
|
const assistant = assistantText.toLowerCase();
|
|
@@ -8953,30 +9099,18 @@ function buildPhaseReminder(userMessage, assistantText) {
|
|
|
8953
9099
|
}
|
|
8954
9100
|
return "";
|
|
8955
9101
|
}
|
|
8956
|
-
function
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
}
|
|
8969
|
-
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8970
|
-
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8971
|
-
if (structuredUrl) {
|
|
8972
|
-
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8973
|
-
}
|
|
8974
|
-
const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
|
|
8975
|
-
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
8976
|
-
if (navigatedUrl) {
|
|
8977
|
-
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8978
|
-
}
|
|
8979
|
-
return "";
|
|
9102
|
+
function isSearchContextResettingTool(name) {
|
|
9103
|
+
return ![
|
|
9104
|
+
"read_page",
|
|
9105
|
+
"current_tab",
|
|
9106
|
+
"list_tabs",
|
|
9107
|
+
"screenshot",
|
|
9108
|
+
"clear_overlays",
|
|
9109
|
+
"accept_cookies",
|
|
9110
|
+
"dismiss_popup",
|
|
9111
|
+
"web_search",
|
|
9112
|
+
"search"
|
|
9113
|
+
].includes(name);
|
|
8980
9114
|
}
|
|
8981
9115
|
function shouldRecoverCompactStall(text, userMessage) {
|
|
8982
9116
|
const trimmed = text.trim().toLowerCase();
|
|
@@ -9061,10 +9195,24 @@ function logAgentLoopDebug(payload) {
|
|
|
9061
9195
|
}
|
|
9062
9196
|
}
|
|
9063
9197
|
function formatOpenAICompatErrorMessage(providerId, message) {
|
|
9198
|
+
if (providerId === "openrouter" && /(timed out after \d+(?:\.\d+)? seconds|request timed out|returned none after all retries|returned no content|empty response)/i.test(
|
|
9199
|
+
message
|
|
9200
|
+
)) {
|
|
9201
|
+
return [
|
|
9202
|
+
message,
|
|
9203
|
+
"OpenRouter reported an upstream model timeout/no-content failure.",
|
|
9204
|
+
"If this persists, retry or pin a specific low-latency tool-calling model instead of the free router."
|
|
9205
|
+
].join(" ");
|
|
9206
|
+
}
|
|
9064
9207
|
if (providerId === "llama_cpp" && /(available context size|context size exceeded|exceeds the available context size|try increasing it)/i.test(
|
|
9065
9208
|
message
|
|
9066
9209
|
)) {
|
|
9067
|
-
return
|
|
9210
|
+
return [
|
|
9211
|
+
message,
|
|
9212
|
+
"llama.cpp sets context size at server startup, not per request.",
|
|
9213
|
+
`Vessel's agent prompt plus tool schema is about 6.5k tokens before browsing history, so run llama-server with`,
|
|
9214
|
+
`--ctx-size ${LLAMA_CPP_MIN_CTX_TOKENS} minimum (${LLAMA_CPP_RECOMMENDED_CTX_TOKENS} recommended).`
|
|
9215
|
+
].join(" ");
|
|
9068
9216
|
}
|
|
9069
9217
|
return message;
|
|
9070
9218
|
}
|
|
@@ -9083,10 +9231,7 @@ class OpenAICompatProvider {
|
|
|
9083
9231
|
apiKey: config.apiKey || "ollama",
|
|
9084
9232
|
baseURL,
|
|
9085
9233
|
...isOpenRouter && {
|
|
9086
|
-
defaultHeaders:
|
|
9087
|
-
"HTTP-Referer": "https://github.com/unmodeled/vessel-browser",
|
|
9088
|
-
"X-Title": "Vessel"
|
|
9089
|
-
}
|
|
9234
|
+
defaultHeaders: buildOpenRouterAttributionHeaders()
|
|
9090
9235
|
}
|
|
9091
9236
|
});
|
|
9092
9237
|
this.providerId = config.id;
|
|
@@ -9113,6 +9258,7 @@ class OpenAICompatProvider {
|
|
|
9113
9258
|
max_tokens: 4096,
|
|
9114
9259
|
stream: true,
|
|
9115
9260
|
messages,
|
|
9261
|
+
...openRouterRoutingOptions(this.providerId),
|
|
9116
9262
|
...openAIPromptCacheOptions({
|
|
9117
9263
|
providerId: this.providerId,
|
|
9118
9264
|
model: this.model,
|
|
@@ -9171,9 +9317,12 @@ class OpenAICompatProvider {
|
|
|
9171
9317
|
let iterationsUsed = 0;
|
|
9172
9318
|
let compactRecoveryCount = 0;
|
|
9173
9319
|
let flightPriceEvidenceRecoveryCount = 0;
|
|
9320
|
+
let highlightCompletionRecoveryCount = 0;
|
|
9174
9321
|
let compactCorrectionCount = 0;
|
|
9175
9322
|
const recentCompactToolSignatures = [];
|
|
9176
9323
|
const recentToolNames = [];
|
|
9324
|
+
const successfulToolNames = [];
|
|
9325
|
+
const searchLoopGuard = new SearchLoopGuard(isSearchContextResettingTool);
|
|
9177
9326
|
let clickReadLoopNudged = false;
|
|
9178
9327
|
for (let i = 0; i < maxIterations; i++) {
|
|
9179
9328
|
iterationsUsed = i + 1;
|
|
@@ -9195,6 +9344,7 @@ class OpenAICompatProvider {
|
|
|
9195
9344
|
tools: openAITools,
|
|
9196
9345
|
tool_choice: "auto",
|
|
9197
9346
|
temperature: agentTemperatureForProfile(this.agentToolProfile),
|
|
9347
|
+
...openRouterRoutingOptions(this.providerId),
|
|
9198
9348
|
...openAIPromptCacheOptions({
|
|
9199
9349
|
providerId: this.providerId,
|
|
9200
9350
|
model: this.model,
|
|
@@ -9342,6 +9492,19 @@ class OpenAICompatProvider {
|
|
|
9342
9492
|
});
|
|
9343
9493
|
continue;
|
|
9344
9494
|
}
|
|
9495
|
+
if (highlightCompletionRecoveryCount < 1 && shouldRetryUnexecutedHighlightCompletion(
|
|
9496
|
+
userMessage,
|
|
9497
|
+
textAccum,
|
|
9498
|
+
successfulToolNames
|
|
9499
|
+
)) {
|
|
9500
|
+
highlightCompletionRecoveryCount += 1;
|
|
9501
|
+
if (textAccum.trim()) onChunk("<<erase_prev>>");
|
|
9502
|
+
messages.push({
|
|
9503
|
+
role: "user",
|
|
9504
|
+
content: `[System] ${buildHighlightToolCompletionPrompt()}`
|
|
9505
|
+
});
|
|
9506
|
+
continue;
|
|
9507
|
+
}
|
|
9345
9508
|
break;
|
|
9346
9509
|
}
|
|
9347
9510
|
compactRecoveryCount = 0;
|
|
@@ -9394,6 +9557,25 @@ class OpenAICompatProvider {
|
|
|
9394
9557
|
args = repairedArgs.args;
|
|
9395
9558
|
args = coerceToolArgsForExecution(tc.name, args);
|
|
9396
9559
|
const toolSignature = stableToolSignature(tc.name, args);
|
|
9560
|
+
const searchToolQuery = normalizeSearchToolQuery(tc.name, args);
|
|
9561
|
+
const searchLoopCheck = searchLoopGuard.check(tc.name, searchToolQuery);
|
|
9562
|
+
if (searchLoopCheck) {
|
|
9563
|
+
onChunk(`
|
|
9564
|
+
<<tool:${tc.name}:↻ duplicate suppressed>>
|
|
9565
|
+
`);
|
|
9566
|
+
messages.push({
|
|
9567
|
+
role: "tool",
|
|
9568
|
+
tool_call_id: tc.id,
|
|
9569
|
+
content: buildRepeatedSearchError(
|
|
9570
|
+
searchLoopCheck.previousTool,
|
|
9571
|
+
searchLoopCheck.previousQuery,
|
|
9572
|
+
latestToolMessage ? String(latestToolMessage.content || "") : null,
|
|
9573
|
+
searchLoopCheck.mode
|
|
9574
|
+
)
|
|
9575
|
+
});
|
|
9576
|
+
compactCorrectionCount += 1;
|
|
9577
|
+
continue;
|
|
9578
|
+
}
|
|
9397
9579
|
if (this.agentToolProfile === "compact" && tc.name === "click" && isTargetlessClickArgs(args)) {
|
|
9398
9580
|
onChunk(`
|
|
9399
9581
|
<<tool:${tc.name}:⚠ missing target>>
|
|
@@ -9468,6 +9650,15 @@ class OpenAICompatProvider {
|
|
|
9468
9650
|
recentCompactToolSignatures.shift();
|
|
9469
9651
|
}
|
|
9470
9652
|
}
|
|
9653
|
+
const toolSucceeded = !/^Error:/i.test(toolContent.trim());
|
|
9654
|
+
if (toolSucceeded) {
|
|
9655
|
+
successfulToolNames.push(tc.name);
|
|
9656
|
+
}
|
|
9657
|
+
searchLoopGuard.recordSuccess(
|
|
9658
|
+
tc.name,
|
|
9659
|
+
searchToolQuery,
|
|
9660
|
+
toolSucceeded
|
|
9661
|
+
);
|
|
9471
9662
|
recentToolNames.push(tc.name);
|
|
9472
9663
|
if (recentToolNames.length > 8) recentToolNames.shift();
|
|
9473
9664
|
if (!clickReadLoopNudged && recentToolNames.length >= 6 && isClickReadLoop(recentToolNames)) {
|
|
@@ -9997,59 +10188,13 @@ function previewToolResult(text, maxLength = 800) {
|
|
|
9997
10188
|
if (normalized.length <= maxLength) return normalized;
|
|
9998
10189
|
return `${normalized.slice(0, maxLength)}...`;
|
|
9999
10190
|
}
|
|
10000
|
-
function normalizedSearchToolQuery(name, args) {
|
|
10001
|
-
if (name !== "search" && name !== "web_search") return null;
|
|
10002
|
-
const raw = typeof args.query === "string" ? args.query : typeof args.text === "string" ? args.text : typeof args.term === "string" ? args.term : "";
|
|
10003
|
-
const normalized = raw.replace(/\s+/g, " ").trim().toLowerCase();
|
|
10004
|
-
return normalized || null;
|
|
10005
|
-
}
|
|
10006
10191
|
function hasBlockingOverlaySignal(text) {
|
|
10007
10192
|
if (!text) return false;
|
|
10008
10193
|
if (/\bno blocking overlays detected\b/i.test(text)) return false;
|
|
10009
10194
|
return /\bwarning:\s*blocking overlay detected\b/i.test(text) || /\bblocked-by-overlay\b/i.test(text) || /###\s*immediate blockers\b/i.test(text) || /\bblocking overlays\W+[1-9]\d*\b/i.test(text);
|
|
10010
10195
|
}
|
|
10011
|
-
function buildCodexLatestStateReminder(toolResultPreview) {
|
|
10012
|
-
const text = (toolResultPreview || "").trim();
|
|
10013
|
-
if (!text) return "";
|
|
10014
|
-
const existingReminder = text.match(
|
|
10015
|
-
/\bLatest browser state:\s*URL\s+.+?(?:Trust the latest tool result over the initial page context\.|$)/i
|
|
10016
|
-
)?.[0]?.trim();
|
|
10017
|
-
if (existingReminder) return existingReminder;
|
|
10018
|
-
const stateMatch = text.match(
|
|
10019
|
-
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
10020
|
-
);
|
|
10021
|
-
if (stateMatch) {
|
|
10022
|
-
const url = stateMatch[1]?.trim();
|
|
10023
|
-
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
10024
|
-
if (url) {
|
|
10025
|
-
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
10026
|
-
}
|
|
10027
|
-
}
|
|
10028
|
-
const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to)\s+([^\s\n]+)/i)?.[1]?.trim() ?? text.match(/\b(?:web\s+)?searched "[^"]+"[^\n]*?(?:->|→)\s+([^\s\n]+)/i)?.[1]?.trim();
|
|
10029
|
-
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
10030
|
-
if (navigatedUrl) {
|
|
10031
|
-
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
10032
|
-
}
|
|
10033
|
-
return "";
|
|
10034
|
-
}
|
|
10035
|
-
function buildCodexRepeatedSearchError(previousTool, previousQuery, latestToolResultPreview, mode) {
|
|
10036
|
-
const stateReminder = buildCodexLatestStateReminder(latestToolResultPreview);
|
|
10037
|
-
const header = mode === "drifted" ? `Error: You already performed ${previousTool} successfully for this task.` : `Error: You already searched for "${previousQuery}" successfully with ${previousTool}.`;
|
|
10038
|
-
const lines = [
|
|
10039
|
-
header,
|
|
10040
|
-
mode === "drifted" ? `Do not rewrite or broaden the query with another ${previousTool}. The latest results from your previous ${previousTool} are already in the conversation context.` : `Do not search the same query again with ${previousTool} (or its alias search/web_search). The latest results from your previous ${previousTool} are already in the conversation context.`,
|
|
10041
|
-
// The key change: do NOT suggest read_page as a "recovery" action.
|
|
10042
|
-
// The model was using read_page as a no-op to reset the strike counter
|
|
10043
|
-
// and then issue another web_search. The prior results are sufficient.
|
|
10044
|
-
`Take the next action from the results you already have: click a result link, call inspect_element on a specific item, highlight, or provide the final answer to the user. Do not call any search tool again, and do not call read_page as preparation for another search.`
|
|
10045
|
-
];
|
|
10046
|
-
if (stateReminder) {
|
|
10047
|
-
lines.push(stateReminder);
|
|
10048
|
-
}
|
|
10049
|
-
return lines.join(" ");
|
|
10050
|
-
}
|
|
10051
10196
|
function buildCodexUnsupportedClearOverlayError(latestToolResultPreview) {
|
|
10052
|
-
const stateReminder =
|
|
10197
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10053
10198
|
const lines = [
|
|
10054
10199
|
`Error: No blocking overlay signal is present in the latest browser state.`,
|
|
10055
10200
|
`Do not call clear_overlays unless read_page or the page context explicitly reports a blocking overlay.`,
|
|
@@ -10169,7 +10314,7 @@ function shouldRetryCodexToolLoop(text, hasToolHistory) {
|
|
|
10169
10314
|
return false;
|
|
10170
10315
|
}
|
|
10171
10316
|
function buildCodexRecoveryInput(userMessage, assistantText, latestToolResultPreview) {
|
|
10172
|
-
const stateReminder =
|
|
10317
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10173
10318
|
const lines = [
|
|
10174
10319
|
`[System] The task is still in progress: ${userMessage}`,
|
|
10175
10320
|
`Do not ask the user what they want next unless the original request is genuinely ambiguous or blocked.`,
|
|
@@ -10202,7 +10347,7 @@ function buildCodexFlightPriceEvidenceRecoveryInput(userMessage, assistantText,
|
|
|
10202
10347
|
};
|
|
10203
10348
|
}
|
|
10204
10349
|
function buildCodexFailedClickRecoveryInput(attemptedTarget, latestToolResultPreview, failedClickCount = 1) {
|
|
10205
|
-
const stateReminder =
|
|
10350
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10206
10351
|
const lines = [
|
|
10207
10352
|
`[System] The previous click did not complete${attemptedTarget ? ` for ${attemptedTarget}` : ""}.`,
|
|
10208
10353
|
`Take the next step yourself: try a different target, refresh the page state with read_page, call inspect_element on the intended element, or answer from the results already visible in the conversation. Do not ask the user to inspect or click the result for you.`
|
|
@@ -10421,9 +10566,7 @@ class CodexProvider {
|
|
|
10421
10566
|
let clickReadLoopNudged = false;
|
|
10422
10567
|
let latestToolResultPreview = null;
|
|
10423
10568
|
let failedClickCountSinceProgress = 0;
|
|
10424
|
-
const
|
|
10425
|
-
const recentSuccessfulSearchToolByQuery = /* @__PURE__ */ new Map();
|
|
10426
|
-
let lastSuccessfulWebSearchQuery = null;
|
|
10569
|
+
const searchLoopGuard = new SearchLoopGuard(isRealProgressTool);
|
|
10427
10570
|
try {
|
|
10428
10571
|
for (let i = 0; i < maxIterations; i++) {
|
|
10429
10572
|
iterationsUsed = i + 1;
|
|
@@ -10513,30 +10656,29 @@ class CodexProvider {
|
|
|
10513
10656
|
prepared.prepared.name,
|
|
10514
10657
|
prepared.prepared.args
|
|
10515
10658
|
);
|
|
10516
|
-
const searchToolQuery =
|
|
10659
|
+
const searchToolQuery = normalizeSearchToolQuery(
|
|
10517
10660
|
prepared.prepared.name,
|
|
10518
10661
|
prepared.prepared.args
|
|
10519
10662
|
);
|
|
10520
|
-
const
|
|
10521
|
-
|
|
10663
|
+
const searchLoopCheck = searchLoopGuard.check(
|
|
10664
|
+
prepared.prepared.name,
|
|
10665
|
+
searchToolQuery
|
|
10666
|
+
);
|
|
10522
10667
|
const isUnsupportedClearOverlay = prepared.prepared.name === "clear_overlays" && !hasBlockingOverlaySignal(
|
|
10523
10668
|
`${systemPrompt}
|
|
10524
10669
|
${latestToolResultPreview || ""}`
|
|
10525
10670
|
);
|
|
10526
|
-
if (
|
|
10671
|
+
if (searchLoopCheck) {
|
|
10527
10672
|
onChunk(`
|
|
10528
10673
|
<<tool:${prepared.prepared.name}:↻ duplicate suppressed>>
|
|
10529
10674
|
`);
|
|
10530
|
-
const previousTool = isRepeatedSearchAcrossTools ? recentSuccessfulSearchToolByQuery.get(searchToolQuery ?? "") ?? (prepared.prepared.name === "web_search" ? "search" : "web_search") : "web_search";
|
|
10531
|
-
const previousQuery = isRepeatedSearchAcrossTools ? searchToolQuery ?? "" : lastSuccessfulWebSearchQuery ?? "";
|
|
10532
|
-
const mode = isRepeatedSearchAcrossTools ? "repeated" : "drifted";
|
|
10533
10675
|
const output2 = createCodexToolOutput(
|
|
10534
10676
|
prepared.prepared.callId,
|
|
10535
|
-
|
|
10536
|
-
previousTool,
|
|
10537
|
-
previousQuery,
|
|
10677
|
+
buildRepeatedSearchError(
|
|
10678
|
+
searchLoopCheck.previousTool,
|
|
10679
|
+
searchLoopCheck.previousQuery,
|
|
10538
10680
|
latestToolResultPreview,
|
|
10539
|
-
mode
|
|
10681
|
+
searchLoopCheck.mode
|
|
10540
10682
|
)
|
|
10541
10683
|
);
|
|
10542
10684
|
currentInput.push(output2);
|
|
@@ -10590,30 +10732,15 @@ ${latestToolResultPreview || ""}`
|
|
|
10590
10732
|
toolHistoryCount += 1;
|
|
10591
10733
|
latestToolResultPreview = previewToolResult(output.output);
|
|
10592
10734
|
const outputText = toolResultTextContent(output.output);
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
failedClickCountSinceProgress = 0;
|
|
10597
|
-
}
|
|
10598
|
-
}
|
|
10599
|
-
if (searchToolQuery && !looksLikeFailedToolOutput(outputText) && !recentSuccessfulSearchQueries.includes(searchToolQuery)) {
|
|
10600
|
-
recentSuccessfulSearchQueries.push(searchToolQuery);
|
|
10601
|
-
recentSuccessfulSearchToolByQuery.set(
|
|
10602
|
-
searchToolQuery,
|
|
10603
|
-
prepared.prepared.name
|
|
10604
|
-
);
|
|
10605
|
-
if (recentSuccessfulSearchQueries.length > 4) {
|
|
10606
|
-
const dropped = recentSuccessfulSearchQueries.shift();
|
|
10607
|
-
if (dropped) {
|
|
10608
|
-
recentSuccessfulSearchToolByQuery.delete(dropped);
|
|
10609
|
-
}
|
|
10610
|
-
}
|
|
10611
|
-
}
|
|
10612
|
-
if (prepared.prepared.name === "web_search" && !looksLikeFailedToolOutput(outputText)) {
|
|
10613
|
-
if (searchToolQuery) {
|
|
10614
|
-
lastSuccessfulWebSearchQuery = searchToolQuery;
|
|
10615
|
-
}
|
|
10735
|
+
const toolSucceeded = !looksLikeFailedToolOutput(outputText);
|
|
10736
|
+
if (toolSucceeded && isRealProgressTool(prepared.prepared.name)) {
|
|
10737
|
+
failedClickCountSinceProgress = 0;
|
|
10616
10738
|
}
|
|
10739
|
+
searchLoopGuard.recordSuccess(
|
|
10740
|
+
prepared.prepared.name,
|
|
10741
|
+
searchToolQuery,
|
|
10742
|
+
toolSucceeded
|
|
10743
|
+
);
|
|
10617
10744
|
if (prepared.prepared.name === "click" && looksLikeFailedToolOutput(outputText)) {
|
|
10618
10745
|
failedClickCountSinceProgress += 1;
|
|
10619
10746
|
currentInput.push(
|
|
@@ -11529,13 +11656,15 @@ const TOOL_DEFINITIONS = [
|
|
|
11529
11656
|
{
|
|
11530
11657
|
name: "highlight",
|
|
11531
11658
|
title: "Highlight Element",
|
|
11532
|
-
description: "Visually highlight
|
|
11659
|
+
description: "Visually highlight page content for the user. For named items like story titles, result titles, links, headings, or article passages, prefer text with the exact visible title/text. Use index only when you have a current read_page element index for that exact item. Highlights persist until cleared.",
|
|
11533
11660
|
inputSchema: {
|
|
11534
|
-
index: zod.z.number().optional().describe("Element index from page content to highlight"),
|
|
11535
|
-
selector: zod.z.string().optional().describe("CSS selector of element to highlight"),
|
|
11536
11661
|
text: normalizedOptionalStringSchema().describe(
|
|
11537
|
-
"
|
|
11662
|
+
"Exact visible text/title to find and highlight on the page. Preferred for story titles, result titles, links, headings, and passages."
|
|
11663
|
+
),
|
|
11664
|
+
index: zod.z.number().optional().describe(
|
|
11665
|
+
"Element index from the latest page content listing. Use only when it identifies the exact item to highlight."
|
|
11538
11666
|
),
|
|
11667
|
+
selector: zod.z.string().optional().describe("CSS selector of element to highlight"),
|
|
11539
11668
|
label: zod.z.string().optional().describe("Annotation label to display near the highlight"),
|
|
11540
11669
|
durationMs: zod.z.number().optional().describe(
|
|
11541
11670
|
"Auto-clear after this many milliseconds (omit for permanent)"
|
|
@@ -11647,7 +11776,7 @@ const TOOL_DEFINITIONS = [
|
|
|
11647
11776
|
{
|
|
11648
11777
|
name: "web_search",
|
|
11649
11778
|
title: "Web Search",
|
|
11650
|
-
description: "Search the open web using the configured default search engine. Use this for broad discovery tasks instead of typing into the current page.",
|
|
11779
|
+
description: "Search the open web using the configured default search engine. Use this for broad discovery tasks instead of typing into the current page. For named venues, businesses, organizations, schools, or local places, use web search to find the official/direct result, then click that result; do not use site: queries as a substitute for opening an available official result.",
|
|
11651
11780
|
inputSchema: {
|
|
11652
11781
|
query: zod.z.string().describe("Web search query text")
|
|
11653
11782
|
},
|
|
@@ -12568,17 +12697,23 @@ function formatCartSnapshot(page) {
|
|
|
12568
12697
|
function isVisibleToUser(el) {
|
|
12569
12698
|
return el.visible === true && el.inViewport === true && el.obscured !== true && el.blockedByOverlay !== true;
|
|
12570
12699
|
}
|
|
12700
|
+
function elementSearchText(el, extraFields = []) {
|
|
12701
|
+
const fields = [
|
|
12702
|
+
el.text,
|
|
12703
|
+
el.label,
|
|
12704
|
+
el.name,
|
|
12705
|
+
el.placeholder,
|
|
12706
|
+
el.description,
|
|
12707
|
+
el.href,
|
|
12708
|
+
...extraFields.map((field) => el[field])
|
|
12709
|
+
];
|
|
12710
|
+
return normalizeComparable(fields.filter(Boolean).join(" "));
|
|
12711
|
+
}
|
|
12712
|
+
function formatElementOptions(options, maxOptions) {
|
|
12713
|
+
return options?.slice(0, maxOptions).map((o) => typeof o === "string" ? o : o.label || o.value).join("|") ?? "";
|
|
12714
|
+
}
|
|
12571
12715
|
function purchaseActionPriority(el) {
|
|
12572
|
-
const haystack =
|
|
12573
|
-
[
|
|
12574
|
-
el.text,
|
|
12575
|
-
el.label,
|
|
12576
|
-
el.name,
|
|
12577
|
-
el.placeholder,
|
|
12578
|
-
el.description,
|
|
12579
|
-
el.href
|
|
12580
|
-
].filter(Boolean).join(" ")
|
|
12581
|
-
);
|
|
12716
|
+
const haystack = elementSearchText(el);
|
|
12582
12717
|
if (!haystack) return Number.POSITIVE_INFINITY;
|
|
12583
12718
|
if (/\badd(?: item)? to (?:cart|bag|basket)\b/.test(haystack)) return 0;
|
|
12584
12719
|
if (/\b(?:buy now|preorder|pre-order|reserve now|shop now)\b/.test(haystack)) {
|
|
@@ -12589,6 +12724,27 @@ function purchaseActionPriority(el) {
|
|
|
12589
12724
|
}
|
|
12590
12725
|
return Number.POSITIVE_INFINITY;
|
|
12591
12726
|
}
|
|
12727
|
+
function dateOrShowtimeControlPriority(el) {
|
|
12728
|
+
const haystack = elementSearchText(el, ["role"]);
|
|
12729
|
+
if (!haystack) return Number.POSITIVE_INFINITY;
|
|
12730
|
+
if (/\b(today|tomorrow|mon(?:day)?|tue(?:s|sday)?|wed(?:nesday)?|thu(?:rs|rsday)?|fri(?:day)?|sat(?:urday)?|sun(?:day)?)\b/.test(
|
|
12731
|
+
haystack
|
|
12732
|
+
)) {
|
|
12733
|
+
return 0;
|
|
12734
|
+
}
|
|
12735
|
+
if (/\b(showtimes?|showings?|screenings?|movie times?|date|calendar)\b/.test(
|
|
12736
|
+
haystack
|
|
12737
|
+
)) {
|
|
12738
|
+
return 1;
|
|
12739
|
+
}
|
|
12740
|
+
if (/\b(ticketing|tickets?|formovietickets|seat selection)\b/.test(haystack)) {
|
|
12741
|
+
return 2;
|
|
12742
|
+
}
|
|
12743
|
+
return Number.POSITIVE_INFINITY;
|
|
12744
|
+
}
|
|
12745
|
+
function isDateOrShowtimeControl(el) {
|
|
12746
|
+
return Number.isFinite(dateOrShowtimeControlPriority(el));
|
|
12747
|
+
}
|
|
12592
12748
|
function isPurchaseActionElement(el) {
|
|
12593
12749
|
if (el.type !== "button" && el.type !== "link" && !(el.type === "input" && (el.inputType === "submit" || el.inputType === "button"))) {
|
|
12594
12750
|
return false;
|
|
@@ -12678,20 +12834,34 @@ function formatDialogFocus(page) {
|
|
|
12678
12834
|
}
|
|
12679
12835
|
function formatInteractiveElements(elements) {
|
|
12680
12836
|
if (elements.length === 0) return "None";
|
|
12837
|
+
const DIALOG_PRIORITY_BONUS = 40;
|
|
12838
|
+
const HIDDEN_VISIBILITY_PENALTY = 100;
|
|
12839
|
+
const OFFSCREEN_PENALTY = 50;
|
|
12840
|
+
const NAVIGATION_CONTEXT_PENALTY = 30;
|
|
12841
|
+
const OBSCURED_PENALTY = 20;
|
|
12842
|
+
const LINK_TYPE_PENALTY = 5;
|
|
12843
|
+
const PURCHASE_BASE_WEIGHT = 25;
|
|
12844
|
+
const PURCHASE_PRIORITY_MULTIPLIER = 5;
|
|
12845
|
+
const DATE_BASE_WEIGHT = 18;
|
|
12846
|
+
const DATE_PRIORITY_MULTIPLIER = 4;
|
|
12681
12847
|
const sorted = [...elements].sort((a, b) => {
|
|
12682
12848
|
const scoreEl = (el) => {
|
|
12683
12849
|
let s = 0;
|
|
12684
|
-
if (el.context === "dialog") s -=
|
|
12850
|
+
if (el.context === "dialog") s -= DIALOG_PRIORITY_BONUS;
|
|
12685
12851
|
const purchasePriority = purchaseActionPriority(el);
|
|
12686
12852
|
if (Number.isFinite(purchasePriority)) {
|
|
12687
|
-
s -=
|
|
12853
|
+
s -= PURCHASE_BASE_WEIGHT - purchasePriority * PURCHASE_PRIORITY_MULTIPLIER;
|
|
12854
|
+
}
|
|
12855
|
+
const datePriority = dateOrShowtimeControlPriority(el);
|
|
12856
|
+
if (Number.isFinite(datePriority)) {
|
|
12857
|
+
s -= DATE_BASE_WEIGHT - datePriority * DATE_PRIORITY_MULTIPLIER;
|
|
12688
12858
|
}
|
|
12689
|
-
if (el.visible === false) s +=
|
|
12690
|
-
if (el.inViewport === false) s +=
|
|
12859
|
+
if (el.visible === false) s += HIDDEN_VISIBILITY_PENALTY;
|
|
12860
|
+
if (el.inViewport === false) s += OFFSCREEN_PENALTY;
|
|
12691
12861
|
if (el.context === "nav" || el.context === "footer" || el.context === "sidebar")
|
|
12692
|
-
s +=
|
|
12693
|
-
if (el.obscured) s +=
|
|
12694
|
-
if (el.type === "link") s +=
|
|
12862
|
+
s += NAVIGATION_CONTEXT_PENALTY;
|
|
12863
|
+
if (el.obscured) s += OBSCURED_PENALTY;
|
|
12864
|
+
if (el.type === "link") s += LINK_TYPE_PENALTY;
|
|
12695
12865
|
return s;
|
|
12696
12866
|
};
|
|
12697
12867
|
return scoreEl(a) - scoreEl(b);
|
|
@@ -12722,9 +12892,8 @@ function formatInteractiveElements(elements) {
|
|
|
12722
12892
|
if (summary) parts.push(`${summary.label}="${summary.value}"`);
|
|
12723
12893
|
appendFieldAffordances(parts, el);
|
|
12724
12894
|
if (el.options?.length) {
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
);
|
|
12895
|
+
const maxOptions = isDateOrShowtimeControl(el) ? 10 : 5;
|
|
12896
|
+
parts.push(`options=${formatElementOptions(el.options, maxOptions)}`);
|
|
12728
12897
|
}
|
|
12729
12898
|
} else if (el.type === "textarea") {
|
|
12730
12899
|
parts.push(`[${el.label || "Text Area"}]`);
|
|
@@ -12785,8 +12954,9 @@ function formatForms(forms) {
|
|
|
12785
12954
|
if (summary) fieldParts.push(`${summary.label}="${summary.value}"`);
|
|
12786
12955
|
appendFieldAffordances(fieldParts, field);
|
|
12787
12956
|
if (field.options?.length) {
|
|
12957
|
+
const maxOptions = isDateOrShowtimeControl(field) ? 10 : 5;
|
|
12788
12958
|
fieldParts.push(
|
|
12789
|
-
`options=${field.options
|
|
12959
|
+
`options=${formatElementOptions(field.options, maxOptions)}`
|
|
12790
12960
|
);
|
|
12791
12961
|
}
|
|
12792
12962
|
} else if (field.type === "textarea") {
|
|
@@ -13051,7 +13221,66 @@ function chooseAgentReadMode(page) {
|
|
|
13051
13221
|
return "visible_only";
|
|
13052
13222
|
}
|
|
13053
13223
|
}
|
|
13224
|
+
const SITE_RESULT_FILTERS = [
|
|
13225
|
+
{
|
|
13226
|
+
hostname: "news.ycombinator.com",
|
|
13227
|
+
listingPaths: [
|
|
13228
|
+
"/",
|
|
13229
|
+
"/news",
|
|
13230
|
+
"/newest",
|
|
13231
|
+
"/front",
|
|
13232
|
+
"/ask",
|
|
13233
|
+
"/show",
|
|
13234
|
+
"/jobs",
|
|
13235
|
+
"/best",
|
|
13236
|
+
"/active",
|
|
13237
|
+
"/classic",
|
|
13238
|
+
"/noobstories"
|
|
13239
|
+
],
|
|
13240
|
+
utilityPathnames: ["/hide", "/user"],
|
|
13241
|
+
utilityTextPatterns: [
|
|
13242
|
+
/^(hide|past|favorite|unfavorite|flag|unflag|discuss|reply|parent|more)$/,
|
|
13243
|
+
/^\d+\s+(?:comments?|points?)$/
|
|
13244
|
+
]
|
|
13245
|
+
}
|
|
13246
|
+
];
|
|
13247
|
+
function matchesSiteFilter(url, filter, baseHostname) {
|
|
13248
|
+
try {
|
|
13249
|
+
const parsed = new URL(url, baseHostname ? `https://${baseHostname}` : void 0);
|
|
13250
|
+
return parsed.hostname === filter.hostname;
|
|
13251
|
+
} catch {
|
|
13252
|
+
return false;
|
|
13253
|
+
}
|
|
13254
|
+
}
|
|
13255
|
+
function isSiteListingPage(url) {
|
|
13256
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13257
|
+
if (!matchesSiteFilter(url, filter, "")) continue;
|
|
13258
|
+
try {
|
|
13259
|
+
const pathname = new URL(url).pathname.replace(/\/+$/, "") || "/";
|
|
13260
|
+
if (filter.listingPaths?.includes(pathname)) return true;
|
|
13261
|
+
} catch {
|
|
13262
|
+
}
|
|
13263
|
+
}
|
|
13264
|
+
return false;
|
|
13265
|
+
}
|
|
13266
|
+
function isSiteUtilityLink(element) {
|
|
13267
|
+
if (!element.href) return false;
|
|
13268
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13269
|
+
if (!matchesSiteFilter(element.href, filter, "")) continue;
|
|
13270
|
+
const text = normalizeComparable(element.text || "");
|
|
13271
|
+
for (const pattern of filter.utilityTextPatterns ?? []) {
|
|
13272
|
+
if (pattern.test(text)) return true;
|
|
13273
|
+
}
|
|
13274
|
+
try {
|
|
13275
|
+
const pathname = new URL(element.href).pathname.replace(/\/+$/, "") || "/";
|
|
13276
|
+
if (filter.utilityPathnames?.includes(pathname)) return true;
|
|
13277
|
+
} catch {
|
|
13278
|
+
}
|
|
13279
|
+
}
|
|
13280
|
+
return false;
|
|
13281
|
+
}
|
|
13054
13282
|
function isSearchOrListingPage(page) {
|
|
13283
|
+
if (isSiteListingPage(page.url)) return true;
|
|
13055
13284
|
const haystack = normalizeComparable(
|
|
13056
13285
|
[
|
|
13057
13286
|
page.url,
|
|
@@ -13103,7 +13332,7 @@ function getResultCandidates(page) {
|
|
|
13103
13332
|
const pageHost = normalizeUrlForMatch(page.url);
|
|
13104
13333
|
const searchOrListingPage = isSearchOrListingPage(page);
|
|
13105
13334
|
const scored = page.interactiveElements.filter(
|
|
13106
|
-
(element) => element.type === "link" && element.text?.trim() && element.href
|
|
13335
|
+
(element) => element.type === "link" && element.text?.trim() && element.href && !isSiteUtilityLink(element)
|
|
13107
13336
|
).map((element) => {
|
|
13108
13337
|
const text = element.text?.trim() || "";
|
|
13109
13338
|
const comparableText = normalizeComparable(text);
|
|
@@ -13563,6 +13792,7 @@ function detectPageType(page) {
|
|
|
13563
13792
|
if (hasResults && hasSearchInput && listingLike) return "SEARCH_RESULTS";
|
|
13564
13793
|
if (hasCart) return "SHOPPING";
|
|
13565
13794
|
if (formCount > 0 && !hasPasswordField) return "FORM";
|
|
13795
|
+
if (isSiteListingPage(page.url)) return "PAGINATED_LIST";
|
|
13566
13796
|
if (hasPagination && listingLike) return "PAGINATED_LIST";
|
|
13567
13797
|
if (hasSearchInput && !listingLike) return "SEARCH_READY";
|
|
13568
13798
|
if (page.content.length > 3e3 && page.interactiveElements.length < 10)
|
|
@@ -15111,7 +15341,7 @@ function vesselIsEditableElement(el) {
|
|
|
15111
15341
|
if (!(el instanceof HTMLElement)) return false;
|
|
15112
15342
|
var role = (el.getAttribute("role") || "").toLowerCase();
|
|
15113
15343
|
return el.isContentEditable ||
|
|
15114
|
-
el.getAttribute("contenteditable")
|
|
15344
|
+
(el.hasAttribute("contenteditable") && el.getAttribute("contenteditable") !== "false") ||
|
|
15115
15345
|
role === "textbox" ||
|
|
15116
15346
|
role === "searchbox";
|
|
15117
15347
|
}
|
|
@@ -15120,7 +15350,7 @@ function vesselResolveFillableControl(el) {
|
|
|
15120
15350
|
if (!el) return null;
|
|
15121
15351
|
if (vesselIsNativeField(el) || vesselIsEditableElement(el)) return el;
|
|
15122
15352
|
if (!(el instanceof Element)) return null;
|
|
15123
|
-
var nested = el.querySelector("input:not([type='hidden']):not([type='submit']):not([type='button']), textarea, select, [contenteditable='
|
|
15353
|
+
var nested = el.querySelector("input:not([type='hidden']):not([type='submit']):not([type='button']), textarea, select, [contenteditable]:not([contenteditable='false']), [role='textbox'], [role='searchbox']");
|
|
15124
15354
|
return nested instanceof HTMLElement ? nested : null;
|
|
15125
15355
|
}
|
|
15126
15356
|
|
|
@@ -15138,7 +15368,7 @@ function vesselFindVisibleFillableControl(original) {
|
|
|
15138
15368
|
|
|
15139
15369
|
var scopes = Array.from(document.querySelectorAll("dialog[open], [role='dialog'], [role='alertdialog'], [aria-modal='true'], [role='listbox'], [role='combobox'][aria-expanded='true']"));
|
|
15140
15370
|
scopes.push(document.body);
|
|
15141
|
-
var selector = "input:not([type='hidden']):not([type='submit']):not([type='button']), textarea, select, [contenteditable='
|
|
15371
|
+
var selector = "input:not([type='hidden']):not([type='submit']):not([type='button']), textarea, select, [contenteditable]:not([contenteditable='false']), [role='textbox'], [role='searchbox']";
|
|
15142
15372
|
for (var i = 0; i < scopes.length; i += 1) {
|
|
15143
15373
|
var scope = scopes[i];
|
|
15144
15374
|
if (!scope || !(scope instanceof Element)) continue;
|
|
@@ -15436,7 +15666,7 @@ async function resolveFieldSelector(wc, field) {
|
|
|
15436
15666
|
if (!(el instanceof HTMLElement)) return false;
|
|
15437
15667
|
var role = (el.getAttribute("role") || "").toLowerCase();
|
|
15438
15668
|
return el.isContentEditable ||
|
|
15439
|
-
el.getAttribute("contenteditable")
|
|
15669
|
+
(el.hasAttribute("contenteditable") && el.getAttribute("contenteditable") !== "false") ||
|
|
15440
15670
|
role === "textbox" ||
|
|
15441
15671
|
role === "searchbox" ||
|
|
15442
15672
|
role === "combobox";
|
|
@@ -15476,7 +15706,7 @@ async function resolveFieldSelector(wc, field) {
|
|
|
15476
15706
|
return score;
|
|
15477
15707
|
}
|
|
15478
15708
|
|
|
15479
|
-
const candidates = Array.from(document.querySelectorAll("input, textarea, select, [contenteditable='
|
|
15709
|
+
const candidates = Array.from(document.querySelectorAll("input, textarea, select, [contenteditable]:not([contenteditable='false']), [role='textbox'], [role='searchbox'], [role='combobox']"));
|
|
15480
15710
|
let best = null;
|
|
15481
15711
|
let bestScore = -1;
|
|
15482
15712
|
for (const el of candidates) {
|
|
@@ -17969,7 +18199,7 @@ async function locateImplicitTextTarget(wc) {
|
|
|
17969
18199
|
const role = normalize(el.getAttribute("role"));
|
|
17970
18200
|
if (
|
|
17971
18201
|
el.isContentEditable ||
|
|
17972
|
-
el.getAttribute("contenteditable")
|
|
18202
|
+
(el.hasAttribute("contenteditable") && el.getAttribute("contenteditable") !== "false") ||
|
|
17973
18203
|
role === "textbox" ||
|
|
17974
18204
|
role === "searchbox" ||
|
|
17975
18205
|
role === "combobox"
|
|
@@ -17993,7 +18223,7 @@ async function locateImplicitTextTarget(wc) {
|
|
|
17993
18223
|
}
|
|
17994
18224
|
|
|
17995
18225
|
const candidates = Array.from(
|
|
17996
|
-
document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="image"]), textarea, [contenteditable="
|
|
18226
|
+
document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="image"]), textarea, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="searchbox"], [role="combobox"]')
|
|
17997
18227
|
).filter((el) => isFillable(el) && isVisible(el));
|
|
17998
18228
|
|
|
17999
18229
|
let best = null;
|
|
@@ -19608,9 +19838,11 @@ async function handleHighlight(ctx, args) {
|
|
|
19608
19838
|
}
|
|
19609
19839
|
return highlightOnPage(wc, selector, highlightText, args.label, args.durationMs, highlightColor);
|
|
19610
19840
|
}
|
|
19611
|
-
function handleClearHighlights(ctx) {
|
|
19841
|
+
async function handleClearHighlights(ctx) {
|
|
19612
19842
|
const wc = ctx.tabManager.getActiveTab()?.view.webContents;
|
|
19613
19843
|
if (!wc) return "Error: No active tab";
|
|
19844
|
+
const url = normalizeUrl$1(wc.getURL());
|
|
19845
|
+
clearHighlightsForUrl(url);
|
|
19614
19846
|
return clearHighlights(wc);
|
|
19615
19847
|
}
|
|
19616
19848
|
function trimText(value) {
|
|
@@ -22419,7 +22651,7 @@ function registerPrivateIpcHandlers(state2) {
|
|
|
22419
22651
|
});
|
|
22420
22652
|
ipc.handle(Channels.IS_PRIVATE_MODE, () => true);
|
|
22421
22653
|
ipc.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
|
|
22422
|
-
|
|
22654
|
+
return openPrivateWindowSafely();
|
|
22423
22655
|
});
|
|
22424
22656
|
ipc.handle(Channels.OPEN_NEW_WINDOW, async () => {
|
|
22425
22657
|
const { createSecondaryWindow: createSecondaryWindow2 } = await Promise.resolve().then(() => window$1);
|
|
@@ -22463,70 +22695,120 @@ function registerPrivateIpcHandlers(state2) {
|
|
|
22463
22695
|
function createPrivateWindow() {
|
|
22464
22696
|
const privateSessionPartition = `private-${crypto$2.randomUUID()}`;
|
|
22465
22697
|
const privateSession = electron.session.fromPartition(privateSessionPartition);
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22477
|
-
|
|
22478
|
-
|
|
22479
|
-
|
|
22480
|
-
|
|
22481
|
-
|
|
22482
|
-
|
|
22698
|
+
let win = null;
|
|
22699
|
+
let tabManager = null;
|
|
22700
|
+
let state2 = null;
|
|
22701
|
+
try {
|
|
22702
|
+
privateSession.setUserAgent(electron.session.defaultSession.getUserAgent());
|
|
22703
|
+
win = new electron.BaseWindow({
|
|
22704
|
+
width: 1280,
|
|
22705
|
+
height: 800,
|
|
22706
|
+
minWidth: 800,
|
|
22707
|
+
minHeight: 600,
|
|
22708
|
+
frame: false,
|
|
22709
|
+
show: false,
|
|
22710
|
+
backgroundColor: "#1e1a2e",
|
|
22711
|
+
title: "Vessel - Private Browsing"
|
|
22712
|
+
});
|
|
22713
|
+
const chromeView = new electron.WebContentsView({
|
|
22714
|
+
webPreferences: {
|
|
22715
|
+
preload: path$1.join(__dirname, "../preload/index.js"),
|
|
22716
|
+
sandbox: true,
|
|
22717
|
+
contextIsolation: true,
|
|
22718
|
+
nodeIntegration: false
|
|
22719
|
+
}
|
|
22720
|
+
});
|
|
22721
|
+
chromeView.setBackgroundColor("#00000000");
|
|
22722
|
+
win.contentView.addChildView(chromeView);
|
|
22723
|
+
tabManager = new TabManager(
|
|
22724
|
+
win,
|
|
22725
|
+
(tabs, activeId) => {
|
|
22726
|
+
sendSafe(chromeView.webContents, Channels.TAB_STATE_UPDATE, tabs, activeId);
|
|
22727
|
+
if (state2) layoutPrivateViews(state2);
|
|
22728
|
+
},
|
|
22729
|
+
{ isPrivate: true, sessionPartition: privateSessionPartition }
|
|
22730
|
+
);
|
|
22731
|
+
state2 = {
|
|
22732
|
+
window: win,
|
|
22733
|
+
chromeView,
|
|
22734
|
+
tabManager,
|
|
22735
|
+
session: privateSession,
|
|
22736
|
+
sessionPartition: privateSessionPartition
|
|
22737
|
+
};
|
|
22738
|
+
installAdBlockingForSession(privateSession, tabManager);
|
|
22739
|
+
installDownloadHandlerForSession(privateSession, chromeView);
|
|
22740
|
+
registerPrivateIpcHandlers(state2);
|
|
22741
|
+
win.on("resize", () => {
|
|
22742
|
+
if (state2) layoutPrivateViews(state2);
|
|
22743
|
+
});
|
|
22744
|
+
win.on("show", () => {
|
|
22745
|
+
if (state2) layoutPrivateViews(state2);
|
|
22746
|
+
});
|
|
22747
|
+
win.on("closed", () => {
|
|
22748
|
+
if (!state2) return;
|
|
22749
|
+
privateWindows.delete(state2);
|
|
22750
|
+
tabManager?.destroyAllTabs();
|
|
22751
|
+
void Promise.all([
|
|
22752
|
+
privateSession.clearStorageData(),
|
|
22753
|
+
privateSession.clearCache()
|
|
22754
|
+
]).catch((error) => {
|
|
22755
|
+
logger$m.warn("Failed to clear private browsing session:", error);
|
|
22756
|
+
});
|
|
22757
|
+
});
|
|
22758
|
+
privateWindows.add(state2);
|
|
22759
|
+
chromeView.webContents.once("dom-ready", () => {
|
|
22760
|
+
try {
|
|
22761
|
+
tabManager?.createTab("about:blank");
|
|
22762
|
+
if (state2) layoutPrivateViews(state2);
|
|
22763
|
+
} catch (error) {
|
|
22764
|
+
logger$m.error("Failed to initialize private browsing tab:", error);
|
|
22765
|
+
try {
|
|
22766
|
+
win?.close();
|
|
22767
|
+
} catch (cleanupError) {
|
|
22768
|
+
logger$m.warn("Failed to close private window after tab init failure:", cleanupError);
|
|
22769
|
+
}
|
|
22770
|
+
}
|
|
22771
|
+
});
|
|
22772
|
+
loadPrivateRenderer(chromeView);
|
|
22773
|
+
win.show();
|
|
22774
|
+
logger$m.info("Private browsing window opened");
|
|
22775
|
+
return state2;
|
|
22776
|
+
} catch (error) {
|
|
22777
|
+
logger$m.error("Failed to create private browsing window:", error);
|
|
22778
|
+
if (state2) {
|
|
22779
|
+
privateWindows.delete(state2);
|
|
22780
|
+
}
|
|
22781
|
+
try {
|
|
22782
|
+
tabManager?.destroyAllTabs();
|
|
22783
|
+
} catch (cleanupError) {
|
|
22784
|
+
logger$m.warn("Failed to clean up private tabs after launch failure:", cleanupError);
|
|
22785
|
+
}
|
|
22786
|
+
try {
|
|
22787
|
+
win?.close();
|
|
22788
|
+
} catch (cleanupError) {
|
|
22789
|
+
logger$m.warn("Failed to close private window after launch failure:", cleanupError);
|
|
22483
22790
|
}
|
|
22484
|
-
});
|
|
22485
|
-
chromeView.setBackgroundColor("#00000000");
|
|
22486
|
-
win.contentView.addChildView(chromeView);
|
|
22487
|
-
const tabManager = new TabManager(
|
|
22488
|
-
win,
|
|
22489
|
-
(tabs, activeId) => {
|
|
22490
|
-
sendSafe(chromeView.webContents, Channels.TAB_STATE_UPDATE, tabs, activeId);
|
|
22491
|
-
layoutPrivateViews(state2);
|
|
22492
|
-
},
|
|
22493
|
-
{ isPrivate: true, sessionPartition: privateSessionPartition }
|
|
22494
|
-
);
|
|
22495
|
-
const state2 = {
|
|
22496
|
-
window: win,
|
|
22497
|
-
chromeView,
|
|
22498
|
-
tabManager,
|
|
22499
|
-
session: privateSession,
|
|
22500
|
-
sessionPartition: privateSessionPartition
|
|
22501
|
-
};
|
|
22502
|
-
installAdBlockingForSession(privateSession, tabManager);
|
|
22503
|
-
installDownloadHandlerForSession(privateSession, chromeView);
|
|
22504
|
-
registerPrivateIpcHandlers(state2);
|
|
22505
|
-
win.on("resize", () => layoutPrivateViews(state2));
|
|
22506
|
-
win.on("show", () => layoutPrivateViews(state2));
|
|
22507
|
-
win.on("closed", () => {
|
|
22508
|
-
privateWindows.delete(state2);
|
|
22509
|
-
tabManager.destroyAllTabs();
|
|
22510
22791
|
void Promise.all([
|
|
22511
22792
|
privateSession.clearStorageData(),
|
|
22512
22793
|
privateSession.clearCache()
|
|
22513
|
-
]).catch((
|
|
22514
|
-
logger$m.warn("Failed to clear private browsing session:",
|
|
22794
|
+
]).catch((cleanupError) => {
|
|
22795
|
+
logger$m.warn("Failed to clear failed private browsing session:", cleanupError);
|
|
22515
22796
|
});
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
|
|
22520
|
-
|
|
22521
|
-
|
|
22522
|
-
|
|
22523
|
-
|
|
22524
|
-
|
|
22525
|
-
|
|
22797
|
+
throw error;
|
|
22798
|
+
}
|
|
22799
|
+
}
|
|
22800
|
+
function openPrivateWindowSafely() {
|
|
22801
|
+
try {
|
|
22802
|
+
createPrivateWindow();
|
|
22803
|
+
return true;
|
|
22804
|
+
} catch {
|
|
22805
|
+
return false;
|
|
22806
|
+
}
|
|
22526
22807
|
}
|
|
22527
22808
|
const window$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
22528
22809
|
__proto__: null,
|
|
22529
|
-
createPrivateWindow
|
|
22810
|
+
createPrivateWindow,
|
|
22811
|
+
openPrivateWindowSafely
|
|
22530
22812
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
22531
22813
|
const secondaryWindows = /* @__PURE__ */ new Set();
|
|
22532
22814
|
function layoutSecondaryViews(state2) {
|
|
@@ -22644,8 +22926,8 @@ function registerSecondaryIpcHandlers(state2) {
|
|
|
22644
22926
|
);
|
|
22645
22927
|
ipc.handle(Channels.OPEN_NEW_WINDOW, () => createSecondaryWindow());
|
|
22646
22928
|
ipc.handle(Channels.OPEN_PRIVATE_WINDOW, async () => {
|
|
22647
|
-
const {
|
|
22648
|
-
|
|
22929
|
+
const { openPrivateWindowSafely: openPrivateWindowSafely2 } = await Promise.resolve().then(() => window$2);
|
|
22930
|
+
return openPrivateWindowSafely2();
|
|
22649
22931
|
});
|
|
22650
22932
|
ipc.handle(Channels.IS_PRIVATE_MODE, () => false);
|
|
22651
22933
|
ipc.handle(Channels.WINDOW_MINIMIZE, () => state2.window.minimize());
|
|
@@ -22733,7 +23015,7 @@ function registerTabHandlers(windowState, _sendToRendererViews) {
|
|
|
22733
23015
|
const { tabManager, mainWindow } = windowState;
|
|
22734
23016
|
electron.ipcMain.handle(Channels.OPEN_PRIVATE_WINDOW, (event) => {
|
|
22735
23017
|
assertTrustedIpcSender(event);
|
|
22736
|
-
|
|
23018
|
+
return openPrivateWindowSafely();
|
|
22737
23019
|
});
|
|
22738
23020
|
electron.ipcMain.handle(Channels.OPEN_NEW_WINDOW, (event) => {
|
|
22739
23021
|
assertTrustedIpcSender(event);
|
|
@@ -22951,10 +23233,11 @@ const SHARED_NAVIGATION_INSTRUCTIONS = [
|
|
|
22951
23233
|
"Prefer select_option for dropdowns and submit_form for forms instead of guessing with clicks.",
|
|
22952
23234
|
"After navigating to a new site, do not call read_page immediately unless you are genuinely stuck. Prefer the site's search box, known navigation patterns, or clicking a visible section first.",
|
|
22953
23235
|
"For open-web discovery, current facts, prices, flights, news, or search-engine home pages, call web_search(query). Use search(query) only to search within the current site or app.",
|
|
23236
|
+
"For questions about a named venue, business, organization, school, or local place, use search results to find the official site, then open that site and answer from its page. Do not keep rewriting generic web_search queries or switch to site: searches when an official or clearly direct result is available.",
|
|
22954
23237
|
"For flight price-shopping, include the route, date, and trip type in web_search(query); do not send vague or partial flight queries.",
|
|
22955
23238
|
"On flight/travel booking pages with visible route, destination, or date fields, use those visible controls before constructing direct Google Flights or travel-search URLs. Direct travel URLs are a fallback only after the visible controls fail or the page is unusable.",
|
|
22956
23239
|
"On retail and marketplace sites, prefer the site's visible search box, filters, and result pages over direct product URLs.",
|
|
22957
|
-
"For broad discovery tasks, prefer direct sources
|
|
23240
|
+
"For broad discovery tasks, prefer direct sources over generic search snippets. Use site-specific search only after opening the direct source fails to expose the needed information."
|
|
22958
23241
|
];
|
|
22959
23242
|
const VESSEL_SOURCE_INSTRUCTIONS = [
|
|
22960
23243
|
"When the user asks about Vessel, Vessel Browser, or Quanta Intellect, use these official sources directly instead of hunting around the open web first: Official page https://quantaintellect.com, GitHub repo https://github.com/unmodeled-tyler/vessel-browser, npm package https://www.npmjs.com/package/@quanta-intellect/vessel-browser.",
|
|
@@ -23911,8 +24194,16 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
23911
24194
|
const info = getActiveTabInfo(tabManager);
|
|
23912
24195
|
if (!info) return false;
|
|
23913
24196
|
try {
|
|
24197
|
+
const url = normalizeUrl$1(info.wc.getURL());
|
|
24198
|
+
const text = await getHighlightTextAtIndex(info.wc, validatedIndex);
|
|
23914
24199
|
const removed = await removeHighlightAtIndex(info.wc, validatedIndex);
|
|
23915
24200
|
if (removed) {
|
|
24201
|
+
if (text) {
|
|
24202
|
+
const persisted = findHighlightByText(url, text);
|
|
24203
|
+
if (persisted) {
|
|
24204
|
+
removeHighlight(persisted.id);
|
|
24205
|
+
}
|
|
24206
|
+
}
|
|
23916
24207
|
await emitHighlightCount();
|
|
23917
24208
|
}
|
|
23918
24209
|
return removed;
|
|
@@ -23926,6 +24217,8 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
23926
24217
|
const info = getActiveTabInfo(tabManager);
|
|
23927
24218
|
if (!info) return false;
|
|
23928
24219
|
try {
|
|
24220
|
+
const url = normalizeUrl$1(info.wc.getURL());
|
|
24221
|
+
clearHighlightsForUrl(url);
|
|
23929
24222
|
const cleared = await clearAllHighlightElements(info.wc);
|
|
23930
24223
|
if (cleared) {
|
|
23931
24224
|
await emitHighlightCount();
|