@quanta-intellect/vessel-browser 0.1.153 → 0.1.157
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 +531 -476
- package/out/preload/content-script.js +65 -65
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -227,7 +227,14 @@ const SAVE_DEBOUNCE_MS$4 = 150;
|
|
|
227
227
|
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
228
228
|
const CODEX_TOKENS_FILENAME = "vessel-codex-tokens";
|
|
229
229
|
const logger$C = createLogger("Settings");
|
|
230
|
-
const
|
|
230
|
+
const INTERNAL_SETTING_KEYS = /* @__PURE__ */ new Set([
|
|
231
|
+
"premium"
|
|
232
|
+
]);
|
|
233
|
+
const RENDERER_SETTABLE_KEYS = new Set(
|
|
234
|
+
Object.keys(defaults).filter(
|
|
235
|
+
(key2) => !INTERNAL_SETTING_KEYS.has(key2)
|
|
236
|
+
)
|
|
237
|
+
);
|
|
231
238
|
const SettingsValueSchemas = {
|
|
232
239
|
defaultUrl: zod.z.string().url(),
|
|
233
240
|
theme: zod.z.enum(["dark", "light"]),
|
|
@@ -8084,6 +8091,22 @@ function shouldBlockOffGoalDomainNavigation(goal, targetUrl) {
|
|
|
8084
8091
|
function hasRecentDuplicateToolCall(recentToolSignatures, signature) {
|
|
8085
8092
|
return recentToolSignatures.includes(signature);
|
|
8086
8093
|
}
|
|
8094
|
+
const DUPLICATE_TOOL_CALL_RETRYABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
8095
|
+
"read_page",
|
|
8096
|
+
"current_tab",
|
|
8097
|
+
"inspect_element",
|
|
8098
|
+
"screenshot",
|
|
8099
|
+
"go_back",
|
|
8100
|
+
"go_forward",
|
|
8101
|
+
"click"
|
|
8102
|
+
]);
|
|
8103
|
+
const REPEATED_TOOL_CALL_NUDGE = `[System] You are stuck repeating the same action. Stop repeating navigate/search. Use a different supported tool that advances the task, such as click, read_page, or scroll.`;
|
|
8104
|
+
function shouldSuppressDuplicateToolCall(recentToolSignatures, toolName, signature) {
|
|
8105
|
+
return !DUPLICATE_TOOL_CALL_RETRYABLE_TOOLS.has(toolName) && hasRecentDuplicateToolCall(recentToolSignatures, signature);
|
|
8106
|
+
}
|
|
8107
|
+
function buildRepeatedToolCallError(toolName) {
|
|
8108
|
+
return `Error: Repeated the same tool call (${toolName}) with the same arguments twice in a row. Do not repeat it. Continue with the next logical step for the original task.`;
|
|
8109
|
+
}
|
|
8087
8110
|
function isClickReadLoop(names) {
|
|
8088
8111
|
if (names.length < 6) return false;
|
|
8089
8112
|
const tail = names.slice(-6);
|
|
@@ -10089,17 +10112,9 @@ class OpenAICompatProvider {
|
|
|
10089
10112
|
compactCorrectionCount += 1;
|
|
10090
10113
|
continue;
|
|
10091
10114
|
}
|
|
10092
|
-
|
|
10093
|
-
"read_page",
|
|
10094
|
-
"current_tab",
|
|
10095
|
-
"inspect_element",
|
|
10096
|
-
"screenshot",
|
|
10097
|
-
"go_back",
|
|
10098
|
-
"go_forward",
|
|
10099
|
-
"click"
|
|
10100
|
-
].includes(tc.name);
|
|
10101
|
-
if (this.agentToolProfile === "compact" && !neverSuppressDuplicate && hasRecentDuplicateToolCall(
|
|
10115
|
+
if (this.agentToolProfile === "compact" && shouldSuppressDuplicateToolCall(
|
|
10102
10116
|
recentCompactToolSignatures,
|
|
10117
|
+
tc.name,
|
|
10103
10118
|
toolSignature
|
|
10104
10119
|
)) {
|
|
10105
10120
|
onChunk(`
|
|
@@ -10108,13 +10123,13 @@ class OpenAICompatProvider {
|
|
|
10108
10123
|
messages.push({
|
|
10109
10124
|
role: "tool",
|
|
10110
10125
|
tool_call_id: tc.id,
|
|
10111
|
-
content:
|
|
10126
|
+
content: buildRepeatedToolCallError(tc.name)
|
|
10112
10127
|
});
|
|
10113
10128
|
compactCorrectionCount += 1;
|
|
10114
10129
|
if (compactCorrectionCount >= 2) {
|
|
10115
10130
|
messages.push({
|
|
10116
10131
|
role: "user",
|
|
10117
|
-
content:
|
|
10132
|
+
content: REPEATED_TOOL_CALL_NUDGE
|
|
10118
10133
|
});
|
|
10119
10134
|
}
|
|
10120
10135
|
continue;
|
|
@@ -11228,21 +11243,17 @@ ${latestToolResultPreview || ""}`
|
|
|
11228
11243
|
correctionCount += 1;
|
|
11229
11244
|
continue;
|
|
11230
11245
|
}
|
|
11231
|
-
if (
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
"go_back",
|
|
11237
|
-
"go_forward",
|
|
11238
|
-
"click"
|
|
11239
|
-
].includes(prepared.prepared.name) && hasRecentDuplicateToolCall(recentToolSignatures, toolSignature)) {
|
|
11246
|
+
if (shouldSuppressDuplicateToolCall(
|
|
11247
|
+
recentToolSignatures,
|
|
11248
|
+
prepared.prepared.name,
|
|
11249
|
+
toolSignature
|
|
11250
|
+
)) {
|
|
11240
11251
|
onChunk(`
|
|
11241
11252
|
<<tool:${prepared.prepared.name}:↻ duplicate suppressed>>
|
|
11242
11253
|
`);
|
|
11243
11254
|
const output2 = createCodexToolOutput(
|
|
11244
11255
|
prepared.prepared.callId,
|
|
11245
|
-
|
|
11256
|
+
buildRepeatedToolCallError(prepared.prepared.name)
|
|
11246
11257
|
);
|
|
11247
11258
|
currentInput.push(output2);
|
|
11248
11259
|
latestToolResultPreview = previewToolResult(output2.output);
|
|
@@ -12977,6 +12988,166 @@ function getUrlPathSegments(value) {
|
|
|
12977
12988
|
return value.split("?")[0].split("#")[0].split("/").filter(Boolean);
|
|
12978
12989
|
}
|
|
12979
12990
|
}
|
|
12991
|
+
const SITE_RESULT_FILTERS = [
|
|
12992
|
+
{
|
|
12993
|
+
hostname: "news.ycombinator.com",
|
|
12994
|
+
listingPaths: [
|
|
12995
|
+
"/",
|
|
12996
|
+
"/news",
|
|
12997
|
+
"/newest",
|
|
12998
|
+
"/front",
|
|
12999
|
+
"/ask",
|
|
13000
|
+
"/show",
|
|
13001
|
+
"/jobs",
|
|
13002
|
+
"/best",
|
|
13003
|
+
"/active",
|
|
13004
|
+
"/classic",
|
|
13005
|
+
"/noobstories"
|
|
13006
|
+
],
|
|
13007
|
+
utilityPathnames: ["/hide", "/user"],
|
|
13008
|
+
utilityTextPatterns: [
|
|
13009
|
+
/^(hide|past|favorite|unfavorite|flag|unflag|discuss|reply|parent|more)$/,
|
|
13010
|
+
/^\d+\s+(?:comments?|points?)$/
|
|
13011
|
+
]
|
|
13012
|
+
}
|
|
13013
|
+
];
|
|
13014
|
+
function matchesSiteFilter(url, filter, baseHostname) {
|
|
13015
|
+
try {
|
|
13016
|
+
const parsed = new URL(url, baseHostname ? `https://${baseHostname}` : void 0);
|
|
13017
|
+
return parsed.hostname === filter.hostname;
|
|
13018
|
+
} catch {
|
|
13019
|
+
return false;
|
|
13020
|
+
}
|
|
13021
|
+
}
|
|
13022
|
+
function isSiteListingPage(url) {
|
|
13023
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13024
|
+
if (!matchesSiteFilter(url, filter, "")) continue;
|
|
13025
|
+
try {
|
|
13026
|
+
const pathname = new URL(url).pathname.replace(/\/+$/, "") || "/";
|
|
13027
|
+
if (filter.listingPaths?.includes(pathname)) return true;
|
|
13028
|
+
} catch {
|
|
13029
|
+
}
|
|
13030
|
+
}
|
|
13031
|
+
return false;
|
|
13032
|
+
}
|
|
13033
|
+
function isSiteUtilityLink(element) {
|
|
13034
|
+
if (!element.href) return false;
|
|
13035
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13036
|
+
if (!matchesSiteFilter(element.href, filter, "")) continue;
|
|
13037
|
+
const text = normalizeComparable(element.text || "");
|
|
13038
|
+
for (const pattern of filter.utilityTextPatterns ?? []) {
|
|
13039
|
+
if (pattern.test(text)) return true;
|
|
13040
|
+
}
|
|
13041
|
+
try {
|
|
13042
|
+
const pathname = new URL(element.href).pathname.replace(/\/+$/, "") || "/";
|
|
13043
|
+
if (filter.utilityPathnames?.includes(pathname)) return true;
|
|
13044
|
+
} catch {
|
|
13045
|
+
}
|
|
13046
|
+
}
|
|
13047
|
+
return false;
|
|
13048
|
+
}
|
|
13049
|
+
function isSearchOrListingPage(page) {
|
|
13050
|
+
if (isSiteListingPage(page.url)) return true;
|
|
13051
|
+
const haystack = normalizeComparable(
|
|
13052
|
+
[page.url, page.title, page.excerpt, page.headings.map((heading) => heading.text).join(" ")].filter(Boolean).join(" ")
|
|
13053
|
+
);
|
|
13054
|
+
return /\b(search|results|find|discover|browse|repositories|repository|issues|pull requests|prs|users|events|listings)\b/.test(
|
|
13055
|
+
haystack
|
|
13056
|
+
);
|
|
13057
|
+
}
|
|
13058
|
+
function collectJsonLdEntityItems(input, results = []) {
|
|
13059
|
+
if (!input) return results;
|
|
13060
|
+
if (Array.isArray(input)) {
|
|
13061
|
+
input.forEach((item2) => collectJsonLdEntityItems(item2, results));
|
|
13062
|
+
return results;
|
|
13063
|
+
}
|
|
13064
|
+
if (typeof input !== "object") return results;
|
|
13065
|
+
const item = input;
|
|
13066
|
+
const type = item["@type"];
|
|
13067
|
+
const types = Array.isArray(type) ? type : [type];
|
|
13068
|
+
const typeNames = types.filter((entry) => typeof entry === "string");
|
|
13069
|
+
if ((typeof item.name === "string" || typeof item.url === "string") && !typeNames.some(
|
|
13070
|
+
(entry) => ["BreadcrumbList", "Organization", "WebSite", "WebPage"].includes(entry)
|
|
13071
|
+
)) {
|
|
13072
|
+
results.push(item);
|
|
13073
|
+
}
|
|
13074
|
+
collectJsonLdEntityItems(item["@graph"], results);
|
|
13075
|
+
collectJsonLdEntityItems(item.mainEntity, results);
|
|
13076
|
+
collectJsonLdEntityItems(item.itemListElement, results);
|
|
13077
|
+
collectJsonLdEntityItems(item.item, results);
|
|
13078
|
+
return results;
|
|
13079
|
+
}
|
|
13080
|
+
function getResultCandidates(page) {
|
|
13081
|
+
const entityItems = collectJsonLdEntityItems(page.jsonLd ?? []);
|
|
13082
|
+
const entityNames = new Set(
|
|
13083
|
+
entityItems.map((item) => typeof item.name === "string" ? normalizeComparable(item.name) : "").filter(Boolean)
|
|
13084
|
+
);
|
|
13085
|
+
const entityUrls = new Set(
|
|
13086
|
+
entityItems.map((item) => typeof item.url === "string" ? normalizeUrlForMatch(item.url) : null).filter((value) => Boolean(value))
|
|
13087
|
+
);
|
|
13088
|
+
const pageHost = normalizeUrlForMatch(page.url);
|
|
13089
|
+
const searchOrListingPage = isSearchOrListingPage(page);
|
|
13090
|
+
const scored = page.interactiveElements.filter(
|
|
13091
|
+
(element) => element.type === "link" && element.text?.trim() && element.href && !isSiteUtilityLink(element)
|
|
13092
|
+
).map((element) => {
|
|
13093
|
+
const text = element.text?.trim() || "";
|
|
13094
|
+
const comparableText = normalizeComparable(text);
|
|
13095
|
+
const href = normalizeUrlForMatch(element.href);
|
|
13096
|
+
const haystack = normalizeComparable(
|
|
13097
|
+
[element.text, element.description, element.selector, element.href].filter(Boolean).join(" ")
|
|
13098
|
+
);
|
|
13099
|
+
let score = 0;
|
|
13100
|
+
if (entityNames.has(comparableText)) score += 6;
|
|
13101
|
+
if (href && entityUrls.has(href)) score += 6;
|
|
13102
|
+
if (entityItems.some((item) => {
|
|
13103
|
+
const name = typeof item.name === "string" ? normalizeComparable(item.name) : "";
|
|
13104
|
+
return Boolean(name) && (name.includes(comparableText) || comparableText.includes(name));
|
|
13105
|
+
})) {
|
|
13106
|
+
score += 4;
|
|
13107
|
+
}
|
|
13108
|
+
if (element.context === "article") score += 3;
|
|
13109
|
+
else if (element.context === "main" || element.context === "content") score += 1;
|
|
13110
|
+
if (href && pageHost) {
|
|
13111
|
+
try {
|
|
13112
|
+
if (new URL(href).origin === new URL(pageHost).origin) score += 1;
|
|
13113
|
+
} catch {
|
|
13114
|
+
}
|
|
13115
|
+
}
|
|
13116
|
+
const hrefSegments = getUrlPathSegments(element.href);
|
|
13117
|
+
if (hrefSegments.length >= 2) score += 1;
|
|
13118
|
+
if (text.includes("/")) score += 1;
|
|
13119
|
+
if (searchOrListingPage && (element.context === "article" || element.context === "main" || element.context === "content")) {
|
|
13120
|
+
score += 2;
|
|
13121
|
+
}
|
|
13122
|
+
if (/\b(card|tile|result|rating|review)\b/.test(haystack)) score += 1;
|
|
13123
|
+
if (/\b(item|list|row|repo|repository|issue|pull request|event)\b/.test(haystack)) {
|
|
13124
|
+
score += 1;
|
|
13125
|
+
}
|
|
13126
|
+
if (text.length >= 12 && text.split(/\s+/).length >= 2) score += 1;
|
|
13127
|
+
if (element.context === "nav" || element.context === "header" || element.context === "footer" || element.context === "sidebar" || element.context === "dialog") {
|
|
13128
|
+
score -= 5;
|
|
13129
|
+
}
|
|
13130
|
+
if (/\b(home|menu|about|contact|privacy|terms|login|sign in|sign up|subscribe|newsletter|facebook|instagram|pinterest|share|print|next|previous|prev|sort|filter|star|sponsor)\b/.test(
|
|
13131
|
+
comparableText
|
|
13132
|
+
)) {
|
|
13133
|
+
score -= 4;
|
|
13134
|
+
}
|
|
13135
|
+
return { element, score };
|
|
13136
|
+
}).filter(({ score, element }) => {
|
|
13137
|
+
if (entityItems.length > 0) return score >= 4;
|
|
13138
|
+
if (searchOrListingPage) {
|
|
13139
|
+
return score >= 4 || score >= 3 && (element.context === "article" || element.context === "main" || element.context === "content");
|
|
13140
|
+
}
|
|
13141
|
+
return score >= 4 || score >= 3 && element.context === "article";
|
|
13142
|
+
}).sort((a, b) => b.score - a.score || (a.element.index ?? 0) - (b.element.index ?? 0));
|
|
13143
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13144
|
+
return scored.map(({ element }) => element).filter((element) => {
|
|
13145
|
+
const key2 = `${normalizeComparable(element.text || "")}|${normalizeUrlForMatch(element.href) || ""}`;
|
|
13146
|
+
if (seen.has(key2)) return false;
|
|
13147
|
+
seen.add(key2);
|
|
13148
|
+
return true;
|
|
13149
|
+
});
|
|
13150
|
+
}
|
|
12980
13151
|
const MAX_STRUCTURED_ITEMS = 100;
|
|
12981
13152
|
const LARGE_PAGE_HINT_THRESHOLD = 12e3;
|
|
12982
13153
|
function truncateContent(content) {
|
|
@@ -13729,189 +13900,29 @@ const PAGE_TYPE_READ_MODE = {
|
|
|
13729
13900
|
ARTICLE: "summary",
|
|
13730
13901
|
GENERAL: "visible_only"
|
|
13731
13902
|
};
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
listingPaths: [
|
|
13736
|
-
"/",
|
|
13737
|
-
"/news",
|
|
13738
|
-
"/newest",
|
|
13739
|
-
"/front",
|
|
13740
|
-
"/ask",
|
|
13741
|
-
"/show",
|
|
13742
|
-
"/jobs",
|
|
13743
|
-
"/best",
|
|
13744
|
-
"/active",
|
|
13745
|
-
"/classic",
|
|
13746
|
-
"/noobstories"
|
|
13747
|
-
],
|
|
13748
|
-
utilityPathnames: ["/hide", "/user"],
|
|
13749
|
-
utilityTextPatterns: [
|
|
13750
|
-
/^(hide|past|favorite|unfavorite|flag|unflag|discuss|reply|parent|more)$/,
|
|
13751
|
-
/^\d+\s+(?:comments?|points?)$/
|
|
13752
|
-
]
|
|
13753
|
-
}
|
|
13754
|
-
];
|
|
13755
|
-
function matchesSiteFilter(url, filter, baseHostname) {
|
|
13756
|
-
try {
|
|
13757
|
-
const parsed = new URL(url, baseHostname ? `https://${baseHostname}` : void 0);
|
|
13758
|
-
return parsed.hostname === filter.hostname;
|
|
13759
|
-
} catch {
|
|
13760
|
-
return false;
|
|
13761
|
-
}
|
|
13903
|
+
function buildScopedContext(page, mode) {
|
|
13904
|
+
const render = SCOPED_CONTEXT_RENDERERS.get(mode) ?? buildStructuredContext;
|
|
13905
|
+
return render(page);
|
|
13762
13906
|
}
|
|
13763
|
-
function
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13907
|
+
function buildSummaryContext(page) {
|
|
13908
|
+
const sections = [];
|
|
13909
|
+
const cartSnapshot = formatCartSnapshot(page);
|
|
13910
|
+
sections.push(`**URL:** ${page.url}`);
|
|
13911
|
+
sections.push(`**Title:** ${page.title}`);
|
|
13912
|
+
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
13913
|
+
if (page.byline) sections.push(`**Author:** ${page.byline}`);
|
|
13914
|
+
if (page.excerpt) sections.push(`**Summary:** ${page.excerpt}`);
|
|
13915
|
+
const largePageHint = formatLargePageHint(page);
|
|
13916
|
+
if (largePageHint) sections.push(`**Reading Hint:** ${largePageHint}`);
|
|
13917
|
+
const scrollHints = getScrollHints(page);
|
|
13918
|
+
if (scrollHints.length > 0) {
|
|
13919
|
+
sections.push(`**Scroll Hint:** ${scrollHints[0]}`);
|
|
13771
13920
|
}
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
if (!matchesSiteFilter(element.href, filter, "")) continue;
|
|
13778
|
-
const text = normalizeComparable(element.text || "");
|
|
13779
|
-
for (const pattern of filter.utilityTextPatterns ?? []) {
|
|
13780
|
-
if (pattern.test(text)) return true;
|
|
13781
|
-
}
|
|
13782
|
-
try {
|
|
13783
|
-
const pathname = new URL(element.href).pathname.replace(/\/+$/, "") || "/";
|
|
13784
|
-
if (filter.utilityPathnames?.includes(pathname)) return true;
|
|
13785
|
-
} catch {
|
|
13786
|
-
}
|
|
13787
|
-
}
|
|
13788
|
-
return false;
|
|
13789
|
-
}
|
|
13790
|
-
function isSearchOrListingPage(page) {
|
|
13791
|
-
if (isSiteListingPage(page.url)) return true;
|
|
13792
|
-
const haystack = normalizeComparable(
|
|
13793
|
-
[page.url, page.title, page.excerpt, page.headings.map((heading) => heading.text).join(" ")].filter(Boolean).join(" ")
|
|
13794
|
-
);
|
|
13795
|
-
return /\b(search|results|find|discover|browse|repositories|repository|issues|pull requests|prs|users|events|listings)\b/.test(
|
|
13796
|
-
haystack
|
|
13797
|
-
);
|
|
13798
|
-
}
|
|
13799
|
-
function collectJsonLdEntityItems(input, results = []) {
|
|
13800
|
-
if (!input) return results;
|
|
13801
|
-
if (Array.isArray(input)) {
|
|
13802
|
-
input.forEach((item2) => collectJsonLdEntityItems(item2, results));
|
|
13803
|
-
return results;
|
|
13804
|
-
}
|
|
13805
|
-
if (typeof input !== "object") return results;
|
|
13806
|
-
const item = input;
|
|
13807
|
-
const type = item["@type"];
|
|
13808
|
-
const types = Array.isArray(type) ? type : [type];
|
|
13809
|
-
const typeNames = types.filter((entry) => typeof entry === "string");
|
|
13810
|
-
if ((typeof item.name === "string" || typeof item.url === "string") && !typeNames.some(
|
|
13811
|
-
(entry) => ["BreadcrumbList", "Organization", "WebSite", "WebPage"].includes(entry)
|
|
13812
|
-
)) {
|
|
13813
|
-
results.push(item);
|
|
13814
|
-
}
|
|
13815
|
-
collectJsonLdEntityItems(item["@graph"], results);
|
|
13816
|
-
collectJsonLdEntityItems(item.mainEntity, results);
|
|
13817
|
-
collectJsonLdEntityItems(item.itemListElement, results);
|
|
13818
|
-
collectJsonLdEntityItems(item.item, results);
|
|
13819
|
-
return results;
|
|
13820
|
-
}
|
|
13821
|
-
function getResultCandidates(page) {
|
|
13822
|
-
const entityItems = collectJsonLdEntityItems(page.jsonLd ?? []);
|
|
13823
|
-
const entityNames = new Set(
|
|
13824
|
-
entityItems.map((item) => typeof item.name === "string" ? normalizeComparable(item.name) : "").filter(Boolean)
|
|
13825
|
-
);
|
|
13826
|
-
const entityUrls = new Set(
|
|
13827
|
-
entityItems.map((item) => typeof item.url === "string" ? normalizeUrlForMatch(item.url) : null).filter((value) => Boolean(value))
|
|
13828
|
-
);
|
|
13829
|
-
const pageHost = normalizeUrlForMatch(page.url);
|
|
13830
|
-
const searchOrListingPage = isSearchOrListingPage(page);
|
|
13831
|
-
const scored = page.interactiveElements.filter(
|
|
13832
|
-
(element) => element.type === "link" && element.text?.trim() && element.href && !isSiteUtilityLink(element)
|
|
13833
|
-
).map((element) => {
|
|
13834
|
-
const text = element.text?.trim() || "";
|
|
13835
|
-
const comparableText = normalizeComparable(text);
|
|
13836
|
-
const href = normalizeUrlForMatch(element.href);
|
|
13837
|
-
const haystack = normalizeComparable(
|
|
13838
|
-
[element.text, element.description, element.selector, element.href].filter(Boolean).join(" ")
|
|
13839
|
-
);
|
|
13840
|
-
let score = 0;
|
|
13841
|
-
if (entityNames.has(comparableText)) score += 6;
|
|
13842
|
-
if (href && entityUrls.has(href)) score += 6;
|
|
13843
|
-
if (entityItems.some((item) => {
|
|
13844
|
-
const name = typeof item.name === "string" ? normalizeComparable(item.name) : "";
|
|
13845
|
-
return Boolean(name) && (name.includes(comparableText) || comparableText.includes(name));
|
|
13846
|
-
})) {
|
|
13847
|
-
score += 4;
|
|
13848
|
-
}
|
|
13849
|
-
if (element.context === "article") score += 3;
|
|
13850
|
-
else if (element.context === "main" || element.context === "content") score += 1;
|
|
13851
|
-
if (href && pageHost) {
|
|
13852
|
-
try {
|
|
13853
|
-
if (new URL(href).origin === new URL(pageHost).origin) score += 1;
|
|
13854
|
-
} catch {
|
|
13855
|
-
}
|
|
13856
|
-
}
|
|
13857
|
-
const hrefSegments = getUrlPathSegments(element.href);
|
|
13858
|
-
if (hrefSegments.length >= 2) score += 1;
|
|
13859
|
-
if (text.includes("/")) score += 1;
|
|
13860
|
-
if (searchOrListingPage && (element.context === "article" || element.context === "main" || element.context === "content")) {
|
|
13861
|
-
score += 2;
|
|
13862
|
-
}
|
|
13863
|
-
if (/\b(card|tile|result|rating|review)\b/.test(haystack)) score += 1;
|
|
13864
|
-
if (/\b(item|list|row|repo|repository|issue|pull request|event)\b/.test(haystack)) {
|
|
13865
|
-
score += 1;
|
|
13866
|
-
}
|
|
13867
|
-
if (text.length >= 12 && text.split(/\s+/).length >= 2) score += 1;
|
|
13868
|
-
if (element.context === "nav" || element.context === "header" || element.context === "footer" || element.context === "sidebar" || element.context === "dialog") {
|
|
13869
|
-
score -= 5;
|
|
13870
|
-
}
|
|
13871
|
-
if (/\b(home|menu|about|contact|privacy|terms|login|sign in|sign up|subscribe|newsletter|facebook|instagram|pinterest|share|print|next|previous|prev|sort|filter|star|sponsor)\b/.test(
|
|
13872
|
-
comparableText
|
|
13873
|
-
)) {
|
|
13874
|
-
score -= 4;
|
|
13875
|
-
}
|
|
13876
|
-
return { element, score };
|
|
13877
|
-
}).filter(({ score, element }) => {
|
|
13878
|
-
if (entityItems.length > 0) return score >= 4;
|
|
13879
|
-
if (searchOrListingPage) {
|
|
13880
|
-
return score >= 4 || score >= 3 && (element.context === "article" || element.context === "main" || element.context === "content");
|
|
13881
|
-
}
|
|
13882
|
-
return score >= 4 || score >= 3 && element.context === "article";
|
|
13883
|
-
}).sort((a, b) => b.score - a.score || (a.element.index ?? 0) - (b.element.index ?? 0));
|
|
13884
|
-
const seen = /* @__PURE__ */ new Set();
|
|
13885
|
-
return scored.map(({ element }) => element).filter((element) => {
|
|
13886
|
-
const key2 = `${normalizeComparable(element.text || "")}|${normalizeUrlForMatch(element.href) || ""}`;
|
|
13887
|
-
if (seen.has(key2)) return false;
|
|
13888
|
-
seen.add(key2);
|
|
13889
|
-
return true;
|
|
13890
|
-
});
|
|
13891
|
-
}
|
|
13892
|
-
function buildScopedContext(page, mode) {
|
|
13893
|
-
const render = SCOPED_CONTEXT_RENDERERS.get(mode) ?? buildStructuredContext;
|
|
13894
|
-
return render(page);
|
|
13895
|
-
}
|
|
13896
|
-
function buildSummaryContext(page) {
|
|
13897
|
-
const sections = [];
|
|
13898
|
-
const cartSnapshot = formatCartSnapshot(page);
|
|
13899
|
-
sections.push(`**URL:** ${page.url}`);
|
|
13900
|
-
sections.push(`**Title:** ${page.title}`);
|
|
13901
|
-
sections.push(`**Viewport:** ${formatViewport(page)}`);
|
|
13902
|
-
if (page.byline) sections.push(`**Author:** ${page.byline}`);
|
|
13903
|
-
if (page.excerpt) sections.push(`**Summary:** ${page.excerpt}`);
|
|
13904
|
-
const largePageHint = formatLargePageHint(page);
|
|
13905
|
-
if (largePageHint) sections.push(`**Reading Hint:** ${largePageHint}`);
|
|
13906
|
-
const scrollHints = getScrollHints(page);
|
|
13907
|
-
if (scrollHints.length > 0) {
|
|
13908
|
-
sections.push(`**Scroll Hint:** ${scrollHints[0]}`);
|
|
13909
|
-
}
|
|
13910
|
-
sections.push("");
|
|
13911
|
-
const summaryIntent = analyzePageIntent(page);
|
|
13912
|
-
if (summaryIntent) {
|
|
13913
|
-
sections.push(summaryIntent);
|
|
13914
|
-
sections.push("");
|
|
13921
|
+
sections.push("");
|
|
13922
|
+
const summaryIntent = analyzePageIntent(page);
|
|
13923
|
+
if (summaryIntent) {
|
|
13924
|
+
sections.push(summaryIntent);
|
|
13925
|
+
sections.push("");
|
|
13915
13926
|
}
|
|
13916
13927
|
if (cartSnapshot) {
|
|
13917
13928
|
sections.push("### Cart Snapshot");
|
|
@@ -19074,16 +19085,216 @@ function handleCreateCheckpoint(ctx, args) {
|
|
|
19074
19085
|
const checkpoint = ctx.runtime.createCheckpoint(args.name, args.note);
|
|
19075
19086
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
19076
19087
|
}
|
|
19077
|
-
function handleRestoreCheckpoint(ctx, args) {
|
|
19078
|
-
const checkpoint = findCheckpoint(ctx.runtime.getState().checkpoints, args);
|
|
19079
|
-
if (!checkpoint) {
|
|
19080
|
-
return "Error: No matching checkpoint found";
|
|
19088
|
+
function handleRestoreCheckpoint(ctx, args) {
|
|
19089
|
+
const checkpoint = findCheckpoint(ctx.runtime.getState().checkpoints, args);
|
|
19090
|
+
if (!checkpoint) {
|
|
19091
|
+
return "Error: No matching checkpoint found";
|
|
19092
|
+
}
|
|
19093
|
+
ctx.runtime.restoreCheckpoint(checkpoint.id);
|
|
19094
|
+
return `Restored checkpoint ${checkpoint.name}`;
|
|
19095
|
+
}
|
|
19096
|
+
function unixNow() {
|
|
19097
|
+
return Math.floor(Date.now() / 1e3);
|
|
19098
|
+
}
|
|
19099
|
+
const logger$q = createLogger("VaultShared");
|
|
19100
|
+
const ALGORITHM = "aes-256-gcm";
|
|
19101
|
+
const IV_LENGTH = 12;
|
|
19102
|
+
const AUTH_TAG_LENGTH = 16;
|
|
19103
|
+
const KEY_STORAGE_PREFIX = "base64:";
|
|
19104
|
+
function encodeEncryptionKeyForStorage(key2) {
|
|
19105
|
+
return `${KEY_STORAGE_PREFIX}${key2.toString("base64")}`;
|
|
19106
|
+
}
|
|
19107
|
+
function decodeEncryptionKeyFromStorage(value) {
|
|
19108
|
+
if (value.startsWith(KEY_STORAGE_PREFIX)) {
|
|
19109
|
+
return Buffer.from(value.slice(KEY_STORAGE_PREFIX.length), "base64");
|
|
19110
|
+
}
|
|
19111
|
+
return Buffer.from(value, "utf-8");
|
|
19112
|
+
}
|
|
19113
|
+
function assertSecretStorageAvailable(customMessage) {
|
|
19114
|
+
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
19115
|
+
throw new Error(
|
|
19116
|
+
"Vault requires OS-backed secret storage (Keychain, DPAPI, or libsecret)."
|
|
19117
|
+
);
|
|
19118
|
+
}
|
|
19119
|
+
}
|
|
19120
|
+
function getOrCreateEncryptionKey(keyFilename) {
|
|
19121
|
+
assertSecretStorageAvailable();
|
|
19122
|
+
const keyPath = path.join(electron.app.getPath("userData"), keyFilename);
|
|
19123
|
+
if (fs$1.existsSync(keyPath)) {
|
|
19124
|
+
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
19125
|
+
const key22 = decodeEncryptionKeyFromStorage(
|
|
19126
|
+
electron.safeStorage.decryptString(encryptedKey)
|
|
19127
|
+
);
|
|
19128
|
+
if (key22.length !== 32) {
|
|
19129
|
+
throw new Error("Stored vault encryption key has an invalid length.");
|
|
19130
|
+
}
|
|
19131
|
+
return key22;
|
|
19132
|
+
}
|
|
19133
|
+
const key2 = crypto$1.randomBytes(32);
|
|
19134
|
+
fs$1.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
19135
|
+
const encrypted = electron.safeStorage.encryptString(encodeEncryptionKeyForStorage(key2));
|
|
19136
|
+
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
19137
|
+
fs$1.chmodSync(keyPath, 384);
|
|
19138
|
+
return key2;
|
|
19139
|
+
}
|
|
19140
|
+
function createEncryptDecrypt(keyFilename) {
|
|
19141
|
+
let cachedKey = null;
|
|
19142
|
+
function getKey() {
|
|
19143
|
+
if (!cachedKey) cachedKey = getOrCreateEncryptionKey(keyFilename);
|
|
19144
|
+
return cachedKey;
|
|
19145
|
+
}
|
|
19146
|
+
function encrypt2(plaintext) {
|
|
19147
|
+
const key2 = getKey();
|
|
19148
|
+
const iv = crypto$1.randomBytes(IV_LENGTH);
|
|
19149
|
+
const cipher = crypto$1.createCipheriv(ALGORITHM, key2, iv, {
|
|
19150
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
19151
|
+
});
|
|
19152
|
+
const encrypted = Buffer.concat([
|
|
19153
|
+
cipher.update(plaintext, "utf-8"),
|
|
19154
|
+
cipher.final()
|
|
19155
|
+
]);
|
|
19156
|
+
const authTag = cipher.getAuthTag();
|
|
19157
|
+
return Buffer.concat([iv, authTag, encrypted]);
|
|
19158
|
+
}
|
|
19159
|
+
function decrypt2(data) {
|
|
19160
|
+
const key2 = getKey();
|
|
19161
|
+
const iv = data.subarray(0, IV_LENGTH);
|
|
19162
|
+
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
19163
|
+
const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
19164
|
+
const decipher = crypto$1.createDecipheriv(ALGORITHM, key2, iv, {
|
|
19165
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
19166
|
+
});
|
|
19167
|
+
decipher.setAuthTag(authTag);
|
|
19168
|
+
return decipher.update(ciphertext, void 0, "utf-8") + decipher.final("utf-8");
|
|
19169
|
+
}
|
|
19170
|
+
function resetKey() {
|
|
19171
|
+
cachedKey = null;
|
|
19172
|
+
}
|
|
19173
|
+
return { encrypt: encrypt2, decrypt: decrypt2, resetKey };
|
|
19174
|
+
}
|
|
19175
|
+
function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
19176
|
+
let cachedEntries = null;
|
|
19177
|
+
function getVaultPath() {
|
|
19178
|
+
return path.join(electron.app.getPath("userData"), vaultFilename);
|
|
19179
|
+
}
|
|
19180
|
+
function loadVault2() {
|
|
19181
|
+
if (cachedEntries) return cachedEntries;
|
|
19182
|
+
const vaultPath = getVaultPath();
|
|
19183
|
+
if (!fs$1.existsSync(vaultPath)) {
|
|
19184
|
+
cachedEntries = [];
|
|
19185
|
+
return cachedEntries;
|
|
19186
|
+
}
|
|
19187
|
+
try {
|
|
19188
|
+
const raw = fs$1.readFileSync(vaultPath);
|
|
19189
|
+
const json = decrypt2(raw);
|
|
19190
|
+
cachedEntries = JSON.parse(json);
|
|
19191
|
+
return cachedEntries;
|
|
19192
|
+
} catch (err) {
|
|
19193
|
+
logger$q.error("Failed to load vault:", err);
|
|
19194
|
+
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
19195
|
+
}
|
|
19196
|
+
}
|
|
19197
|
+
function saveVault2(entries) {
|
|
19198
|
+
const json = JSON.stringify(entries, null, 2);
|
|
19199
|
+
const encrypted = encrypt2(json);
|
|
19200
|
+
const vaultPath = getVaultPath();
|
|
19201
|
+
fs$1.mkdirSync(path.dirname(vaultPath), { recursive: true });
|
|
19202
|
+
fs$1.writeFileSync(vaultPath, encrypted, { mode: 384 });
|
|
19203
|
+
fs$1.chmodSync(vaultPath, 384);
|
|
19204
|
+
cachedEntries = entries;
|
|
19205
|
+
}
|
|
19206
|
+
function resetCache() {
|
|
19207
|
+
cachedEntries = null;
|
|
19208
|
+
}
|
|
19209
|
+
return { loadVault: loadVault2, saveVault: saveVault2, resetCache };
|
|
19210
|
+
}
|
|
19211
|
+
function normalizeCredentialHost(value) {
|
|
19212
|
+
try {
|
|
19213
|
+
const parsed = new URL(value.includes("://") ? value : `https://${value}`);
|
|
19214
|
+
return parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
19215
|
+
} catch {
|
|
19216
|
+
const normalized = value.toLowerCase().trim().replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/.*$/, "");
|
|
19217
|
+
return normalized && !normalized.includes(" ") ? normalized : null;
|
|
19218
|
+
}
|
|
19219
|
+
}
|
|
19220
|
+
function domainMatches(pattern, hostname) {
|
|
19221
|
+
const isWildcard = pattern.trim().startsWith("*.");
|
|
19222
|
+
const p = normalizeCredentialHost(isWildcard ? pattern.slice(2) : pattern);
|
|
19223
|
+
const h = normalizeCredentialHost(hostname);
|
|
19224
|
+
if (!p || !h) return false;
|
|
19225
|
+
return isWildcard ? h.endsWith("." + p) : p === h;
|
|
19226
|
+
}
|
|
19227
|
+
function generateTotpCode(secret) {
|
|
19228
|
+
const epoch = unixNow();
|
|
19229
|
+
const counter = Math.floor(epoch / 30);
|
|
19230
|
+
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
19231
|
+
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
19232
|
+
let bits = "";
|
|
19233
|
+
for (const ch of cleanSecret) {
|
|
19234
|
+
const val = base32Chars.indexOf(ch);
|
|
19235
|
+
if (val === -1) continue;
|
|
19236
|
+
bits += val.toString(2).padStart(5, "0");
|
|
19237
|
+
}
|
|
19238
|
+
const keyBytes = Buffer.alloc(Math.floor(bits.length / 8));
|
|
19239
|
+
for (let i = 0; i < keyBytes.length; i++) {
|
|
19240
|
+
keyBytes[i] = parseInt(bits.slice(i * 8, i * 8 + 8), 2);
|
|
19241
|
+
}
|
|
19242
|
+
const counterBuf = Buffer.alloc(8);
|
|
19243
|
+
counterBuf.writeUInt32BE(Math.floor(counter / 4294967296), 0);
|
|
19244
|
+
counterBuf.writeUInt32BE(counter & 4294967295, 4);
|
|
19245
|
+
const hmac = crypto$1.createHmac("sha1", keyBytes).update(counterBuf).digest();
|
|
19246
|
+
const offset = hmac[hmac.length - 1] & 15;
|
|
19247
|
+
const code = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
|
|
19248
|
+
return (code % 1e6).toString().padStart(6, "0");
|
|
19249
|
+
}
|
|
19250
|
+
function createAuditLog(filename, maxEntries) {
|
|
19251
|
+
function getAuditPath2() {
|
|
19252
|
+
return path.join(electron.app.getPath("userData"), filename);
|
|
19081
19253
|
}
|
|
19082
|
-
|
|
19083
|
-
|
|
19254
|
+
function appendAudit(entry) {
|
|
19255
|
+
try {
|
|
19256
|
+
const auditPath = getAuditPath2();
|
|
19257
|
+
fs$1.mkdirSync(path.dirname(auditPath), { recursive: true });
|
|
19258
|
+
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n", {
|
|
19259
|
+
encoding: "utf-8",
|
|
19260
|
+
mode: 384
|
|
19261
|
+
});
|
|
19262
|
+
fs$1.chmodSync(auditPath, 384);
|
|
19263
|
+
try {
|
|
19264
|
+
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19265
|
+
if (lines.length > maxEntries) {
|
|
19266
|
+
const trimmed = lines.slice(-maxEntries);
|
|
19267
|
+
fs$1.writeFileSync(auditPath, trimmed.join("\n") + "\n", {
|
|
19268
|
+
encoding: "utf-8",
|
|
19269
|
+
mode: 384
|
|
19270
|
+
});
|
|
19271
|
+
fs$1.chmodSync(auditPath, 384);
|
|
19272
|
+
}
|
|
19273
|
+
} catch (err) {
|
|
19274
|
+
logger$q.warn("Failed to trim audit log:", err);
|
|
19275
|
+
}
|
|
19276
|
+
} catch (err) {
|
|
19277
|
+
logger$q.error("Failed to write audit log:", err);
|
|
19278
|
+
}
|
|
19279
|
+
}
|
|
19280
|
+
function readAuditLog2(limit = 100) {
|
|
19281
|
+
try {
|
|
19282
|
+
const auditPath = getAuditPath2();
|
|
19283
|
+
if (!fs$1.existsSync(auditPath)) return [];
|
|
19284
|
+
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19285
|
+
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
19286
|
+
} catch (err) {
|
|
19287
|
+
logger$q.error("Failed to read audit log:", err);
|
|
19288
|
+
return [];
|
|
19289
|
+
}
|
|
19290
|
+
}
|
|
19291
|
+
return { appendAudit, readAuditLog: readAuditLog2 };
|
|
19084
19292
|
}
|
|
19085
|
-
const logger$
|
|
19293
|
+
const logger$p = createLogger("Sessions");
|
|
19086
19294
|
const SESSION_VERSION = 1;
|
|
19295
|
+
const ENCRYPTED_SESSION_FORMAT = "vessel:named-session:v2";
|
|
19296
|
+
const SESSION_KEY_FILENAME = "vessel-named-sessions.key";
|
|
19297
|
+
const sessionCrypto = createEncryptDecrypt(SESSION_KEY_FILENAME);
|
|
19087
19298
|
function getSessionsDir() {
|
|
19088
19299
|
return path.join(electron.app.getPath("userData"), "named-sessions");
|
|
19089
19300
|
}
|
|
@@ -19109,17 +19320,40 @@ async function getSessionPath(name) {
|
|
|
19109
19320
|
const dir = await ensureSessionsDir();
|
|
19110
19321
|
return path.join(dir, sessionFileName(name));
|
|
19111
19322
|
}
|
|
19323
|
+
function isEncryptedSessionFile(value) {
|
|
19324
|
+
return !!value && typeof value === "object" && value.format === ENCRYPTED_SESSION_FORMAT && typeof value.payload === "string";
|
|
19325
|
+
}
|
|
19326
|
+
function encodeSessionFile(data) {
|
|
19327
|
+
const plaintext = JSON.stringify({ version: SESSION_VERSION, ...data });
|
|
19328
|
+
const encrypted = sessionCrypto.encrypt(plaintext);
|
|
19329
|
+
return JSON.stringify(
|
|
19330
|
+
{
|
|
19331
|
+
format: ENCRYPTED_SESSION_FORMAT,
|
|
19332
|
+
payload: encrypted.toString("base64")
|
|
19333
|
+
},
|
|
19334
|
+
null,
|
|
19335
|
+
2
|
|
19336
|
+
);
|
|
19337
|
+
}
|
|
19338
|
+
function decodeSessionFile(raw) {
|
|
19339
|
+
const parsed = JSON.parse(raw);
|
|
19340
|
+
if (isEncryptedSessionFile(parsed)) {
|
|
19341
|
+
const decrypted = sessionCrypto.decrypt(Buffer.from(parsed.payload, "base64"));
|
|
19342
|
+
return parseSessionData(JSON.parse(decrypted));
|
|
19343
|
+
}
|
|
19344
|
+
return parseSessionData(parsed);
|
|
19345
|
+
}
|
|
19112
19346
|
async function writeSessionFile(filePath2, data) {
|
|
19113
|
-
const payload =
|
|
19347
|
+
const payload = encodeSessionFile(data);
|
|
19114
19348
|
await writeFileAtomic(filePath2, payload, { mode: 384 });
|
|
19115
19349
|
}
|
|
19116
19350
|
async function readSessionFile(filePath2) {
|
|
19117
19351
|
const raw = await readIfExists(filePath2, "utf-8");
|
|
19118
19352
|
if (raw == null) return null;
|
|
19119
19353
|
try {
|
|
19120
|
-
return
|
|
19354
|
+
return decodeSessionFile(raw);
|
|
19121
19355
|
} catch (err) {
|
|
19122
|
-
logger$
|
|
19356
|
+
logger$p.warn(`Failed to read session file ${filePath2}:`, err);
|
|
19123
19357
|
return null;
|
|
19124
19358
|
}
|
|
19125
19359
|
}
|
|
@@ -19231,7 +19465,7 @@ async function captureLocalStorageForOrigin(tabManager, origin) {
|
|
|
19231
19465
|
);
|
|
19232
19466
|
}
|
|
19233
19467
|
} catch (err) {
|
|
19234
|
-
logger$
|
|
19468
|
+
logger$p.debug(`Failed to capture localStorage for origin ${origin}:`, err);
|
|
19235
19469
|
return {};
|
|
19236
19470
|
}
|
|
19237
19471
|
return {};
|
|
@@ -19325,7 +19559,7 @@ async function loadNamedSession(tabManager, name) {
|
|
|
19325
19559
|
try {
|
|
19326
19560
|
await electron.session.defaultSession.cookies.set(cookieSetDetails(cookie));
|
|
19327
19561
|
} catch (err) {
|
|
19328
|
-
logger$
|
|
19562
|
+
logger$p.debug(`Skipping cookie ${cookie.name} for ${cookie.domain}:`, err);
|
|
19329
19563
|
continue;
|
|
19330
19564
|
}
|
|
19331
19565
|
}
|
|
@@ -19394,7 +19628,7 @@ function handleFlowEnd(ctx) {
|
|
|
19394
19628
|
ctx.runtime.clearFlow();
|
|
19395
19629
|
return "Workflow ended.";
|
|
19396
19630
|
}
|
|
19397
|
-
const logger$
|
|
19631
|
+
const logger$o = createLogger("Screenshot");
|
|
19398
19632
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
19399
19633
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
19400
19634
|
async function captureScreenshot(wc) {
|
|
@@ -19416,7 +19650,7 @@ async function captureScreenshot(wc) {
|
|
|
19416
19650
|
}
|
|
19417
19651
|
}
|
|
19418
19652
|
} catch (err) {
|
|
19419
|
-
logger$
|
|
19653
|
+
logger$o.debug(
|
|
19420
19654
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
19421
19655
|
getErrorMessage(err)
|
|
19422
19656
|
);
|
|
@@ -20584,10 +20818,7 @@ function normalizeBookmarkMetadataUpdate(input) {
|
|
|
20584
20818
|
}
|
|
20585
20819
|
return normalized;
|
|
20586
20820
|
}
|
|
20587
|
-
|
|
20588
|
-
return Math.floor(Date.now() / 1e3);
|
|
20589
|
-
}
|
|
20590
|
-
const logger$o = createLogger("BookmarkManager");
|
|
20821
|
+
const logger$n = createLogger("BookmarkManager");
|
|
20591
20822
|
const UNSORTED_ID = "unsorted";
|
|
20592
20823
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
20593
20824
|
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
@@ -21160,7 +21391,7 @@ function importBookmarksFromJson(content) {
|
|
|
21160
21391
|
emit$2();
|
|
21161
21392
|
}
|
|
21162
21393
|
} catch (err) {
|
|
21163
|
-
logger$
|
|
21394
|
+
logger$n.warn("Failed to import bookmarks from JSON:", err);
|
|
21164
21395
|
errors++;
|
|
21165
21396
|
}
|
|
21166
21397
|
return { imported, skipped, errors };
|
|
@@ -21819,7 +22050,7 @@ const DANGEROUS_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
21819
22050
|
function isDangerousAction(name) {
|
|
21820
22051
|
return DANGEROUS_ACTIONS.has(name);
|
|
21821
22052
|
}
|
|
21822
|
-
const logger$
|
|
22053
|
+
const logger$m = createLogger("ResearchOrchestrator");
|
|
21823
22054
|
const MAX_THREADS = 5;
|
|
21824
22055
|
const MAX_TRACE_ARGS_CHARS = 1200;
|
|
21825
22056
|
const MAX_TRACE_RESULT_CHARS = 2e3;
|
|
@@ -22008,7 +22239,7 @@ class ResearchOrchestrator {
|
|
|
22008
22239
|
}
|
|
22009
22240
|
stopAndSynthesizeCurrentFindings() {
|
|
22010
22241
|
if (this.state.phase !== "executing") {
|
|
22011
|
-
logger$
|
|
22242
|
+
logger$m.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
|
|
22012
22243
|
return;
|
|
22013
22244
|
}
|
|
22014
22245
|
this.stopRequested = true;
|
|
@@ -22042,23 +22273,23 @@ class ResearchOrchestrator {
|
|
|
22042
22273
|
async startBrief(userQuery) {
|
|
22043
22274
|
const query = userQuery.trim();
|
|
22044
22275
|
if (!query) {
|
|
22045
|
-
logger$
|
|
22276
|
+
logger$m.warn("Ignoring empty Research Desk query");
|
|
22046
22277
|
return;
|
|
22047
22278
|
}
|
|
22048
22279
|
if (this.state.phase !== "idle") {
|
|
22049
|
-
logger$
|
|
22280
|
+
logger$m.warn("Research already in progress, ignoring startBrief");
|
|
22050
22281
|
return;
|
|
22051
22282
|
}
|
|
22052
22283
|
this.state = this.initialState();
|
|
22053
22284
|
this.state.originalQuery = query;
|
|
22054
22285
|
this.state.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22055
22286
|
this.setPhase("briefing");
|
|
22056
|
-
logger$
|
|
22287
|
+
logger$m.info(`Brief started for query: ${query.slice(0, 120)}`);
|
|
22057
22288
|
}
|
|
22058
22289
|
// ── phase: briefing → planning ─────────────────────────────────
|
|
22059
22290
|
confirmBrief() {
|
|
22060
22291
|
if (this.state.phase !== "briefing") {
|
|
22061
|
-
logger$
|
|
22292
|
+
logger$m.warn("Not in briefing phase, ignoring confirmBrief");
|
|
22062
22293
|
return;
|
|
22063
22294
|
}
|
|
22064
22295
|
this.setPhase("planning");
|
|
@@ -22066,7 +22297,7 @@ class ResearchOrchestrator {
|
|
|
22066
22297
|
// ── phase: planning → awaiting_approval ────────────────────────
|
|
22067
22298
|
setObjectives(objectives) {
|
|
22068
22299
|
if (this.state.phase !== "planning") {
|
|
22069
|
-
logger$
|
|
22300
|
+
logger$m.warn("Not in planning phase, ignoring setObjectives");
|
|
22070
22301
|
return;
|
|
22071
22302
|
}
|
|
22072
22303
|
const threads = objectives.threads.slice(0, MAX_THREADS).map(mergeBlockedSourceDomains);
|
|
@@ -22095,11 +22326,11 @@ class ResearchOrchestrator {
|
|
|
22095
22326
|
try {
|
|
22096
22327
|
const parsed = JSON.parse(json);
|
|
22097
22328
|
if (typeof parsed.researchQuestion !== "string" || !parsed.researchQuestion.trim()) {
|
|
22098
|
-
logger$
|
|
22329
|
+
logger$m.warn("Missing researchQuestion in objectives JSON");
|
|
22099
22330
|
return false;
|
|
22100
22331
|
}
|
|
22101
22332
|
if (!Array.isArray(parsed.threads) || parsed.threads.length === 0) {
|
|
22102
|
-
logger$
|
|
22333
|
+
logger$m.warn("Missing or empty threads array in objectives JSON");
|
|
22103
22334
|
return false;
|
|
22104
22335
|
}
|
|
22105
22336
|
const threads = parsed.threads.map((t, i) => {
|
|
@@ -22117,7 +22348,7 @@ class ResearchOrchestrator {
|
|
|
22117
22348
|
};
|
|
22118
22349
|
}).filter((thread) => thread.question && thread.searchQueries.length > 0).slice(0, MAX_THREADS);
|
|
22119
22350
|
if (threads.length === 0) {
|
|
22120
|
-
logger$
|
|
22351
|
+
logger$m.warn("Objectives JSON did not contain any valid research threads");
|
|
22121
22352
|
return false;
|
|
22122
22353
|
}
|
|
22123
22354
|
const objectives = {
|
|
@@ -22128,17 +22359,17 @@ class ResearchOrchestrator {
|
|
|
22128
22359
|
totalSourceBudget: threads.reduce((sum, t) => sum + t.sourceBudget, 0)
|
|
22129
22360
|
};
|
|
22130
22361
|
this.setObjectives(objectives);
|
|
22131
|
-
logger$
|
|
22362
|
+
logger$m.info(`Parsed ${objectives.threads.length} threads from objectives`);
|
|
22132
22363
|
return true;
|
|
22133
22364
|
} catch (err) {
|
|
22134
|
-
logger$
|
|
22365
|
+
logger$m.warn("Failed to parse objectives JSON", err);
|
|
22135
22366
|
return false;
|
|
22136
22367
|
}
|
|
22137
22368
|
}
|
|
22138
22369
|
// ── phase: awaiting_approval → executing ───────────────────────
|
|
22139
22370
|
approveObjectives(mode, includeTraces) {
|
|
22140
22371
|
if (this.state.phase !== "awaiting_approval") {
|
|
22141
|
-
logger$
|
|
22372
|
+
logger$m.warn("Not awaiting approval, ignoring approveObjectives");
|
|
22142
22373
|
return;
|
|
22143
22374
|
}
|
|
22144
22375
|
if (mode) this.state.supervisionMode = mode;
|
|
@@ -22173,7 +22404,7 @@ class ResearchOrchestrator {
|
|
|
22173
22404
|
this.state.threads.map((thread) => {
|
|
22174
22405
|
if (this.state.phase !== "executing") return null;
|
|
22175
22406
|
return this.runSubAgent(thread, tabMutex).catch((err) => {
|
|
22176
|
-
logger$
|
|
22407
|
+
logger$m.error(`Sub-agent "${thread.label}" failed`, err);
|
|
22177
22408
|
return {
|
|
22178
22409
|
threadLabel: thread.label,
|
|
22179
22410
|
threadQuestion: thread.question,
|
|
@@ -22202,7 +22433,7 @@ class ResearchOrchestrator {
|
|
|
22202
22433
|
try {
|
|
22203
22434
|
await this.synthesizeReport();
|
|
22204
22435
|
} catch (err) {
|
|
22205
|
-
logger$
|
|
22436
|
+
logger$m.error("Auto-synthesis failed", err);
|
|
22206
22437
|
this.state.error = `Synthesis failed: ${String(err)}`;
|
|
22207
22438
|
this.setPhase("delivered");
|
|
22208
22439
|
}
|
|
@@ -22313,7 +22544,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
22313
22544
|
try {
|
|
22314
22545
|
this.tabManager.closeTab(tabId);
|
|
22315
22546
|
} catch (err) {
|
|
22316
|
-
logger$
|
|
22547
|
+
logger$m.warn(`Failed to close sub-agent tab ${tabId}`, err);
|
|
22317
22548
|
}
|
|
22318
22549
|
}
|
|
22319
22550
|
}
|
|
@@ -22322,7 +22553,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
22322
22553
|
try {
|
|
22323
22554
|
claims = await this.extractClaimsFromTranscript(thread, transcript);
|
|
22324
22555
|
} catch (err) {
|
|
22325
|
-
logger$
|
|
22556
|
+
logger$m.warn(`Claim extraction failed for "${thread.label}"`, err);
|
|
22326
22557
|
}
|
|
22327
22558
|
}
|
|
22328
22559
|
if (this.state.phase === "executing" && this.state.includeTraces) {
|
|
@@ -22412,7 +22643,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
22412
22643
|
(claim) => claim.claim && claim.sourceUrl && claim.extractedQuote
|
|
22413
22644
|
);
|
|
22414
22645
|
} catch {
|
|
22415
|
-
logger$
|
|
22646
|
+
logger$m.warn(`Failed to parse claims JSON for "${thread.label}"`);
|
|
22416
22647
|
return [];
|
|
22417
22648
|
}
|
|
22418
22649
|
}
|
|
@@ -22497,7 +22728,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
22497
22728
|
objectives
|
|
22498
22729
|
};
|
|
22499
22730
|
} catch (err) {
|
|
22500
|
-
logger$
|
|
22731
|
+
logger$m.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
|
|
22501
22732
|
return buildFallbackReport(objectives, findings, String(err));
|
|
22502
22733
|
}
|
|
22503
22734
|
}
|
|
@@ -23072,7 +23303,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
|
23072
23303
|
});
|
|
23073
23304
|
}
|
|
23074
23305
|
}
|
|
23075
|
-
const logger$
|
|
23306
|
+
const logger$l = createLogger("PrivateWindow");
|
|
23076
23307
|
const privateWindows = /* @__PURE__ */ new Set();
|
|
23077
23308
|
function layoutPrivateViews(state2) {
|
|
23078
23309
|
const { window: win, chromeView, tabManager } = state2;
|
|
@@ -23302,7 +23533,7 @@ function createPrivateWindow() {
|
|
|
23302
23533
|
privateSession.clearStorageData(),
|
|
23303
23534
|
privateSession.clearCache()
|
|
23304
23535
|
]).catch((error) => {
|
|
23305
|
-
logger$
|
|
23536
|
+
logger$l.warn("Failed to clear private browsing session:", error);
|
|
23306
23537
|
});
|
|
23307
23538
|
});
|
|
23308
23539
|
privateWindows.add(state2);
|
|
@@ -23311,38 +23542,38 @@ function createPrivateWindow() {
|
|
|
23311
23542
|
tabManager?.createTab("about:blank");
|
|
23312
23543
|
if (state2) layoutPrivateViews(state2);
|
|
23313
23544
|
} catch (error) {
|
|
23314
|
-
logger$
|
|
23545
|
+
logger$l.error("Failed to initialize private browsing tab:", error);
|
|
23315
23546
|
try {
|
|
23316
23547
|
win?.close();
|
|
23317
23548
|
} catch (cleanupError) {
|
|
23318
|
-
logger$
|
|
23549
|
+
logger$l.warn("Failed to close private window after tab init failure:", cleanupError);
|
|
23319
23550
|
}
|
|
23320
23551
|
}
|
|
23321
23552
|
});
|
|
23322
23553
|
loadPrivateRenderer(chromeView);
|
|
23323
23554
|
win.show();
|
|
23324
|
-
logger$
|
|
23555
|
+
logger$l.info("Private browsing window opened");
|
|
23325
23556
|
return state2;
|
|
23326
23557
|
} catch (error) {
|
|
23327
|
-
logger$
|
|
23558
|
+
logger$l.error("Failed to create private browsing window:", error);
|
|
23328
23559
|
if (state2) {
|
|
23329
23560
|
privateWindows.delete(state2);
|
|
23330
23561
|
}
|
|
23331
23562
|
try {
|
|
23332
23563
|
tabManager?.destroyAllTabs();
|
|
23333
23564
|
} catch (cleanupError) {
|
|
23334
|
-
logger$
|
|
23565
|
+
logger$l.warn("Failed to clean up private tabs after launch failure:", cleanupError);
|
|
23335
23566
|
}
|
|
23336
23567
|
try {
|
|
23337
23568
|
win?.close();
|
|
23338
23569
|
} catch (cleanupError) {
|
|
23339
|
-
logger$
|
|
23570
|
+
logger$l.warn("Failed to close private window after launch failure:", cleanupError);
|
|
23340
23571
|
}
|
|
23341
23572
|
void Promise.all([
|
|
23342
23573
|
privateSession.clearStorageData(),
|
|
23343
23574
|
privateSession.clearCache()
|
|
23344
23575
|
]).catch((cleanupError) => {
|
|
23345
|
-
logger$
|
|
23576
|
+
logger$l.warn("Failed to clear failed private browsing session:", cleanupError);
|
|
23346
23577
|
});
|
|
23347
23578
|
throw error;
|
|
23348
23579
|
}
|
|
@@ -23559,7 +23790,11 @@ const window$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
|
|
|
23559
23790
|
const TabIdSchema$1 = zod.z.string().min(1);
|
|
23560
23791
|
const GroupIdSchema = zod.z.string().min(1);
|
|
23561
23792
|
const UrlSchema = zod.z.string().min(1);
|
|
23562
|
-
const
|
|
23793
|
+
const NavigationPostBodySchema = zod.z.record(zod.z.string(), zod.z.string()).optional();
|
|
23794
|
+
const ColorSchema = zod.z.custom(
|
|
23795
|
+
(color) => typeof color === "string" && TAB_GROUP_COLORS.includes(color),
|
|
23796
|
+
{ message: "Invalid tab group color" }
|
|
23797
|
+
);
|
|
23563
23798
|
const FindActionSchema = zod.z.enum(["clearSelection", "keepSelection", "activateSelection"]);
|
|
23564
23799
|
const FindTextSchema = zod.z.string().min(1).max(1e4);
|
|
23565
23800
|
const FindOptionsSchema = zod.z.object({
|
|
@@ -23605,7 +23840,12 @@ function registerTabHandlers(windowState, _sendToRendererViews) {
|
|
|
23605
23840
|
assertTrustedIpcSender(event);
|
|
23606
23841
|
const validatedId = parseIpc(TabIdSchema$1, id, "tabId");
|
|
23607
23842
|
const validatedUrl = parseIpc(UrlSchema, url, "url");
|
|
23608
|
-
|
|
23843
|
+
const validatedPostBody = parseIpc(
|
|
23844
|
+
NavigationPostBodySchema,
|
|
23845
|
+
postBody,
|
|
23846
|
+
"postBody"
|
|
23847
|
+
);
|
|
23848
|
+
return tabManager.navigateTab(validatedId, validatedUrl, validatedPostBody);
|
|
23609
23849
|
}
|
|
23610
23850
|
);
|
|
23611
23851
|
electron.ipcMain.handle(Channels.TAB_BACK, (event, id) => {
|
|
@@ -23741,7 +23981,7 @@ function registerTabHandlers(windowState, _sendToRendererViews) {
|
|
|
23741
23981
|
);
|
|
23742
23982
|
}
|
|
23743
23983
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
23744
|
-
const logger$
|
|
23984
|
+
const logger$k = createLogger("DevTrace");
|
|
23745
23985
|
let cachedFactory;
|
|
23746
23986
|
function createNoopTraceSession() {
|
|
23747
23987
|
return {
|
|
@@ -23774,7 +24014,7 @@ function loadLocalFactory() {
|
|
|
23774
24014
|
return cachedFactory;
|
|
23775
24015
|
}
|
|
23776
24016
|
} catch (err) {
|
|
23777
|
-
logger$
|
|
24017
|
+
logger$k.warn("Failed to load local trace logger:", err);
|
|
23778
24018
|
}
|
|
23779
24019
|
}
|
|
23780
24020
|
return cachedFactory;
|
|
@@ -24682,7 +24922,7 @@ function registerContentHandlers(windowState) {
|
|
|
24682
24922
|
return windowState.uiState.focusMode;
|
|
24683
24923
|
});
|
|
24684
24924
|
}
|
|
24685
|
-
const logger$
|
|
24925
|
+
const logger$j = createLogger("HighlightIPC");
|
|
24686
24926
|
const HighlightIndexSchema = zod.z.number().int().min(0);
|
|
24687
24927
|
function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
24688
24928
|
const { tabManager, chromeView } = windowState;
|
|
@@ -24692,7 +24932,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24692
24932
|
try {
|
|
24693
24933
|
return await getHighlightCount(info.wc) ?? 0;
|
|
24694
24934
|
} catch (err) {
|
|
24695
|
-
logger$
|
|
24935
|
+
logger$j.warn("Failed to get active highlight count:", err);
|
|
24696
24936
|
return 0;
|
|
24697
24937
|
}
|
|
24698
24938
|
};
|
|
@@ -24717,13 +24957,13 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24717
24957
|
const result = await captureSelectionHighlight(wc);
|
|
24718
24958
|
if (result.success && result.text) {
|
|
24719
24959
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
24720
|
-
(err) => logger$
|
|
24960
|
+
(err) => logger$j.warn("Failed to highlight captured selection:", err)
|
|
24721
24961
|
);
|
|
24722
24962
|
await emitHighlightCount();
|
|
24723
24963
|
}
|
|
24724
24964
|
return result;
|
|
24725
24965
|
} catch (err) {
|
|
24726
|
-
logger$
|
|
24966
|
+
logger$j.warn("Failed to capture highlight from active tab:", err);
|
|
24727
24967
|
return { success: false, message: "Could not capture selection" };
|
|
24728
24968
|
}
|
|
24729
24969
|
});
|
|
@@ -24741,7 +24981,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24741
24981
|
}
|
|
24742
24982
|
});
|
|
24743
24983
|
} catch (err) {
|
|
24744
|
-
logger$
|
|
24984
|
+
logger$j.warn("Failed to persist auto-highlight selection:", err);
|
|
24745
24985
|
}
|
|
24746
24986
|
});
|
|
24747
24987
|
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, (event) => {
|
|
@@ -24756,7 +24996,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24756
24996
|
try {
|
|
24757
24997
|
return scrollToHighlight(info.wc, validatedIndex);
|
|
24758
24998
|
} catch (err) {
|
|
24759
|
-
logger$
|
|
24999
|
+
logger$j.warn("Failed to scroll to highlight:", err);
|
|
24760
25000
|
return false;
|
|
24761
25001
|
}
|
|
24762
25002
|
});
|
|
@@ -24780,7 +25020,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24780
25020
|
}
|
|
24781
25021
|
return removed;
|
|
24782
25022
|
} catch (err) {
|
|
24783
|
-
logger$
|
|
25023
|
+
logger$j.warn("Failed to remove highlight at index:", err);
|
|
24784
25024
|
return false;
|
|
24785
25025
|
}
|
|
24786
25026
|
});
|
|
@@ -24797,7 +25037,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24797
25037
|
}
|
|
24798
25038
|
return cleared;
|
|
24799
25039
|
} catch (err) {
|
|
24800
|
-
logger$
|
|
25040
|
+
logger$j.warn("Failed to clear highlight elements:", err);
|
|
24801
25041
|
return false;
|
|
24802
25042
|
}
|
|
24803
25043
|
});
|
|
@@ -26160,7 +26400,7 @@ async function linkBookmarkToMemory({
|
|
|
26160
26400
|
}
|
|
26161
26401
|
});
|
|
26162
26402
|
}
|
|
26163
|
-
const logger$
|
|
26403
|
+
const logger$i = createLogger("MCP");
|
|
26164
26404
|
function asTextResponse(text) {
|
|
26165
26405
|
return { content: [{ type: "text", text }] };
|
|
26166
26406
|
}
|
|
@@ -26261,7 +26501,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
26261
26501
|
}
|
|
26262
26502
|
}
|
|
26263
26503
|
} catch (err) {
|
|
26264
|
-
logger$
|
|
26504
|
+
logger$i.warn("Failed to compute post-action state warning:", err);
|
|
26265
26505
|
}
|
|
26266
26506
|
return `${warning}
|
|
26267
26507
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -26366,7 +26606,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
26366
26606
|
}
|
|
26367
26607
|
})()
|
|
26368
26608
|
`).catch((err) => {
|
|
26369
|
-
logger$
|
|
26609
|
+
logger$i.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
26370
26610
|
return null;
|
|
26371
26611
|
});
|
|
26372
26612
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -27234,7 +27474,7 @@ function registerSessionTools(server, tabManager, runtime2) {
|
|
|
27234
27474
|
)
|
|
27235
27475
|
);
|
|
27236
27476
|
}
|
|
27237
|
-
const logger$
|
|
27477
|
+
const logger$h = createLogger("MCPContentTools");
|
|
27238
27478
|
const EXTRACT_MODES = [
|
|
27239
27479
|
"full",
|
|
27240
27480
|
"summary",
|
|
@@ -27659,7 +27899,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
27659
27899
|
try {
|
|
27660
27900
|
page = await extractContent(wc);
|
|
27661
27901
|
} catch (err) {
|
|
27662
|
-
logger$
|
|
27902
|
+
logger$h.warn("Failed to extract page while generating suggestions:", err);
|
|
27663
27903
|
return asTextResponse(
|
|
27664
27904
|
"Could not read page. Try navigate to a working URL."
|
|
27665
27905
|
);
|
|
@@ -28511,7 +28751,7 @@ function registerNavigationTools(server, tabManager, runtime2) {
|
|
|
28511
28751
|
}
|
|
28512
28752
|
);
|
|
28513
28753
|
}
|
|
28514
|
-
const logger$
|
|
28754
|
+
const logger$g = createLogger("MCPPrompts");
|
|
28515
28755
|
function registerPromptTools(server, tabManager, runtime2) {
|
|
28516
28756
|
server.registerPrompt(
|
|
28517
28757
|
"vessel-supervisor-brief",
|
|
@@ -28590,7 +28830,7 @@ function registerPromptTools(server, tabManager, runtime2) {
|
|
|
28590
28830
|
const page = await extractContent(wc);
|
|
28591
28831
|
pageType = detectPageType(page);
|
|
28592
28832
|
} catch (err) {
|
|
28593
|
-
logger$
|
|
28833
|
+
logger$g.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
28594
28834
|
}
|
|
28595
28835
|
}
|
|
28596
28836
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -28635,6 +28875,11 @@ function registerPromptTools(server, tabManager, runtime2) {
|
|
|
28635
28875
|
}
|
|
28636
28876
|
);
|
|
28637
28877
|
}
|
|
28878
|
+
const TAB_GROUP_COLOR_SET = new Set(TAB_GROUP_COLORS);
|
|
28879
|
+
const TabGroupColorSchema = zod.z.custom(
|
|
28880
|
+
(color) => typeof color === "string" && TAB_GROUP_COLOR_SET.has(color),
|
|
28881
|
+
{ message: "Invalid tab group color" }
|
|
28882
|
+
);
|
|
28638
28883
|
function registerGroupTools(server, tabManager, runtime2) {
|
|
28639
28884
|
server.registerTool(
|
|
28640
28885
|
"list_groups",
|
|
@@ -28663,7 +28908,7 @@ function registerGroupTools(server, tabManager, runtime2) {
|
|
|
28663
28908
|
inputSchema: {
|
|
28664
28909
|
tabId: zod.z.string().optional().describe("Tab ID to group (defaults to active tab)"),
|
|
28665
28910
|
name: zod.z.string().optional().describe("Optional group name"),
|
|
28666
|
-
color:
|
|
28911
|
+
color: TabGroupColorSchema.optional().describe("Optional group color")
|
|
28667
28912
|
}
|
|
28668
28913
|
},
|
|
28669
28914
|
async ({ tabId, name, color }) => withAction(runtime2, tabManager, "create_group", { tabId, name, color }, async () => {
|
|
@@ -28742,16 +28987,20 @@ function registerGroupTools(server, tabManager, runtime2) {
|
|
|
28742
28987
|
description: "Change the color of a tab group.",
|
|
28743
28988
|
inputSchema: {
|
|
28744
28989
|
groupId: zod.z.string().describe("Group ID"),
|
|
28745
|
-
color:
|
|
28990
|
+
color: TabGroupColorSchema.describe("New color")
|
|
28746
28991
|
}
|
|
28747
28992
|
},
|
|
28748
28993
|
async ({ groupId, color }) => withAction(runtime2, tabManager, "set_group_color", { groupId, color }, async () => {
|
|
28994
|
+
const groupExists = tabManager.getGroups().some((group) => group.id === groupId);
|
|
28995
|
+
if (!groupExists) {
|
|
28996
|
+
return "Error: Group not found";
|
|
28997
|
+
}
|
|
28749
28998
|
tabManager.setGroupColor(groupId, color);
|
|
28750
28999
|
return `Set group ${groupId} color to ${color}`;
|
|
28751
29000
|
})
|
|
28752
29001
|
);
|
|
28753
29002
|
}
|
|
28754
|
-
const logger$
|
|
29003
|
+
const logger$f = createLogger("MCPHighlightTools");
|
|
28755
29004
|
function registerHighlightTools(server, tabManager, runtime2) {
|
|
28756
29005
|
server.registerTool(
|
|
28757
29006
|
"highlight",
|
|
@@ -28968,7 +29217,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
28968
29217
|
void 0,
|
|
28969
29218
|
h.color
|
|
28970
29219
|
).catch(
|
|
28971
|
-
(err) => logger$
|
|
29220
|
+
(err) => logger$f.warn("Failed to restore highlight after removal:", err)
|
|
28972
29221
|
);
|
|
28973
29222
|
}
|
|
28974
29223
|
}
|
|
@@ -29487,7 +29736,7 @@ ${steps.join("\n")}`;
|
|
|
29487
29736
|
}
|
|
29488
29737
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
29489
29738
|
const MAX_ENTRIES = 1e3;
|
|
29490
|
-
const logger$
|
|
29739
|
+
const logger$e = createLogger("VaultAudit");
|
|
29491
29740
|
function getAuditPath() {
|
|
29492
29741
|
return path.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
29493
29742
|
}
|
|
@@ -29501,7 +29750,7 @@ function appendAuditEntry(entry) {
|
|
|
29501
29750
|
});
|
|
29502
29751
|
fs$1.chmodSync(auditPath, 384);
|
|
29503
29752
|
} catch (err) {
|
|
29504
|
-
logger$
|
|
29753
|
+
logger$e.error("Failed to write audit log:", err);
|
|
29505
29754
|
}
|
|
29506
29755
|
}
|
|
29507
29756
|
function readAuditLog$1(limit = 100) {
|
|
@@ -29511,7 +29760,7 @@ function readAuditLog$1(limit = 100) {
|
|
|
29511
29760
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
29512
29761
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
29513
29762
|
} catch (err) {
|
|
29514
|
-
logger$
|
|
29763
|
+
logger$e.error("Failed to read audit log:", err);
|
|
29515
29764
|
return [];
|
|
29516
29765
|
}
|
|
29517
29766
|
}
|
|
@@ -29580,200 +29829,6 @@ async function requestHumanVaultConsent(request) {
|
|
|
29580
29829
|
});
|
|
29581
29830
|
return { approved: response === 1 };
|
|
29582
29831
|
}
|
|
29583
|
-
const logger$e = createLogger("VaultShared");
|
|
29584
|
-
const ALGORITHM = "aes-256-gcm";
|
|
29585
|
-
const IV_LENGTH = 12;
|
|
29586
|
-
const AUTH_TAG_LENGTH = 16;
|
|
29587
|
-
const KEY_STORAGE_PREFIX = "base64:";
|
|
29588
|
-
function encodeEncryptionKeyForStorage(key2) {
|
|
29589
|
-
return `${KEY_STORAGE_PREFIX}${key2.toString("base64")}`;
|
|
29590
|
-
}
|
|
29591
|
-
function decodeEncryptionKeyFromStorage(value) {
|
|
29592
|
-
if (value.startsWith(KEY_STORAGE_PREFIX)) {
|
|
29593
|
-
return Buffer.from(value.slice(KEY_STORAGE_PREFIX.length), "base64");
|
|
29594
|
-
}
|
|
29595
|
-
return Buffer.from(value, "utf-8");
|
|
29596
|
-
}
|
|
29597
|
-
function assertSecretStorageAvailable(customMessage) {
|
|
29598
|
-
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
29599
|
-
throw new Error(
|
|
29600
|
-
"Vault requires OS-backed secret storage (Keychain, DPAPI, or libsecret)."
|
|
29601
|
-
);
|
|
29602
|
-
}
|
|
29603
|
-
}
|
|
29604
|
-
function getOrCreateEncryptionKey(keyFilename) {
|
|
29605
|
-
assertSecretStorageAvailable();
|
|
29606
|
-
const keyPath = path.join(electron.app.getPath("userData"), keyFilename);
|
|
29607
|
-
if (fs$1.existsSync(keyPath)) {
|
|
29608
|
-
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
29609
|
-
const key22 = decodeEncryptionKeyFromStorage(
|
|
29610
|
-
electron.safeStorage.decryptString(encryptedKey)
|
|
29611
|
-
);
|
|
29612
|
-
if (key22.length !== 32) {
|
|
29613
|
-
throw new Error("Stored vault encryption key has an invalid length.");
|
|
29614
|
-
}
|
|
29615
|
-
return key22;
|
|
29616
|
-
}
|
|
29617
|
-
const key2 = crypto$1.randomBytes(32);
|
|
29618
|
-
fs$1.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
29619
|
-
const encrypted = electron.safeStorage.encryptString(encodeEncryptionKeyForStorage(key2));
|
|
29620
|
-
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
29621
|
-
fs$1.chmodSync(keyPath, 384);
|
|
29622
|
-
return key2;
|
|
29623
|
-
}
|
|
29624
|
-
function createEncryptDecrypt(keyFilename) {
|
|
29625
|
-
let cachedKey = null;
|
|
29626
|
-
function getKey() {
|
|
29627
|
-
if (!cachedKey) cachedKey = getOrCreateEncryptionKey(keyFilename);
|
|
29628
|
-
return cachedKey;
|
|
29629
|
-
}
|
|
29630
|
-
function encrypt2(plaintext) {
|
|
29631
|
-
const key2 = getKey();
|
|
29632
|
-
const iv = crypto$1.randomBytes(IV_LENGTH);
|
|
29633
|
-
const cipher = crypto$1.createCipheriv(ALGORITHM, key2, iv, {
|
|
29634
|
-
authTagLength: AUTH_TAG_LENGTH
|
|
29635
|
-
});
|
|
29636
|
-
const encrypted = Buffer.concat([
|
|
29637
|
-
cipher.update(plaintext, "utf-8"),
|
|
29638
|
-
cipher.final()
|
|
29639
|
-
]);
|
|
29640
|
-
const authTag = cipher.getAuthTag();
|
|
29641
|
-
return Buffer.concat([iv, authTag, encrypted]);
|
|
29642
|
-
}
|
|
29643
|
-
function decrypt2(data) {
|
|
29644
|
-
const key2 = getKey();
|
|
29645
|
-
const iv = data.subarray(0, IV_LENGTH);
|
|
29646
|
-
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
29647
|
-
const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
29648
|
-
const decipher = crypto$1.createDecipheriv(ALGORITHM, key2, iv, {
|
|
29649
|
-
authTagLength: AUTH_TAG_LENGTH
|
|
29650
|
-
});
|
|
29651
|
-
decipher.setAuthTag(authTag);
|
|
29652
|
-
return decipher.update(ciphertext, void 0, "utf-8") + decipher.final("utf-8");
|
|
29653
|
-
}
|
|
29654
|
-
function resetKey() {
|
|
29655
|
-
cachedKey = null;
|
|
29656
|
-
}
|
|
29657
|
-
return { encrypt: encrypt2, decrypt: decrypt2, resetKey };
|
|
29658
|
-
}
|
|
29659
|
-
function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
29660
|
-
let cachedEntries = null;
|
|
29661
|
-
function getVaultPath() {
|
|
29662
|
-
return path.join(electron.app.getPath("userData"), vaultFilename);
|
|
29663
|
-
}
|
|
29664
|
-
function loadVault2() {
|
|
29665
|
-
if (cachedEntries) return cachedEntries;
|
|
29666
|
-
const vaultPath = getVaultPath();
|
|
29667
|
-
if (!fs$1.existsSync(vaultPath)) {
|
|
29668
|
-
cachedEntries = [];
|
|
29669
|
-
return cachedEntries;
|
|
29670
|
-
}
|
|
29671
|
-
try {
|
|
29672
|
-
const raw = fs$1.readFileSync(vaultPath);
|
|
29673
|
-
const json = decrypt2(raw);
|
|
29674
|
-
cachedEntries = JSON.parse(json);
|
|
29675
|
-
return cachedEntries;
|
|
29676
|
-
} catch (err) {
|
|
29677
|
-
logger$e.error("Failed to load vault:", err);
|
|
29678
|
-
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
29679
|
-
}
|
|
29680
|
-
}
|
|
29681
|
-
function saveVault2(entries) {
|
|
29682
|
-
const json = JSON.stringify(entries, null, 2);
|
|
29683
|
-
const encrypted = encrypt2(json);
|
|
29684
|
-
const vaultPath = getVaultPath();
|
|
29685
|
-
fs$1.mkdirSync(path.dirname(vaultPath), { recursive: true });
|
|
29686
|
-
fs$1.writeFileSync(vaultPath, encrypted, { mode: 384 });
|
|
29687
|
-
fs$1.chmodSync(vaultPath, 384);
|
|
29688
|
-
cachedEntries = entries;
|
|
29689
|
-
}
|
|
29690
|
-
function resetCache() {
|
|
29691
|
-
cachedEntries = null;
|
|
29692
|
-
}
|
|
29693
|
-
return { loadVault: loadVault2, saveVault: saveVault2, resetCache };
|
|
29694
|
-
}
|
|
29695
|
-
function normalizeCredentialHost(value) {
|
|
29696
|
-
try {
|
|
29697
|
-
const parsed = new URL(value.includes("://") ? value : `https://${value}`);
|
|
29698
|
-
return parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
29699
|
-
} catch {
|
|
29700
|
-
const normalized = value.toLowerCase().trim().replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/.*$/, "");
|
|
29701
|
-
return normalized && !normalized.includes(" ") ? normalized : null;
|
|
29702
|
-
}
|
|
29703
|
-
}
|
|
29704
|
-
function domainMatches(pattern, hostname) {
|
|
29705
|
-
const isWildcard = pattern.trim().startsWith("*.");
|
|
29706
|
-
const p = normalizeCredentialHost(isWildcard ? pattern.slice(2) : pattern);
|
|
29707
|
-
const h = normalizeCredentialHost(hostname);
|
|
29708
|
-
if (!p || !h) return false;
|
|
29709
|
-
return isWildcard ? h.endsWith("." + p) : p === h;
|
|
29710
|
-
}
|
|
29711
|
-
function generateTotpCode(secret) {
|
|
29712
|
-
const epoch = unixNow();
|
|
29713
|
-
const counter = Math.floor(epoch / 30);
|
|
29714
|
-
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
29715
|
-
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
29716
|
-
let bits = "";
|
|
29717
|
-
for (const ch of cleanSecret) {
|
|
29718
|
-
const val = base32Chars.indexOf(ch);
|
|
29719
|
-
if (val === -1) continue;
|
|
29720
|
-
bits += val.toString(2).padStart(5, "0");
|
|
29721
|
-
}
|
|
29722
|
-
const keyBytes = Buffer.alloc(Math.floor(bits.length / 8));
|
|
29723
|
-
for (let i = 0; i < keyBytes.length; i++) {
|
|
29724
|
-
keyBytes[i] = parseInt(bits.slice(i * 8, i * 8 + 8), 2);
|
|
29725
|
-
}
|
|
29726
|
-
const counterBuf = Buffer.alloc(8);
|
|
29727
|
-
counterBuf.writeUInt32BE(Math.floor(counter / 4294967296), 0);
|
|
29728
|
-
counterBuf.writeUInt32BE(counter & 4294967295, 4);
|
|
29729
|
-
const hmac = crypto$1.createHmac("sha1", keyBytes).update(counterBuf).digest();
|
|
29730
|
-
const offset = hmac[hmac.length - 1] & 15;
|
|
29731
|
-
const code = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
|
|
29732
|
-
return (code % 1e6).toString().padStart(6, "0");
|
|
29733
|
-
}
|
|
29734
|
-
function createAuditLog(filename, maxEntries) {
|
|
29735
|
-
function getAuditPath2() {
|
|
29736
|
-
return path.join(electron.app.getPath("userData"), filename);
|
|
29737
|
-
}
|
|
29738
|
-
function appendAudit(entry) {
|
|
29739
|
-
try {
|
|
29740
|
-
const auditPath = getAuditPath2();
|
|
29741
|
-
fs$1.mkdirSync(path.dirname(auditPath), { recursive: true });
|
|
29742
|
-
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n", {
|
|
29743
|
-
encoding: "utf-8",
|
|
29744
|
-
mode: 384
|
|
29745
|
-
});
|
|
29746
|
-
fs$1.chmodSync(auditPath, 384);
|
|
29747
|
-
try {
|
|
29748
|
-
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
29749
|
-
if (lines.length > maxEntries) {
|
|
29750
|
-
const trimmed = lines.slice(-maxEntries);
|
|
29751
|
-
fs$1.writeFileSync(auditPath, trimmed.join("\n") + "\n", {
|
|
29752
|
-
encoding: "utf-8",
|
|
29753
|
-
mode: 384
|
|
29754
|
-
});
|
|
29755
|
-
fs$1.chmodSync(auditPath, 384);
|
|
29756
|
-
}
|
|
29757
|
-
} catch (err) {
|
|
29758
|
-
logger$e.warn("Failed to trim audit log:", err);
|
|
29759
|
-
}
|
|
29760
|
-
} catch (err) {
|
|
29761
|
-
logger$e.error("Failed to write audit log:", err);
|
|
29762
|
-
}
|
|
29763
|
-
}
|
|
29764
|
-
function readAuditLog2(limit = 100) {
|
|
29765
|
-
try {
|
|
29766
|
-
const auditPath = getAuditPath2();
|
|
29767
|
-
if (!fs$1.existsSync(auditPath)) return [];
|
|
29768
|
-
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
29769
|
-
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
29770
|
-
} catch (err) {
|
|
29771
|
-
logger$e.error("Failed to read audit log:", err);
|
|
29772
|
-
return [];
|
|
29773
|
-
}
|
|
29774
|
-
}
|
|
29775
|
-
return { appendAudit, readAuditLog: readAuditLog2 };
|
|
29776
|
-
}
|
|
29777
29832
|
const VAULT_FILENAME$1 = "vessel-human-vault.enc";
|
|
29778
29833
|
const KEY_FILENAME$1 = "vessel-human-vault.key";
|
|
29779
29834
|
const AUDIT_MAX_ENTRIES = 2e3;
|
|
@@ -30710,9 +30765,10 @@ async function submitFeedback(payload) {
|
|
|
30710
30765
|
return errorResult(getErrorMessage(error, "Failed to send feedback."));
|
|
30711
30766
|
}
|
|
30712
30767
|
}
|
|
30713
|
-
const SettingsKeySchema = zod.z.
|
|
30714
|
-
|
|
30715
|
-
}
|
|
30768
|
+
const SettingsKeySchema = zod.z.custom(
|
|
30769
|
+
(key2) => typeof key2 === "string" && RENDERER_SETTABLE_KEYS.has(key2),
|
|
30770
|
+
{ message: "Unknown setting key" }
|
|
30771
|
+
);
|
|
30716
30772
|
function registerSettingsHandlers(tabManager, runtime2, sendToRendererViews, getResearchOrchestrator) {
|
|
30717
30773
|
const applySettingChange = async (key2, value) => {
|
|
30718
30774
|
const updatedSettings = setSetting(key2, value);
|
|
@@ -30758,8 +30814,7 @@ function registerSettingsHandlers(tabManager, runtime2, sendToRendererViews, get
|
|
|
30758
30814
|
});
|
|
30759
30815
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (event, key2, value) => {
|
|
30760
30816
|
assertTrustedIpcSender(event);
|
|
30761
|
-
const
|
|
30762
|
-
const settingsKey = validatedKey;
|
|
30817
|
+
const settingsKey = parseIpc(SettingsKeySchema, key2, "key");
|
|
30763
30818
|
const validatedValue = parseSettingValue(settingsKey, value);
|
|
30764
30819
|
return applySettingChange(settingsKey, validatedValue);
|
|
30765
30820
|
});
|