@quanta-intellect/vessel-browser 0.1.143 → 0.1.145
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 +722 -259
- package/out/preload/content-script.js +1 -1
- package/out/preload/index.js +16 -2
- package/out/renderer/assets/{index-B4pY2BdC.css → index-BJctp4s0.css} +111 -0
- package/out/renderer/assets/{index-BW4Oa1R1.js → index-DPKkNFWo.js} +155 -51
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -1467,7 +1467,12 @@ function loadJsonFile({
|
|
|
1467
1467
|
const decoded = decodeStoredData(raw, secure);
|
|
1468
1468
|
return parse(JSON.parse(decoded));
|
|
1469
1469
|
} catch (err) {
|
|
1470
|
-
|
|
1470
|
+
const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
1471
|
+
if (isMissingFile) {
|
|
1472
|
+
logger$A.info(`Persistence file not found; using fallback defaults: ${filePath2}`);
|
|
1473
|
+
} else {
|
|
1474
|
+
logger$A.warn(`Failed to load ${filePath2}, using fallback:`, err);
|
|
1475
|
+
}
|
|
1471
1476
|
return fallback;
|
|
1472
1477
|
}
|
|
1473
1478
|
}
|
|
@@ -3867,7 +3872,14 @@ const AIChannels = {
|
|
|
3867
3872
|
AGENT_CHECKPOINT_UPDATE_NOTE: "agent:checkpoint-update-note",
|
|
3868
3873
|
AGENT_UNDO_LAST_ACTION: "agent:undo-last-action",
|
|
3869
3874
|
AGENT_SESSION_CAPTURE: "agent:session-capture",
|
|
3870
|
-
AGENT_SESSION_RESTORE: "agent:session-restore"
|
|
3875
|
+
AGENT_SESSION_RESTORE: "agent:session-restore",
|
|
3876
|
+
AGENT_TASK_START: "agent:task-start",
|
|
3877
|
+
AGENT_TASK_UPDATE: "agent:task-update",
|
|
3878
|
+
AGENT_TASK_NOTE: "agent:task-note",
|
|
3879
|
+
AGENT_TASK_BLOCKER: "agent:task-blocker",
|
|
3880
|
+
AGENT_TASK_RESOLVE: "agent:task-resolve",
|
|
3881
|
+
AGENT_TASK_ABANDON: "agent:task-abandon",
|
|
3882
|
+
AGENT_TASK_CLEAR: "agent:task-clear"
|
|
3871
3883
|
};
|
|
3872
3884
|
const AutofillChannels = {
|
|
3873
3885
|
AUTOFILL_LIST: "autofill:list",
|
|
@@ -8683,6 +8695,106 @@ function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
|
8683
8695
|
}
|
|
8684
8696
|
return recovered;
|
|
8685
8697
|
}
|
|
8698
|
+
function findInlineToolMarkerBodies(text) {
|
|
8699
|
+
const bodies = [];
|
|
8700
|
+
const lowerText = text.toLowerCase();
|
|
8701
|
+
let searchIndex = 0;
|
|
8702
|
+
while (searchIndex < text.length) {
|
|
8703
|
+
const start = lowerText.indexOf("<<tool", searchIndex);
|
|
8704
|
+
if (start === -1) break;
|
|
8705
|
+
let quote = null;
|
|
8706
|
+
let escaped = false;
|
|
8707
|
+
let end = -1;
|
|
8708
|
+
for (let index = start + 2; index < text.length - 1; index += 1) {
|
|
8709
|
+
const char = text[index];
|
|
8710
|
+
if (quote) {
|
|
8711
|
+
if (escaped) {
|
|
8712
|
+
escaped = false;
|
|
8713
|
+
} else if (char === "\\") {
|
|
8714
|
+
escaped = true;
|
|
8715
|
+
} else if (char === quote) {
|
|
8716
|
+
quote = null;
|
|
8717
|
+
}
|
|
8718
|
+
continue;
|
|
8719
|
+
}
|
|
8720
|
+
if (char === '"' || char === "'") {
|
|
8721
|
+
quote = char;
|
|
8722
|
+
continue;
|
|
8723
|
+
}
|
|
8724
|
+
if (char === ">" && text[index + 1] === ">") {
|
|
8725
|
+
end = index;
|
|
8726
|
+
break;
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
if (end === -1) {
|
|
8730
|
+
searchIndex = start + 2;
|
|
8731
|
+
continue;
|
|
8732
|
+
}
|
|
8733
|
+
bodies.push(text.slice(start + 2, end));
|
|
8734
|
+
searchIndex = end + 2;
|
|
8735
|
+
}
|
|
8736
|
+
return bodies;
|
|
8737
|
+
}
|
|
8738
|
+
function recoverInlineToolMarkerToolCalls(text, availableToolNames) {
|
|
8739
|
+
const recovered = [];
|
|
8740
|
+
for (const markerBody of findInlineToolMarkerBodies(text)) {
|
|
8741
|
+
const match = markerBody.trim().match(/^tool[:=]([a-z_][a-z0-9_]*)(?:[:=]([\s\S]*))?$/i);
|
|
8742
|
+
if (!match) continue;
|
|
8743
|
+
const rawName = match[1] ?? "";
|
|
8744
|
+
const rawArgs = (match[2] ?? "").trim();
|
|
8745
|
+
const resolvedName = resolveToolCallName(
|
|
8746
|
+
rawName,
|
|
8747
|
+
{},
|
|
8748
|
+
availableToolNames
|
|
8749
|
+
);
|
|
8750
|
+
if (!availableToolNames.has(resolvedName)) continue;
|
|
8751
|
+
let parsedArgs = null;
|
|
8752
|
+
if (rawArgs.startsWith("{")) {
|
|
8753
|
+
const repaired = parseToolArgsWithRepair(resolvedName, rawArgs);
|
|
8754
|
+
if (repaired) {
|
|
8755
|
+
parsedArgs = repaired.args;
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8758
|
+
if (!parsedArgs) {
|
|
8759
|
+
const kvArgs = {};
|
|
8760
|
+
const kvPattern = /([a-z_][a-z0-9_]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^,\s]*))/gi;
|
|
8761
|
+
let kvMatch;
|
|
8762
|
+
while ((kvMatch = kvPattern.exec(rawArgs)) !== null) {
|
|
8763
|
+
const key2 = kvMatch[1];
|
|
8764
|
+
const value = kvMatch[2] ?? kvMatch[3] ?? kvMatch[4] ?? "";
|
|
8765
|
+
kvArgs[key2] = value;
|
|
8766
|
+
}
|
|
8767
|
+
if (Object.keys(kvArgs).length > 0) {
|
|
8768
|
+
parsedArgs = kvArgs;
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8771
|
+
if (!parsedArgs && rawArgs) {
|
|
8772
|
+
const repaired = parseToolArgsWithRepair(resolvedName, rawArgs);
|
|
8773
|
+
if (repaired) {
|
|
8774
|
+
parsedArgs = repaired.args;
|
|
8775
|
+
}
|
|
8776
|
+
}
|
|
8777
|
+
if (!parsedArgs) {
|
|
8778
|
+
parsedArgs = {};
|
|
8779
|
+
}
|
|
8780
|
+
recovered.push({
|
|
8781
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8782
|
+
name: resolvedName,
|
|
8783
|
+
argsJson: JSON.stringify(parsedArgs)
|
|
8784
|
+
});
|
|
8785
|
+
}
|
|
8786
|
+
return recovered;
|
|
8787
|
+
}
|
|
8788
|
+
function recoverAssistantTextToolCalls(text, availableToolNames) {
|
|
8789
|
+
const textEncodedCalls = recoverTextEncodedToolCalls(text, availableToolNames);
|
|
8790
|
+
if (textEncodedCalls.length > 0) return textEncodedCalls;
|
|
8791
|
+
const inlineMarkerCalls = recoverInlineToolMarkerToolCalls(
|
|
8792
|
+
text,
|
|
8793
|
+
availableToolNames
|
|
8794
|
+
);
|
|
8795
|
+
if (inlineMarkerCalls.length > 0) return inlineMarkerCalls;
|
|
8796
|
+
return recoverNarratedActionToolCalls(text, availableToolNames);
|
|
8797
|
+
}
|
|
8686
8798
|
function recoverNarratedActionToolCalls(text, availableToolNames) {
|
|
8687
8799
|
const trimmed = text.trim();
|
|
8688
8800
|
if (!trimmed) return [];
|
|
@@ -8817,6 +8929,106 @@ function buildFlightPriceEvidenceRecoveryPrompt(userMessage, assistantText, late
|
|
|
8817
8929
|
`Last unsupported answer: ${assistantText.replace(/\s+/g, " ").trim().slice(0, 500) || "(empty)"}`
|
|
8818
8930
|
].join("\n");
|
|
8819
8931
|
}
|
|
8932
|
+
const SEARCH_HISTORY_LIMIT = 4;
|
|
8933
|
+
function normalizeSearchToolQuery(name, args) {
|
|
8934
|
+
if (name !== "search" && name !== "web_search") return null;
|
|
8935
|
+
const raw = typeof args.query === "string" ? args.query : typeof args.text === "string" ? args.text : typeof args.term === "string" ? args.term : "";
|
|
8936
|
+
const normalized = raw.replace(/\s+/g, " ").trim().toLowerCase();
|
|
8937
|
+
return normalized || null;
|
|
8938
|
+
}
|
|
8939
|
+
function buildLatestStateReminder(toolResultPreview) {
|
|
8940
|
+
const text = (toolResultPreview || "").trim();
|
|
8941
|
+
if (!text) return "";
|
|
8942
|
+
const existingReminder = text.match(
|
|
8943
|
+
/\bLatest browser state:\s*URL\s+.+?(?:Trust the latest tool result over the initial page context\.|$)/i
|
|
8944
|
+
)?.[0]?.trim();
|
|
8945
|
+
if (existingReminder) return existingReminder;
|
|
8946
|
+
const stateMatch = text.match(
|
|
8947
|
+
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
8948
|
+
);
|
|
8949
|
+
if (stateMatch) {
|
|
8950
|
+
const url = stateMatch[1]?.trim();
|
|
8951
|
+
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
8952
|
+
if (url) {
|
|
8953
|
+
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8957
|
+
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8958
|
+
if (structuredUrl) {
|
|
8959
|
+
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8960
|
+
}
|
|
8961
|
+
const navigatedUrl = text.match(
|
|
8962
|
+
/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i
|
|
8963
|
+
)?.[1]?.trim() ?? text.match(
|
|
8964
|
+
/\b(?:web\s+)?searched "[^"]+"[^\n]*?(?:->|→)\s+([^\s\n]+)/i
|
|
8965
|
+
)?.[1]?.trim();
|
|
8966
|
+
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
8967
|
+
if (navigatedUrl) {
|
|
8968
|
+
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8969
|
+
}
|
|
8970
|
+
return "";
|
|
8971
|
+
}
|
|
8972
|
+
function buildRepeatedSearchError(previousTool, previousQuery, latestToolResultPreview, mode) {
|
|
8973
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8974
|
+
const lines = [
|
|
8975
|
+
mode === "drifted" ? `Error: You already performed ${previousTool} successfully for this task.` : `Error: You already searched for "${previousQuery}" successfully with ${previousTool}.`,
|
|
8976
|
+
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.`,
|
|
8977
|
+
`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.`,
|
|
8978
|
+
`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.`
|
|
8979
|
+
];
|
|
8980
|
+
if (stateReminder) {
|
|
8981
|
+
lines.push(stateReminder);
|
|
8982
|
+
}
|
|
8983
|
+
return lines.join(" ");
|
|
8984
|
+
}
|
|
8985
|
+
class SearchLoopGuard {
|
|
8986
|
+
recentSuccessfulSearchQueries = [];
|
|
8987
|
+
recentSuccessfulSearchToolByQuery = /* @__PURE__ */ new Map();
|
|
8988
|
+
lastSuccessfulWebSearchQuery = null;
|
|
8989
|
+
isContextResettingTool;
|
|
8990
|
+
constructor(isContextResettingTool) {
|
|
8991
|
+
this.isContextResettingTool = isContextResettingTool;
|
|
8992
|
+
}
|
|
8993
|
+
/**
|
|
8994
|
+
* Check whether a search/web_search call should be suppressed. Returns the
|
|
8995
|
+
* details needed to build the corrective error, or null if the call is OK.
|
|
8996
|
+
*/
|
|
8997
|
+
check(toolName, query) {
|
|
8998
|
+
const isRepeatedSearchAcrossTools = query !== null && this.recentSuccessfulSearchQueries.includes(query);
|
|
8999
|
+
const isQueryDriftedWebSearch = toolName === "web_search" && this.lastSuccessfulWebSearchQuery !== null && query !== null && query !== this.lastSuccessfulWebSearchQuery;
|
|
9000
|
+
if (!isRepeatedSearchAcrossTools && !isQueryDriftedWebSearch) return null;
|
|
9001
|
+
const mode = isRepeatedSearchAcrossTools ? "repeated" : "drifted";
|
|
9002
|
+
const previousTool = isRepeatedSearchAcrossTools ? this.recentSuccessfulSearchToolByQuery.get(query ?? "") ?? (toolName === "web_search" ? "search" : "web_search") : "web_search";
|
|
9003
|
+
const previousQuery = isRepeatedSearchAcrossTools ? query ?? "" : this.lastSuccessfulWebSearchQuery ?? "";
|
|
9004
|
+
return { mode, previousTool, previousQuery };
|
|
9005
|
+
}
|
|
9006
|
+
/**
|
|
9007
|
+
* Record a successfully executed tool. Search queries are added to the
|
|
9008
|
+
* recent-history ring buffer, and real-progress tools clear the drift anchor
|
|
9009
|
+
* so a later distinct search is not flagged as drift.
|
|
9010
|
+
*/
|
|
9011
|
+
recordSuccess(toolName, query, wasSuccessful) {
|
|
9012
|
+
if (wasSuccessful && this.isContextResettingTool(toolName)) {
|
|
9013
|
+
this.lastSuccessfulWebSearchQuery = null;
|
|
9014
|
+
}
|
|
9015
|
+
if (wasSuccessful && query) {
|
|
9016
|
+
if (!this.recentSuccessfulSearchQueries.includes(query)) {
|
|
9017
|
+
this.recentSuccessfulSearchQueries.push(query);
|
|
9018
|
+
this.recentSuccessfulSearchToolByQuery.set(query, toolName);
|
|
9019
|
+
if (this.recentSuccessfulSearchQueries.length > SEARCH_HISTORY_LIMIT) {
|
|
9020
|
+
const dropped = this.recentSuccessfulSearchQueries.shift();
|
|
9021
|
+
if (dropped) {
|
|
9022
|
+
this.recentSuccessfulSearchToolByQuery.delete(dropped);
|
|
9023
|
+
}
|
|
9024
|
+
}
|
|
9025
|
+
}
|
|
9026
|
+
}
|
|
9027
|
+
if (wasSuccessful && toolName === "web_search" && query) {
|
|
9028
|
+
this.lastSuccessfulWebSearchQuery = query;
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
9031
|
+
}
|
|
8820
9032
|
const logger$v = createLogger("OpenAIProvider");
|
|
8821
9033
|
function shouldDebugAgentLoop() {
|
|
8822
9034
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
@@ -8886,7 +9098,7 @@ function buildOpenRouterAttributionHeaders() {
|
|
|
8886
9098
|
function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
|
|
8887
9099
|
if (profile !== "compact") return null;
|
|
8888
9100
|
const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
|
|
8889
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview
|
|
9101
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8890
9102
|
return {
|
|
8891
9103
|
role: "user",
|
|
8892
9104
|
content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
|
|
@@ -8896,13 +9108,13 @@ ${phaseReminder}` : "")
|
|
|
8896
9108
|
};
|
|
8897
9109
|
}
|
|
8898
9110
|
function extractSingleGoalDomain(goal) {
|
|
8899
|
-
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.
|
|
9111
|
+
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.[a-z]{2,})\b/g);
|
|
8900
9112
|
if (!matches || matches.length !== 1) return null;
|
|
8901
9113
|
return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
|
|
8902
9114
|
}
|
|
8903
9115
|
function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
|
|
8904
9116
|
const phaseReminder = buildPhaseReminder(userMessage, assistantText);
|
|
8905
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview
|
|
9117
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
8906
9118
|
const goalDomain = extractSingleGoalDomain(userMessage);
|
|
8907
9119
|
const latest = (latestToolResultPreview || "").toLowerCase();
|
|
8908
9120
|
const assistant = assistantText.toLowerCase();
|
|
@@ -8999,38 +9211,7 @@ function buildPhaseReminder(userMessage, assistantText) {
|
|
|
8999
9211
|
}
|
|
9000
9212
|
return "";
|
|
9001
9213
|
}
|
|
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) {
|
|
9214
|
+
function isSearchContextResettingTool(name) {
|
|
9034
9215
|
return ![
|
|
9035
9216
|
"read_page",
|
|
9036
9217
|
"current_tab",
|
|
@@ -9043,18 +9224,6 @@ function isOpenAIRealProgressToolForSearch(name) {
|
|
|
9043
9224
|
"search"
|
|
9044
9225
|
].includes(name);
|
|
9045
9226
|
}
|
|
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
9227
|
function shouldRecoverCompactStall(text, userMessage) {
|
|
9059
9228
|
const trimmed = text.trim().toLowerCase();
|
|
9060
9229
|
if (!trimmed) return true;
|
|
@@ -9138,15 +9307,24 @@ function logAgentLoopDebug(payload) {
|
|
|
9138
9307
|
}
|
|
9139
9308
|
}
|
|
9140
9309
|
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(
|
|
9310
|
+
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
9311
|
message
|
|
9143
9312
|
)) {
|
|
9144
|
-
return
|
|
9313
|
+
return [
|
|
9314
|
+
message,
|
|
9315
|
+
"OpenRouter reported an upstream model timeout/no-content failure.",
|
|
9316
|
+
"If this persists, retry or pin a specific low-latency tool-calling model instead of the free router."
|
|
9317
|
+
].join(" ");
|
|
9145
9318
|
}
|
|
9146
9319
|
if (providerId === "llama_cpp" && /(available context size|context size exceeded|exceeds the available context size|try increasing it)/i.test(
|
|
9147
9320
|
message
|
|
9148
9321
|
)) {
|
|
9149
|
-
return
|
|
9322
|
+
return [
|
|
9323
|
+
message,
|
|
9324
|
+
"llama.cpp sets context size at server startup, not per request.",
|
|
9325
|
+
`Vessel's agent prompt plus tool schema is about 6.5k tokens before browsing history, so run llama-server with`,
|
|
9326
|
+
`--ctx-size ${LLAMA_CPP_MIN_CTX_TOKENS} minimum (${LLAMA_CPP_RECOMMENDED_CTX_TOKENS} recommended).`
|
|
9327
|
+
].join(" ");
|
|
9150
9328
|
}
|
|
9151
9329
|
return message;
|
|
9152
9330
|
}
|
|
@@ -9256,9 +9434,7 @@ class OpenAICompatProvider {
|
|
|
9256
9434
|
const recentCompactToolSignatures = [];
|
|
9257
9435
|
const recentToolNames = [];
|
|
9258
9436
|
const successfulToolNames = [];
|
|
9259
|
-
const
|
|
9260
|
-
const recentSuccessfulSearchToolByQuery = /* @__PURE__ */ new Map();
|
|
9261
|
-
let lastSuccessfulWebSearchQuery = null;
|
|
9437
|
+
const searchLoopGuard = new SearchLoopGuard(isSearchContextResettingTool);
|
|
9262
9438
|
let clickReadLoopNudged = false;
|
|
9263
9439
|
for (let i = 0; i < maxIterations; i++) {
|
|
9264
9440
|
iterationsUsed = i + 1;
|
|
@@ -9334,22 +9510,13 @@ class OpenAICompatProvider {
|
|
|
9334
9510
|
tc.name = resolveToolCallName(tc.name, parsedArgs, availableToolNames);
|
|
9335
9511
|
}
|
|
9336
9512
|
if (toolCalls.length === 0) {
|
|
9337
|
-
const recoveredToolCalls =
|
|
9513
|
+
const recoveredToolCalls = recoverAssistantTextToolCalls(
|
|
9338
9514
|
textAccum,
|
|
9339
9515
|
availableToolNames
|
|
9340
9516
|
);
|
|
9341
9517
|
if (recoveredToolCalls.length > 0) {
|
|
9342
9518
|
toolCalls = recoveredToolCalls;
|
|
9343
9519
|
if (textAccum.trim()) onChunk("<<erase_prev>>");
|
|
9344
|
-
} else {
|
|
9345
|
-
const narratedToolCalls = recoverNarratedActionToolCalls(
|
|
9346
|
-
textAccum,
|
|
9347
|
-
availableToolNames
|
|
9348
|
-
);
|
|
9349
|
-
if (narratedToolCalls.length > 0) {
|
|
9350
|
-
toolCalls = narratedToolCalls;
|
|
9351
|
-
if (textAccum.trim()) onChunk("<<erase_prev>>");
|
|
9352
|
-
}
|
|
9353
9520
|
}
|
|
9354
9521
|
}
|
|
9355
9522
|
logAgentLoopDebug({
|
|
@@ -9493,24 +9660,20 @@ class OpenAICompatProvider {
|
|
|
9493
9660
|
args = repairedArgs.args;
|
|
9494
9661
|
args = coerceToolArgsForExecution(tc.name, args);
|
|
9495
9662
|
const toolSignature = stableToolSignature(tc.name, args);
|
|
9496
|
-
const searchToolQuery =
|
|
9497
|
-
const
|
|
9498
|
-
|
|
9499
|
-
if (isRepeatedSearchAcrossTools || isQueryDriftedWebSearch) {
|
|
9663
|
+
const searchToolQuery = normalizeSearchToolQuery(tc.name, args);
|
|
9664
|
+
const searchLoopCheck = searchLoopGuard.check(tc.name, searchToolQuery);
|
|
9665
|
+
if (searchLoopCheck) {
|
|
9500
9666
|
onChunk(`
|
|
9501
9667
|
<<tool:${tc.name}:↻ duplicate suppressed>>
|
|
9502
9668
|
`);
|
|
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
9669
|
messages.push({
|
|
9507
9670
|
role: "tool",
|
|
9508
9671
|
tool_call_id: tc.id,
|
|
9509
|
-
content:
|
|
9510
|
-
previousTool,
|
|
9511
|
-
previousQuery,
|
|
9672
|
+
content: buildRepeatedSearchError(
|
|
9673
|
+
searchLoopCheck.previousTool,
|
|
9674
|
+
searchLoopCheck.previousQuery,
|
|
9512
9675
|
latestToolMessage ? String(latestToolMessage.content || "") : null,
|
|
9513
|
-
mode
|
|
9676
|
+
searchLoopCheck.mode
|
|
9514
9677
|
)
|
|
9515
9678
|
});
|
|
9516
9679
|
compactCorrectionCount += 1;
|
|
@@ -9590,25 +9753,15 @@ class OpenAICompatProvider {
|
|
|
9590
9753
|
recentCompactToolSignatures.shift();
|
|
9591
9754
|
}
|
|
9592
9755
|
}
|
|
9593
|
-
|
|
9756
|
+
const toolSucceeded = !/^Error:/i.test(toolContent.trim());
|
|
9757
|
+
if (toolSucceeded) {
|
|
9594
9758
|
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
9759
|
}
|
|
9760
|
+
searchLoopGuard.recordSuccess(
|
|
9761
|
+
tc.name,
|
|
9762
|
+
searchToolQuery,
|
|
9763
|
+
toolSucceeded
|
|
9764
|
+
);
|
|
9612
9765
|
recentToolNames.push(tc.name);
|
|
9613
9766
|
if (recentToolNames.length > 8) recentToolNames.shift();
|
|
9614
9767
|
if (!clickReadLoopNudged && recentToolNames.length >= 6 && isClickReadLoop(recentToolNames)) {
|
|
@@ -10138,59 +10291,13 @@ function previewToolResult(text, maxLength = 800) {
|
|
|
10138
10291
|
if (normalized.length <= maxLength) return normalized;
|
|
10139
10292
|
return `${normalized.slice(0, maxLength)}...`;
|
|
10140
10293
|
}
|
|
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
10294
|
function hasBlockingOverlaySignal(text) {
|
|
10148
10295
|
if (!text) return false;
|
|
10149
10296
|
if (/\bno blocking overlays detected\b/i.test(text)) return false;
|
|
10150
10297
|
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
10298
|
}
|
|
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
10299
|
function buildCodexUnsupportedClearOverlayError(latestToolResultPreview) {
|
|
10193
|
-
const stateReminder =
|
|
10300
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10194
10301
|
const lines = [
|
|
10195
10302
|
`Error: No blocking overlay signal is present in the latest browser state.`,
|
|
10196
10303
|
`Do not call clear_overlays unless read_page or the page context explicitly reports a blocking overlay.`,
|
|
@@ -10310,7 +10417,7 @@ function shouldRetryCodexToolLoop(text, hasToolHistory) {
|
|
|
10310
10417
|
return false;
|
|
10311
10418
|
}
|
|
10312
10419
|
function buildCodexRecoveryInput(userMessage, assistantText, latestToolResultPreview) {
|
|
10313
|
-
const stateReminder =
|
|
10420
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10314
10421
|
const lines = [
|
|
10315
10422
|
`[System] The task is still in progress: ${userMessage}`,
|
|
10316
10423
|
`Do not ask the user what they want next unless the original request is genuinely ambiguous or blocked.`,
|
|
@@ -10343,7 +10450,7 @@ function buildCodexFlightPriceEvidenceRecoveryInput(userMessage, assistantText,
|
|
|
10343
10450
|
};
|
|
10344
10451
|
}
|
|
10345
10452
|
function buildCodexFailedClickRecoveryInput(attemptedTarget, latestToolResultPreview, failedClickCount = 1) {
|
|
10346
|
-
const stateReminder =
|
|
10453
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10347
10454
|
const lines = [
|
|
10348
10455
|
`[System] The previous click did not complete${attemptedTarget ? ` for ${attemptedTarget}` : ""}.`,
|
|
10349
10456
|
`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 +10669,7 @@ class CodexProvider {
|
|
|
10562
10669
|
let clickReadLoopNudged = false;
|
|
10563
10670
|
let latestToolResultPreview = null;
|
|
10564
10671
|
let failedClickCountSinceProgress = 0;
|
|
10565
|
-
const
|
|
10566
|
-
const recentSuccessfulSearchToolByQuery = /* @__PURE__ */ new Map();
|
|
10567
|
-
let lastSuccessfulWebSearchQuery = null;
|
|
10672
|
+
const searchLoopGuard = new SearchLoopGuard(isRealProgressTool);
|
|
10568
10673
|
try {
|
|
10569
10674
|
for (let i = 0; i < maxIterations; i++) {
|
|
10570
10675
|
iterationsUsed = i + 1;
|
|
@@ -10585,11 +10690,10 @@ class CodexProvider {
|
|
|
10585
10690
|
(item) => item.type === "function_call"
|
|
10586
10691
|
);
|
|
10587
10692
|
if (functionCalls.length === 0) {
|
|
10588
|
-
const
|
|
10693
|
+
const recoveredCalls = recoverAssistantTextToolCalls(
|
|
10589
10694
|
result.text,
|
|
10590
10695
|
availableToolNames
|
|
10591
10696
|
);
|
|
10592
|
-
const recoveredCalls = recoveredTextCalls.length > 0 ? recoveredTextCalls : recoverNarratedActionToolCalls(result.text, availableToolNames);
|
|
10593
10697
|
if (recoveredCalls.length > 0) {
|
|
10594
10698
|
if (result.text.trim()) onChunk("<<erase_prev>>");
|
|
10595
10699
|
functionCalls = recoveredCalls.map((toolCall) => ({
|
|
@@ -10654,30 +10758,29 @@ class CodexProvider {
|
|
|
10654
10758
|
prepared.prepared.name,
|
|
10655
10759
|
prepared.prepared.args
|
|
10656
10760
|
);
|
|
10657
|
-
const searchToolQuery =
|
|
10761
|
+
const searchToolQuery = normalizeSearchToolQuery(
|
|
10658
10762
|
prepared.prepared.name,
|
|
10659
10763
|
prepared.prepared.args
|
|
10660
10764
|
);
|
|
10661
|
-
const
|
|
10662
|
-
|
|
10765
|
+
const searchLoopCheck = searchLoopGuard.check(
|
|
10766
|
+
prepared.prepared.name,
|
|
10767
|
+
searchToolQuery
|
|
10768
|
+
);
|
|
10663
10769
|
const isUnsupportedClearOverlay = prepared.prepared.name === "clear_overlays" && !hasBlockingOverlaySignal(
|
|
10664
10770
|
`${systemPrompt}
|
|
10665
10771
|
${latestToolResultPreview || ""}`
|
|
10666
10772
|
);
|
|
10667
|
-
if (
|
|
10773
|
+
if (searchLoopCheck) {
|
|
10668
10774
|
onChunk(`
|
|
10669
10775
|
<<tool:${prepared.prepared.name}:↻ duplicate suppressed>>
|
|
10670
10776
|
`);
|
|
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
10777
|
const output2 = createCodexToolOutput(
|
|
10675
10778
|
prepared.prepared.callId,
|
|
10676
|
-
|
|
10677
|
-
previousTool,
|
|
10678
|
-
previousQuery,
|
|
10779
|
+
buildRepeatedSearchError(
|
|
10780
|
+
searchLoopCheck.previousTool,
|
|
10781
|
+
searchLoopCheck.previousQuery,
|
|
10679
10782
|
latestToolResultPreview,
|
|
10680
|
-
mode
|
|
10783
|
+
searchLoopCheck.mode
|
|
10681
10784
|
)
|
|
10682
10785
|
);
|
|
10683
10786
|
currentInput.push(output2);
|
|
@@ -10731,30 +10834,15 @@ ${latestToolResultPreview || ""}`
|
|
|
10731
10834
|
toolHistoryCount += 1;
|
|
10732
10835
|
latestToolResultPreview = previewToolResult(output.output);
|
|
10733
10836
|
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
|
-
}
|
|
10837
|
+
const toolSucceeded = !looksLikeFailedToolOutput(outputText);
|
|
10838
|
+
if (toolSucceeded && isRealProgressTool(prepared.prepared.name)) {
|
|
10839
|
+
failedClickCountSinceProgress = 0;
|
|
10757
10840
|
}
|
|
10841
|
+
searchLoopGuard.recordSuccess(
|
|
10842
|
+
prepared.prepared.name,
|
|
10843
|
+
searchToolQuery,
|
|
10844
|
+
toolSucceeded
|
|
10845
|
+
);
|
|
10758
10846
|
if (prepared.prepared.name === "click" && looksLikeFailedToolOutput(outputText)) {
|
|
10759
10847
|
failedClickCountSinceProgress += 1;
|
|
10760
10848
|
currentInput.push(
|
|
@@ -12711,17 +12799,23 @@ function formatCartSnapshot(page) {
|
|
|
12711
12799
|
function isVisibleToUser(el) {
|
|
12712
12800
|
return el.visible === true && el.inViewport === true && el.obscured !== true && el.blockedByOverlay !== true;
|
|
12713
12801
|
}
|
|
12802
|
+
function elementSearchText(el, extraFields = []) {
|
|
12803
|
+
const fields = [
|
|
12804
|
+
el.text,
|
|
12805
|
+
el.label,
|
|
12806
|
+
el.name,
|
|
12807
|
+
el.placeholder,
|
|
12808
|
+
el.description,
|
|
12809
|
+
el.href,
|
|
12810
|
+
...extraFields.map((field) => el[field])
|
|
12811
|
+
];
|
|
12812
|
+
return normalizeComparable(fields.filter(Boolean).join(" "));
|
|
12813
|
+
}
|
|
12814
|
+
function formatElementOptions(options, maxOptions) {
|
|
12815
|
+
return options?.slice(0, maxOptions).map((o) => typeof o === "string" ? o : o.label || o.value).join("|") ?? "";
|
|
12816
|
+
}
|
|
12714
12817
|
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
|
-
);
|
|
12818
|
+
const haystack = elementSearchText(el);
|
|
12725
12819
|
if (!haystack) return Number.POSITIVE_INFINITY;
|
|
12726
12820
|
if (/\badd(?: item)? to (?:cart|bag|basket)\b/.test(haystack)) return 0;
|
|
12727
12821
|
if (/\b(?:buy now|preorder|pre-order|reserve now|shop now)\b/.test(haystack)) {
|
|
@@ -12733,17 +12827,7 @@ function purchaseActionPriority(el) {
|
|
|
12733
12827
|
return Number.POSITIVE_INFINITY;
|
|
12734
12828
|
}
|
|
12735
12829
|
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
|
-
);
|
|
12830
|
+
const haystack = elementSearchText(el, ["role"]);
|
|
12747
12831
|
if (!haystack) return Number.POSITIVE_INFINITY;
|
|
12748
12832
|
if (/\b(today|tomorrow|mon(?:day)?|tue(?:s|sday)?|wed(?:nesday)?|thu(?:rs|rsday)?|fri(?:day)?|sat(?:urday)?|sun(?:day)?)\b/.test(
|
|
12749
12833
|
haystack
|
|
@@ -12852,24 +12936,34 @@ function formatDialogFocus(page) {
|
|
|
12852
12936
|
}
|
|
12853
12937
|
function formatInteractiveElements(elements) {
|
|
12854
12938
|
if (elements.length === 0) return "None";
|
|
12939
|
+
const DIALOG_PRIORITY_BONUS = 40;
|
|
12940
|
+
const HIDDEN_VISIBILITY_PENALTY = 100;
|
|
12941
|
+
const OFFSCREEN_PENALTY = 50;
|
|
12942
|
+
const NAVIGATION_CONTEXT_PENALTY = 30;
|
|
12943
|
+
const OBSCURED_PENALTY = 20;
|
|
12944
|
+
const LINK_TYPE_PENALTY = 5;
|
|
12945
|
+
const PURCHASE_BASE_WEIGHT = 25;
|
|
12946
|
+
const PURCHASE_PRIORITY_MULTIPLIER = 5;
|
|
12947
|
+
const DATE_BASE_WEIGHT = 18;
|
|
12948
|
+
const DATE_PRIORITY_MULTIPLIER = 4;
|
|
12855
12949
|
const sorted = [...elements].sort((a, b) => {
|
|
12856
12950
|
const scoreEl = (el) => {
|
|
12857
12951
|
let s = 0;
|
|
12858
|
-
if (el.context === "dialog") s -=
|
|
12952
|
+
if (el.context === "dialog") s -= DIALOG_PRIORITY_BONUS;
|
|
12859
12953
|
const purchasePriority = purchaseActionPriority(el);
|
|
12860
12954
|
if (Number.isFinite(purchasePriority)) {
|
|
12861
|
-
s -=
|
|
12955
|
+
s -= PURCHASE_BASE_WEIGHT - purchasePriority * PURCHASE_PRIORITY_MULTIPLIER;
|
|
12862
12956
|
}
|
|
12863
12957
|
const datePriority = dateOrShowtimeControlPriority(el);
|
|
12864
12958
|
if (Number.isFinite(datePriority)) {
|
|
12865
|
-
s -=
|
|
12959
|
+
s -= DATE_BASE_WEIGHT - datePriority * DATE_PRIORITY_MULTIPLIER;
|
|
12866
12960
|
}
|
|
12867
|
-
if (el.visible === false) s +=
|
|
12868
|
-
if (el.inViewport === false) s +=
|
|
12961
|
+
if (el.visible === false) s += HIDDEN_VISIBILITY_PENALTY;
|
|
12962
|
+
if (el.inViewport === false) s += OFFSCREEN_PENALTY;
|
|
12869
12963
|
if (el.context === "nav" || el.context === "footer" || el.context === "sidebar")
|
|
12870
|
-
s +=
|
|
12871
|
-
if (el.obscured) s +=
|
|
12872
|
-
if (el.type === "link") s +=
|
|
12964
|
+
s += NAVIGATION_CONTEXT_PENALTY;
|
|
12965
|
+
if (el.obscured) s += OBSCURED_PENALTY;
|
|
12966
|
+
if (el.type === "link") s += LINK_TYPE_PENALTY;
|
|
12873
12967
|
return s;
|
|
12874
12968
|
};
|
|
12875
12969
|
return scoreEl(a) - scoreEl(b);
|
|
@@ -12901,9 +12995,7 @@ function formatInteractiveElements(elements) {
|
|
|
12901
12995
|
appendFieldAffordances(parts, el);
|
|
12902
12996
|
if (el.options?.length) {
|
|
12903
12997
|
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
|
-
);
|
|
12998
|
+
parts.push(`options=${formatElementOptions(el.options, maxOptions)}`);
|
|
12907
12999
|
}
|
|
12908
13000
|
} else if (el.type === "textarea") {
|
|
12909
13001
|
parts.push(`[${el.label || "Text Area"}]`);
|
|
@@ -12966,7 +13058,7 @@ function formatForms(forms) {
|
|
|
12966
13058
|
if (field.options?.length) {
|
|
12967
13059
|
const maxOptions = isDateOrShowtimeControl(field) ? 10 : 5;
|
|
12968
13060
|
fieldParts.push(
|
|
12969
|
-
`options=${field.options
|
|
13061
|
+
`options=${formatElementOptions(field.options, maxOptions)}`
|
|
12970
13062
|
);
|
|
12971
13063
|
}
|
|
12972
13064
|
} else if (field.type === "textarea") {
|
|
@@ -13231,26 +13323,10 @@ function chooseAgentReadMode(page) {
|
|
|
13231
13323
|
return "visible_only";
|
|
13232
13324
|
}
|
|
13233
13325
|
}
|
|
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 [
|
|
13326
|
+
const SITE_RESULT_FILTERS = [
|
|
13327
|
+
{
|
|
13328
|
+
hostname: "news.ycombinator.com",
|
|
13329
|
+
listingPaths: [
|
|
13254
13330
|
"/",
|
|
13255
13331
|
"/news",
|
|
13256
13332
|
"/newest",
|
|
@@ -13262,30 +13338,62 @@ function isHackerNewsListingPage(url) {
|
|
|
13262
13338
|
"/active",
|
|
13263
13339
|
"/classic",
|
|
13264
13340
|
"/noobstories"
|
|
13265
|
-
]
|
|
13266
|
-
|
|
13267
|
-
|
|
13341
|
+
],
|
|
13342
|
+
utilityPathnames: ["/hide", "/user"],
|
|
13343
|
+
utilityTextPatterns: [
|
|
13344
|
+
/^(hide|past|favorite|unfavorite|flag|unflag|discuss|reply|parent|more)$/,
|
|
13345
|
+
/^\d+\s+(?:comments?|points?)$/
|
|
13346
|
+
]
|
|
13268
13347
|
}
|
|
13269
|
-
|
|
13270
|
-
function
|
|
13271
|
-
if (!element.href) return false;
|
|
13272
|
-
let url;
|
|
13348
|
+
];
|
|
13349
|
+
function matchesSiteFilter(url, filter, baseHostname) {
|
|
13273
13350
|
try {
|
|
13274
|
-
|
|
13351
|
+
const parsed = new URL(url, baseHostname ? `https://${baseHostname}` : void 0);
|
|
13352
|
+
return parsed.hostname === filter.hostname;
|
|
13275
13353
|
} catch {
|
|
13276
13354
|
return false;
|
|
13277
13355
|
}
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
const
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13356
|
+
}
|
|
13357
|
+
function isSiteListingPage(url) {
|
|
13358
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13359
|
+
if (!matchesSiteFilter(url, filter, "")) continue;
|
|
13360
|
+
try {
|
|
13361
|
+
const pathname = new URL(url).pathname.replace(/\/+$/, "") || "/";
|
|
13362
|
+
if (filter.listingPaths?.includes(pathname)) return true;
|
|
13363
|
+
} catch {
|
|
13364
|
+
}
|
|
13365
|
+
}
|
|
13366
|
+
return false;
|
|
13367
|
+
}
|
|
13368
|
+
function isSiteUtilityLink(element) {
|
|
13369
|
+
if (!element.href) return false;
|
|
13370
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13371
|
+
if (!matchesSiteFilter(element.href, filter, "")) continue;
|
|
13372
|
+
const text = normalizeComparable(element.text || "");
|
|
13373
|
+
for (const pattern of filter.utilityTextPatterns ?? []) {
|
|
13374
|
+
if (pattern.test(text)) return true;
|
|
13375
|
+
}
|
|
13376
|
+
try {
|
|
13377
|
+
const pathname = new URL(element.href).pathname.replace(/\/+$/, "") || "/";
|
|
13378
|
+
if (filter.utilityPathnames?.includes(pathname)) return true;
|
|
13379
|
+
} catch {
|
|
13380
|
+
}
|
|
13285
13381
|
}
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13382
|
+
return false;
|
|
13383
|
+
}
|
|
13384
|
+
function isSearchOrListingPage(page) {
|
|
13385
|
+
if (isSiteListingPage(page.url)) return true;
|
|
13386
|
+
const haystack = normalizeComparable(
|
|
13387
|
+
[
|
|
13388
|
+
page.url,
|
|
13389
|
+
page.title,
|
|
13390
|
+
page.excerpt,
|
|
13391
|
+
page.headings.map((heading) => heading.text).join(" ")
|
|
13392
|
+
].filter(Boolean).join(" ")
|
|
13393
|
+
);
|
|
13394
|
+
return /\b(search|results|find|discover|browse|repositories|repository|issues|pull requests|prs|users|events|listings)\b/.test(
|
|
13395
|
+
haystack
|
|
13396
|
+
);
|
|
13289
13397
|
}
|
|
13290
13398
|
function collectJsonLdEntityItems(input, results = []) {
|
|
13291
13399
|
if (!input) return results;
|
|
@@ -13326,7 +13434,7 @@ function getResultCandidates(page) {
|
|
|
13326
13434
|
const pageHost = normalizeUrlForMatch(page.url);
|
|
13327
13435
|
const searchOrListingPage = isSearchOrListingPage(page);
|
|
13328
13436
|
const scored = page.interactiveElements.filter(
|
|
13329
|
-
(element) => element.type === "link" && element.text?.trim() && element.href && !
|
|
13437
|
+
(element) => element.type === "link" && element.text?.trim() && element.href && !isSiteUtilityLink(element)
|
|
13330
13438
|
).map((element) => {
|
|
13331
13439
|
const text = element.text?.trim() || "";
|
|
13332
13440
|
const comparableText = normalizeComparable(text);
|
|
@@ -13786,7 +13894,7 @@ function detectPageType(page) {
|
|
|
13786
13894
|
if (hasResults && hasSearchInput && listingLike) return "SEARCH_RESULTS";
|
|
13787
13895
|
if (hasCart) return "SHOPPING";
|
|
13788
13896
|
if (formCount > 0 && !hasPasswordField) return "FORM";
|
|
13789
|
-
if (
|
|
13897
|
+
if (isSiteListingPage(page.url)) return "PAGINATED_LIST";
|
|
13790
13898
|
if (hasPagination && listingLike) return "PAGINATED_LIST";
|
|
13791
13899
|
if (hasSearchInput && !listingLike) return "SEARCH_READY";
|
|
13792
13900
|
if (page.content.length > 3e3 && page.interactiveElements.length < 10)
|
|
@@ -23306,7 +23414,10 @@ Recent checkpoints:
|
|
|
23306
23414
|
${input.recentCheckpoints || "- none"}
|
|
23307
23415
|
|
|
23308
23416
|
Task tracker:
|
|
23309
|
-
${input.taskTrackerContext || "- none"}
|
|
23417
|
+
${input.taskTrackerContext || "- none"}
|
|
23418
|
+
|
|
23419
|
+
Task memory:
|
|
23420
|
+
${input.taskMemoryContext || "- none"}`;
|
|
23310
23421
|
}
|
|
23311
23422
|
function buildAgentSystemPrompt(input) {
|
|
23312
23423
|
const instructionBlocks = input.profile === "compact" ? [
|
|
@@ -23687,6 +23798,7 @@ async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd,
|
|
|
23687
23798
|
const runtimeState = runtime2.getState();
|
|
23688
23799
|
const recentCheckpoints = runtimeState.checkpoints.slice(-3).map((item) => `- ${item.name} (${item.id})`).join("\n");
|
|
23689
23800
|
const taskTrackerContext = runtime2.getTaskTrackerContext();
|
|
23801
|
+
const taskMemoryContext = runtime2.getTaskMemoryContext();
|
|
23690
23802
|
const activeTabTitle = pageContent.title || "(untitled)";
|
|
23691
23803
|
const activeTabUrl = pageContent.url || activeWebContents.getURL();
|
|
23692
23804
|
const allTabs = tabManager.getAllStates();
|
|
@@ -23705,7 +23817,8 @@ All open tabs: ${allTabs.map((t) => `${t.id === activeTabId ? "→ " : ""}${t.ti
|
|
|
23705
23817
|
approvalMode: runtimeState.supervisor.approvalMode,
|
|
23706
23818
|
pendingApprovals: runtimeState.supervisor.pendingApprovals.length,
|
|
23707
23819
|
recentCheckpoints: recentCheckpoints || "- none",
|
|
23708
|
-
taskTrackerContext: taskTrackerContext || "- none"
|
|
23820
|
+
taskTrackerContext: taskTrackerContext || "- none",
|
|
23821
|
+
taskMemoryContext: taskMemoryContext || "- none"
|
|
23709
23822
|
});
|
|
23710
23823
|
const actionCtx = {
|
|
23711
23824
|
tabManager,
|
|
@@ -24305,6 +24418,17 @@ function setMcpHealth(update) {
|
|
|
24305
24418
|
}
|
|
24306
24419
|
const ApprovalModeSchema = zod.z.enum(["auto", "confirm-dangerous", "manual"]);
|
|
24307
24420
|
const CheckpointIdSchema = zod.z.string().min(1);
|
|
24421
|
+
const TaskTextSchema = zod.z.string().trim().min(1).max(2e4);
|
|
24422
|
+
const OptionalTaskTextSchema = zod.z.string().trim().max(2e4).optional();
|
|
24423
|
+
const OptionalNullableTaskTextSchema = zod.z.string().trim().max(2e4).nullable().optional();
|
|
24424
|
+
const TaskFactsSchema = zod.z.record(
|
|
24425
|
+
zod.z.string().trim().min(1).max(200),
|
|
24426
|
+
zod.z.string().max(2e4)
|
|
24427
|
+
);
|
|
24428
|
+
const TaskMemoryPatchSchema = zod.z.object({
|
|
24429
|
+
nextStep: OptionalNullableTaskTextSchema,
|
|
24430
|
+
facts: TaskFactsSchema.optional()
|
|
24431
|
+
});
|
|
24308
24432
|
function registerAgentRuntimeHandlers(runtime2, chromeView, sidebarView, sendToRendererViews) {
|
|
24309
24433
|
let runtimeUpdateTimer = null;
|
|
24310
24434
|
let pendingRuntimeState = null;
|
|
@@ -24397,6 +24521,45 @@ function registerAgentRuntimeHandlers(runtime2, chromeView, sidebarView, sendToR
|
|
|
24397
24521
|
return runtime2.restoreSession(snapshot2);
|
|
24398
24522
|
}
|
|
24399
24523
|
);
|
|
24524
|
+
electron.ipcMain.handle(Channels.AGENT_TASK_START, (event, goal) => {
|
|
24525
|
+
assertTrustedIpcSender(event);
|
|
24526
|
+
return runtime2.startTaskMemory(parseIpc(TaskTextSchema, goal, "goal"));
|
|
24527
|
+
});
|
|
24528
|
+
electron.ipcMain.handle(
|
|
24529
|
+
Channels.AGENT_TASK_UPDATE,
|
|
24530
|
+
(event, patch) => {
|
|
24531
|
+
assertTrustedIpcSender(event);
|
|
24532
|
+
return runtime2.updateTaskMemory(
|
|
24533
|
+
parseIpc(TaskMemoryPatchSchema, patch ?? {}, "patch")
|
|
24534
|
+
);
|
|
24535
|
+
}
|
|
24536
|
+
);
|
|
24537
|
+
electron.ipcMain.handle(Channels.AGENT_TASK_NOTE, (event, text) => {
|
|
24538
|
+
assertTrustedIpcSender(event);
|
|
24539
|
+
return runtime2.addTaskNote(parseIpc(TaskTextSchema, text, "text"));
|
|
24540
|
+
});
|
|
24541
|
+
electron.ipcMain.handle(Channels.AGENT_TASK_BLOCKER, (event, blocker) => {
|
|
24542
|
+
assertTrustedIpcSender(event);
|
|
24543
|
+
const validated = blocker == null ? null : parseIpc(OptionalNullableTaskTextSchema, blocker, "blocker");
|
|
24544
|
+
return runtime2.setTaskBlocker(validated ?? null);
|
|
24545
|
+
});
|
|
24546
|
+
electron.ipcMain.handle(Channels.AGENT_TASK_RESOLVE, (event, summary) => {
|
|
24547
|
+
assertTrustedIpcSender(event);
|
|
24548
|
+
return runtime2.resolveTaskMemory(
|
|
24549
|
+
summary == null ? void 0 : parseIpc(OptionalTaskTextSchema, summary, "summary")
|
|
24550
|
+
);
|
|
24551
|
+
});
|
|
24552
|
+
electron.ipcMain.handle(Channels.AGENT_TASK_ABANDON, (event, reason) => {
|
|
24553
|
+
assertTrustedIpcSender(event);
|
|
24554
|
+
return runtime2.abandonTaskMemory(
|
|
24555
|
+
reason == null ? void 0 : parseIpc(OptionalTaskTextSchema, reason, "reason")
|
|
24556
|
+
);
|
|
24557
|
+
});
|
|
24558
|
+
electron.ipcMain.handle(Channels.AGENT_TASK_CLEAR, (event) => {
|
|
24559
|
+
assertTrustedIpcSender(event);
|
|
24560
|
+
runtime2.clearTaskMemory();
|
|
24561
|
+
return null;
|
|
24562
|
+
});
|
|
24400
24563
|
}
|
|
24401
24564
|
function asTextResponse$1(text) {
|
|
24402
24565
|
return { content: [{ type: "text", text }] };
|
|
@@ -28123,6 +28286,137 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
28123
28286
|
}
|
|
28124
28287
|
);
|
|
28125
28288
|
}
|
|
28289
|
+
function registerTaskMemoryTools(server, _tabManager, runtime2) {
|
|
28290
|
+
server.registerTool(
|
|
28291
|
+
"task_start",
|
|
28292
|
+
{
|
|
28293
|
+
title: "Start Task",
|
|
28294
|
+
description: "Start tracking a task. Creates a task memory record with a goal that persists across actions and browser navigation. Use this at the beginning of a multi-step task so the human supervisor can see what you are working on.",
|
|
28295
|
+
inputSchema: {
|
|
28296
|
+
goal: zod.z.string().describe("What this task aims to accomplish"),
|
|
28297
|
+
nextStep: zod.z.string().optional().describe("The first step you plan to take"),
|
|
28298
|
+
facts: zod.z.record(zod.z.string()).optional().describe("Key-value facts relevant to this task (e.g. { username: alice })")
|
|
28299
|
+
}
|
|
28300
|
+
},
|
|
28301
|
+
async ({ goal, nextStep, facts }) => {
|
|
28302
|
+
const task = runtime2.startTaskMemory(goal, {
|
|
28303
|
+
nextStep: nextStep ?? null,
|
|
28304
|
+
facts: facts ?? {}
|
|
28305
|
+
});
|
|
28306
|
+
return asTextResponse(
|
|
28307
|
+
`Task started: ${task.goal}
|
|
28308
|
+
Status: ${task.status}${task.nextStep ? `
|
|
28309
|
+
Next step: ${task.nextStep}` : ""}`
|
|
28310
|
+
);
|
|
28311
|
+
}
|
|
28312
|
+
);
|
|
28313
|
+
server.registerTool(
|
|
28314
|
+
"task_update",
|
|
28315
|
+
{
|
|
28316
|
+
title: "Update Task",
|
|
28317
|
+
description: "Update the current task's next step or facts. Facts are merged with existing facts. Use this to record progress and keep the human supervisor informed.",
|
|
28318
|
+
inputSchema: {
|
|
28319
|
+
nextStep: zod.z.string().optional().describe("The next step you plan to take"),
|
|
28320
|
+
facts: zod.z.record(zod.z.string()).optional().describe("Key-value facts to merge into the task (existing keys are overwritten)")
|
|
28321
|
+
}
|
|
28322
|
+
},
|
|
28323
|
+
async ({ nextStep, facts }) => {
|
|
28324
|
+
const updated = runtime2.updateTaskMemory({
|
|
28325
|
+
nextStep,
|
|
28326
|
+
facts
|
|
28327
|
+
});
|
|
28328
|
+
if (!updated) return asTextResponse("No active task to update. Start one with task_start first.");
|
|
28329
|
+
return asTextResponse(
|
|
28330
|
+
`Task updated: ${updated.goal}
|
|
28331
|
+
Status: ${updated.status}${updated.nextStep ? `
|
|
28332
|
+
Next step: ${updated.nextStep}` : ""}${Object.keys(updated.facts).length > 0 ? `
|
|
28333
|
+
Facts: ${Object.entries(updated.facts).map(([k, v]) => `${k}=${v}`).join(", ")}` : ""}`
|
|
28334
|
+
);
|
|
28335
|
+
}
|
|
28336
|
+
);
|
|
28337
|
+
server.registerTool(
|
|
28338
|
+
"task_note",
|
|
28339
|
+
{
|
|
28340
|
+
title: "Add Task Note",
|
|
28341
|
+
description: "Add a note to the current task. Use this to record observations, intermediate results, or context for the human supervisor.",
|
|
28342
|
+
inputSchema: {
|
|
28343
|
+
text: zod.z.string().describe("The note text to add")
|
|
28344
|
+
}
|
|
28345
|
+
},
|
|
28346
|
+
async ({ text }) => {
|
|
28347
|
+
const updated = runtime2.addTaskNote(text);
|
|
28348
|
+
if (!updated) return asTextResponse("No active task to add a note to. Start one with task_start first.");
|
|
28349
|
+
return asTextResponse(`Note added to task: ${updated.goal}`);
|
|
28350
|
+
}
|
|
28351
|
+
);
|
|
28352
|
+
server.registerTool(
|
|
28353
|
+
"task_blocker",
|
|
28354
|
+
{
|
|
28355
|
+
title: "Set or Clear Task Blocker",
|
|
28356
|
+
description: "Mark the task as blocked with a reason, or clear a blocker to resume. Use this when you are stuck and need human input to continue.",
|
|
28357
|
+
inputSchema: {
|
|
28358
|
+
blocker: zod.z.string().optional().describe("Description of what is blocking progress. Omit or empty string to clear a blocker.")
|
|
28359
|
+
}
|
|
28360
|
+
},
|
|
28361
|
+
async ({ blocker }) => {
|
|
28362
|
+
const updated = runtime2.setTaskBlocker(blocker?.trim() || null);
|
|
28363
|
+
if (!updated) return asTextResponse("No active task. Start one with task_start first.");
|
|
28364
|
+
if (updated.blocker) {
|
|
28365
|
+
return asTextResponse(`Task blocked: ${updated.blocker}
|
|
28366
|
+
Status: ${updated.status}`);
|
|
28367
|
+
}
|
|
28368
|
+
return asTextResponse(`Blocker cleared. Task: ${updated.goal}
|
|
28369
|
+
Status: ${updated.status}`);
|
|
28370
|
+
}
|
|
28371
|
+
);
|
|
28372
|
+
server.registerTool(
|
|
28373
|
+
"task_resolve",
|
|
28374
|
+
{
|
|
28375
|
+
title: "Resolve Task",
|
|
28376
|
+
description: "Mark the current task as completed. Optionally add a summary note. Use this when the task goal has been achieved.",
|
|
28377
|
+
inputSchema: {
|
|
28378
|
+
summary: zod.z.string().optional().describe("Brief summary of the completed task")
|
|
28379
|
+
}
|
|
28380
|
+
},
|
|
28381
|
+
async ({ summary }) => {
|
|
28382
|
+
const resolved = runtime2.resolveTaskMemory(summary);
|
|
28383
|
+
if (!resolved) return asTextResponse("No active task to resolve. Start one with task_start first.");
|
|
28384
|
+
return asTextResponse(
|
|
28385
|
+
`Task completed: ${resolved.goal}${resolved.notes.length > 0 ? `
|
|
28386
|
+
Notes: ${resolved.notes.length} note(s)` : ""}`
|
|
28387
|
+
);
|
|
28388
|
+
}
|
|
28389
|
+
);
|
|
28390
|
+
server.registerTool(
|
|
28391
|
+
"task_abandon",
|
|
28392
|
+
{
|
|
28393
|
+
title: "Abandon Task",
|
|
28394
|
+
description: "Mark the current task as abandoned. Use this when the task cannot be completed or is no longer relevant.",
|
|
28395
|
+
inputSchema: {
|
|
28396
|
+
reason: zod.z.string().optional().describe("Reason for abandoning the task")
|
|
28397
|
+
}
|
|
28398
|
+
},
|
|
28399
|
+
async ({ reason }) => {
|
|
28400
|
+
const abandoned = runtime2.abandonTaskMemory(reason);
|
|
28401
|
+
if (!abandoned) return asTextResponse("No active task to abandon. Start one with task_start first.");
|
|
28402
|
+
return asTextResponse(
|
|
28403
|
+
`Task abandoned: ${abandoned.goal}${reason ? ` (${reason})` : ""}`
|
|
28404
|
+
);
|
|
28405
|
+
}
|
|
28406
|
+
);
|
|
28407
|
+
server.registerTool(
|
|
28408
|
+
"task_status",
|
|
28409
|
+
{
|
|
28410
|
+
title: "Task Status",
|
|
28411
|
+
description: "Check the current task memory status including goal, progress, notes, and blocker."
|
|
28412
|
+
},
|
|
28413
|
+
async () => {
|
|
28414
|
+
const ctx = runtime2.getTaskMemoryContext();
|
|
28415
|
+
if (!ctx) return asTextResponse("No active task. Start one with task_start.");
|
|
28416
|
+
return asTextResponse(ctx);
|
|
28417
|
+
}
|
|
28418
|
+
);
|
|
28419
|
+
}
|
|
28126
28420
|
function registerMacroTools(server, tabManager, runtime2) {
|
|
28127
28421
|
server.registerTool(
|
|
28128
28422
|
"fill_form",
|
|
@@ -29423,6 +29717,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
29423
29717
|
registerSessionTools(server, tabManager, runtime2);
|
|
29424
29718
|
registerMemoryTools(server, tabManager, runtime2);
|
|
29425
29719
|
registerFlowTools(server, tabManager, runtime2);
|
|
29720
|
+
registerTaskMemoryTools(server, tabManager, runtime2);
|
|
29426
29721
|
registerMacroTools(server, tabManager, runtime2);
|
|
29427
29722
|
registerVaultTools(server, tabManager);
|
|
29428
29723
|
registerMetricsTools(server, tabManager, runtime2);
|
|
@@ -32666,6 +32961,121 @@ function formatTaskTracker(state2) {
|
|
|
32666
32961
|
${lines.join("\n")}
|
|
32667
32962
|
---`;
|
|
32668
32963
|
}
|
|
32964
|
+
function createTaskMemory(goal, options = {}) {
|
|
32965
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
32966
|
+
return {
|
|
32967
|
+
id: crypto$1.randomUUID(),
|
|
32968
|
+
goal: goal.trim(),
|
|
32969
|
+
status: "active",
|
|
32970
|
+
blocker: null,
|
|
32971
|
+
notes: [],
|
|
32972
|
+
nextStep: options.nextStep?.trim() || null,
|
|
32973
|
+
facts: { ...options.facts ?? {} },
|
|
32974
|
+
startedAt: now,
|
|
32975
|
+
updatedAt: now,
|
|
32976
|
+
completedAt: null
|
|
32977
|
+
};
|
|
32978
|
+
}
|
|
32979
|
+
function updateTaskMemory(task, patch) {
|
|
32980
|
+
const updated = {
|
|
32981
|
+
...task,
|
|
32982
|
+
nextStep: patch.nextStep !== void 0 ? patch.nextStep : task.nextStep,
|
|
32983
|
+
facts: {
|
|
32984
|
+
...task.facts,
|
|
32985
|
+
...patch.facts ?? {}
|
|
32986
|
+
},
|
|
32987
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
32988
|
+
};
|
|
32989
|
+
return updated;
|
|
32990
|
+
}
|
|
32991
|
+
function addTaskNote(task, text) {
|
|
32992
|
+
const note = {
|
|
32993
|
+
id: crypto$1.randomUUID(),
|
|
32994
|
+
text: text.trim(),
|
|
32995
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
32996
|
+
};
|
|
32997
|
+
const notes = [...task.notes, note].slice(-50);
|
|
32998
|
+
return {
|
|
32999
|
+
...task,
|
|
33000
|
+
notes,
|
|
33001
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
33002
|
+
};
|
|
33003
|
+
}
|
|
33004
|
+
function setTaskBlocker(task, blocker) {
|
|
33005
|
+
const status = blocker ? "blocked" : task.status === "blocked" ? "active" : task.status;
|
|
33006
|
+
return {
|
|
33007
|
+
...task,
|
|
33008
|
+
status,
|
|
33009
|
+
blocker,
|
|
33010
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
33011
|
+
};
|
|
33012
|
+
}
|
|
33013
|
+
function resolveTaskMemory(task, summary) {
|
|
33014
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
33015
|
+
let notes = task.notes;
|
|
33016
|
+
if (summary?.trim()) {
|
|
33017
|
+
const note = {
|
|
33018
|
+
id: crypto$1.randomUUID(),
|
|
33019
|
+
text: summary.trim(),
|
|
33020
|
+
createdAt: now
|
|
33021
|
+
};
|
|
33022
|
+
notes = [...task.notes, note].slice(-50);
|
|
33023
|
+
}
|
|
33024
|
+
return {
|
|
33025
|
+
...task,
|
|
33026
|
+
status: "completed",
|
|
33027
|
+
blocker: null,
|
|
33028
|
+
notes,
|
|
33029
|
+
completedAt: now,
|
|
33030
|
+
updatedAt: now
|
|
33031
|
+
};
|
|
33032
|
+
}
|
|
33033
|
+
function abandonTaskMemory(task, reason) {
|
|
33034
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
33035
|
+
let notes = task.notes;
|
|
33036
|
+
if (reason?.trim()) {
|
|
33037
|
+
const note = {
|
|
33038
|
+
id: crypto$1.randomUUID(),
|
|
33039
|
+
text: `Abandoned: ${reason.trim()}`,
|
|
33040
|
+
createdAt: now
|
|
33041
|
+
};
|
|
33042
|
+
notes = [...task.notes, note].slice(-50);
|
|
33043
|
+
}
|
|
33044
|
+
return {
|
|
33045
|
+
...task,
|
|
33046
|
+
status: "abandoned",
|
|
33047
|
+
blocker: null,
|
|
33048
|
+
notes,
|
|
33049
|
+
completedAt: now,
|
|
33050
|
+
updatedAt: now
|
|
33051
|
+
};
|
|
33052
|
+
}
|
|
33053
|
+
function formatTaskMemory(task) {
|
|
33054
|
+
if (!task) return "";
|
|
33055
|
+
const lines = [
|
|
33056
|
+
"--- Task Memory ---",
|
|
33057
|
+
`Goal: ${task.goal}`,
|
|
33058
|
+
`Status: ${task.status}${task.blocker ? ` (blocked: ${task.blocker})` : ""}`
|
|
33059
|
+
];
|
|
33060
|
+
if (task.nextStep) {
|
|
33061
|
+
lines.push(`Next step: ${task.nextStep}`);
|
|
33062
|
+
}
|
|
33063
|
+
if (Object.keys(task.facts).length > 0) {
|
|
33064
|
+
lines.push("Facts:");
|
|
33065
|
+
for (const [key2, value] of Object.entries(task.facts)) {
|
|
33066
|
+
lines.push(` ${key2}: ${value}`);
|
|
33067
|
+
}
|
|
33068
|
+
}
|
|
33069
|
+
if (task.notes.length > 0) {
|
|
33070
|
+
lines.push("Notes:");
|
|
33071
|
+
for (const note of task.notes.slice(-10)) {
|
|
33072
|
+
const time = note.createdAt.slice(11, 16);
|
|
33073
|
+
lines.push(` [${time}] ${note.text}`);
|
|
33074
|
+
}
|
|
33075
|
+
}
|
|
33076
|
+
lines.push("---");
|
|
33077
|
+
return lines.join("\n");
|
|
33078
|
+
}
|
|
32669
33079
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
32670
33080
|
const PERSIST_DEBOUNCE_MS = 500;
|
|
32671
33081
|
const INTERRUPTED_ACTION_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -32695,6 +33105,7 @@ function getRuntimeStatePath() {
|
|
|
32695
33105
|
}
|
|
32696
33106
|
function sanitizePersistence(persisted) {
|
|
32697
33107
|
const recoveredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33108
|
+
const persistedTaskMemory = persisted?.taskMemory?.completedAt ? null : persisted?.taskMemory ?? null;
|
|
32698
33109
|
const actions = Array.isArray(persisted?.actions) ? persisted.actions.slice(-120).map(
|
|
32699
33110
|
(action) => INTERRUPTED_ACTION_STATUSES.has(action.status) ? {
|
|
32700
33111
|
...action,
|
|
@@ -32716,7 +33127,8 @@ function sanitizePersistence(persisted) {
|
|
|
32716
33127
|
transcript: [],
|
|
32717
33128
|
mcpStatus: "stopped",
|
|
32718
33129
|
flowState: null,
|
|
32719
|
-
taskTracker: null
|
|
33130
|
+
taskTracker: null,
|
|
33131
|
+
taskMemory: persistedTaskMemory
|
|
32720
33132
|
};
|
|
32721
33133
|
}
|
|
32722
33134
|
class AgentRuntime {
|
|
@@ -32803,7 +33215,8 @@ class AgentRuntime {
|
|
|
32803
33215
|
name: name?.trim() || `Checkpoint ${this.state.checkpoints.length + 1}`,
|
|
32804
33216
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32805
33217
|
note: note?.trim() || void 0,
|
|
32806
|
-
snapshot: snapshot2
|
|
33218
|
+
snapshot: snapshot2,
|
|
33219
|
+
taskMemory: this.state.taskMemory ? clone(this.state.taskMemory) : null
|
|
32807
33220
|
};
|
|
32808
33221
|
this.state.checkpoints = [...this.state.checkpoints, checkpoint].slice(
|
|
32809
33222
|
-20
|
|
@@ -32816,6 +33229,7 @@ class AgentRuntime {
|
|
|
32816
33229
|
const checkpoint = this.state.checkpoints.find((item) => item.id === checkpointId) || null;
|
|
32817
33230
|
if (!checkpoint) return null;
|
|
32818
33231
|
this.tabManager.restoreSession(checkpoint.snapshot);
|
|
33232
|
+
this.state.taskMemory = checkpoint.taskMemory ? clone(checkpoint.taskMemory) : null;
|
|
32819
33233
|
this.captureSession(`Restored ${checkpoint.name}`);
|
|
32820
33234
|
return clone(checkpoint);
|
|
32821
33235
|
}
|
|
@@ -32932,6 +33346,54 @@ class AgentRuntime {
|
|
|
32932
33346
|
getTaskTrackerContext() {
|
|
32933
33347
|
return formatTaskTracker(this.state.taskTracker);
|
|
32934
33348
|
}
|
|
33349
|
+
// --- Task Memory ---
|
|
33350
|
+
startTaskMemory(goal, options) {
|
|
33351
|
+
this.state.taskMemory = createTaskMemory(goal, options);
|
|
33352
|
+
this.emit();
|
|
33353
|
+
return clone(this.state.taskMemory);
|
|
33354
|
+
}
|
|
33355
|
+
updateTaskMemory(patch) {
|
|
33356
|
+
if (!this.state.taskMemory || this.state.taskMemory.completedAt) return null;
|
|
33357
|
+
this.state.taskMemory = updateTaskMemory(this.state.taskMemory, patch);
|
|
33358
|
+
this.emit();
|
|
33359
|
+
return clone(this.state.taskMemory);
|
|
33360
|
+
}
|
|
33361
|
+
addTaskNote(text) {
|
|
33362
|
+
if (!this.state.taskMemory || this.state.taskMemory.completedAt) return null;
|
|
33363
|
+
this.state.taskMemory = addTaskNote(this.state.taskMemory, text);
|
|
33364
|
+
this.emit();
|
|
33365
|
+
return clone(this.state.taskMemory);
|
|
33366
|
+
}
|
|
33367
|
+
setTaskBlocker(blocker) {
|
|
33368
|
+
if (!this.state.taskMemory || this.state.taskMemory.completedAt) return null;
|
|
33369
|
+
this.state.taskMemory = setTaskBlocker(
|
|
33370
|
+
this.state.taskMemory,
|
|
33371
|
+
blocker
|
|
33372
|
+
);
|
|
33373
|
+
this.emit();
|
|
33374
|
+
return clone(this.state.taskMemory);
|
|
33375
|
+
}
|
|
33376
|
+
resolveTaskMemory(summary) {
|
|
33377
|
+
if (!this.state.taskMemory || this.state.taskMemory.completedAt) return null;
|
|
33378
|
+
const resolved = resolveTaskMemory(this.state.taskMemory, summary);
|
|
33379
|
+
this.state.taskMemory = null;
|
|
33380
|
+
this.emit();
|
|
33381
|
+
return clone(resolved);
|
|
33382
|
+
}
|
|
33383
|
+
abandonTaskMemory(reason) {
|
|
33384
|
+
if (!this.state.taskMemory || this.state.taskMemory.completedAt) return null;
|
|
33385
|
+
const abandoned = abandonTaskMemory(this.state.taskMemory, reason);
|
|
33386
|
+
this.state.taskMemory = null;
|
|
33387
|
+
this.emit();
|
|
33388
|
+
return clone(abandoned);
|
|
33389
|
+
}
|
|
33390
|
+
clearTaskMemory() {
|
|
33391
|
+
this.state.taskMemory = null;
|
|
33392
|
+
this.emit();
|
|
33393
|
+
}
|
|
33394
|
+
getTaskMemoryContext() {
|
|
33395
|
+
return formatTaskMemory(this.state.taskMemory);
|
|
33396
|
+
}
|
|
32935
33397
|
// --- Speedee Flow State ---
|
|
32936
33398
|
startFlow(goal, steps, startUrl) {
|
|
32937
33399
|
const flow = {
|
|
@@ -33157,7 +33619,8 @@ ${progress}
|
|
|
33157
33619
|
lastError: this.state.supervisor.lastError
|
|
33158
33620
|
},
|
|
33159
33621
|
actions: this.state.actions.slice(-120),
|
|
33160
|
-
checkpoints: this.state.checkpoints.slice(-20)
|
|
33622
|
+
checkpoints: this.state.checkpoints.slice(-20),
|
|
33623
|
+
taskMemory: this.state.taskMemory
|
|
33161
33624
|
};
|
|
33162
33625
|
return fs$1.promises.mkdir(path.dirname(getRuntimeStatePath()), { recursive: true }).then(
|
|
33163
33626
|
() => fs$1.promises.writeFile(
|