@quanta-intellect/vessel-browser 0.1.149 → 0.1.155
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 +475 -445
- package/out/preload/content-script.js +65 -65
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -8084,6 +8084,22 @@ function shouldBlockOffGoalDomainNavigation(goal, targetUrl) {
|
|
|
8084
8084
|
function hasRecentDuplicateToolCall(recentToolSignatures, signature) {
|
|
8085
8085
|
return recentToolSignatures.includes(signature);
|
|
8086
8086
|
}
|
|
8087
|
+
const DUPLICATE_TOOL_CALL_RETRYABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
8088
|
+
"read_page",
|
|
8089
|
+
"current_tab",
|
|
8090
|
+
"inspect_element",
|
|
8091
|
+
"screenshot",
|
|
8092
|
+
"go_back",
|
|
8093
|
+
"go_forward",
|
|
8094
|
+
"click"
|
|
8095
|
+
]);
|
|
8096
|
+
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.`;
|
|
8097
|
+
function shouldSuppressDuplicateToolCall(recentToolSignatures, toolName, signature) {
|
|
8098
|
+
return !DUPLICATE_TOOL_CALL_RETRYABLE_TOOLS.has(toolName) && hasRecentDuplicateToolCall(recentToolSignatures, signature);
|
|
8099
|
+
}
|
|
8100
|
+
function buildRepeatedToolCallError(toolName) {
|
|
8101
|
+
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.`;
|
|
8102
|
+
}
|
|
8087
8103
|
function isClickReadLoop(names) {
|
|
8088
8104
|
if (names.length < 6) return false;
|
|
8089
8105
|
const tail = names.slice(-6);
|
|
@@ -10089,17 +10105,9 @@ class OpenAICompatProvider {
|
|
|
10089
10105
|
compactCorrectionCount += 1;
|
|
10090
10106
|
continue;
|
|
10091
10107
|
}
|
|
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(
|
|
10108
|
+
if (this.agentToolProfile === "compact" && shouldSuppressDuplicateToolCall(
|
|
10102
10109
|
recentCompactToolSignatures,
|
|
10110
|
+
tc.name,
|
|
10103
10111
|
toolSignature
|
|
10104
10112
|
)) {
|
|
10105
10113
|
onChunk(`
|
|
@@ -10108,13 +10116,13 @@ class OpenAICompatProvider {
|
|
|
10108
10116
|
messages.push({
|
|
10109
10117
|
role: "tool",
|
|
10110
10118
|
tool_call_id: tc.id,
|
|
10111
|
-
content:
|
|
10119
|
+
content: buildRepeatedToolCallError(tc.name)
|
|
10112
10120
|
});
|
|
10113
10121
|
compactCorrectionCount += 1;
|
|
10114
10122
|
if (compactCorrectionCount >= 2) {
|
|
10115
10123
|
messages.push({
|
|
10116
10124
|
role: "user",
|
|
10117
|
-
content:
|
|
10125
|
+
content: REPEATED_TOOL_CALL_NUDGE
|
|
10118
10126
|
});
|
|
10119
10127
|
}
|
|
10120
10128
|
continue;
|
|
@@ -11228,21 +11236,17 @@ ${latestToolResultPreview || ""}`
|
|
|
11228
11236
|
correctionCount += 1;
|
|
11229
11237
|
continue;
|
|
11230
11238
|
}
|
|
11231
|
-
if (
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
"go_back",
|
|
11237
|
-
"go_forward",
|
|
11238
|
-
"click"
|
|
11239
|
-
].includes(prepared.prepared.name) && hasRecentDuplicateToolCall(recentToolSignatures, toolSignature)) {
|
|
11239
|
+
if (shouldSuppressDuplicateToolCall(
|
|
11240
|
+
recentToolSignatures,
|
|
11241
|
+
prepared.prepared.name,
|
|
11242
|
+
toolSignature
|
|
11243
|
+
)) {
|
|
11240
11244
|
onChunk(`
|
|
11241
11245
|
<<tool:${prepared.prepared.name}:↻ duplicate suppressed>>
|
|
11242
11246
|
`);
|
|
11243
11247
|
const output2 = createCodexToolOutput(
|
|
11244
11248
|
prepared.prepared.callId,
|
|
11245
|
-
|
|
11249
|
+
buildRepeatedToolCallError(prepared.prepared.name)
|
|
11246
11250
|
);
|
|
11247
11251
|
currentInput.push(output2);
|
|
11248
11252
|
latestToolResultPreview = previewToolResult(output2.output);
|
|
@@ -12977,6 +12981,166 @@ function getUrlPathSegments(value) {
|
|
|
12977
12981
|
return value.split("?")[0].split("#")[0].split("/").filter(Boolean);
|
|
12978
12982
|
}
|
|
12979
12983
|
}
|
|
12984
|
+
const SITE_RESULT_FILTERS = [
|
|
12985
|
+
{
|
|
12986
|
+
hostname: "news.ycombinator.com",
|
|
12987
|
+
listingPaths: [
|
|
12988
|
+
"/",
|
|
12989
|
+
"/news",
|
|
12990
|
+
"/newest",
|
|
12991
|
+
"/front",
|
|
12992
|
+
"/ask",
|
|
12993
|
+
"/show",
|
|
12994
|
+
"/jobs",
|
|
12995
|
+
"/best",
|
|
12996
|
+
"/active",
|
|
12997
|
+
"/classic",
|
|
12998
|
+
"/noobstories"
|
|
12999
|
+
],
|
|
13000
|
+
utilityPathnames: ["/hide", "/user"],
|
|
13001
|
+
utilityTextPatterns: [
|
|
13002
|
+
/^(hide|past|favorite|unfavorite|flag|unflag|discuss|reply|parent|more)$/,
|
|
13003
|
+
/^\d+\s+(?:comments?|points?)$/
|
|
13004
|
+
]
|
|
13005
|
+
}
|
|
13006
|
+
];
|
|
13007
|
+
function matchesSiteFilter(url, filter, baseHostname) {
|
|
13008
|
+
try {
|
|
13009
|
+
const parsed = new URL(url, baseHostname ? `https://${baseHostname}` : void 0);
|
|
13010
|
+
return parsed.hostname === filter.hostname;
|
|
13011
|
+
} catch {
|
|
13012
|
+
return false;
|
|
13013
|
+
}
|
|
13014
|
+
}
|
|
13015
|
+
function isSiteListingPage(url) {
|
|
13016
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13017
|
+
if (!matchesSiteFilter(url, filter, "")) continue;
|
|
13018
|
+
try {
|
|
13019
|
+
const pathname = new URL(url).pathname.replace(/\/+$/, "") || "/";
|
|
13020
|
+
if (filter.listingPaths?.includes(pathname)) return true;
|
|
13021
|
+
} catch {
|
|
13022
|
+
}
|
|
13023
|
+
}
|
|
13024
|
+
return false;
|
|
13025
|
+
}
|
|
13026
|
+
function isSiteUtilityLink(element) {
|
|
13027
|
+
if (!element.href) return false;
|
|
13028
|
+
for (const filter of SITE_RESULT_FILTERS) {
|
|
13029
|
+
if (!matchesSiteFilter(element.href, filter, "")) continue;
|
|
13030
|
+
const text = normalizeComparable(element.text || "");
|
|
13031
|
+
for (const pattern of filter.utilityTextPatterns ?? []) {
|
|
13032
|
+
if (pattern.test(text)) return true;
|
|
13033
|
+
}
|
|
13034
|
+
try {
|
|
13035
|
+
const pathname = new URL(element.href).pathname.replace(/\/+$/, "") || "/";
|
|
13036
|
+
if (filter.utilityPathnames?.includes(pathname)) return true;
|
|
13037
|
+
} catch {
|
|
13038
|
+
}
|
|
13039
|
+
}
|
|
13040
|
+
return false;
|
|
13041
|
+
}
|
|
13042
|
+
function isSearchOrListingPage(page) {
|
|
13043
|
+
if (isSiteListingPage(page.url)) return true;
|
|
13044
|
+
const haystack = normalizeComparable(
|
|
13045
|
+
[page.url, page.title, page.excerpt, page.headings.map((heading) => heading.text).join(" ")].filter(Boolean).join(" ")
|
|
13046
|
+
);
|
|
13047
|
+
return /\b(search|results|find|discover|browse|repositories|repository|issues|pull requests|prs|users|events|listings)\b/.test(
|
|
13048
|
+
haystack
|
|
13049
|
+
);
|
|
13050
|
+
}
|
|
13051
|
+
function collectJsonLdEntityItems(input, results = []) {
|
|
13052
|
+
if (!input) return results;
|
|
13053
|
+
if (Array.isArray(input)) {
|
|
13054
|
+
input.forEach((item2) => collectJsonLdEntityItems(item2, results));
|
|
13055
|
+
return results;
|
|
13056
|
+
}
|
|
13057
|
+
if (typeof input !== "object") return results;
|
|
13058
|
+
const item = input;
|
|
13059
|
+
const type = item["@type"];
|
|
13060
|
+
const types = Array.isArray(type) ? type : [type];
|
|
13061
|
+
const typeNames = types.filter((entry) => typeof entry === "string");
|
|
13062
|
+
if ((typeof item.name === "string" || typeof item.url === "string") && !typeNames.some(
|
|
13063
|
+
(entry) => ["BreadcrumbList", "Organization", "WebSite", "WebPage"].includes(entry)
|
|
13064
|
+
)) {
|
|
13065
|
+
results.push(item);
|
|
13066
|
+
}
|
|
13067
|
+
collectJsonLdEntityItems(item["@graph"], results);
|
|
13068
|
+
collectJsonLdEntityItems(item.mainEntity, results);
|
|
13069
|
+
collectJsonLdEntityItems(item.itemListElement, results);
|
|
13070
|
+
collectJsonLdEntityItems(item.item, results);
|
|
13071
|
+
return results;
|
|
13072
|
+
}
|
|
13073
|
+
function getResultCandidates(page) {
|
|
13074
|
+
const entityItems = collectJsonLdEntityItems(page.jsonLd ?? []);
|
|
13075
|
+
const entityNames = new Set(
|
|
13076
|
+
entityItems.map((item) => typeof item.name === "string" ? normalizeComparable(item.name) : "").filter(Boolean)
|
|
13077
|
+
);
|
|
13078
|
+
const entityUrls = new Set(
|
|
13079
|
+
entityItems.map((item) => typeof item.url === "string" ? normalizeUrlForMatch(item.url) : null).filter((value) => Boolean(value))
|
|
13080
|
+
);
|
|
13081
|
+
const pageHost = normalizeUrlForMatch(page.url);
|
|
13082
|
+
const searchOrListingPage = isSearchOrListingPage(page);
|
|
13083
|
+
const scored = page.interactiveElements.filter(
|
|
13084
|
+
(element) => element.type === "link" && element.text?.trim() && element.href && !isSiteUtilityLink(element)
|
|
13085
|
+
).map((element) => {
|
|
13086
|
+
const text = element.text?.trim() || "";
|
|
13087
|
+
const comparableText = normalizeComparable(text);
|
|
13088
|
+
const href = normalizeUrlForMatch(element.href);
|
|
13089
|
+
const haystack = normalizeComparable(
|
|
13090
|
+
[element.text, element.description, element.selector, element.href].filter(Boolean).join(" ")
|
|
13091
|
+
);
|
|
13092
|
+
let score = 0;
|
|
13093
|
+
if (entityNames.has(comparableText)) score += 6;
|
|
13094
|
+
if (href && entityUrls.has(href)) score += 6;
|
|
13095
|
+
if (entityItems.some((item) => {
|
|
13096
|
+
const name = typeof item.name === "string" ? normalizeComparable(item.name) : "";
|
|
13097
|
+
return Boolean(name) && (name.includes(comparableText) || comparableText.includes(name));
|
|
13098
|
+
})) {
|
|
13099
|
+
score += 4;
|
|
13100
|
+
}
|
|
13101
|
+
if (element.context === "article") score += 3;
|
|
13102
|
+
else if (element.context === "main" || element.context === "content") score += 1;
|
|
13103
|
+
if (href && pageHost) {
|
|
13104
|
+
try {
|
|
13105
|
+
if (new URL(href).origin === new URL(pageHost).origin) score += 1;
|
|
13106
|
+
} catch {
|
|
13107
|
+
}
|
|
13108
|
+
}
|
|
13109
|
+
const hrefSegments = getUrlPathSegments(element.href);
|
|
13110
|
+
if (hrefSegments.length >= 2) score += 1;
|
|
13111
|
+
if (text.includes("/")) score += 1;
|
|
13112
|
+
if (searchOrListingPage && (element.context === "article" || element.context === "main" || element.context === "content")) {
|
|
13113
|
+
score += 2;
|
|
13114
|
+
}
|
|
13115
|
+
if (/\b(card|tile|result|rating|review)\b/.test(haystack)) score += 1;
|
|
13116
|
+
if (/\b(item|list|row|repo|repository|issue|pull request|event)\b/.test(haystack)) {
|
|
13117
|
+
score += 1;
|
|
13118
|
+
}
|
|
13119
|
+
if (text.length >= 12 && text.split(/\s+/).length >= 2) score += 1;
|
|
13120
|
+
if (element.context === "nav" || element.context === "header" || element.context === "footer" || element.context === "sidebar" || element.context === "dialog") {
|
|
13121
|
+
score -= 5;
|
|
13122
|
+
}
|
|
13123
|
+
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(
|
|
13124
|
+
comparableText
|
|
13125
|
+
)) {
|
|
13126
|
+
score -= 4;
|
|
13127
|
+
}
|
|
13128
|
+
return { element, score };
|
|
13129
|
+
}).filter(({ score, element }) => {
|
|
13130
|
+
if (entityItems.length > 0) return score >= 4;
|
|
13131
|
+
if (searchOrListingPage) {
|
|
13132
|
+
return score >= 4 || score >= 3 && (element.context === "article" || element.context === "main" || element.context === "content");
|
|
13133
|
+
}
|
|
13134
|
+
return score >= 4 || score >= 3 && element.context === "article";
|
|
13135
|
+
}).sort((a, b) => b.score - a.score || (a.element.index ?? 0) - (b.element.index ?? 0));
|
|
13136
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13137
|
+
return scored.map(({ element }) => element).filter((element) => {
|
|
13138
|
+
const key2 = `${normalizeComparable(element.text || "")}|${normalizeUrlForMatch(element.href) || ""}`;
|
|
13139
|
+
if (seen.has(key2)) return false;
|
|
13140
|
+
seen.add(key2);
|
|
13141
|
+
return true;
|
|
13142
|
+
});
|
|
13143
|
+
}
|
|
12980
13144
|
const MAX_STRUCTURED_ITEMS = 100;
|
|
12981
13145
|
const LARGE_PAGE_HINT_THRESHOLD = 12e3;
|
|
12982
13146
|
function truncateContent(content) {
|
|
@@ -13729,166 +13893,6 @@ const PAGE_TYPE_READ_MODE = {
|
|
|
13729
13893
|
ARTICLE: "summary",
|
|
13730
13894
|
GENERAL: "visible_only"
|
|
13731
13895
|
};
|
|
13732
|
-
const SITE_RESULT_FILTERS = [
|
|
13733
|
-
{
|
|
13734
|
-
hostname: "news.ycombinator.com",
|
|
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
|
-
}
|
|
13762
|
-
}
|
|
13763
|
-
function isSiteListingPage(url) {
|
|
13764
|
-
for (const filter of SITE_RESULT_FILTERS) {
|
|
13765
|
-
if (!matchesSiteFilter(url, filter, "")) continue;
|
|
13766
|
-
try {
|
|
13767
|
-
const pathname = new URL(url).pathname.replace(/\/+$/, "") || "/";
|
|
13768
|
-
if (filter.listingPaths?.includes(pathname)) return true;
|
|
13769
|
-
} catch {
|
|
13770
|
-
}
|
|
13771
|
-
}
|
|
13772
|
-
return false;
|
|
13773
|
-
}
|
|
13774
|
-
function isSiteUtilityLink(element) {
|
|
13775
|
-
if (!element.href) return false;
|
|
13776
|
-
for (const filter of SITE_RESULT_FILTERS) {
|
|
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
13896
|
function buildScopedContext(page, mode) {
|
|
13893
13897
|
const render = SCOPED_CONTEXT_RENDERERS.get(mode) ?? buildStructuredContext;
|
|
13894
13898
|
return render(page);
|
|
@@ -19074,16 +19078,216 @@ function handleCreateCheckpoint(ctx, args) {
|
|
|
19074
19078
|
const checkpoint = ctx.runtime.createCheckpoint(args.name, args.note);
|
|
19075
19079
|
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
19076
19080
|
}
|
|
19077
|
-
function handleRestoreCheckpoint(ctx, args) {
|
|
19078
|
-
const checkpoint = findCheckpoint(ctx.runtime.getState().checkpoints, args);
|
|
19079
|
-
if (!checkpoint) {
|
|
19080
|
-
return "Error: No matching checkpoint found";
|
|
19081
|
+
function handleRestoreCheckpoint(ctx, args) {
|
|
19082
|
+
const checkpoint = findCheckpoint(ctx.runtime.getState().checkpoints, args);
|
|
19083
|
+
if (!checkpoint) {
|
|
19084
|
+
return "Error: No matching checkpoint found";
|
|
19085
|
+
}
|
|
19086
|
+
ctx.runtime.restoreCheckpoint(checkpoint.id);
|
|
19087
|
+
return `Restored checkpoint ${checkpoint.name}`;
|
|
19088
|
+
}
|
|
19089
|
+
function unixNow() {
|
|
19090
|
+
return Math.floor(Date.now() / 1e3);
|
|
19091
|
+
}
|
|
19092
|
+
const logger$q = createLogger("VaultShared");
|
|
19093
|
+
const ALGORITHM = "aes-256-gcm";
|
|
19094
|
+
const IV_LENGTH = 12;
|
|
19095
|
+
const AUTH_TAG_LENGTH = 16;
|
|
19096
|
+
const KEY_STORAGE_PREFIX = "base64:";
|
|
19097
|
+
function encodeEncryptionKeyForStorage(key2) {
|
|
19098
|
+
return `${KEY_STORAGE_PREFIX}${key2.toString("base64")}`;
|
|
19099
|
+
}
|
|
19100
|
+
function decodeEncryptionKeyFromStorage(value) {
|
|
19101
|
+
if (value.startsWith(KEY_STORAGE_PREFIX)) {
|
|
19102
|
+
return Buffer.from(value.slice(KEY_STORAGE_PREFIX.length), "base64");
|
|
19103
|
+
}
|
|
19104
|
+
return Buffer.from(value, "utf-8");
|
|
19105
|
+
}
|
|
19106
|
+
function assertSecretStorageAvailable(customMessage) {
|
|
19107
|
+
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
19108
|
+
throw new Error(
|
|
19109
|
+
"Vault requires OS-backed secret storage (Keychain, DPAPI, or libsecret)."
|
|
19110
|
+
);
|
|
19111
|
+
}
|
|
19112
|
+
}
|
|
19113
|
+
function getOrCreateEncryptionKey(keyFilename) {
|
|
19114
|
+
assertSecretStorageAvailable();
|
|
19115
|
+
const keyPath = path.join(electron.app.getPath("userData"), keyFilename);
|
|
19116
|
+
if (fs$1.existsSync(keyPath)) {
|
|
19117
|
+
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
19118
|
+
const key22 = decodeEncryptionKeyFromStorage(
|
|
19119
|
+
electron.safeStorage.decryptString(encryptedKey)
|
|
19120
|
+
);
|
|
19121
|
+
if (key22.length !== 32) {
|
|
19122
|
+
throw new Error("Stored vault encryption key has an invalid length.");
|
|
19123
|
+
}
|
|
19124
|
+
return key22;
|
|
19125
|
+
}
|
|
19126
|
+
const key2 = crypto$1.randomBytes(32);
|
|
19127
|
+
fs$1.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
19128
|
+
const encrypted = electron.safeStorage.encryptString(encodeEncryptionKeyForStorage(key2));
|
|
19129
|
+
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
19130
|
+
fs$1.chmodSync(keyPath, 384);
|
|
19131
|
+
return key2;
|
|
19132
|
+
}
|
|
19133
|
+
function createEncryptDecrypt(keyFilename) {
|
|
19134
|
+
let cachedKey = null;
|
|
19135
|
+
function getKey() {
|
|
19136
|
+
if (!cachedKey) cachedKey = getOrCreateEncryptionKey(keyFilename);
|
|
19137
|
+
return cachedKey;
|
|
19138
|
+
}
|
|
19139
|
+
function encrypt2(plaintext) {
|
|
19140
|
+
const key2 = getKey();
|
|
19141
|
+
const iv = crypto$1.randomBytes(IV_LENGTH);
|
|
19142
|
+
const cipher = crypto$1.createCipheriv(ALGORITHM, key2, iv, {
|
|
19143
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
19144
|
+
});
|
|
19145
|
+
const encrypted = Buffer.concat([
|
|
19146
|
+
cipher.update(plaintext, "utf-8"),
|
|
19147
|
+
cipher.final()
|
|
19148
|
+
]);
|
|
19149
|
+
const authTag = cipher.getAuthTag();
|
|
19150
|
+
return Buffer.concat([iv, authTag, encrypted]);
|
|
19151
|
+
}
|
|
19152
|
+
function decrypt2(data) {
|
|
19153
|
+
const key2 = getKey();
|
|
19154
|
+
const iv = data.subarray(0, IV_LENGTH);
|
|
19155
|
+
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
19156
|
+
const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
19157
|
+
const decipher = crypto$1.createDecipheriv(ALGORITHM, key2, iv, {
|
|
19158
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
19159
|
+
});
|
|
19160
|
+
decipher.setAuthTag(authTag);
|
|
19161
|
+
return decipher.update(ciphertext, void 0, "utf-8") + decipher.final("utf-8");
|
|
19162
|
+
}
|
|
19163
|
+
function resetKey() {
|
|
19164
|
+
cachedKey = null;
|
|
19165
|
+
}
|
|
19166
|
+
return { encrypt: encrypt2, decrypt: decrypt2, resetKey };
|
|
19167
|
+
}
|
|
19168
|
+
function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
19169
|
+
let cachedEntries = null;
|
|
19170
|
+
function getVaultPath() {
|
|
19171
|
+
return path.join(electron.app.getPath("userData"), vaultFilename);
|
|
19172
|
+
}
|
|
19173
|
+
function loadVault2() {
|
|
19174
|
+
if (cachedEntries) return cachedEntries;
|
|
19175
|
+
const vaultPath = getVaultPath();
|
|
19176
|
+
if (!fs$1.existsSync(vaultPath)) {
|
|
19177
|
+
cachedEntries = [];
|
|
19178
|
+
return cachedEntries;
|
|
19179
|
+
}
|
|
19180
|
+
try {
|
|
19181
|
+
const raw = fs$1.readFileSync(vaultPath);
|
|
19182
|
+
const json = decrypt2(raw);
|
|
19183
|
+
cachedEntries = JSON.parse(json);
|
|
19184
|
+
return cachedEntries;
|
|
19185
|
+
} catch (err) {
|
|
19186
|
+
logger$q.error("Failed to load vault:", err);
|
|
19187
|
+
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
19188
|
+
}
|
|
19189
|
+
}
|
|
19190
|
+
function saveVault2(entries) {
|
|
19191
|
+
const json = JSON.stringify(entries, null, 2);
|
|
19192
|
+
const encrypted = encrypt2(json);
|
|
19193
|
+
const vaultPath = getVaultPath();
|
|
19194
|
+
fs$1.mkdirSync(path.dirname(vaultPath), { recursive: true });
|
|
19195
|
+
fs$1.writeFileSync(vaultPath, encrypted, { mode: 384 });
|
|
19196
|
+
fs$1.chmodSync(vaultPath, 384);
|
|
19197
|
+
cachedEntries = entries;
|
|
19198
|
+
}
|
|
19199
|
+
function resetCache() {
|
|
19200
|
+
cachedEntries = null;
|
|
19201
|
+
}
|
|
19202
|
+
return { loadVault: loadVault2, saveVault: saveVault2, resetCache };
|
|
19203
|
+
}
|
|
19204
|
+
function normalizeCredentialHost(value) {
|
|
19205
|
+
try {
|
|
19206
|
+
const parsed = new URL(value.includes("://") ? value : `https://${value}`);
|
|
19207
|
+
return parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
19208
|
+
} catch {
|
|
19209
|
+
const normalized = value.toLowerCase().trim().replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/.*$/, "");
|
|
19210
|
+
return normalized && !normalized.includes(" ") ? normalized : null;
|
|
19211
|
+
}
|
|
19212
|
+
}
|
|
19213
|
+
function domainMatches(pattern, hostname) {
|
|
19214
|
+
const isWildcard = pattern.trim().startsWith("*.");
|
|
19215
|
+
const p = normalizeCredentialHost(isWildcard ? pattern.slice(2) : pattern);
|
|
19216
|
+
const h = normalizeCredentialHost(hostname);
|
|
19217
|
+
if (!p || !h) return false;
|
|
19218
|
+
return isWildcard ? h.endsWith("." + p) : p === h;
|
|
19219
|
+
}
|
|
19220
|
+
function generateTotpCode(secret) {
|
|
19221
|
+
const epoch = unixNow();
|
|
19222
|
+
const counter = Math.floor(epoch / 30);
|
|
19223
|
+
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
19224
|
+
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
19225
|
+
let bits = "";
|
|
19226
|
+
for (const ch of cleanSecret) {
|
|
19227
|
+
const val = base32Chars.indexOf(ch);
|
|
19228
|
+
if (val === -1) continue;
|
|
19229
|
+
bits += val.toString(2).padStart(5, "0");
|
|
19230
|
+
}
|
|
19231
|
+
const keyBytes = Buffer.alloc(Math.floor(bits.length / 8));
|
|
19232
|
+
for (let i = 0; i < keyBytes.length; i++) {
|
|
19233
|
+
keyBytes[i] = parseInt(bits.slice(i * 8, i * 8 + 8), 2);
|
|
19234
|
+
}
|
|
19235
|
+
const counterBuf = Buffer.alloc(8);
|
|
19236
|
+
counterBuf.writeUInt32BE(Math.floor(counter / 4294967296), 0);
|
|
19237
|
+
counterBuf.writeUInt32BE(counter & 4294967295, 4);
|
|
19238
|
+
const hmac = crypto$1.createHmac("sha1", keyBytes).update(counterBuf).digest();
|
|
19239
|
+
const offset = hmac[hmac.length - 1] & 15;
|
|
19240
|
+
const code = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
|
|
19241
|
+
return (code % 1e6).toString().padStart(6, "0");
|
|
19242
|
+
}
|
|
19243
|
+
function createAuditLog(filename, maxEntries) {
|
|
19244
|
+
function getAuditPath2() {
|
|
19245
|
+
return path.join(electron.app.getPath("userData"), filename);
|
|
19081
19246
|
}
|
|
19082
|
-
|
|
19083
|
-
|
|
19247
|
+
function appendAudit(entry) {
|
|
19248
|
+
try {
|
|
19249
|
+
const auditPath = getAuditPath2();
|
|
19250
|
+
fs$1.mkdirSync(path.dirname(auditPath), { recursive: true });
|
|
19251
|
+
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n", {
|
|
19252
|
+
encoding: "utf-8",
|
|
19253
|
+
mode: 384
|
|
19254
|
+
});
|
|
19255
|
+
fs$1.chmodSync(auditPath, 384);
|
|
19256
|
+
try {
|
|
19257
|
+
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19258
|
+
if (lines.length > maxEntries) {
|
|
19259
|
+
const trimmed = lines.slice(-maxEntries);
|
|
19260
|
+
fs$1.writeFileSync(auditPath, trimmed.join("\n") + "\n", {
|
|
19261
|
+
encoding: "utf-8",
|
|
19262
|
+
mode: 384
|
|
19263
|
+
});
|
|
19264
|
+
fs$1.chmodSync(auditPath, 384);
|
|
19265
|
+
}
|
|
19266
|
+
} catch (err) {
|
|
19267
|
+
logger$q.warn("Failed to trim audit log:", err);
|
|
19268
|
+
}
|
|
19269
|
+
} catch (err) {
|
|
19270
|
+
logger$q.error("Failed to write audit log:", err);
|
|
19271
|
+
}
|
|
19272
|
+
}
|
|
19273
|
+
function readAuditLog2(limit = 100) {
|
|
19274
|
+
try {
|
|
19275
|
+
const auditPath = getAuditPath2();
|
|
19276
|
+
if (!fs$1.existsSync(auditPath)) return [];
|
|
19277
|
+
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19278
|
+
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
19279
|
+
} catch (err) {
|
|
19280
|
+
logger$q.error("Failed to read audit log:", err);
|
|
19281
|
+
return [];
|
|
19282
|
+
}
|
|
19283
|
+
}
|
|
19284
|
+
return { appendAudit, readAuditLog: readAuditLog2 };
|
|
19084
19285
|
}
|
|
19085
|
-
const logger$
|
|
19286
|
+
const logger$p = createLogger("Sessions");
|
|
19086
19287
|
const SESSION_VERSION = 1;
|
|
19288
|
+
const ENCRYPTED_SESSION_FORMAT = "vessel:named-session:v2";
|
|
19289
|
+
const SESSION_KEY_FILENAME = "vessel-named-sessions.key";
|
|
19290
|
+
const sessionCrypto = createEncryptDecrypt(SESSION_KEY_FILENAME);
|
|
19087
19291
|
function getSessionsDir() {
|
|
19088
19292
|
return path.join(electron.app.getPath("userData"), "named-sessions");
|
|
19089
19293
|
}
|
|
@@ -19109,17 +19313,40 @@ async function getSessionPath(name) {
|
|
|
19109
19313
|
const dir = await ensureSessionsDir();
|
|
19110
19314
|
return path.join(dir, sessionFileName(name));
|
|
19111
19315
|
}
|
|
19316
|
+
function isEncryptedSessionFile(value) {
|
|
19317
|
+
return !!value && typeof value === "object" && value.format === ENCRYPTED_SESSION_FORMAT && typeof value.payload === "string";
|
|
19318
|
+
}
|
|
19319
|
+
function encodeSessionFile(data) {
|
|
19320
|
+
const plaintext = JSON.stringify({ version: SESSION_VERSION, ...data });
|
|
19321
|
+
const encrypted = sessionCrypto.encrypt(plaintext);
|
|
19322
|
+
return JSON.stringify(
|
|
19323
|
+
{
|
|
19324
|
+
format: ENCRYPTED_SESSION_FORMAT,
|
|
19325
|
+
payload: encrypted.toString("base64")
|
|
19326
|
+
},
|
|
19327
|
+
null,
|
|
19328
|
+
2
|
|
19329
|
+
);
|
|
19330
|
+
}
|
|
19331
|
+
function decodeSessionFile(raw) {
|
|
19332
|
+
const parsed = JSON.parse(raw);
|
|
19333
|
+
if (isEncryptedSessionFile(parsed)) {
|
|
19334
|
+
const decrypted = sessionCrypto.decrypt(Buffer.from(parsed.payload, "base64"));
|
|
19335
|
+
return parseSessionData(JSON.parse(decrypted));
|
|
19336
|
+
}
|
|
19337
|
+
return parseSessionData(parsed);
|
|
19338
|
+
}
|
|
19112
19339
|
async function writeSessionFile(filePath2, data) {
|
|
19113
|
-
const payload =
|
|
19340
|
+
const payload = encodeSessionFile(data);
|
|
19114
19341
|
await writeFileAtomic(filePath2, payload, { mode: 384 });
|
|
19115
19342
|
}
|
|
19116
19343
|
async function readSessionFile(filePath2) {
|
|
19117
19344
|
const raw = await readIfExists(filePath2, "utf-8");
|
|
19118
19345
|
if (raw == null) return null;
|
|
19119
19346
|
try {
|
|
19120
|
-
return
|
|
19347
|
+
return decodeSessionFile(raw);
|
|
19121
19348
|
} catch (err) {
|
|
19122
|
-
logger$
|
|
19349
|
+
logger$p.warn(`Failed to read session file ${filePath2}:`, err);
|
|
19123
19350
|
return null;
|
|
19124
19351
|
}
|
|
19125
19352
|
}
|
|
@@ -19231,7 +19458,7 @@ async function captureLocalStorageForOrigin(tabManager, origin) {
|
|
|
19231
19458
|
);
|
|
19232
19459
|
}
|
|
19233
19460
|
} catch (err) {
|
|
19234
|
-
logger$
|
|
19461
|
+
logger$p.debug(`Failed to capture localStorage for origin ${origin}:`, err);
|
|
19235
19462
|
return {};
|
|
19236
19463
|
}
|
|
19237
19464
|
return {};
|
|
@@ -19325,7 +19552,7 @@ async function loadNamedSession(tabManager, name) {
|
|
|
19325
19552
|
try {
|
|
19326
19553
|
await electron.session.defaultSession.cookies.set(cookieSetDetails(cookie));
|
|
19327
19554
|
} catch (err) {
|
|
19328
|
-
logger$
|
|
19555
|
+
logger$p.debug(`Skipping cookie ${cookie.name} for ${cookie.domain}:`, err);
|
|
19329
19556
|
continue;
|
|
19330
19557
|
}
|
|
19331
19558
|
}
|
|
@@ -19394,7 +19621,7 @@ function handleFlowEnd(ctx) {
|
|
|
19394
19621
|
ctx.runtime.clearFlow();
|
|
19395
19622
|
return "Workflow ended.";
|
|
19396
19623
|
}
|
|
19397
|
-
const logger$
|
|
19624
|
+
const logger$o = createLogger("Screenshot");
|
|
19398
19625
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
19399
19626
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
19400
19627
|
async function captureScreenshot(wc) {
|
|
@@ -19416,7 +19643,7 @@ async function captureScreenshot(wc) {
|
|
|
19416
19643
|
}
|
|
19417
19644
|
}
|
|
19418
19645
|
} catch (err) {
|
|
19419
|
-
logger$
|
|
19646
|
+
logger$o.debug(
|
|
19420
19647
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
19421
19648
|
getErrorMessage(err)
|
|
19422
19649
|
);
|
|
@@ -20584,10 +20811,7 @@ function normalizeBookmarkMetadataUpdate(input) {
|
|
|
20584
20811
|
}
|
|
20585
20812
|
return normalized;
|
|
20586
20813
|
}
|
|
20587
|
-
|
|
20588
|
-
return Math.floor(Date.now() / 1e3);
|
|
20589
|
-
}
|
|
20590
|
-
const logger$o = createLogger("BookmarkManager");
|
|
20814
|
+
const logger$n = createLogger("BookmarkManager");
|
|
20591
20815
|
const UNSORTED_ID = "unsorted";
|
|
20592
20816
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
20593
20817
|
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
@@ -21160,7 +21384,7 @@ function importBookmarksFromJson(content) {
|
|
|
21160
21384
|
emit$2();
|
|
21161
21385
|
}
|
|
21162
21386
|
} catch (err) {
|
|
21163
|
-
logger$
|
|
21387
|
+
logger$n.warn("Failed to import bookmarks from JSON:", err);
|
|
21164
21388
|
errors++;
|
|
21165
21389
|
}
|
|
21166
21390
|
return { imported, skipped, errors };
|
|
@@ -21819,7 +22043,7 @@ const DANGEROUS_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
21819
22043
|
function isDangerousAction(name) {
|
|
21820
22044
|
return DANGEROUS_ACTIONS.has(name);
|
|
21821
22045
|
}
|
|
21822
|
-
const logger$
|
|
22046
|
+
const logger$m = createLogger("ResearchOrchestrator");
|
|
21823
22047
|
const MAX_THREADS = 5;
|
|
21824
22048
|
const MAX_TRACE_ARGS_CHARS = 1200;
|
|
21825
22049
|
const MAX_TRACE_RESULT_CHARS = 2e3;
|
|
@@ -22008,7 +22232,7 @@ class ResearchOrchestrator {
|
|
|
22008
22232
|
}
|
|
22009
22233
|
stopAndSynthesizeCurrentFindings() {
|
|
22010
22234
|
if (this.state.phase !== "executing") {
|
|
22011
|
-
logger$
|
|
22235
|
+
logger$m.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
|
|
22012
22236
|
return;
|
|
22013
22237
|
}
|
|
22014
22238
|
this.stopRequested = true;
|
|
@@ -22042,23 +22266,23 @@ class ResearchOrchestrator {
|
|
|
22042
22266
|
async startBrief(userQuery) {
|
|
22043
22267
|
const query = userQuery.trim();
|
|
22044
22268
|
if (!query) {
|
|
22045
|
-
logger$
|
|
22269
|
+
logger$m.warn("Ignoring empty Research Desk query");
|
|
22046
22270
|
return;
|
|
22047
22271
|
}
|
|
22048
22272
|
if (this.state.phase !== "idle") {
|
|
22049
|
-
logger$
|
|
22273
|
+
logger$m.warn("Research already in progress, ignoring startBrief");
|
|
22050
22274
|
return;
|
|
22051
22275
|
}
|
|
22052
22276
|
this.state = this.initialState();
|
|
22053
22277
|
this.state.originalQuery = query;
|
|
22054
22278
|
this.state.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22055
22279
|
this.setPhase("briefing");
|
|
22056
|
-
logger$
|
|
22280
|
+
logger$m.info(`Brief started for query: ${query.slice(0, 120)}`);
|
|
22057
22281
|
}
|
|
22058
22282
|
// ── phase: briefing → planning ─────────────────────────────────
|
|
22059
22283
|
confirmBrief() {
|
|
22060
22284
|
if (this.state.phase !== "briefing") {
|
|
22061
|
-
logger$
|
|
22285
|
+
logger$m.warn("Not in briefing phase, ignoring confirmBrief");
|
|
22062
22286
|
return;
|
|
22063
22287
|
}
|
|
22064
22288
|
this.setPhase("planning");
|
|
@@ -22066,7 +22290,7 @@ class ResearchOrchestrator {
|
|
|
22066
22290
|
// ── phase: planning → awaiting_approval ────────────────────────
|
|
22067
22291
|
setObjectives(objectives) {
|
|
22068
22292
|
if (this.state.phase !== "planning") {
|
|
22069
|
-
logger$
|
|
22293
|
+
logger$m.warn("Not in planning phase, ignoring setObjectives");
|
|
22070
22294
|
return;
|
|
22071
22295
|
}
|
|
22072
22296
|
const threads = objectives.threads.slice(0, MAX_THREADS).map(mergeBlockedSourceDomains);
|
|
@@ -22095,11 +22319,11 @@ class ResearchOrchestrator {
|
|
|
22095
22319
|
try {
|
|
22096
22320
|
const parsed = JSON.parse(json);
|
|
22097
22321
|
if (typeof parsed.researchQuestion !== "string" || !parsed.researchQuestion.trim()) {
|
|
22098
|
-
logger$
|
|
22322
|
+
logger$m.warn("Missing researchQuestion in objectives JSON");
|
|
22099
22323
|
return false;
|
|
22100
22324
|
}
|
|
22101
22325
|
if (!Array.isArray(parsed.threads) || parsed.threads.length === 0) {
|
|
22102
|
-
logger$
|
|
22326
|
+
logger$m.warn("Missing or empty threads array in objectives JSON");
|
|
22103
22327
|
return false;
|
|
22104
22328
|
}
|
|
22105
22329
|
const threads = parsed.threads.map((t, i) => {
|
|
@@ -22117,7 +22341,7 @@ class ResearchOrchestrator {
|
|
|
22117
22341
|
};
|
|
22118
22342
|
}).filter((thread) => thread.question && thread.searchQueries.length > 0).slice(0, MAX_THREADS);
|
|
22119
22343
|
if (threads.length === 0) {
|
|
22120
|
-
logger$
|
|
22344
|
+
logger$m.warn("Objectives JSON did not contain any valid research threads");
|
|
22121
22345
|
return false;
|
|
22122
22346
|
}
|
|
22123
22347
|
const objectives = {
|
|
@@ -22128,17 +22352,17 @@ class ResearchOrchestrator {
|
|
|
22128
22352
|
totalSourceBudget: threads.reduce((sum, t) => sum + t.sourceBudget, 0)
|
|
22129
22353
|
};
|
|
22130
22354
|
this.setObjectives(objectives);
|
|
22131
|
-
logger$
|
|
22355
|
+
logger$m.info(`Parsed ${objectives.threads.length} threads from objectives`);
|
|
22132
22356
|
return true;
|
|
22133
22357
|
} catch (err) {
|
|
22134
|
-
logger$
|
|
22358
|
+
logger$m.warn("Failed to parse objectives JSON", err);
|
|
22135
22359
|
return false;
|
|
22136
22360
|
}
|
|
22137
22361
|
}
|
|
22138
22362
|
// ── phase: awaiting_approval → executing ───────────────────────
|
|
22139
22363
|
approveObjectives(mode, includeTraces) {
|
|
22140
22364
|
if (this.state.phase !== "awaiting_approval") {
|
|
22141
|
-
logger$
|
|
22365
|
+
logger$m.warn("Not awaiting approval, ignoring approveObjectives");
|
|
22142
22366
|
return;
|
|
22143
22367
|
}
|
|
22144
22368
|
if (mode) this.state.supervisionMode = mode;
|
|
@@ -22173,7 +22397,7 @@ class ResearchOrchestrator {
|
|
|
22173
22397
|
this.state.threads.map((thread) => {
|
|
22174
22398
|
if (this.state.phase !== "executing") return null;
|
|
22175
22399
|
return this.runSubAgent(thread, tabMutex).catch((err) => {
|
|
22176
|
-
logger$
|
|
22400
|
+
logger$m.error(`Sub-agent "${thread.label}" failed`, err);
|
|
22177
22401
|
return {
|
|
22178
22402
|
threadLabel: thread.label,
|
|
22179
22403
|
threadQuestion: thread.question,
|
|
@@ -22202,7 +22426,7 @@ class ResearchOrchestrator {
|
|
|
22202
22426
|
try {
|
|
22203
22427
|
await this.synthesizeReport();
|
|
22204
22428
|
} catch (err) {
|
|
22205
|
-
logger$
|
|
22429
|
+
logger$m.error("Auto-synthesis failed", err);
|
|
22206
22430
|
this.state.error = `Synthesis failed: ${String(err)}`;
|
|
22207
22431
|
this.setPhase("delivered");
|
|
22208
22432
|
}
|
|
@@ -22313,7 +22537,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
22313
22537
|
try {
|
|
22314
22538
|
this.tabManager.closeTab(tabId);
|
|
22315
22539
|
} catch (err) {
|
|
22316
|
-
logger$
|
|
22540
|
+
logger$m.warn(`Failed to close sub-agent tab ${tabId}`, err);
|
|
22317
22541
|
}
|
|
22318
22542
|
}
|
|
22319
22543
|
}
|
|
@@ -22322,7 +22546,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
22322
22546
|
try {
|
|
22323
22547
|
claims = await this.extractClaimsFromTranscript(thread, transcript);
|
|
22324
22548
|
} catch (err) {
|
|
22325
|
-
logger$
|
|
22549
|
+
logger$m.warn(`Claim extraction failed for "${thread.label}"`, err);
|
|
22326
22550
|
}
|
|
22327
22551
|
}
|
|
22328
22552
|
if (this.state.phase === "executing" && this.state.includeTraces) {
|
|
@@ -22412,7 +22636,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
22412
22636
|
(claim) => claim.claim && claim.sourceUrl && claim.extractedQuote
|
|
22413
22637
|
);
|
|
22414
22638
|
} catch {
|
|
22415
|
-
logger$
|
|
22639
|
+
logger$m.warn(`Failed to parse claims JSON for "${thread.label}"`);
|
|
22416
22640
|
return [];
|
|
22417
22641
|
}
|
|
22418
22642
|
}
|
|
@@ -22497,7 +22721,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
22497
22721
|
objectives
|
|
22498
22722
|
};
|
|
22499
22723
|
} catch (err) {
|
|
22500
|
-
logger$
|
|
22724
|
+
logger$m.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
|
|
22501
22725
|
return buildFallbackReport(objectives, findings, String(err));
|
|
22502
22726
|
}
|
|
22503
22727
|
}
|
|
@@ -23072,7 +23296,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
|
23072
23296
|
});
|
|
23073
23297
|
}
|
|
23074
23298
|
}
|
|
23075
|
-
const logger$
|
|
23299
|
+
const logger$l = createLogger("PrivateWindow");
|
|
23076
23300
|
const privateWindows = /* @__PURE__ */ new Set();
|
|
23077
23301
|
function layoutPrivateViews(state2) {
|
|
23078
23302
|
const { window: win, chromeView, tabManager } = state2;
|
|
@@ -23302,7 +23526,7 @@ function createPrivateWindow() {
|
|
|
23302
23526
|
privateSession.clearStorageData(),
|
|
23303
23527
|
privateSession.clearCache()
|
|
23304
23528
|
]).catch((error) => {
|
|
23305
|
-
logger$
|
|
23529
|
+
logger$l.warn("Failed to clear private browsing session:", error);
|
|
23306
23530
|
});
|
|
23307
23531
|
});
|
|
23308
23532
|
privateWindows.add(state2);
|
|
@@ -23311,38 +23535,38 @@ function createPrivateWindow() {
|
|
|
23311
23535
|
tabManager?.createTab("about:blank");
|
|
23312
23536
|
if (state2) layoutPrivateViews(state2);
|
|
23313
23537
|
} catch (error) {
|
|
23314
|
-
logger$
|
|
23538
|
+
logger$l.error("Failed to initialize private browsing tab:", error);
|
|
23315
23539
|
try {
|
|
23316
23540
|
win?.close();
|
|
23317
23541
|
} catch (cleanupError) {
|
|
23318
|
-
logger$
|
|
23542
|
+
logger$l.warn("Failed to close private window after tab init failure:", cleanupError);
|
|
23319
23543
|
}
|
|
23320
23544
|
}
|
|
23321
23545
|
});
|
|
23322
23546
|
loadPrivateRenderer(chromeView);
|
|
23323
23547
|
win.show();
|
|
23324
|
-
logger$
|
|
23548
|
+
logger$l.info("Private browsing window opened");
|
|
23325
23549
|
return state2;
|
|
23326
23550
|
} catch (error) {
|
|
23327
|
-
logger$
|
|
23551
|
+
logger$l.error("Failed to create private browsing window:", error);
|
|
23328
23552
|
if (state2) {
|
|
23329
23553
|
privateWindows.delete(state2);
|
|
23330
23554
|
}
|
|
23331
23555
|
try {
|
|
23332
23556
|
tabManager?.destroyAllTabs();
|
|
23333
23557
|
} catch (cleanupError) {
|
|
23334
|
-
logger$
|
|
23558
|
+
logger$l.warn("Failed to clean up private tabs after launch failure:", cleanupError);
|
|
23335
23559
|
}
|
|
23336
23560
|
try {
|
|
23337
23561
|
win?.close();
|
|
23338
23562
|
} catch (cleanupError) {
|
|
23339
|
-
logger$
|
|
23563
|
+
logger$l.warn("Failed to close private window after launch failure:", cleanupError);
|
|
23340
23564
|
}
|
|
23341
23565
|
void Promise.all([
|
|
23342
23566
|
privateSession.clearStorageData(),
|
|
23343
23567
|
privateSession.clearCache()
|
|
23344
23568
|
]).catch((cleanupError) => {
|
|
23345
|
-
logger$
|
|
23569
|
+
logger$l.warn("Failed to clear failed private browsing session:", cleanupError);
|
|
23346
23570
|
});
|
|
23347
23571
|
throw error;
|
|
23348
23572
|
}
|
|
@@ -23741,7 +23965,7 @@ function registerTabHandlers(windowState, _sendToRendererViews) {
|
|
|
23741
23965
|
);
|
|
23742
23966
|
}
|
|
23743
23967
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
23744
|
-
const logger$
|
|
23968
|
+
const logger$k = createLogger("DevTrace");
|
|
23745
23969
|
let cachedFactory;
|
|
23746
23970
|
function createNoopTraceSession() {
|
|
23747
23971
|
return {
|
|
@@ -23774,7 +23998,7 @@ function loadLocalFactory() {
|
|
|
23774
23998
|
return cachedFactory;
|
|
23775
23999
|
}
|
|
23776
24000
|
} catch (err) {
|
|
23777
|
-
logger$
|
|
24001
|
+
logger$k.warn("Failed to load local trace logger:", err);
|
|
23778
24002
|
}
|
|
23779
24003
|
}
|
|
23780
24004
|
return cachedFactory;
|
|
@@ -24682,7 +24906,7 @@ function registerContentHandlers(windowState) {
|
|
|
24682
24906
|
return windowState.uiState.focusMode;
|
|
24683
24907
|
});
|
|
24684
24908
|
}
|
|
24685
|
-
const logger$
|
|
24909
|
+
const logger$j = createLogger("HighlightIPC");
|
|
24686
24910
|
const HighlightIndexSchema = zod.z.number().int().min(0);
|
|
24687
24911
|
function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
24688
24912
|
const { tabManager, chromeView } = windowState;
|
|
@@ -24692,7 +24916,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24692
24916
|
try {
|
|
24693
24917
|
return await getHighlightCount(info.wc) ?? 0;
|
|
24694
24918
|
} catch (err) {
|
|
24695
|
-
logger$
|
|
24919
|
+
logger$j.warn("Failed to get active highlight count:", err);
|
|
24696
24920
|
return 0;
|
|
24697
24921
|
}
|
|
24698
24922
|
};
|
|
@@ -24717,13 +24941,13 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24717
24941
|
const result = await captureSelectionHighlight(wc);
|
|
24718
24942
|
if (result.success && result.text) {
|
|
24719
24943
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
24720
|
-
(err) => logger$
|
|
24944
|
+
(err) => logger$j.warn("Failed to highlight captured selection:", err)
|
|
24721
24945
|
);
|
|
24722
24946
|
await emitHighlightCount();
|
|
24723
24947
|
}
|
|
24724
24948
|
return result;
|
|
24725
24949
|
} catch (err) {
|
|
24726
|
-
logger$
|
|
24950
|
+
logger$j.warn("Failed to capture highlight from active tab:", err);
|
|
24727
24951
|
return { success: false, message: "Could not capture selection" };
|
|
24728
24952
|
}
|
|
24729
24953
|
});
|
|
@@ -24741,7 +24965,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24741
24965
|
}
|
|
24742
24966
|
});
|
|
24743
24967
|
} catch (err) {
|
|
24744
|
-
logger$
|
|
24968
|
+
logger$j.warn("Failed to persist auto-highlight selection:", err);
|
|
24745
24969
|
}
|
|
24746
24970
|
});
|
|
24747
24971
|
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, (event) => {
|
|
@@ -24756,7 +24980,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24756
24980
|
try {
|
|
24757
24981
|
return scrollToHighlight(info.wc, validatedIndex);
|
|
24758
24982
|
} catch (err) {
|
|
24759
|
-
logger$
|
|
24983
|
+
logger$j.warn("Failed to scroll to highlight:", err);
|
|
24760
24984
|
return false;
|
|
24761
24985
|
}
|
|
24762
24986
|
});
|
|
@@ -24780,7 +25004,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24780
25004
|
}
|
|
24781
25005
|
return removed;
|
|
24782
25006
|
} catch (err) {
|
|
24783
|
-
logger$
|
|
25007
|
+
logger$j.warn("Failed to remove highlight at index:", err);
|
|
24784
25008
|
return false;
|
|
24785
25009
|
}
|
|
24786
25010
|
});
|
|
@@ -24797,7 +25021,7 @@ function registerHighlightHandlers(windowState, sendToRendererViews) {
|
|
|
24797
25021
|
}
|
|
24798
25022
|
return cleared;
|
|
24799
25023
|
} catch (err) {
|
|
24800
|
-
logger$
|
|
25024
|
+
logger$j.warn("Failed to clear highlight elements:", err);
|
|
24801
25025
|
return false;
|
|
24802
25026
|
}
|
|
24803
25027
|
});
|
|
@@ -26160,7 +26384,7 @@ async function linkBookmarkToMemory({
|
|
|
26160
26384
|
}
|
|
26161
26385
|
});
|
|
26162
26386
|
}
|
|
26163
|
-
const logger$
|
|
26387
|
+
const logger$i = createLogger("MCP");
|
|
26164
26388
|
function asTextResponse(text) {
|
|
26165
26389
|
return { content: [{ type: "text", text }] };
|
|
26166
26390
|
}
|
|
@@ -26261,7 +26485,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
26261
26485
|
}
|
|
26262
26486
|
}
|
|
26263
26487
|
} catch (err) {
|
|
26264
|
-
logger$
|
|
26488
|
+
logger$i.warn("Failed to compute post-action state warning:", err);
|
|
26265
26489
|
}
|
|
26266
26490
|
return `${warning}
|
|
26267
26491
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -26366,7 +26590,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
26366
26590
|
}
|
|
26367
26591
|
})()
|
|
26368
26592
|
`).catch((err) => {
|
|
26369
|
-
logger$
|
|
26593
|
+
logger$i.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
26370
26594
|
return null;
|
|
26371
26595
|
});
|
|
26372
26596
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -27234,7 +27458,7 @@ function registerSessionTools(server, tabManager, runtime2) {
|
|
|
27234
27458
|
)
|
|
27235
27459
|
);
|
|
27236
27460
|
}
|
|
27237
|
-
const logger$
|
|
27461
|
+
const logger$h = createLogger("MCPContentTools");
|
|
27238
27462
|
const EXTRACT_MODES = [
|
|
27239
27463
|
"full",
|
|
27240
27464
|
"summary",
|
|
@@ -27659,7 +27883,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
27659
27883
|
try {
|
|
27660
27884
|
page = await extractContent(wc);
|
|
27661
27885
|
} catch (err) {
|
|
27662
|
-
logger$
|
|
27886
|
+
logger$h.warn("Failed to extract page while generating suggestions:", err);
|
|
27663
27887
|
return asTextResponse(
|
|
27664
27888
|
"Could not read page. Try navigate to a working URL."
|
|
27665
27889
|
);
|
|
@@ -28511,7 +28735,7 @@ function registerNavigationTools(server, tabManager, runtime2) {
|
|
|
28511
28735
|
}
|
|
28512
28736
|
);
|
|
28513
28737
|
}
|
|
28514
|
-
const logger$
|
|
28738
|
+
const logger$g = createLogger("MCPPrompts");
|
|
28515
28739
|
function registerPromptTools(server, tabManager, runtime2) {
|
|
28516
28740
|
server.registerPrompt(
|
|
28517
28741
|
"vessel-supervisor-brief",
|
|
@@ -28590,7 +28814,7 @@ function registerPromptTools(server, tabManager, runtime2) {
|
|
|
28590
28814
|
const page = await extractContent(wc);
|
|
28591
28815
|
pageType = detectPageType(page);
|
|
28592
28816
|
} catch (err) {
|
|
28593
|
-
logger$
|
|
28817
|
+
logger$g.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
28594
28818
|
}
|
|
28595
28819
|
}
|
|
28596
28820
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -28751,7 +28975,7 @@ function registerGroupTools(server, tabManager, runtime2) {
|
|
|
28751
28975
|
})
|
|
28752
28976
|
);
|
|
28753
28977
|
}
|
|
28754
|
-
const logger$
|
|
28978
|
+
const logger$f = createLogger("MCPHighlightTools");
|
|
28755
28979
|
function registerHighlightTools(server, tabManager, runtime2) {
|
|
28756
28980
|
server.registerTool(
|
|
28757
28981
|
"highlight",
|
|
@@ -28968,7 +29192,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
28968
29192
|
void 0,
|
|
28969
29193
|
h.color
|
|
28970
29194
|
).catch(
|
|
28971
|
-
(err) => logger$
|
|
29195
|
+
(err) => logger$f.warn("Failed to restore highlight after removal:", err)
|
|
28972
29196
|
);
|
|
28973
29197
|
}
|
|
28974
29198
|
}
|
|
@@ -29487,7 +29711,7 @@ ${steps.join("\n")}`;
|
|
|
29487
29711
|
}
|
|
29488
29712
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
29489
29713
|
const MAX_ENTRIES = 1e3;
|
|
29490
|
-
const logger$
|
|
29714
|
+
const logger$e = createLogger("VaultAudit");
|
|
29491
29715
|
function getAuditPath() {
|
|
29492
29716
|
return path.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
29493
29717
|
}
|
|
@@ -29501,7 +29725,7 @@ function appendAuditEntry(entry) {
|
|
|
29501
29725
|
});
|
|
29502
29726
|
fs$1.chmodSync(auditPath, 384);
|
|
29503
29727
|
} catch (err) {
|
|
29504
|
-
logger$
|
|
29728
|
+
logger$e.error("Failed to write audit log:", err);
|
|
29505
29729
|
}
|
|
29506
29730
|
}
|
|
29507
29731
|
function readAuditLog$1(limit = 100) {
|
|
@@ -29511,7 +29735,7 @@ function readAuditLog$1(limit = 100) {
|
|
|
29511
29735
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
29512
29736
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
29513
29737
|
} catch (err) {
|
|
29514
|
-
logger$
|
|
29738
|
+
logger$e.error("Failed to read audit log:", err);
|
|
29515
29739
|
return [];
|
|
29516
29740
|
}
|
|
29517
29741
|
}
|
|
@@ -29580,200 +29804,6 @@ async function requestHumanVaultConsent(request) {
|
|
|
29580
29804
|
});
|
|
29581
29805
|
return { approved: response === 1 };
|
|
29582
29806
|
}
|
|
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
29807
|
const VAULT_FILENAME$1 = "vessel-human-vault.enc";
|
|
29778
29808
|
const KEY_FILENAME$1 = "vessel-human-vault.key";
|
|
29779
29809
|
const AUDIT_MAX_ENTRIES = 2e3;
|