@quanta-intellect/vessel-browser 0.1.143 → 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 +246 -240
- package/out/preload/content-script.js +1 -1
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -8817,6 +8817,106 @@ function buildFlightPriceEvidenceRecoveryPrompt(userMessage, assistantText, late
|
|
|
8817
8817
|
`Last unsupported answer: ${assistantText.replace(/\s+/g, " ").trim().slice(0, 500) || "(empty)"}`
|
|
8818
8818
|
].join("\n");
|
|
8819
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
|
+
}
|
|
8820
8920
|
const logger$v = createLogger("OpenAIProvider");
|
|
8821
8921
|
function shouldDebugAgentLoop() {
|
|
8822
8922
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
@@ -8886,7 +8986,7 @@ function buildOpenRouterAttributionHeaders() {
|
|
|
8886
8986
|
function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
|
|
8887
8987
|
if (profile !== "compact") return null;
|
|
8888
8988
|
const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
|
|
8889
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview
|
|
8989
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8890
8990
|
return {
|
|
8891
8991
|
role: "user",
|
|
8892
8992
|
content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
|
|
@@ -8896,13 +8996,13 @@ ${phaseReminder}` : "")
|
|
|
8896
8996
|
};
|
|
8897
8997
|
}
|
|
8898
8998
|
function extractSingleGoalDomain(goal) {
|
|
8899
|
-
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);
|
|
8900
9000
|
if (!matches || matches.length !== 1) return null;
|
|
8901
9001
|
return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
|
|
8902
9002
|
}
|
|
8903
9003
|
function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
|
|
8904
9004
|
const phaseReminder = buildPhaseReminder(userMessage, assistantText);
|
|
8905
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview
|
|
9005
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8906
9006
|
const goalDomain = extractSingleGoalDomain(userMessage);
|
|
8907
9007
|
const latest = (latestToolResultPreview || "").toLowerCase();
|
|
8908
9008
|
const assistant = assistantText.toLowerCase();
|
|
@@ -8999,38 +9099,7 @@ function buildPhaseReminder(userMessage, assistantText) {
|
|
|
8999
9099
|
}
|
|
9000
9100
|
return "";
|
|
9001
9101
|
}
|
|
9002
|
-
function
|
|
9003
|
-
const text = toolResultPreview.trim();
|
|
9004
|
-
if (!text) return "";
|
|
9005
|
-
const stateMatch = text.match(
|
|
9006
|
-
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
9007
|
-
);
|
|
9008
|
-
if (stateMatch) {
|
|
9009
|
-
const url = stateMatch[1]?.trim();
|
|
9010
|
-
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
9011
|
-
if (url) {
|
|
9012
|
-
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
9013
|
-
}
|
|
9014
|
-
}
|
|
9015
|
-
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
9016
|
-
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
9017
|
-
if (structuredUrl) {
|
|
9018
|
-
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
9019
|
-
}
|
|
9020
|
-
const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
|
|
9021
|
-
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
9022
|
-
if (navigatedUrl) {
|
|
9023
|
-
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
9024
|
-
}
|
|
9025
|
-
return "";
|
|
9026
|
-
}
|
|
9027
|
-
function normalizedSearchToolQuery$1(name, args) {
|
|
9028
|
-
if (name !== "search" && name !== "web_search") return null;
|
|
9029
|
-
const raw = typeof args.query === "string" ? args.query : typeof args.text === "string" ? args.text : typeof args.term === "string" ? args.term : "";
|
|
9030
|
-
const normalized = raw.replace(/\s+/g, " ").trim().toLowerCase();
|
|
9031
|
-
return normalized || null;
|
|
9032
|
-
}
|
|
9033
|
-
function isOpenAIRealProgressToolForSearch(name) {
|
|
9102
|
+
function isSearchContextResettingTool(name) {
|
|
9034
9103
|
return ![
|
|
9035
9104
|
"read_page",
|
|
9036
9105
|
"current_tab",
|
|
@@ -9043,18 +9112,6 @@ function isOpenAIRealProgressToolForSearch(name) {
|
|
|
9043
9112
|
"search"
|
|
9044
9113
|
].includes(name);
|
|
9045
9114
|
}
|
|
9046
|
-
function buildOpenAIRepeatedSearchError(previousTool, previousQuery, latestToolResultPreview, mode) {
|
|
9047
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
|
|
9048
|
-
const lines = [
|
|
9049
|
-
mode === "drifted" ? `Error: You already performed ${previousTool} successfully for this task.` : `Error: You already searched for "${previousQuery}" successfully with ${previousTool}.`,
|
|
9050
|
-
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.`,
|
|
9051
|
-
`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. Next action: click a result, inspect a specific result, or answer from the result you already opened. Do not call any search tool again as preparation.`
|
|
9052
|
-
];
|
|
9053
|
-
if (stateReminder) {
|
|
9054
|
-
lines.push(stateReminder);
|
|
9055
|
-
}
|
|
9056
|
-
return lines.join(" ");
|
|
9057
|
-
}
|
|
9058
9115
|
function shouldRecoverCompactStall(text, userMessage) {
|
|
9059
9116
|
const trimmed = text.trim().toLowerCase();
|
|
9060
9117
|
if (!trimmed) return true;
|
|
@@ -9138,15 +9195,24 @@ function logAgentLoopDebug(payload) {
|
|
|
9138
9195
|
}
|
|
9139
9196
|
}
|
|
9140
9197
|
function formatOpenAICompatErrorMessage(providerId, message) {
|
|
9141
|
-
if (providerId === "openrouter" && /(timed out after \d+(?:\.\d+)? seconds|request timed out|returned none after all retries|no content|empty response)/i.test(
|
|
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(
|
|
9142
9199
|
message
|
|
9143
9200
|
)) {
|
|
9144
|
-
return
|
|
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(" ");
|
|
9145
9206
|
}
|
|
9146
9207
|
if (providerId === "llama_cpp" && /(available context size|context size exceeded|exceeds the available context size|try increasing it)/i.test(
|
|
9147
9208
|
message
|
|
9148
9209
|
)) {
|
|
9149
|
-
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(" ");
|
|
9150
9216
|
}
|
|
9151
9217
|
return message;
|
|
9152
9218
|
}
|
|
@@ -9256,9 +9322,7 @@ class OpenAICompatProvider {
|
|
|
9256
9322
|
const recentCompactToolSignatures = [];
|
|
9257
9323
|
const recentToolNames = [];
|
|
9258
9324
|
const successfulToolNames = [];
|
|
9259
|
-
const
|
|
9260
|
-
const recentSuccessfulSearchToolByQuery = /* @__PURE__ */ new Map();
|
|
9261
|
-
let lastSuccessfulWebSearchQuery = null;
|
|
9325
|
+
const searchLoopGuard = new SearchLoopGuard(isSearchContextResettingTool);
|
|
9262
9326
|
let clickReadLoopNudged = false;
|
|
9263
9327
|
for (let i = 0; i < maxIterations; i++) {
|
|
9264
9328
|
iterationsUsed = i + 1;
|
|
@@ -9493,24 +9557,20 @@ class OpenAICompatProvider {
|
|
|
9493
9557
|
args = repairedArgs.args;
|
|
9494
9558
|
args = coerceToolArgsForExecution(tc.name, args);
|
|
9495
9559
|
const toolSignature = stableToolSignature(tc.name, args);
|
|
9496
|
-
const searchToolQuery =
|
|
9497
|
-
const
|
|
9498
|
-
|
|
9499
|
-
if (isRepeatedSearchAcrossTools || isQueryDriftedWebSearch) {
|
|
9560
|
+
const searchToolQuery = normalizeSearchToolQuery(tc.name, args);
|
|
9561
|
+
const searchLoopCheck = searchLoopGuard.check(tc.name, searchToolQuery);
|
|
9562
|
+
if (searchLoopCheck) {
|
|
9500
9563
|
onChunk(`
|
|
9501
9564
|
<<tool:${tc.name}:↻ duplicate suppressed>>
|
|
9502
9565
|
`);
|
|
9503
|
-
const previousTool = isRepeatedSearchAcrossTools ? recentSuccessfulSearchToolByQuery.get(searchToolQuery ?? "") ?? (tc.name === "web_search" ? "search" : "web_search") : "web_search";
|
|
9504
|
-
const previousQuery = isRepeatedSearchAcrossTools ? searchToolQuery ?? "" : lastSuccessfulWebSearchQuery ?? "";
|
|
9505
|
-
const mode = isRepeatedSearchAcrossTools ? "repeated" : "drifted";
|
|
9506
9566
|
messages.push({
|
|
9507
9567
|
role: "tool",
|
|
9508
9568
|
tool_call_id: tc.id,
|
|
9509
|
-
content:
|
|
9510
|
-
previousTool,
|
|
9511
|
-
previousQuery,
|
|
9569
|
+
content: buildRepeatedSearchError(
|
|
9570
|
+
searchLoopCheck.previousTool,
|
|
9571
|
+
searchLoopCheck.previousQuery,
|
|
9512
9572
|
latestToolMessage ? String(latestToolMessage.content || "") : null,
|
|
9513
|
-
mode
|
|
9573
|
+
searchLoopCheck.mode
|
|
9514
9574
|
)
|
|
9515
9575
|
});
|
|
9516
9576
|
compactCorrectionCount += 1;
|
|
@@ -9590,25 +9650,15 @@ class OpenAICompatProvider {
|
|
|
9590
9650
|
recentCompactToolSignatures.shift();
|
|
9591
9651
|
}
|
|
9592
9652
|
}
|
|
9593
|
-
|
|
9653
|
+
const toolSucceeded = !/^Error:/i.test(toolContent.trim());
|
|
9654
|
+
if (toolSucceeded) {
|
|
9594
9655
|
successfulToolNames.push(tc.name);
|
|
9595
|
-
if (isOpenAIRealProgressToolForSearch(tc.name)) {
|
|
9596
|
-
lastSuccessfulWebSearchQuery = null;
|
|
9597
|
-
}
|
|
9598
|
-
if (searchToolQuery && !recentSuccessfulSearchQueries.includes(searchToolQuery)) {
|
|
9599
|
-
recentSuccessfulSearchQueries.push(searchToolQuery);
|
|
9600
|
-
recentSuccessfulSearchToolByQuery.set(searchToolQuery, tc.name);
|
|
9601
|
-
if (recentSuccessfulSearchQueries.length > 4) {
|
|
9602
|
-
const dropped = recentSuccessfulSearchQueries.shift();
|
|
9603
|
-
if (dropped) {
|
|
9604
|
-
recentSuccessfulSearchToolByQuery.delete(dropped);
|
|
9605
|
-
}
|
|
9606
|
-
}
|
|
9607
|
-
}
|
|
9608
|
-
if (tc.name === "web_search" && searchToolQuery) {
|
|
9609
|
-
lastSuccessfulWebSearchQuery = searchToolQuery;
|
|
9610
|
-
}
|
|
9611
9656
|
}
|
|
9657
|
+
searchLoopGuard.recordSuccess(
|
|
9658
|
+
tc.name,
|
|
9659
|
+
searchToolQuery,
|
|
9660
|
+
toolSucceeded
|
|
9661
|
+
);
|
|
9612
9662
|
recentToolNames.push(tc.name);
|
|
9613
9663
|
if (recentToolNames.length > 8) recentToolNames.shift();
|
|
9614
9664
|
if (!clickReadLoopNudged && recentToolNames.length >= 6 && isClickReadLoop(recentToolNames)) {
|
|
@@ -10138,59 +10188,13 @@ function previewToolResult(text, maxLength = 800) {
|
|
|
10138
10188
|
if (normalized.length <= maxLength) return normalized;
|
|
10139
10189
|
return `${normalized.slice(0, maxLength)}...`;
|
|
10140
10190
|
}
|
|
10141
|
-
function normalizedSearchToolQuery(name, args) {
|
|
10142
|
-
if (name !== "search" && name !== "web_search") return null;
|
|
10143
|
-
const raw = typeof args.query === "string" ? args.query : typeof args.text === "string" ? args.text : typeof args.term === "string" ? args.term : "";
|
|
10144
|
-
const normalized = raw.replace(/\s+/g, " ").trim().toLowerCase();
|
|
10145
|
-
return normalized || null;
|
|
10146
|
-
}
|
|
10147
10191
|
function hasBlockingOverlaySignal(text) {
|
|
10148
10192
|
if (!text) return false;
|
|
10149
10193
|
if (/\bno blocking overlays detected\b/i.test(text)) return false;
|
|
10150
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);
|
|
10151
10195
|
}
|
|
10152
|
-
function buildCodexLatestStateReminder(toolResultPreview) {
|
|
10153
|
-
const text = (toolResultPreview || "").trim();
|
|
10154
|
-
if (!text) return "";
|
|
10155
|
-
const existingReminder = text.match(
|
|
10156
|
-
/\bLatest browser state:\s*URL\s+.+?(?:Trust the latest tool result over the initial page context\.|$)/i
|
|
10157
|
-
)?.[0]?.trim();
|
|
10158
|
-
if (existingReminder) return existingReminder;
|
|
10159
|
-
const stateMatch = text.match(
|
|
10160
|
-
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
10161
|
-
);
|
|
10162
|
-
if (stateMatch) {
|
|
10163
|
-
const url = stateMatch[1]?.trim();
|
|
10164
|
-
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
10165
|
-
if (url) {
|
|
10166
|
-
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
10167
|
-
}
|
|
10168
|
-
}
|
|
10169
|
-
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();
|
|
10170
|
-
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
10171
|
-
if (navigatedUrl) {
|
|
10172
|
-
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
10173
|
-
}
|
|
10174
|
-
return "";
|
|
10175
|
-
}
|
|
10176
|
-
function buildCodexRepeatedSearchError(previousTool, previousQuery, latestToolResultPreview, mode) {
|
|
10177
|
-
const stateReminder = buildCodexLatestStateReminder(latestToolResultPreview);
|
|
10178
|
-
const header = mode === "drifted" ? `Error: You already performed ${previousTool} successfully for this task.` : `Error: You already searched for "${previousQuery}" successfully with ${previousTool}.`;
|
|
10179
|
-
const lines = [
|
|
10180
|
-
header,
|
|
10181
|
-
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.`,
|
|
10182
|
-
// The key change: do NOT suggest read_page as a "recovery" action.
|
|
10183
|
-
// The model was using read_page as a no-op to reset the strike counter
|
|
10184
|
-
// and then issue another web_search. The prior results are sufficient.
|
|
10185
|
-
`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.`
|
|
10186
|
-
];
|
|
10187
|
-
if (stateReminder) {
|
|
10188
|
-
lines.push(stateReminder);
|
|
10189
|
-
}
|
|
10190
|
-
return lines.join(" ");
|
|
10191
|
-
}
|
|
10192
10196
|
function buildCodexUnsupportedClearOverlayError(latestToolResultPreview) {
|
|
10193
|
-
const stateReminder =
|
|
10197
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10194
10198
|
const lines = [
|
|
10195
10199
|
`Error: No blocking overlay signal is present in the latest browser state.`,
|
|
10196
10200
|
`Do not call clear_overlays unless read_page or the page context explicitly reports a blocking overlay.`,
|
|
@@ -10310,7 +10314,7 @@ function shouldRetryCodexToolLoop(text, hasToolHistory) {
|
|
|
10310
10314
|
return false;
|
|
10311
10315
|
}
|
|
10312
10316
|
function buildCodexRecoveryInput(userMessage, assistantText, latestToolResultPreview) {
|
|
10313
|
-
const stateReminder =
|
|
10317
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10314
10318
|
const lines = [
|
|
10315
10319
|
`[System] The task is still in progress: ${userMessage}`,
|
|
10316
10320
|
`Do not ask the user what they want next unless the original request is genuinely ambiguous or blocked.`,
|
|
@@ -10343,7 +10347,7 @@ function buildCodexFlightPriceEvidenceRecoveryInput(userMessage, assistantText,
|
|
|
10343
10347
|
};
|
|
10344
10348
|
}
|
|
10345
10349
|
function buildCodexFailedClickRecoveryInput(attemptedTarget, latestToolResultPreview, failedClickCount = 1) {
|
|
10346
|
-
const stateReminder =
|
|
10350
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10347
10351
|
const lines = [
|
|
10348
10352
|
`[System] The previous click did not complete${attemptedTarget ? ` for ${attemptedTarget}` : ""}.`,
|
|
10349
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.`
|
|
@@ -10562,9 +10566,7 @@ class CodexProvider {
|
|
|
10562
10566
|
let clickReadLoopNudged = false;
|
|
10563
10567
|
let latestToolResultPreview = null;
|
|
10564
10568
|
let failedClickCountSinceProgress = 0;
|
|
10565
|
-
const
|
|
10566
|
-
const recentSuccessfulSearchToolByQuery = /* @__PURE__ */ new Map();
|
|
10567
|
-
let lastSuccessfulWebSearchQuery = null;
|
|
10569
|
+
const searchLoopGuard = new SearchLoopGuard(isRealProgressTool);
|
|
10568
10570
|
try {
|
|
10569
10571
|
for (let i = 0; i < maxIterations; i++) {
|
|
10570
10572
|
iterationsUsed = i + 1;
|
|
@@ -10654,30 +10656,29 @@ class CodexProvider {
|
|
|
10654
10656
|
prepared.prepared.name,
|
|
10655
10657
|
prepared.prepared.args
|
|
10656
10658
|
);
|
|
10657
|
-
const searchToolQuery =
|
|
10659
|
+
const searchToolQuery = normalizeSearchToolQuery(
|
|
10658
10660
|
prepared.prepared.name,
|
|
10659
10661
|
prepared.prepared.args
|
|
10660
10662
|
);
|
|
10661
|
-
const
|
|
10662
|
-
|
|
10663
|
+
const searchLoopCheck = searchLoopGuard.check(
|
|
10664
|
+
prepared.prepared.name,
|
|
10665
|
+
searchToolQuery
|
|
10666
|
+
);
|
|
10663
10667
|
const isUnsupportedClearOverlay = prepared.prepared.name === "clear_overlays" && !hasBlockingOverlaySignal(
|
|
10664
10668
|
`${systemPrompt}
|
|
10665
10669
|
${latestToolResultPreview || ""}`
|
|
10666
10670
|
);
|
|
10667
|
-
if (
|
|
10671
|
+
if (searchLoopCheck) {
|
|
10668
10672
|
onChunk(`
|
|
10669
10673
|
<<tool:${prepared.prepared.name}:↻ duplicate suppressed>>
|
|
10670
10674
|
`);
|
|
10671
|
-
const previousTool = isRepeatedSearchAcrossTools ? recentSuccessfulSearchToolByQuery.get(searchToolQuery ?? "") ?? (prepared.prepared.name === "web_search" ? "search" : "web_search") : "web_search";
|
|
10672
|
-
const previousQuery = isRepeatedSearchAcrossTools ? searchToolQuery ?? "" : lastSuccessfulWebSearchQuery ?? "";
|
|
10673
|
-
const mode = isRepeatedSearchAcrossTools ? "repeated" : "drifted";
|
|
10674
10675
|
const output2 = createCodexToolOutput(
|
|
10675
10676
|
prepared.prepared.callId,
|
|
10676
|
-
|
|
10677
|
-
previousTool,
|
|
10678
|
-
previousQuery,
|
|
10677
|
+
buildRepeatedSearchError(
|
|
10678
|
+
searchLoopCheck.previousTool,
|
|
10679
|
+
searchLoopCheck.previousQuery,
|
|
10679
10680
|
latestToolResultPreview,
|
|
10680
|
-
mode
|
|
10681
|
+
searchLoopCheck.mode
|
|
10681
10682
|
)
|
|
10682
10683
|
);
|
|
10683
10684
|
currentInput.push(output2);
|
|
@@ -10731,30 +10732,15 @@ ${latestToolResultPreview || ""}`
|
|
|
10731
10732
|
toolHistoryCount += 1;
|
|
10732
10733
|
latestToolResultPreview = previewToolResult(output.output);
|
|
10733
10734
|
const outputText = toolResultTextContent(output.output);
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
failedClickCountSinceProgress = 0;
|
|
10738
|
-
}
|
|
10739
|
-
}
|
|
10740
|
-
if (searchToolQuery && !looksLikeFailedToolOutput(outputText) && !recentSuccessfulSearchQueries.includes(searchToolQuery)) {
|
|
10741
|
-
recentSuccessfulSearchQueries.push(searchToolQuery);
|
|
10742
|
-
recentSuccessfulSearchToolByQuery.set(
|
|
10743
|
-
searchToolQuery,
|
|
10744
|
-
prepared.prepared.name
|
|
10745
|
-
);
|
|
10746
|
-
if (recentSuccessfulSearchQueries.length > 4) {
|
|
10747
|
-
const dropped = recentSuccessfulSearchQueries.shift();
|
|
10748
|
-
if (dropped) {
|
|
10749
|
-
recentSuccessfulSearchToolByQuery.delete(dropped);
|
|
10750
|
-
}
|
|
10751
|
-
}
|
|
10752
|
-
}
|
|
10753
|
-
if (prepared.prepared.name === "web_search" && !looksLikeFailedToolOutput(outputText)) {
|
|
10754
|
-
if (searchToolQuery) {
|
|
10755
|
-
lastSuccessfulWebSearchQuery = searchToolQuery;
|
|
10756
|
-
}
|
|
10735
|
+
const toolSucceeded = !looksLikeFailedToolOutput(outputText);
|
|
10736
|
+
if (toolSucceeded && isRealProgressTool(prepared.prepared.name)) {
|
|
10737
|
+
failedClickCountSinceProgress = 0;
|
|
10757
10738
|
}
|
|
10739
|
+
searchLoopGuard.recordSuccess(
|
|
10740
|
+
prepared.prepared.name,
|
|
10741
|
+
searchToolQuery,
|
|
10742
|
+
toolSucceeded
|
|
10743
|
+
);
|
|
10758
10744
|
if (prepared.prepared.name === "click" && looksLikeFailedToolOutput(outputText)) {
|
|
10759
10745
|
failedClickCountSinceProgress += 1;
|
|
10760
10746
|
currentInput.push(
|
|
@@ -12711,17 +12697,23 @@ function formatCartSnapshot(page) {
|
|
|
12711
12697
|
function isVisibleToUser(el) {
|
|
12712
12698
|
return el.visible === true && el.inViewport === true && el.obscured !== true && el.blockedByOverlay !== true;
|
|
12713
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
|
+
}
|
|
12714
12715
|
function purchaseActionPriority(el) {
|
|
12715
|
-
const haystack =
|
|
12716
|
-
[
|
|
12717
|
-
el.text,
|
|
12718
|
-
el.label,
|
|
12719
|
-
el.name,
|
|
12720
|
-
el.placeholder,
|
|
12721
|
-
el.description,
|
|
12722
|
-
el.href
|
|
12723
|
-
].filter(Boolean).join(" ")
|
|
12724
|
-
);
|
|
12716
|
+
const haystack = elementSearchText(el);
|
|
12725
12717
|
if (!haystack) return Number.POSITIVE_INFINITY;
|
|
12726
12718
|
if (/\badd(?: item)? to (?:cart|bag|basket)\b/.test(haystack)) return 0;
|
|
12727
12719
|
if (/\b(?:buy now|preorder|pre-order|reserve now|shop now)\b/.test(haystack)) {
|
|
@@ -12733,17 +12725,7 @@ function purchaseActionPriority(el) {
|
|
|
12733
12725
|
return Number.POSITIVE_INFINITY;
|
|
12734
12726
|
}
|
|
12735
12727
|
function dateOrShowtimeControlPriority(el) {
|
|
12736
|
-
const haystack =
|
|
12737
|
-
[
|
|
12738
|
-
el.text,
|
|
12739
|
-
el.label,
|
|
12740
|
-
el.name,
|
|
12741
|
-
el.placeholder,
|
|
12742
|
-
el.description,
|
|
12743
|
-
el.href,
|
|
12744
|
-
el.role
|
|
12745
|
-
].filter(Boolean).join(" ")
|
|
12746
|
-
);
|
|
12728
|
+
const haystack = elementSearchText(el, ["role"]);
|
|
12747
12729
|
if (!haystack) return Number.POSITIVE_INFINITY;
|
|
12748
12730
|
if (/\b(today|tomorrow|mon(?:day)?|tue(?:s|sday)?|wed(?:nesday)?|thu(?:rs|rsday)?|fri(?:day)?|sat(?:urday)?|sun(?:day)?)\b/.test(
|
|
12749
12731
|
haystack
|
|
@@ -12852,24 +12834,34 @@ function formatDialogFocus(page) {
|
|
|
12852
12834
|
}
|
|
12853
12835
|
function formatInteractiveElements(elements) {
|
|
12854
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;
|
|
12855
12847
|
const sorted = [...elements].sort((a, b) => {
|
|
12856
12848
|
const scoreEl = (el) => {
|
|
12857
12849
|
let s = 0;
|
|
12858
|
-
if (el.context === "dialog") s -=
|
|
12850
|
+
if (el.context === "dialog") s -= DIALOG_PRIORITY_BONUS;
|
|
12859
12851
|
const purchasePriority = purchaseActionPriority(el);
|
|
12860
12852
|
if (Number.isFinite(purchasePriority)) {
|
|
12861
|
-
s -=
|
|
12853
|
+
s -= PURCHASE_BASE_WEIGHT - purchasePriority * PURCHASE_PRIORITY_MULTIPLIER;
|
|
12862
12854
|
}
|
|
12863
12855
|
const datePriority = dateOrShowtimeControlPriority(el);
|
|
12864
12856
|
if (Number.isFinite(datePriority)) {
|
|
12865
|
-
s -=
|
|
12857
|
+
s -= DATE_BASE_WEIGHT - datePriority * DATE_PRIORITY_MULTIPLIER;
|
|
12866
12858
|
}
|
|
12867
|
-
if (el.visible === false) s +=
|
|
12868
|
-
if (el.inViewport === false) s +=
|
|
12859
|
+
if (el.visible === false) s += HIDDEN_VISIBILITY_PENALTY;
|
|
12860
|
+
if (el.inViewport === false) s += OFFSCREEN_PENALTY;
|
|
12869
12861
|
if (el.context === "nav" || el.context === "footer" || el.context === "sidebar")
|
|
12870
|
-
s +=
|
|
12871
|
-
if (el.obscured) s +=
|
|
12872
|
-
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;
|
|
12873
12865
|
return s;
|
|
12874
12866
|
};
|
|
12875
12867
|
return scoreEl(a) - scoreEl(b);
|
|
@@ -12901,9 +12893,7 @@ function formatInteractiveElements(elements) {
|
|
|
12901
12893
|
appendFieldAffordances(parts, el);
|
|
12902
12894
|
if (el.options?.length) {
|
|
12903
12895
|
const maxOptions = isDateOrShowtimeControl(el) ? 10 : 5;
|
|
12904
|
-
parts.push(
|
|
12905
|
-
`options=${el.options.slice(0, maxOptions).map((o) => typeof o === "string" ? o : o.label || o.value).join("|")}`
|
|
12906
|
-
);
|
|
12896
|
+
parts.push(`options=${formatElementOptions(el.options, maxOptions)}`);
|
|
12907
12897
|
}
|
|
12908
12898
|
} else if (el.type === "textarea") {
|
|
12909
12899
|
parts.push(`[${el.label || "Text Area"}]`);
|
|
@@ -12966,7 +12956,7 @@ function formatForms(forms) {
|
|
|
12966
12956
|
if (field.options?.length) {
|
|
12967
12957
|
const maxOptions = isDateOrShowtimeControl(field) ? 10 : 5;
|
|
12968
12958
|
fieldParts.push(
|
|
12969
|
-
`options=${field.options
|
|
12959
|
+
`options=${formatElementOptions(field.options, maxOptions)}`
|
|
12970
12960
|
);
|
|
12971
12961
|
}
|
|
12972
12962
|
} else if (field.type === "textarea") {
|
|
@@ -13231,26 +13221,10 @@ function chooseAgentReadMode(page) {
|
|
|
13231
13221
|
return "visible_only";
|
|
13232
13222
|
}
|
|
13233
13223
|
}
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
[
|
|
13238
|
-
page.url,
|
|
13239
|
-
page.title,
|
|
13240
|
-
page.excerpt,
|
|
13241
|
-
page.headings.map((heading) => heading.text).join(" ")
|
|
13242
|
-
].filter(Boolean).join(" ")
|
|
13243
|
-
);
|
|
13244
|
-
return /\b(search|results|find|discover|browse|repositories|repository|issues|pull requests|prs|users|events|listings)\b/.test(
|
|
13245
|
-
haystack
|
|
13246
|
-
);
|
|
13247
|
-
}
|
|
13248
|
-
function isHackerNewsListingPage(url) {
|
|
13249
|
-
try {
|
|
13250
|
-
const parsed = new URL(url);
|
|
13251
|
-
if (parsed.hostname !== "news.ycombinator.com") return false;
|
|
13252
|
-
const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
13253
|
-
return [
|
|
13224
|
+
const SITE_RESULT_FILTERS = [
|
|
13225
|
+
{
|
|
13226
|
+
hostname: "news.ycombinator.com",
|
|
13227
|
+
listingPaths: [
|
|
13254
13228
|
"/",
|
|
13255
13229
|
"/news",
|
|
13256
13230
|
"/newest",
|
|
@@ -13262,30 +13236,62 @@ function isHackerNewsListingPage(url) {
|
|
|
13262
13236
|
"/active",
|
|
13263
13237
|
"/classic",
|
|
13264
13238
|
"/noobstories"
|
|
13265
|
-
]
|
|
13266
|
-
|
|
13267
|
-
|
|
13239
|
+
],
|
|
13240
|
+
utilityPathnames: ["/hide", "/user"],
|
|
13241
|
+
utilityTextPatterns: [
|
|
13242
|
+
/^(hide|past|favorite|unfavorite|flag|unflag|discuss|reply|parent|more)$/,
|
|
13243
|
+
/^\d+\s+(?:comments?|points?)$/
|
|
13244
|
+
]
|
|
13268
13245
|
}
|
|
13269
|
-
|
|
13270
|
-
function
|
|
13271
|
-
if (!element.href) return false;
|
|
13272
|
-
let url;
|
|
13246
|
+
];
|
|
13247
|
+
function matchesSiteFilter(url, filter, baseHostname) {
|
|
13273
13248
|
try {
|
|
13274
|
-
|
|
13249
|
+
const parsed = new URL(url, baseHostname ? `https://${baseHostname}` : void 0);
|
|
13250
|
+
return parsed.hostname === filter.hostname;
|
|
13275
13251
|
} catch {
|
|
13276
13252
|
return false;
|
|
13277
13253
|
}
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
const
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
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
|
+
}
|
|
13285
13279
|
}
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13280
|
+
return false;
|
|
13281
|
+
}
|
|
13282
|
+
function isSearchOrListingPage(page) {
|
|
13283
|
+
if (isSiteListingPage(page.url)) return true;
|
|
13284
|
+
const haystack = normalizeComparable(
|
|
13285
|
+
[
|
|
13286
|
+
page.url,
|
|
13287
|
+
page.title,
|
|
13288
|
+
page.excerpt,
|
|
13289
|
+
page.headings.map((heading) => heading.text).join(" ")
|
|
13290
|
+
].filter(Boolean).join(" ")
|
|
13291
|
+
);
|
|
13292
|
+
return /\b(search|results|find|discover|browse|repositories|repository|issues|pull requests|prs|users|events|listings)\b/.test(
|
|
13293
|
+
haystack
|
|
13294
|
+
);
|
|
13289
13295
|
}
|
|
13290
13296
|
function collectJsonLdEntityItems(input, results = []) {
|
|
13291
13297
|
if (!input) return results;
|
|
@@ -13326,7 +13332,7 @@ function getResultCandidates(page) {
|
|
|
13326
13332
|
const pageHost = normalizeUrlForMatch(page.url);
|
|
13327
13333
|
const searchOrListingPage = isSearchOrListingPage(page);
|
|
13328
13334
|
const scored = page.interactiveElements.filter(
|
|
13329
|
-
(element) => element.type === "link" && element.text?.trim() && element.href && !
|
|
13335
|
+
(element) => element.type === "link" && element.text?.trim() && element.href && !isSiteUtilityLink(element)
|
|
13330
13336
|
).map((element) => {
|
|
13331
13337
|
const text = element.text?.trim() || "";
|
|
13332
13338
|
const comparableText = normalizeComparable(text);
|
|
@@ -13786,7 +13792,7 @@ function detectPageType(page) {
|
|
|
13786
13792
|
if (hasResults && hasSearchInput && listingLike) return "SEARCH_RESULTS";
|
|
13787
13793
|
if (hasCart) return "SHOPPING";
|
|
13788
13794
|
if (formCount > 0 && !hasPasswordField) return "FORM";
|
|
13789
|
-
if (
|
|
13795
|
+
if (isSiteListingPage(page.url)) return "PAGINATED_LIST";
|
|
13790
13796
|
if (hasPagination && listingLike) return "PAGINATED_LIST";
|
|
13791
13797
|
if (hasSearchInput && !listingLike) return "SEARCH_READY";
|
|
13792
13798
|
if (page.content.length > 3e3 && page.interactiveElements.length < 10)
|
|
@@ -3085,7 +3085,7 @@ function buildBaseMetadata(el) {
|
|
|
3085
3085
|
}
|
|
3086
3086
|
function isNavigableEmbeddedSrc(src) {
|
|
3087
3087
|
const normalized = src.trim().toLowerCase();
|
|
3088
|
-
return Boolean(normalized) && !/^(about:blank|javascript:|data:)
|
|
3088
|
+
return Boolean(normalized) && !/^(about:blank|javascript:|data:|blob:|file:)/i.test(normalized);
|
|
3089
3089
|
}
|
|
3090
3090
|
function getEmbeddedFrameLabel(iframe) {
|
|
3091
3091
|
const explicitLabel = getTrimmedText(iframe.getAttribute("title")) || getTrimmedText(iframe.getAttribute("aria-label")) || getTrimmedText(iframe.name) || getTrimmedText(iframe.id);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quanta-intellect/vessel-browser",
|
|
3
3
|
"mcpName": "io.github.unmodeled-tyler/vessel-browser",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.144",
|
|
5
5
|
"description": "AI-native web browser runtime for autonomous agents with human supervision",
|
|
6
6
|
"main": "./out/main/index.js",
|
|
7
7
|
"bin": {
|