@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 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 SETTABLE_KEYS = new Set(Object.keys(defaults));
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
- const neverSuppressDuplicate = [
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: `Error: Repeated the same tool call (${tc.name}) with the same arguments twice in a row. Do not repeat it. Continue with the next logical step for the original task.`
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: `[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.`
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
- "read_page",
11233
- "current_tab",
11234
- "inspect_element",
11235
- "screenshot",
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
- `Error: Repeated the same tool call (${prepared.prepared.name}) with the same arguments twice in a row. Do not repeat it. Continue with the next logical step for the original task.`
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
- 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
- }
13903
+ function buildScopedContext(page, mode) {
13904
+ const render = SCOPED_CONTEXT_RENDERERS.get(mode) ?? buildStructuredContext;
13905
+ return render(page);
13762
13906
  }
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
- }
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
- 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
- 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
- ctx.runtime.restoreCheckpoint(checkpoint.id);
19083
- return `Restored checkpoint ${checkpoint.name}`;
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$q = createLogger("Sessions");
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 = JSON.stringify({ version: SESSION_VERSION, ...data }, null, 2);
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 parseSessionData(JSON.parse(raw));
19354
+ return decodeSessionFile(raw);
19121
19355
  } catch (err) {
19122
- logger$q.warn(`Failed to read session file ${filePath2}:`, err);
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$q.debug(`Failed to capture localStorage for origin ${origin}:`, err);
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$q.debug(`Skipping cookie ${cookie.name} for ${cookie.domain}:`, err);
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$p = createLogger("Screenshot");
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$p.debug(
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
- function unixNow() {
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$o.warn("Failed to import bookmarks from JSON:", err);
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$n = createLogger("ResearchOrchestrator");
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$n.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
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$n.warn("Ignoring empty Research Desk query");
22276
+ logger$m.warn("Ignoring empty Research Desk query");
22046
22277
  return;
22047
22278
  }
22048
22279
  if (this.state.phase !== "idle") {
22049
- logger$n.warn("Research already in progress, ignoring startBrief");
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$n.info(`Brief started for query: ${query.slice(0, 120)}`);
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$n.warn("Not in briefing phase, ignoring confirmBrief");
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$n.warn("Not in planning phase, ignoring setObjectives");
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$n.warn("Missing researchQuestion in objectives JSON");
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$n.warn("Missing or empty threads array in objectives JSON");
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$n.warn("Objectives JSON did not contain any valid research threads");
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$n.info(`Parsed ${objectives.threads.length} threads from objectives`);
22362
+ logger$m.info(`Parsed ${objectives.threads.length} threads from objectives`);
22132
22363
  return true;
22133
22364
  } catch (err) {
22134
- logger$n.warn("Failed to parse objectives JSON", err);
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$n.warn("Not awaiting approval, ignoring approveObjectives");
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$n.error(`Sub-agent "${thread.label}" failed`, err);
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$n.error("Auto-synthesis failed", err);
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$n.warn(`Failed to close sub-agent tab ${tabId}`, err);
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$n.warn(`Claim extraction failed for "${thread.label}"`, err);
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$n.warn(`Failed to parse claims JSON for "${thread.label}"`);
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$n.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
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$m = createLogger("PrivateWindow");
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$m.warn("Failed to clear private browsing session:", error);
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$m.error("Failed to initialize private browsing tab:", error);
23545
+ logger$l.error("Failed to initialize private browsing tab:", error);
23315
23546
  try {
23316
23547
  win?.close();
23317
23548
  } catch (cleanupError) {
23318
- logger$m.warn("Failed to close private window after tab init failure:", cleanupError);
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$m.info("Private browsing window opened");
23555
+ logger$l.info("Private browsing window opened");
23325
23556
  return state2;
23326
23557
  } catch (error) {
23327
- logger$m.error("Failed to create private browsing window:", error);
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$m.warn("Failed to clean up private tabs after launch failure:", cleanupError);
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$m.warn("Failed to close private window after launch failure:", cleanupError);
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$m.warn("Failed to clear failed private browsing session:", cleanupError);
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 ColorSchema = zod.z.string().min(1);
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
- return tabManager.navigateTab(validatedId, validatedUrl, postBody);
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$l = createLogger("DevTrace");
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$l.warn("Failed to load local trace logger:", err);
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$k = createLogger("HighlightIPC");
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$k.warn("Failed to get active highlight count:", err);
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$k.warn("Failed to highlight captured selection:", err)
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$k.warn("Failed to capture highlight from active tab:", err);
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$k.warn("Failed to persist auto-highlight selection:", err);
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$k.warn("Failed to scroll to highlight:", err);
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$k.warn("Failed to remove highlight at index:", err);
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$k.warn("Failed to clear highlight elements:", err);
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$j = createLogger("MCP");
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$j.warn("Failed to compute post-action state warning:", err);
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$j.warn("Failed to gather wait_for timeout diagnostic:", err);
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$i = createLogger("MCPContentTools");
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$i.warn("Failed to extract page while generating suggestions:", err);
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$h = createLogger("MCPPrompts");
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$h.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
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: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).optional().describe("Optional group 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: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).describe("New 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$g = createLogger("MCPHighlightTools");
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$g.warn("Failed to restore highlight after removal:", err)
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$f = createLogger("VaultAudit");
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$f.error("Failed to write audit log:", err);
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$f.error("Failed to read audit log:", err);
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.string().refine((k) => SETTABLE_KEYS.has(k), {
30714
- message: "Unknown setting key"
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 validatedKey = parseIpc(SettingsKeySchema, key2, "key");
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
  });