@quanta-intellect/vessel-browser 0.1.90 → 0.1.94

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
@@ -104,10 +104,23 @@ function canUseSafeStorage$1() {
104
104
  return false;
105
105
  }
106
106
  }
107
+ function writePrivateFile(filePath2, data) {
108
+ fs.writeFileSync(filePath2, data, { mode: 384 });
109
+ try {
110
+ fs.chmodSync(filePath2, 384);
111
+ } catch {
112
+ }
113
+ }
114
+ function assertSafeStorageAvailable() {
115
+ if (!canUseSafeStorage$1()) {
116
+ throw new Error("OS-backed secret storage is unavailable; refusing to store secrets on disk.");
117
+ }
118
+ }
107
119
  function readStoredProviderSecret() {
108
120
  try {
121
+ if (!canUseSafeStorage$1()) return null;
109
122
  const raw = fs.readFileSync(getChatProviderSecretPath());
110
- const decoded = canUseSafeStorage$1() && electron.safeStorage.decryptString ? electron.safeStorage.decryptString(raw) : raw.toString("utf-8");
123
+ const decoded = electron.safeStorage.decryptString(raw);
111
124
  const parsed = JSON.parse(decoded);
112
125
  if (parsed && typeof parsed === "object" && typeof parsed.providerId === "string" && typeof parsed.apiKey === "string") {
113
126
  return parsed;
@@ -117,15 +130,12 @@ function readStoredProviderSecret() {
117
130
  return null;
118
131
  }
119
132
  function writeStoredProviderSecret(secret) {
120
- const filePath = getChatProviderSecretPath();
121
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
133
+ assertSafeStorageAvailable();
134
+ const filePath2 = getChatProviderSecretPath();
135
+ fs.mkdirSync(path.dirname(filePath2), { recursive: true });
122
136
  const payload = JSON.stringify(secret);
123
- if (canUseSafeStorage$1()) {
124
- const encrypted = electron.safeStorage.encryptString(payload);
125
- fs.writeFileSync(filePath, encrypted, { mode: 384 });
126
- return;
127
- }
128
- fs.writeFileSync(filePath, payload, { mode: 384 });
137
+ const encrypted = electron.safeStorage.encryptString(payload);
138
+ writePrivateFile(filePath2, encrypted);
129
139
  }
130
140
  function clearStoredProviderSecret() {
131
141
  try {
@@ -138,8 +148,9 @@ function getCodexTokensPath() {
138
148
  }
139
149
  function readStoredCodexTokens() {
140
150
  try {
151
+ if (!canUseSafeStorage$1()) return null;
141
152
  const raw = fs.readFileSync(getCodexTokensPath());
142
- const decoded = canUseSafeStorage$1() && electron.safeStorage.decryptString ? electron.safeStorage.decryptString(raw) : raw.toString("utf-8");
153
+ const decoded = electron.safeStorage.decryptString(raw);
143
154
  const parsed = JSON.parse(decoded);
144
155
  if (parsed && typeof parsed === "object" && typeof parsed.accessToken === "string" && typeof parsed.refreshToken === "string") {
145
156
  return parsed;
@@ -149,15 +160,12 @@ function readStoredCodexTokens() {
149
160
  return null;
150
161
  }
151
162
  function writeStoredCodexTokens(tokens) {
152
- const filePath = getCodexTokensPath();
153
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
163
+ assertSafeStorageAvailable();
164
+ const filePath2 = getCodexTokensPath();
165
+ fs.mkdirSync(path.dirname(filePath2), { recursive: true });
154
166
  const payload = JSON.stringify(tokens);
155
- if (canUseSafeStorage$1()) {
156
- const encrypted = electron.safeStorage.encryptString(payload);
157
- fs.writeFileSync(filePath, encrypted, { mode: 384 });
158
- return;
159
- }
160
- fs.writeFileSync(filePath, payload, { mode: 384 });
167
+ const encrypted = electron.safeStorage.encryptString(payload);
168
+ writePrivateFile(filePath2, encrypted);
161
169
  }
162
170
  function clearStoredCodexTokens() {
163
171
  try {
@@ -282,11 +290,11 @@ function saveSettings() {
282
290
  }
283
291
  }, SAVE_DEBOUNCE_MS$6);
284
292
  }
285
- function setSetting(key, value) {
293
+ function setSetting(key2, value) {
286
294
  loadSettings();
287
- if (key === "mcpPort") {
295
+ if (key2 === "mcpPort") {
288
296
  settings.mcpPort = sanitizePort(value);
289
- } else if (key === "chatProvider") {
297
+ } else if (key2 === "chatProvider") {
290
298
  const nextProvider = value;
291
299
  if (!nextProvider) {
292
300
  clearStoredProviderSecret();
@@ -314,7 +322,7 @@ function setSetting(key, value) {
314
322
  };
315
323
  }
316
324
  } else {
317
- settings[key] = value;
325
+ settings[key2] = value;
318
326
  }
319
327
  saveSettings();
320
328
  return { ...settings };
@@ -375,6 +383,28 @@ function assertPermittedNavigationURL(url) {
375
383
  throw new Error(policyError);
376
384
  }
377
385
  }
386
+ function loadPermittedNavigationURL(wc, url) {
387
+ assertPermittedNavigationURL(url);
388
+ return wc.loadURL(url);
389
+ }
390
+ function loadInternalDataURL(wc, dataUrl) {
391
+ if (!dataUrl.startsWith("data:text/html;charset=utf-8,")) {
392
+ throw new Error("Blocked unexpected internal data URL");
393
+ }
394
+ return wc.loadURL(dataUrl);
395
+ }
396
+ function loadTrustedAppURL(wc, url) {
397
+ const parsed = new URL(url);
398
+ if (!["file:", "http:", "https:"].includes(parsed.protocol)) {
399
+ throw new Error(`Blocked unexpected app URL scheme: ${parsed.protocol}`);
400
+ }
401
+ const isHttp = parsed.protocol === "http:" || parsed.protocol === "https:";
402
+ const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
403
+ if (isHttp && !isLocalhost) {
404
+ throw new Error(`Blocked unexpected app URL host: ${parsed.hostname}`);
405
+ }
406
+ return wc.loadURL(parsed.toString());
407
+ }
378
408
  const MAX_CUSTOM_HISTORY = 50;
379
409
  const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
380
410
  const logger$m = createLogger("Tab");
@@ -490,27 +520,27 @@ class Tab {
490
520
  this.view.webContents.on("before-input-event", (event, input) => {
491
521
  if (!input.control && !input.meta) return;
492
522
  if (input.type !== "keyDown") return;
493
- const key = input.key.toLowerCase();
523
+ const key2 = input.key.toLowerCase();
494
524
  const wc = this.view.webContents;
495
- if (key === "+" || key === "=") {
525
+ if (key2 === "+" || key2 === "=") {
496
526
  this.zoomIn();
497
527
  event.preventDefault();
498
528
  return;
499
529
  }
500
- if (key === "-") {
530
+ if (key2 === "-") {
501
531
  this.zoomOut();
502
532
  event.preventDefault();
503
533
  return;
504
534
  }
505
- if (key === "0") {
535
+ if (key2 === "0") {
506
536
  this.zoomReset();
507
537
  event.preventDefault();
508
538
  return;
509
539
  }
510
- if (key === "c") wc.copy();
511
- else if (key === "v") wc.paste();
512
- else if (key === "x") wc.cut();
513
- else if (key === "a") wc.selectAll();
540
+ if (key2 === "c") wc.copy();
541
+ else if (key2 === "v") wc.paste();
542
+ else if (key2 === "x") wc.cut();
543
+ else if (key2 === "a") wc.selectAll();
514
544
  });
515
545
  this.setupListeners();
516
546
  if (url) {
@@ -784,8 +814,8 @@ class Tab {
784
814
  }
785
815
  if (postBody) {
786
816
  const params = new URLSearchParams();
787
- for (const [key, value] of Object.entries(postBody)) {
788
- params.set(key, value);
817
+ for (const [key2, value] of Object.entries(postBody)) {
818
+ params.set(key2, value);
789
819
  }
790
820
  return this.guardedLoadURL(url, {
791
821
  method: "POST",
@@ -1059,22 +1089,22 @@ function encodeStoredData(payload, secure) {
1059
1089
  return payload;
1060
1090
  }
1061
1091
  function loadJsonFile({
1062
- filePath,
1092
+ filePath: filePath2,
1063
1093
  fallback,
1064
- parse,
1094
+ parse: parse2,
1065
1095
  secure = false
1066
1096
  }) {
1067
1097
  try {
1068
- const raw = fs.readFileSync(filePath);
1098
+ const raw = fs.readFileSync(filePath2);
1069
1099
  const decoded = decodeStoredData(raw, secure);
1070
- return parse(JSON.parse(decoded));
1100
+ return parse2(JSON.parse(decoded));
1071
1101
  } catch {
1072
1102
  return fallback;
1073
1103
  }
1074
1104
  }
1075
1105
  function createDebouncedJsonPersistence({
1076
1106
  debounceMs,
1077
- filePath,
1107
+ filePath: filePath2,
1078
1108
  getValue,
1079
1109
  logLabel,
1080
1110
  resetOnSchedule = false,
@@ -1097,9 +1127,9 @@ function createDebouncedJsonPersistence({
1097
1127
  2
1098
1128
  );
1099
1129
  const data = encodeStoredData(payload, secure);
1100
- await fs.promises.mkdir(path.dirname(filePath), { recursive: true }).then(
1130
+ await fs.promises.mkdir(path.dirname(filePath2), { recursive: true }).then(
1101
1131
  () => fs.promises.writeFile(
1102
- filePath,
1132
+ filePath2,
1103
1133
  data,
1104
1134
  typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
1105
1135
  )
@@ -1125,7 +1155,7 @@ function createDebouncedJsonPersistence({
1125
1155
  flush: flush2
1126
1156
  };
1127
1157
  }
1128
- let state$4 = null;
1158
+ let state$5 = null;
1129
1159
  const listeners$2 = /* @__PURE__ */ new Set();
1130
1160
  const SAVE_DEBOUNCE_MS$5 = 250;
1131
1161
  function getHighlightsPath() {
@@ -1135,19 +1165,19 @@ function createPersistence$1() {
1135
1165
  return createDebouncedJsonPersistence({
1136
1166
  debounceMs: SAVE_DEBOUNCE_MS$5,
1137
1167
  filePath: getHighlightsPath(),
1138
- getValue: () => state$4,
1168
+ getValue: () => state$5,
1139
1169
  logLabel: "highlights",
1140
1170
  resetOnSchedule: true
1141
1171
  });
1142
1172
  }
1143
- let persistence$5 = null;
1173
+ let persistence$7 = null;
1144
1174
  function getPersistence$1() {
1145
- persistence$5 ??= createPersistence$1();
1146
- return persistence$5;
1175
+ persistence$7 ??= createPersistence$1();
1176
+ return persistence$7;
1147
1177
  }
1148
1178
  function load$4() {
1149
- if (state$4) return state$4;
1150
- state$4 = loadJsonFile({
1179
+ if (state$5) return state$5;
1180
+ state$5 = loadJsonFile({
1151
1181
  filePath: getHighlightsPath(),
1152
1182
  fallback: { highlights: [] },
1153
1183
  parse: (raw) => {
@@ -1157,16 +1187,16 @@ function load$4() {
1157
1187
  };
1158
1188
  }
1159
1189
  });
1160
- return state$4;
1190
+ return state$5;
1161
1191
  }
1162
- function save$2() {
1192
+ function save$3() {
1163
1193
  getPersistence$1().schedule();
1164
1194
  }
1165
- function emit$2() {
1166
- if (!state$4) return;
1167
- const snapshot = { highlights: [...state$4.highlights] };
1195
+ function emit$4() {
1196
+ if (!state$5) return;
1197
+ const snapshot2 = { highlights: [...state$5.highlights] };
1168
1198
  for (const listener of listeners$2) {
1169
- listener(snapshot);
1199
+ listener(snapshot2);
1170
1200
  }
1171
1201
  }
1172
1202
  function normalizeUrl$1(rawUrl) {
@@ -1180,12 +1210,12 @@ function normalizeUrl$1(rawUrl) {
1180
1210
  }
1181
1211
  function getState$2() {
1182
1212
  load$4();
1183
- return { highlights: [...state$4.highlights] };
1213
+ return { highlights: [...state$5.highlights] };
1184
1214
  }
1185
1215
  function getHighlightsForUrl(url) {
1186
1216
  load$4();
1187
1217
  const normalized = normalizeUrl$1(url);
1188
- return state$4.highlights.filter((h) => h.url === normalized);
1218
+ return state$5.highlights.filter((h) => h.url === normalized);
1189
1219
  }
1190
1220
  function addHighlight(url, selector, text, label, color, source) {
1191
1221
  load$4();
@@ -1199,45 +1229,45 @@ function addHighlight(url, selector, text, label, color, source) {
1199
1229
  source: source || void 0,
1200
1230
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1201
1231
  };
1202
- state$4.highlights.push(highlight);
1203
- save$2();
1204
- emit$2();
1232
+ state$5.highlights.push(highlight);
1233
+ save$3();
1234
+ emit$4();
1205
1235
  return highlight;
1206
1236
  }
1207
1237
  function removeHighlight(id) {
1208
1238
  load$4();
1209
- const index = state$4.highlights.findIndex((h) => h.id === id);
1239
+ const index = state$5.highlights.findIndex((h) => h.id === id);
1210
1240
  if (index === -1) return null;
1211
- const [removed] = state$4.highlights.splice(index, 1);
1212
- save$2();
1213
- emit$2();
1241
+ const [removed] = state$5.highlights.splice(index, 1);
1242
+ save$3();
1243
+ emit$4();
1214
1244
  return removed;
1215
1245
  }
1216
1246
  function findHighlightByText(url, text) {
1217
1247
  load$4();
1218
1248
  const normalized = normalizeUrl$1(url);
1219
- return state$4.highlights.find(
1249
+ return state$5.highlights.find(
1220
1250
  (h) => h.url === normalized && h.text && h.text === text
1221
1251
  ) ?? null;
1222
1252
  }
1223
1253
  function updateHighlightColor(id, color) {
1224
1254
  load$4();
1225
- const highlight = state$4.highlights.find((h) => h.id === id);
1255
+ const highlight = state$5.highlights.find((h) => h.id === id);
1226
1256
  if (!highlight) return null;
1227
1257
  highlight.color = color;
1228
- save$2();
1229
- emit$2();
1258
+ save$3();
1259
+ emit$4();
1230
1260
  return highlight;
1231
1261
  }
1232
1262
  function clearHighlightsForUrl(url) {
1233
1263
  load$4();
1234
1264
  const normalized = normalizeUrl$1(url);
1235
- const before = state$4.highlights.length;
1236
- state$4.highlights = state$4.highlights.filter((h) => h.url !== normalized);
1237
- const removed = before - state$4.highlights.length;
1265
+ const before = state$5.highlights.length;
1266
+ state$5.highlights = state$5.highlights.filter((h) => h.url !== normalized);
1267
+ const removed = before - state$5.highlights.length;
1238
1268
  if (removed > 0) {
1239
- save$2();
1240
- emit$2();
1269
+ save$3();
1270
+ emit$4();
1241
1271
  }
1242
1272
  return removed;
1243
1273
  }
@@ -1867,14 +1897,14 @@ function persistHighlight(url, text) {
1867
1897
  }
1868
1898
  const MAX_HISTORY_ENTRIES = 5e3;
1869
1899
  const SAVE_DEBOUNCE_MS$4 = 250;
1870
- let state$3 = null;
1900
+ let state$4 = null;
1871
1901
  const listeners$1 = /* @__PURE__ */ new Set();
1872
1902
  function getHistoryPath() {
1873
1903
  return path.join(electron.app.getPath("userData"), "vessel-history.json");
1874
1904
  }
1875
1905
  function load$3() {
1876
- if (state$3) return state$3;
1877
- state$3 = loadJsonFile({
1906
+ if (state$4) return state$4;
1907
+ state$4 = loadJsonFile({
1878
1908
  filePath: getHistoryPath(),
1879
1909
  fallback: { entries: [] },
1880
1910
  parse: (raw) => {
@@ -1884,27 +1914,27 @@ function load$3() {
1884
1914
  };
1885
1915
  }
1886
1916
  });
1887
- return state$3;
1917
+ return state$4;
1888
1918
  }
1889
- const persistence$4 = createDebouncedJsonPersistence({
1919
+ const persistence$6 = createDebouncedJsonPersistence({
1890
1920
  debounceMs: SAVE_DEBOUNCE_MS$4,
1891
1921
  filePath: getHistoryPath(),
1892
- getValue: () => state$3,
1922
+ getValue: () => state$4,
1893
1923
  logLabel: "history"
1894
1924
  });
1895
- function save$1() {
1896
- persistence$4.schedule();
1925
+ function save$2() {
1926
+ persistence$6.schedule();
1897
1927
  }
1898
- function emit$1() {
1899
- if (!state$3) return;
1900
- const snapshot = { entries: [...state$3.entries] };
1928
+ function emit$3() {
1929
+ if (!state$4) return;
1930
+ const snapshot2 = { entries: [...state$4.entries] };
1901
1931
  for (const listener of listeners$1) {
1902
- listener(snapshot);
1932
+ listener(snapshot2);
1903
1933
  }
1904
1934
  }
1905
1935
  function getState$1() {
1906
1936
  load$3();
1907
- return { entries: [...state$3.entries] };
1937
+ return { entries: [...state$4.entries] };
1908
1938
  }
1909
1939
  function subscribe$1(listener) {
1910
1940
  listeners$1.add(listener);
@@ -1915,12 +1945,12 @@ function subscribe$1(listener) {
1915
1945
  function addEntry$1(url, title) {
1916
1946
  if (!url || url === "about:blank") return;
1917
1947
  load$3();
1918
- const last = state$3.entries[0];
1948
+ const last = state$4.entries[0];
1919
1949
  if (last && last.url === url) {
1920
1950
  if (title && title !== last.title) {
1921
1951
  last.title = title;
1922
- save$1();
1923
- emit$1();
1952
+ save$2();
1953
+ emit$3();
1924
1954
  }
1925
1955
  return;
1926
1956
  }
@@ -1929,25 +1959,25 @@ function addEntry$1(url, title) {
1929
1959
  title: title || url,
1930
1960
  visitedAt: (/* @__PURE__ */ new Date()).toISOString()
1931
1961
  };
1932
- state$3.entries.unshift(entry);
1933
- if (state$3.entries.length > MAX_HISTORY_ENTRIES) {
1934
- state$3.entries = state$3.entries.slice(0, MAX_HISTORY_ENTRIES);
1962
+ state$4.entries.unshift(entry);
1963
+ if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
1964
+ state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
1935
1965
  }
1936
- save$1();
1937
- emit$1();
1966
+ save$2();
1967
+ emit$3();
1938
1968
  }
1939
1969
  function search(query, limit = 50) {
1940
1970
  load$3();
1941
- if (!query.trim()) return state$3.entries.slice(0, limit);
1971
+ if (!query.trim()) return state$4.entries.slice(0, limit);
1942
1972
  const normalized = query.toLowerCase();
1943
- return state$3.entries.filter(
1973
+ return state$4.entries.filter(
1944
1974
  (e) => e.url.toLowerCase().includes(normalized) || e.title.toLowerCase().includes(normalized)
1945
1975
  ).slice(0, limit);
1946
1976
  }
1947
1977
  function clearAll$1() {
1948
- state$3 = { entries: [] };
1949
- save$1();
1950
- emit$1();
1978
+ state$4 = { entries: [] };
1979
+ save$2();
1980
+ emit$3();
1951
1981
  }
1952
1982
  function clearByTimeRange(timeRange) {
1953
1983
  load$3();
@@ -1957,12 +1987,12 @@ function clearByTimeRange(timeRange) {
1957
1987
  }
1958
1988
  const now = Date.now();
1959
1989
  const cutoff = new Date(now - timeRangeToMs(timeRange));
1960
- state$3.entries = state$3.entries.filter((entry) => {
1990
+ state$4.entries = state$4.entries.filter((entry) => {
1961
1991
  const visitedAt = new Date(entry.visitedAt).getTime();
1962
1992
  return Number.isNaN(visitedAt) || visitedAt < cutoff.getTime();
1963
1993
  });
1964
- save$1();
1965
- emit$1();
1994
+ save$2();
1995
+ emit$3();
1966
1996
  }
1967
1997
  function timeRangeToMs(range) {
1968
1998
  switch (range) {
@@ -2016,7 +2046,7 @@ function importHistoryFromJson(content) {
2016
2046
  const parsed = JSON.parse(content);
2017
2047
  const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
2018
2048
  load$3();
2019
- const existingUrls = new Set(state$3.entries.map((e) => e.url));
2049
+ const existingUrls = new Set(state$4.entries.map((e) => e.url));
2020
2050
  for (const entry of entries) {
2021
2051
  if (!entry?.url || typeof entry.url !== "string") {
2022
2052
  errors++;
@@ -2026,7 +2056,7 @@ function importHistoryFromJson(content) {
2026
2056
  skipped++;
2027
2057
  continue;
2028
2058
  }
2029
- state$3.entries.push({
2059
+ state$4.entries.push({
2030
2060
  url: entry.url,
2031
2061
  title: typeof entry.title === "string" ? entry.title : entry.url,
2032
2062
  visitedAt: typeof entry.visitedAt === "string" ? entry.visitedAt : (/* @__PURE__ */ new Date()).toISOString()
@@ -2034,14 +2064,14 @@ function importHistoryFromJson(content) {
2034
2064
  existingUrls.add(entry.url);
2035
2065
  imported++;
2036
2066
  }
2037
- state$3.entries.sort(
2067
+ state$4.entries.sort(
2038
2068
  (a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
2039
2069
  );
2040
- if (state$3.entries.length > MAX_HISTORY_ENTRIES) {
2041
- state$3.entries = state$3.entries.slice(0, MAX_HISTORY_ENTRIES);
2070
+ if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
2071
+ state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
2042
2072
  }
2043
- save$1();
2044
- emit$1();
2073
+ save$2();
2074
+ emit$3();
2045
2075
  } catch {
2046
2076
  errors++;
2047
2077
  }
@@ -2052,7 +2082,7 @@ function importHistoryFromHtml(content) {
2052
2082
  let skipped = 0;
2053
2083
  let errors = 0;
2054
2084
  load$3();
2055
- const existingUrls = new Set(state$3.entries.map((e) => e.url));
2085
+ const existingUrls = new Set(state$4.entries.map((e) => e.url));
2056
2086
  const hrefRegex = /<A\s+[^>]*HREF="([^"]+)"[^>]*>([^<]*)<\/A>/gi;
2057
2087
  let match;
2058
2088
  while ((match = hrefRegex.exec(content)) !== null) {
@@ -2063,7 +2093,7 @@ function importHistoryFromHtml(content) {
2063
2093
  else errors++;
2064
2094
  continue;
2065
2095
  }
2066
- state$3.entries.push({
2096
+ state$4.entries.push({
2067
2097
  url,
2068
2098
  title,
2069
2099
  visitedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2071,18 +2101,18 @@ function importHistoryFromHtml(content) {
2071
2101
  existingUrls.add(url);
2072
2102
  imported++;
2073
2103
  }
2074
- state$3.entries.sort(
2104
+ state$4.entries.sort(
2075
2105
  (a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
2076
2106
  );
2077
- if (state$3.entries.length > MAX_HISTORY_ENTRIES) {
2078
- state$3.entries = state$3.entries.slice(0, MAX_HISTORY_ENTRIES);
2107
+ if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
2108
+ state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
2079
2109
  }
2080
- save$1();
2081
- emit$1();
2110
+ save$2();
2111
+ emit$3();
2082
2112
  return { imported, skipped, errors };
2083
2113
  }
2084
2114
  function flushPersist$3() {
2085
- return persistence$4.flush();
2115
+ return persistence$6.flush();
2086
2116
  }
2087
2117
  const MAX_CONSOLE_ENTRIES = 500;
2088
2118
  const MAX_NETWORK_ENTRIES = 200;
@@ -2093,6 +2123,8 @@ class DevToolsSession {
2093
2123
  this.tabId = tabId;
2094
2124
  this.wc = wc;
2095
2125
  }
2126
+ tabId;
2127
+ wc;
2096
2128
  attached = false;
2097
2129
  attachingPromise = null;
2098
2130
  consoleDomainEnabled = false;
@@ -2439,18 +2471,18 @@ class DevToolsSession {
2439
2471
  if (result?.__error) throw new Error(result.__error);
2440
2472
  return { type, origin, entries: result ?? {} };
2441
2473
  }
2442
- async setStorage(type, key, value) {
2474
+ async setStorage(type, key2, value) {
2443
2475
  const storageType = type === "localStorage" ? "localStorage" : "sessionStorage";
2444
2476
  if (value === null) {
2445
2477
  await this.wc.executeJavaScript(
2446
- `window.${storageType}.removeItem(${JSON.stringify(key)})`
2478
+ `window.${storageType}.removeItem(${JSON.stringify(key2)})`
2447
2479
  );
2448
- return `Removed "${key}" from ${type}`;
2480
+ return `Removed "${key2}" from ${type}`;
2449
2481
  }
2450
2482
  await this.wc.executeJavaScript(
2451
- `window.${storageType}.setItem(${JSON.stringify(key)}, ${JSON.stringify(value)})`
2483
+ `window.${storageType}.setItem(${JSON.stringify(key2)}, ${JSON.stringify(value)})`
2452
2484
  );
2453
- return `Set ${type}["${key}"] = ${value.length > 80 ? value.slice(0, 77) + "..." : value}`;
2485
+ return `Set ${type}["${key2}"] = ${value.length > 80 ? value.slice(0, 77) + "..." : value}`;
2454
2486
  }
2455
2487
  // ---------------------------------------------------------------------------
2456
2488
  // Performance
@@ -3015,23 +3047,23 @@ class TabManager {
3015
3047
  async saveTabAsPdf(id) {
3016
3048
  const tab = this.tabs.get(id);
3017
3049
  if (!tab) return null;
3018
- const { canceled, filePath } = await electron.dialog.showSaveDialog({
3050
+ const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
3019
3051
  title: "Save Page as PDF",
3020
3052
  defaultPath: sanitizePdfFilename(tab.state.title || "Vessel Page"),
3021
3053
  filters: [{ name: "PDF", extensions: ["pdf"] }]
3022
3054
  });
3023
- if (canceled || !filePath) return null;
3055
+ if (canceled || !filePath2) return null;
3024
3056
  const data = await tab.view.webContents.printToPDF({
3025
3057
  printBackground: true
3026
3058
  });
3027
- await fs.promises.writeFile(filePath, data);
3028
- return filePath;
3059
+ await fs.promises.writeFile(filePath2, data);
3060
+ return filePath2;
3029
3061
  }
3030
3062
  async savePage(id, format = "MHTML") {
3031
3063
  const tab = this.tabs.get(id);
3032
3064
  if (!tab) return null;
3033
3065
  const ext = format === "MHTML" ? "mhtml" : "html";
3034
- const { canceled, filePath } = await electron.dialog.showSaveDialog({
3066
+ const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
3035
3067
  title: "Save Page As",
3036
3068
  defaultPath: sanitizePageFilename(
3037
3069
  tab.state.title || "Vessel Page",
@@ -3041,9 +3073,9 @@ class TabManager {
3041
3073
  { name: format === "MHTML" ? "MHTML" : "HTML", extensions: [ext] }
3042
3074
  ]
3043
3075
  });
3044
- if (canceled || !filePath) return null;
3045
- await tab.view.webContents.savePage(filePath, format);
3046
- return filePath;
3076
+ if (canceled || !filePath2) return null;
3077
+ await tab.view.webContents.savePage(filePath2, format);
3078
+ return filePath2;
3047
3079
  }
3048
3080
  getActiveTab() {
3049
3081
  return this.activeTabId ? this.tabs.get(this.activeTabId) : void 0;
@@ -3108,11 +3140,11 @@ class TabManager {
3108
3140
  note
3109
3141
  };
3110
3142
  }
3111
- restoreSession(snapshot) {
3112
- const tabs = snapshot.tabs.length > 0 ? snapshot.tabs : [{ id: "", url: "about:blank", title: "New Tab" }];
3143
+ restoreSession(snapshot2) {
3144
+ const tabs = snapshot2.tabs.length > 0 ? snapshot2.tabs : [{ id: "", url: "about:blank", title: "New Tab" }];
3113
3145
  const activeIndex = Math.max(
3114
3146
  0,
3115
- Math.min(snapshot.activeIndex, tabs.length - 1)
3147
+ Math.min(snapshot2.activeIndex, tabs.length - 1)
3116
3148
  );
3117
3149
  this.destroyAllTabs();
3118
3150
  const restoredGroups = /* @__PURE__ */ new Map();
@@ -3127,11 +3159,11 @@ class TabManager {
3127
3159
  this.pinTab(ids[index]);
3128
3160
  }
3129
3161
  if (tab.groupName && ids[index]) {
3130
- const key = `${tab.groupName}|${tab.groupColor ?? "blue"}`;
3131
- let groupId = restoredGroups.get(key);
3162
+ const key2 = `${tab.groupName}|${tab.groupColor ?? "blue"}`;
3163
+ let groupId = restoredGroups.get(key2);
3132
3164
  if (!groupId) {
3133
3165
  groupId = crypto$1.randomUUID();
3134
- restoredGroups.set(key, groupId);
3166
+ restoredGroups.set(key2, groupId);
3135
3167
  this.tabGroups.set(groupId, {
3136
3168
  id: groupId,
3137
3169
  name: tab.groupName,
@@ -3375,6 +3407,7 @@ const Channels = {
3375
3407
  SETTINGS_UPDATE: "settings:update",
3376
3408
  SETTINGS_HEALTH_GET: "settings:health:get",
3377
3409
  SETTINGS_HEALTH_UPDATE: "settings:health:update",
3410
+ MCP_REGENERATE_TOKEN: "mcp:regenerate-token",
3378
3411
  // Bookmarks
3379
3412
  BOOKMARKS_GET: "bookmarks:get",
3380
3413
  BOOKMARKS_UPDATE: "bookmarks:update",
@@ -3456,6 +3489,11 @@ const Channels = {
3456
3489
  DOWNLOAD_STARTED: "download:started",
3457
3490
  DOWNLOAD_PROGRESS: "download:progress",
3458
3491
  DOWNLOAD_DONE: "download:done",
3492
+ DOWNLOADS_GET: "downloads:get",
3493
+ DOWNLOADS_CLEAR: "downloads:clear",
3494
+ DOWNLOADS_OPEN: "downloads:open",
3495
+ DOWNLOADS_SHOW_IN_FOLDER: "downloads:show-in-folder",
3496
+ DOWNLOADS_UPDATE: "downloads:update",
3459
3497
  // Premium
3460
3498
  PREMIUM_GET_STATE: "premium:get-state",
3461
3499
  PREMIUM_ACTIVATION_START: "premium:activation-start",
@@ -3518,7 +3556,14 @@ const Channels = {
3518
3556
  CODEX_START_AUTH: "codex:start-auth",
3519
3557
  CODEX_CANCEL_AUTH: "codex:cancel-auth",
3520
3558
  CODEX_AUTH_STATUS: "codex:auth-status",
3521
- CODEX_DISCONNECT: "codex:disconnect"
3559
+ CODEX_DISCONNECT: "codex:disconnect",
3560
+ // Updates
3561
+ UPDATES_CHECK: "updates:check",
3562
+ UPDATES_OPEN_DOWNLOAD: "updates:open-download",
3563
+ // Permissions
3564
+ PERMISSIONS_GET: "permissions:get",
3565
+ PERMISSIONS_CLEAR: "permissions:clear",
3566
+ PERMISSIONS_CLEAR_ORIGIN: "permissions:clear-origin"
3522
3567
  };
3523
3568
  const MAX_DETAIL_ITEMS = 3;
3524
3569
  const MIN_BLOCK_SIMILARITY = 0.82;
@@ -3760,25 +3805,25 @@ function normalizeQueryValue(value) {
3760
3805
  }
3761
3806
  function serializeSnapshotParams(params) {
3762
3807
  return params.map(
3763
- ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
3808
+ ([key2, value]) => `${encodeURIComponent(key2)}=${encodeURIComponent(value)}`
3764
3809
  ).join("&");
3765
3810
  }
3766
3811
  function normalizeSnapshotParams(entries, pathname) {
3767
3812
  return Array.from(entries).filter(
3768
- ([key, value]) => shouldKeepSnapshotQueryParam(pathname, key, value)
3769
- ).map(([key, value]) => [
3770
- key.trim().toLowerCase(),
3813
+ ([key2, value]) => shouldKeepSnapshotQueryParam(pathname, key2, value)
3814
+ ).map(([key2, value]) => [
3815
+ key2.trim().toLowerCase(),
3771
3816
  normalizeQueryValue(value)
3772
3817
  ]).sort(
3773
3818
  ([keyA, valueA], [keyB, valueB]) => keyA === keyB ? valueA.localeCompare(valueB) : keyA.localeCompare(keyB)
3774
3819
  );
3775
3820
  }
3776
3821
  function shouldKeepSnapshotQueryParam(pathname, rawKey, value) {
3777
- const key = rawKey.trim().toLowerCase();
3778
- if (!key || !value.trim()) return false;
3779
- if (key.startsWith("utm_")) return false;
3780
- if (TRACKING_QUERY_KEYS.has(key)) return false;
3781
- if (SNAPSHOT_QUERY_KEYS.has(key)) return true;
3822
+ const key2 = rawKey.trim().toLowerCase();
3823
+ if (!key2 || !value.trim()) return false;
3824
+ if (key2.startsWith("utm_")) return false;
3825
+ if (TRACKING_QUERY_KEYS.has(key2)) return false;
3826
+ if (SNAPSHOT_QUERY_KEYS.has(key2)) return true;
3782
3827
  return /\/(search|results|browse|discover|find|category|tag|topics?|collections?|list)(\/|$)/i.test(
3783
3828
  pathname
3784
3829
  );
@@ -3863,15 +3908,15 @@ function load$2() {
3863
3908
  const next = /* @__PURE__ */ new Map();
3864
3909
  if (!Array.isArray(raw)) return next;
3865
3910
  for (const entry of raw) {
3866
- const snapshot = normalizeStoredSnapshot(entry);
3867
- if (snapshot) next.set(snapshot.url, snapshot);
3911
+ const snapshot2 = normalizeStoredSnapshot(entry);
3912
+ if (snapshot2) next.set(snapshot2.url, snapshot2);
3868
3913
  }
3869
3914
  return next;
3870
3915
  }
3871
3916
  });
3872
3917
  return snapshots;
3873
3918
  }
3874
- const persistence$3 = createDebouncedJsonPersistence({
3919
+ const persistence$5 = createDebouncedJsonPersistence({
3875
3920
  debounceMs: SAVE_DEBOUNCE_MS$3,
3876
3921
  filePath: getFilePath$1(),
3877
3922
  getValue: () => snapshots,
@@ -3890,21 +3935,21 @@ function getSnapshot(normalizedUrl) {
3890
3935
  }
3891
3936
  function saveSnapshot(rawUrl, title, textContent, headings) {
3892
3937
  const s = load$2();
3893
- const key = normalizeUrl(rawUrl);
3894
- const snapshot = {
3895
- url: key,
3938
+ const key2 = normalizeUrl(rawUrl);
3939
+ const snapshot2 = {
3940
+ url: key2,
3896
3941
  title,
3897
3942
  textContent: textContent.slice(0, MAX_TEXT_LENGTH),
3898
3943
  headings: headings.map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n"),
3899
3944
  capturedAt: (/* @__PURE__ */ new Date()).toISOString()
3900
3945
  };
3901
- s.delete(key);
3902
- s.set(key, snapshot);
3903
- persistence$3.schedule();
3904
- return snapshot;
3946
+ s.delete(key2);
3947
+ s.set(key2, snapshot2);
3948
+ persistence$5.schedule();
3949
+ return snapshot2;
3905
3950
  }
3906
3951
  function flushPersist$2() {
3907
- return persistence$3.flush();
3952
+ return persistence$5.flush();
3908
3953
  }
3909
3954
  const SEARCH_ENGINE_HOSTS = [
3910
3955
  "google.",
@@ -4219,11 +4264,11 @@ function sanitizeValue(value, depth = 0) {
4219
4264
  const record = asRecord(value);
4220
4265
  if (!record || depth >= MAX_DEPTH) return void 0;
4221
4266
  const objectValue = {};
4222
- for (const [key, entry] of Object.entries(record)) {
4223
- if (SKIP_FIELDS.has(key)) continue;
4267
+ for (const [key2, entry] of Object.entries(record)) {
4268
+ if (SKIP_FIELDS.has(key2)) continue;
4224
4269
  const normalized = sanitizeValue(entry, depth + 1);
4225
4270
  if (normalized !== void 0) {
4226
- objectValue[key] = normalized;
4271
+ objectValue[key2] = normalized;
4227
4272
  }
4228
4273
  }
4229
4274
  return Object.keys(objectValue).length > 0 ? objectValue : void 0;
@@ -4231,10 +4276,10 @@ function sanitizeValue(value, depth = 0) {
4231
4276
  function buildNormalizedAttributes(record, types) {
4232
4277
  const attributes = {};
4233
4278
  const consumed = /* @__PURE__ */ new Set();
4234
- const consume = (key, value) => {
4279
+ const consume = (key2, value) => {
4235
4280
  if (value === void 0) return;
4236
- consumed.add(key);
4237
- attributes[key] = value;
4281
+ consumed.add(key2);
4282
+ attributes[key2] = value;
4238
4283
  };
4239
4284
  if (types.includes("Recipe")) {
4240
4285
  consume("yield", sanitizeValue(record.recipeYield));
@@ -4278,14 +4323,14 @@ function buildNormalizedAttributes(record, types) {
4278
4323
  attributes.questions = questions;
4279
4324
  }
4280
4325
  }
4281
- for (const [key, value] of Object.entries(record)) {
4282
- if (SKIP_FIELDS.has(key) || consumed.has(key)) continue;
4283
- if (key === "name" || key === "headline" || key === "url" || key === "description") {
4326
+ for (const [key2, value] of Object.entries(record)) {
4327
+ if (SKIP_FIELDS.has(key2) || consumed.has(key2)) continue;
4328
+ if (key2 === "name" || key2 === "headline" || key2 === "url" || key2 === "description") {
4284
4329
  continue;
4285
4330
  }
4286
4331
  const normalized = sanitizeValue(value);
4287
4332
  if (normalized !== void 0) {
4288
- attributes[key] = normalized;
4333
+ attributes[key2] = normalized;
4289
4334
  }
4290
4335
  }
4291
4336
  return attributes;
@@ -4305,7 +4350,7 @@ function collectCandidateEntities(value, results = [], seen = /* @__PURE__ */ ne
4305
4350
  if (types.length > 0 || hasIdentity) {
4306
4351
  results.push(record);
4307
4352
  }
4308
- for (const key of [
4353
+ for (const key2 of [
4309
4354
  "@graph",
4310
4355
  "mainEntity",
4311
4356
  "mainEntityOfPage",
@@ -4318,7 +4363,7 @@ function collectCandidateEntities(value, results = [], seen = /* @__PURE__ */ ne
4318
4363
  "acceptedAnswer",
4319
4364
  "suggestedAnswer"
4320
4365
  ]) {
4321
- collectCandidateEntities(record[key], results, seen);
4366
+ collectCandidateEntities(record[key2], results, seen);
4322
4367
  }
4323
4368
  return results;
4324
4369
  }
@@ -4331,11 +4376,11 @@ function dedupeKey(entity) {
4331
4376
  JSON.stringify(entity.attributes)
4332
4377
  ].join("::");
4333
4378
  }
4334
- function extractEntitiesFromRecords(records, source) {
4335
- if (!records || records.length === 0) return [];
4379
+ function extractEntitiesFromRecords(records2, source) {
4380
+ if (!records2 || records2.length === 0) return [];
4336
4381
  const entities = [];
4337
4382
  const seen = /* @__PURE__ */ new Set();
4338
- for (const candidate of collectCandidateEntities(records)) {
4383
+ for (const candidate of collectCandidateEntities(records2)) {
4339
4384
  const types = getTypes(candidate);
4340
4385
  const name = firstString(candidate.name, candidate.headline);
4341
4386
  const url = firstString(candidate.url, candidate["@id"]);
@@ -4355,9 +4400,9 @@ function extractEntitiesFromRecords(records, source) {
4355
4400
  addIfPresent(entity, "name", name);
4356
4401
  addIfPresent(entity, "url", url);
4357
4402
  addIfPresent(entity, "description", description);
4358
- const key = dedupeKey(entity);
4359
- if (seen.has(key)) continue;
4360
- seen.add(key);
4403
+ const key2 = dedupeKey(entity);
4404
+ if (seen.has(key2)) continue;
4405
+ seen.add(key2);
4361
4406
  entities.push(entity);
4362
4407
  }
4363
4408
  return entities.slice(0, 25);
@@ -4380,13 +4425,13 @@ function extractEntityFromMetaTags(metaTags, pageTitle, pageUrl) {
4380
4425
  const url = metaTags["og:url"] || metaTags.canonical || pageUrl;
4381
4426
  const types = getMetaType(metaTags);
4382
4427
  const attributes = {};
4383
- for (const [key, value] of Object.entries(metaTags)) {
4384
- if (key === "og:title" || key === "twitter:title" || key === "title" || key === "og:description" || key === "description" || key === "twitter:description" || key === "og:url" || key === "canonical") {
4428
+ for (const [key2, value] of Object.entries(metaTags)) {
4429
+ if (key2 === "og:title" || key2 === "twitter:title" || key2 === "title" || key2 === "og:description" || key2 === "description" || key2 === "twitter:description" || key2 === "og:url" || key2 === "canonical") {
4385
4430
  continue;
4386
4431
  }
4387
4432
  const normalized = sanitizeValue(value);
4388
4433
  if (normalized !== void 0) {
4389
- attributes[key] = normalized;
4434
+ attributes[key2] = normalized;
4390
4435
  }
4391
4436
  }
4392
4437
  const entity = {
@@ -4439,9 +4484,9 @@ function extractStructuredDataFromJsonLd(jsonLd, microdata, rdfa, metaTags, page
4439
4484
  const deduped = [];
4440
4485
  const seen = /* @__PURE__ */ new Set();
4441
4486
  for (const entity of candidates) {
4442
- const key = dedupeKey(entity);
4443
- if (seen.has(key)) continue;
4444
- seen.add(key);
4487
+ const key2 = dedupeKey(entity);
4488
+ if (seen.has(key2)) continue;
4489
+ seen.add(key2);
4445
4490
  deduped.push(entity);
4446
4491
  }
4447
4492
  if (deduped.length > 0) {
@@ -4455,9 +4500,9 @@ function extractStructuredDataFromJsonLd(jsonLd, microdata, rdfa, metaTags, page
4455
4500
  pageHeadings
4456
4501
  );
4457
4502
  }
4458
- function addIfPresent(target, key, value) {
4503
+ function addIfPresent(target, key2, value) {
4459
4504
  if (value !== void 0) {
4460
- target[key] = value;
4505
+ target[key2] = value;
4461
4506
  }
4462
4507
  }
4463
4508
  function okResult(value) {
@@ -4714,6 +4759,7 @@ const POSTHOG_API_KEY = process.env.POSTHOG_API_KEY || "phc_OMeM3P5cxJwl14lOKxYa
4714
4759
  const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
4715
4760
  const BATCH_INTERVAL_MS = 6e4;
4716
4761
  const MAX_BATCH_SIZE = 50;
4762
+ const SENSITIVE_PROPERTY_RE = /url|uri|query|prompt|content|text|token|secret|key|password|credential|email|domain/i;
4717
4763
  function getDeviceIdPath() {
4718
4764
  return path.join(electron.app.getPath("userData"), ".vessel-device-id");
4719
4765
  }
@@ -4742,12 +4788,22 @@ function isEnabled() {
4742
4788
  if (process.env.VESSEL_DEV === "1") return false;
4743
4789
  return loadSettings().telemetryEnabled !== false;
4744
4790
  }
4791
+ function sanitizeTelemetryProperties(properties) {
4792
+ const safe = {};
4793
+ for (const [key2, value] of Object.entries(properties)) {
4794
+ if (SENSITIVE_PROPERTY_RE.test(key2)) continue;
4795
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
4796
+ safe[key2] = typeof value === "string" ? value.slice(0, 120) : value;
4797
+ }
4798
+ }
4799
+ return safe;
4800
+ }
4745
4801
  function trackEvent(event, properties = {}) {
4746
4802
  if (!isEnabled()) return;
4747
4803
  eventQueue.push({
4748
4804
  event,
4749
4805
  properties: {
4750
- ...properties,
4806
+ ...sanitizeTelemetryProperties(properties),
4751
4807
  premium_status: isPremium() ? "premium" : "free",
4752
4808
  app_version: electron.app.getVersion(),
4753
4809
  platform: process.platform,
@@ -4770,8 +4826,8 @@ function trackProviderConfigured(providerId) {
4770
4826
  provider_id: providerId
4771
4827
  });
4772
4828
  }
4773
- function trackSettingChanged(key) {
4774
- trackEvent("setting_changed", { setting_key: key });
4829
+ function trackSettingChanged(key2) {
4830
+ trackEvent("setting_changed", { setting_key: key2 });
4775
4831
  }
4776
4832
  function trackApprovalModeChanged(mode) {
4777
4833
  trackEvent("approval_mode_changed", { mode });
@@ -4782,8 +4838,8 @@ function trackBookmarkAction(action) {
4782
4838
  function trackVaultAction(action) {
4783
4839
  trackEvent("vault_action", { action });
4784
4840
  }
4785
- function trackExtractionFailed(domain, reason) {
4786
- trackEvent("extraction_failed", { domain, reason });
4841
+ function trackExtractionFailed(_domain, reason) {
4842
+ trackEvent("extraction_failed", { reason });
4787
4843
  }
4788
4844
  function trackPremiumFunnel(step, context) {
4789
4845
  trackEvent("premium_funnel", { step, ...context });
@@ -4953,10 +5009,10 @@ function mapFormFields(forms, interactiveElements) {
4953
5009
  }
4954
5010
  }
4955
5011
  for (const el of interactiveElements) {
4956
- const key = el.selector || el.name || el.label || String(el.index);
4957
- if (formFieldSelectors.has(key)) {
5012
+ const key2 = el.selector || el.name || el.label || String(el.index);
5013
+ if (formFieldSelectors.has(key2)) {
4958
5014
  fields.push({
4959
- name: el.name || el.label || key,
5015
+ name: el.name || el.label || key2,
4960
5016
  type: mapInputType(el),
4961
5017
  label: el.label,
4962
5018
  required: el.required,
@@ -6195,12 +6251,12 @@ function loadHistory() {
6195
6251
  return next;
6196
6252
  }
6197
6253
  });
6198
- for (const [key, bursts] of loaded.entries()) {
6199
- recentPageDiffBursts.set(key, bursts);
6254
+ for (const [key2, bursts] of loaded.entries()) {
6255
+ recentPageDiffBursts.set(key2, bursts);
6200
6256
  }
6201
6257
  return recentPageDiffBursts;
6202
6258
  }
6203
- const persistence$2 = createDebouncedJsonPersistence({
6259
+ const persistence$4 = createDebouncedJsonPersistence({
6204
6260
  debounceMs: SAVE_DEBOUNCE_MS$2,
6205
6261
  filePath: getHistoryFilePath(),
6206
6262
  getValue: () => recentPageDiffBursts,
@@ -6217,20 +6273,20 @@ function getLatestPageDiff(rawUrl) {
6217
6273
  }
6218
6274
  function getPageDiffBursts(rawUrl) {
6219
6275
  if (!shouldTrackSnapshotUrl(rawUrl)) return [];
6220
- const key = normalizeUrl(rawUrl);
6276
+ const key2 = normalizeUrl(rawUrl);
6221
6277
  const history = loadHistory();
6222
- const bursts = prunePageDiffHistory(history.get(key) ?? [], {
6278
+ const bursts = prunePageDiffHistory(history.get(key2) ?? [], {
6223
6279
  maxAgeDays: MAX_HISTORY_DAYS,
6224
6280
  maxItems: MAX_PERSISTED_DIFF_BURSTS
6225
6281
  });
6226
- const current = history.get(key) ?? [];
6282
+ const current = history.get(key2) ?? [];
6227
6283
  if (current.length !== bursts.length) {
6228
6284
  if (bursts.length > 0) {
6229
- history.set(key, bursts);
6285
+ history.set(key2, bursts);
6230
6286
  } else {
6231
- history.delete(key);
6287
+ history.delete(key2);
6232
6288
  }
6233
- persistence$2.schedule();
6289
+ persistence$4.schedule();
6234
6290
  }
6235
6291
  return bursts.slice().reverse();
6236
6292
  }
@@ -6238,20 +6294,20 @@ function summarizeDiffBurst(diff) {
6238
6294
  const items = diff.changes.slice(0, 2).map((change) => `${change.section}: ${change.summary}`);
6239
6295
  return items.join(" | ");
6240
6296
  }
6241
- function enrichWithBurstHistory(key, diff) {
6297
+ function enrichWithBurstHistory(key2, diff) {
6242
6298
  const detectedAt = (/* @__PURE__ */ new Date()).toISOString();
6243
6299
  const nextBurst = {
6244
6300
  detectedAt,
6245
6301
  summary: summarizeDiffBurst(diff)
6246
6302
  };
6247
6303
  const history = loadHistory();
6248
- const bursts = appendPageDiffHistoryItem(history.get(key) ?? [], nextBurst, {
6304
+ const bursts = appendPageDiffHistoryItem(history.get(key2) ?? [], nextBurst, {
6249
6305
  maxAgeDays: MAX_HISTORY_DAYS,
6250
6306
  maxItems: MAX_PERSISTED_DIFF_BURSTS,
6251
6307
  now: Date.parse(detectedAt)
6252
6308
  });
6253
- history.set(key, bursts);
6254
- persistence$2.schedule();
6309
+ history.set(key2, bursts);
6310
+ persistence$4.schedule();
6255
6311
  const recentBursts = bursts.slice(-5);
6256
6312
  return {
6257
6313
  ...diff,
@@ -6264,8 +6320,8 @@ function enrichWithBurstHistory(key, diff) {
6264
6320
  async function capturePageSnapshot(url, wc, sendToRendererViews) {
6265
6321
  try {
6266
6322
  if (!shouldTrackSnapshotUrl(url)) return;
6267
- const key = normalizeUrl(url);
6268
- const oldSnap = getSnapshot(key);
6323
+ const key2 = normalizeUrl(url);
6324
+ const oldSnap = getSnapshot(key2);
6269
6325
  const content = await extractContent(wc);
6270
6326
  const textContent = content.content || "";
6271
6327
  const title = content.title || "";
@@ -6274,14 +6330,14 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
6274
6330
  if (oldSnap) {
6275
6331
  const diff = diffSnapshots(oldSnap, textContent, title, currentHeadings);
6276
6332
  if (diff.hasChanges) {
6277
- const enrichedDiff = enrichWithBurstHistory(key, diff);
6278
- latestPageDiffs.set(key, enrichedDiff);
6333
+ const enrichedDiff = enrichWithBurstHistory(key2, diff);
6334
+ latestPageDiffs.set(key2, enrichedDiff);
6279
6335
  sendToRendererViews(Channels.PAGE_CHANGED, enrichedDiff);
6280
6336
  } else {
6281
- latestPageDiffs.delete(key);
6337
+ latestPageDiffs.delete(key2);
6282
6338
  }
6283
6339
  } else {
6284
- latestPageDiffs.delete(key);
6340
+ latestPageDiffs.delete(key2);
6285
6341
  }
6286
6342
  saveSnapshot(url, title, textContent, headings);
6287
6343
  } catch {
@@ -6333,19 +6389,19 @@ function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
6333
6389
  function enableClipboardShortcuts(view) {
6334
6390
  view.webContents.on("before-input-event", (event, input) => {
6335
6391
  if (!input.control && !input.meta) return;
6336
- const key = input.key.toLowerCase();
6392
+ const key2 = input.key.toLowerCase();
6337
6393
  const wc = view.webContents;
6338
6394
  if (input.type === "keyDown") {
6339
- if (key === "c") {
6395
+ if (key2 === "c") {
6340
6396
  wc.copy();
6341
6397
  event.preventDefault();
6342
- } else if (key === "v") {
6398
+ } else if (key2 === "v") {
6343
6399
  wc.paste();
6344
6400
  event.preventDefault();
6345
- } else if (key === "x") {
6401
+ } else if (key2 === "x") {
6346
6402
  wc.cut();
6347
6403
  event.preventDefault();
6348
- } else if (key === "a") {
6404
+ } else if (key2 === "a") {
6349
6405
  wc.selectAll();
6350
6406
  event.preventDefault();
6351
6407
  }
@@ -6759,15 +6815,15 @@ function onRuntimeHealthChange(listener) {
6759
6815
  };
6760
6816
  }
6761
6817
  function getMcpStatus() {
6762
- return state$2.mcp.status;
6818
+ return state$3.mcp.status;
6763
6819
  }
6764
6820
  function emitRuntimeHealthChange() {
6765
- const snapshot = getRuntimeHealth();
6821
+ const snapshot2 = getRuntimeHealth();
6766
6822
  for (const listener of runtimeHealthChangeListeners) {
6767
- listener(snapshot);
6823
+ listener(snapshot2);
6768
6824
  }
6769
6825
  }
6770
- const state$2 = {
6826
+ const state$3 = {
6771
6827
  userDataPath: "",
6772
6828
  settingsPath: "",
6773
6829
  startupIssues: [],
@@ -6780,43 +6836,43 @@ const state$2 = {
6780
6836
  }
6781
6837
  };
6782
6838
  function initializeRuntimeHealth(paths) {
6783
- state$2.userDataPath = paths.userDataPath;
6784
- state$2.settingsPath = paths.settingsPath;
6785
- state$2.mcp.configuredPort = paths.configuredPort;
6786
- state$2.mcp.activePort = null;
6787
- state$2.mcp.endpoint = null;
6788
- state$2.mcp.status = "stopped";
6789
- state$2.mcp.message = "MCP server has not started yet.";
6839
+ state$3.userDataPath = paths.userDataPath;
6840
+ state$3.settingsPath = paths.settingsPath;
6841
+ state$3.mcp.configuredPort = paths.configuredPort;
6842
+ state$3.mcp.activePort = null;
6843
+ state$3.mcp.endpoint = null;
6844
+ state$3.mcp.status = "stopped";
6845
+ state$3.mcp.message = "MCP server has not started yet.";
6790
6846
  emitRuntimeHealthChange();
6791
6847
  }
6792
6848
  function setStartupIssues(issues) {
6793
- state$2.startupIssues = issues.map((issue) => ({ ...issue }));
6849
+ state$3.startupIssues = issues.map((issue) => ({ ...issue }));
6794
6850
  emitRuntimeHealthChange();
6795
6851
  }
6796
6852
  function getRuntimeHealth() {
6797
6853
  return {
6798
- userDataPath: state$2.userDataPath,
6799
- settingsPath: state$2.settingsPath,
6800
- startupIssues: state$2.startupIssues.map((issue) => ({ ...issue })),
6801
- mcp: { ...state$2.mcp }
6854
+ userDataPath: state$3.userDataPath,
6855
+ settingsPath: state$3.settingsPath,
6856
+ startupIssues: state$3.startupIssues.map((issue) => ({ ...issue })),
6857
+ mcp: { ...state$3.mcp }
6802
6858
  };
6803
6859
  }
6804
6860
  function setMcpHealth(update) {
6805
6861
  if (typeof update.configuredPort === "number") {
6806
- state$2.mcp.configuredPort = update.configuredPort;
6862
+ state$3.mcp.configuredPort = update.configuredPort;
6807
6863
  }
6808
6864
  if ("activePort" in update) {
6809
- state$2.mcp.activePort = update.activePort ?? null;
6865
+ state$3.mcp.activePort = update.activePort ?? null;
6810
6866
  }
6811
6867
  if ("endpoint" in update) {
6812
- state$2.mcp.endpoint = update.endpoint ?? null;
6868
+ state$3.mcp.endpoint = update.endpoint ?? null;
6813
6869
  }
6814
- const prevStatus = state$2.mcp.status;
6815
- state$2.mcp.status = update.status;
6816
- state$2.mcp.message = update.message;
6817
- if (prevStatus !== state$2.mcp.status) {
6870
+ const prevStatus = state$3.mcp.status;
6871
+ state$3.mcp.status = update.status;
6872
+ state$3.mcp.message = update.message;
6873
+ if (prevStatus !== state$3.mcp.status) {
6818
6874
  for (const listener of mcpStatusChangeListeners) {
6819
- listener(state$2.mcp.status);
6875
+ listener(state$3.mcp.status);
6820
6876
  }
6821
6877
  }
6822
6878
  emitRuntimeHealthChange();
@@ -7728,8 +7784,8 @@ function scalarArgsForTool(name, scalar) {
7728
7784
  return null;
7729
7785
  }
7730
7786
  function firstStringArg(args, keys) {
7731
- for (const key of keys) {
7732
- const value = args[key];
7787
+ for (const key2 of keys) {
7788
+ const value = args[key2];
7733
7789
  if (typeof value === "string" && value.trim()) {
7734
7790
  return value.trim();
7735
7791
  }
@@ -8461,6 +8517,17 @@ class OpenAICompatProvider {
8461
8517
  this.abortController?.abort();
8462
8518
  }
8463
8519
  }
8520
+ async function openExternalAllowlisted(url, rule) {
8521
+ const parsed = new URL(url);
8522
+ const schemes = rule.schemes ?? ["https:"];
8523
+ if (!schemes.includes(parsed.protocol)) {
8524
+ throw new Error(`Blocked external URL scheme: ${parsed.protocol}`);
8525
+ }
8526
+ if (rule.hosts && !rule.hosts.includes(parsed.hostname)) {
8527
+ throw new Error(`Blocked external URL host: ${parsed.hostname}`);
8528
+ }
8529
+ await electron.shell.openExternal(parsed.toString());
8530
+ }
8464
8531
  const logger$g = createLogger("CodexOAuth");
8465
8532
  const ISSUER = "https://auth.openai.com";
8466
8533
  const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
@@ -8797,7 +8864,7 @@ async function startCodexOAuth(onStatus) {
8797
8864
  activeFlow.port = port;
8798
8865
  const authUrl = buildAuthorizeUrl(port, pkce, state2);
8799
8866
  safeOnStatus("waiting");
8800
- electron.shell.openExternal(authUrl).catch((err) => {
8867
+ openExternalAllowlisted(authUrl, { hosts: ["auth.openai.com"] }).catch((err) => {
8801
8868
  logger$g.warn("Failed to open browser, user will need the URL:", err);
8802
8869
  });
8803
8870
  }).catch(wrappedReject);
@@ -9020,8 +9087,8 @@ function extractLlamaCppCtxSize(payload) {
9020
9087
  const current = queue.shift();
9021
9088
  if (!current || typeof current !== "object" || visited.has(current)) continue;
9022
9089
  visited.add(current);
9023
- for (const [key, value] of Object.entries(current)) {
9024
- if (typeof value === "number" && Number.isFinite(value) && /^(n_ctx|ctx_size|context_size)$/i.test(key)) {
9090
+ for (const [key2, value] of Object.entries(current)) {
9091
+ if (typeof value === "number" && Number.isFinite(value) && /^(n_ctx|ctx_size|context_size)$/i.test(key2)) {
9025
9092
  return value;
9026
9093
  }
9027
9094
  if (value && typeof value === "object") {
@@ -9243,14 +9310,14 @@ function normalizeStoredRadioOption(option) {
9243
9310
  function dedupeCandidates(actions) {
9244
9311
  const seen = /* @__PURE__ */ new Set();
9245
9312
  return actions.filter((action) => {
9246
- const key = [
9313
+ const key2 = [
9247
9314
  action.selector || "",
9248
9315
  action.label || "",
9249
9316
  action.role || "",
9250
9317
  action.labelSource || ""
9251
9318
  ].join("::");
9252
- if (seen.has(key)) return false;
9253
- seen.add(key);
9319
+ if (seen.has(key2)) return false;
9320
+ seen.add(key2);
9254
9321
  return true;
9255
9322
  });
9256
9323
  }
@@ -9495,11 +9562,11 @@ function getQuantityElements(page) {
9495
9562
  ];
9496
9563
  return elements.filter((el) => {
9497
9564
  if (!isQuantityLike(el)) return false;
9498
- const key = String(
9565
+ const key2 = String(
9499
9566
  el.index ?? el.selector ?? `${el.type}|${el.name || ""}|${el.label || ""}|${el.value || ""}`
9500
9567
  );
9501
- if (seen.has(key)) return false;
9502
- seen.add(key);
9568
+ if (seen.has(key2)) return false;
9569
+ seen.add(key2);
9503
9570
  return true;
9504
9571
  });
9505
9572
  }
@@ -9541,9 +9608,9 @@ function getCartItemLinks(page) {
9541
9608
  return false;
9542
9609
  }
9543
9610
  if (blockedText.test(text) || blockedHref.test(href)) return false;
9544
- const key = `${normalizeComparable(text)}|${normalizeUrlForMatch(href) || href}`;
9545
- if (seen.has(key)) return false;
9546
- seen.add(key);
9611
+ const key2 = `${normalizeComparable(text)}|${normalizeUrlForMatch(href) || href}`;
9612
+ if (seen.has(key2)) return false;
9613
+ seen.add(key2);
9547
9614
  return true;
9548
9615
  }).slice(0, 12);
9549
9616
  }
@@ -9644,11 +9711,11 @@ function getPurchaseActionElements(page, options) {
9644
9711
  if (!isPurchaseActionElement(el)) return false;
9645
9712
  if (visibleOnly && !isVisibleToUser(el)) return false;
9646
9713
  if (el.blockedByOverlay) return false;
9647
- const key = String(
9714
+ const key2 = String(
9648
9715
  el.index ?? el.selector ?? `${el.type}|${el.text || ""}|${el.label || ""}|${el.href || ""}`
9649
9716
  );
9650
- if (seen.has(key)) return false;
9651
- seen.add(key);
9717
+ if (seen.has(key2)) return false;
9718
+ seen.add(key2);
9652
9719
  return true;
9653
9720
  }).sort((a, b) => {
9654
9721
  const delta = purchaseActionPriority(a) - purchaseActionPriority(b);
@@ -9665,10 +9732,10 @@ function getOffscreenPurchaseActionElements(page) {
9665
9732
  )
9666
9733
  );
9667
9734
  return getPurchaseActionElements(page, { visibleOnly: false }).filter((el) => {
9668
- const key = String(
9735
+ const key2 = String(
9669
9736
  el.index ?? el.selector ?? `${el.type}|${el.text || ""}|${el.label || ""}|${el.href || ""}`
9670
9737
  );
9671
- return !visibleKeys.has(key) && el.visible !== false;
9738
+ return !visibleKeys.has(key2) && el.visible !== false;
9672
9739
  });
9673
9740
  }
9674
9741
  function getDialogFocusedElements(page) {
@@ -9934,7 +10001,7 @@ function formatStructuredValue(value, depth = 0) {
9934
10001
  const rendered = value.map((item) => formatStructuredValue(item, depth + 1)).filter(Boolean).slice(0, depth === 0 ? 8 : 5);
9935
10002
  return rendered.join(depth === 0 ? ", " : " | ");
9936
10003
  }
9937
- const entries = Object.entries(value).slice(0, 6).map(([key, entry]) => `${key}: ${formatStructuredValue(entry, depth + 1)}`).filter((entry) => !entry.endsWith(": "));
10004
+ const entries = Object.entries(value).slice(0, 6).map(([key2, entry]) => `${key2}: ${formatStructuredValue(entry, depth + 1)}`).filter((entry) => !entry.endsWith(": "));
9938
10005
  return entries.join(", ");
9939
10006
  }
9940
10007
  function formatStructuredEntities(entities) {
@@ -9949,13 +10016,13 @@ function formatStructuredEntities(entities) {
9949
10016
  if (entity.url && entity.url !== entity.name) {
9950
10017
  lines.push(` url: ${entity.url}`);
9951
10018
  }
9952
- for (const [key, value] of Object.entries(entity.attributes).slice(
10019
+ for (const [key2, value] of Object.entries(entity.attributes).slice(
9953
10020
  0,
9954
10021
  8
9955
10022
  )) {
9956
10023
  const rendered = formatStructuredValue(value);
9957
10024
  if (rendered) {
9958
- lines.push(` ${key}: ${rendered}`);
10025
+ lines.push(` ${key2}: ${rendered}`);
9959
10026
  }
9960
10027
  }
9961
10028
  return lines.join("\n");
@@ -10053,17 +10120,17 @@ function formatJsonLd(items) {
10053
10120
  }
10054
10121
  return String(val);
10055
10122
  };
10056
- for (const key of priorityFields) {
10057
- if (key in item) {
10058
- seen.add(key);
10059
- const rendered = renderValue(item[key]);
10060
- if (rendered) lines.push(` ${key}: ${rendered}`);
10123
+ for (const key2 of priorityFields) {
10124
+ if (key2 in item) {
10125
+ seen.add(key2);
10126
+ const rendered = renderValue(item[key2]);
10127
+ if (rendered) lines.push(` ${key2}: ${rendered}`);
10061
10128
  }
10062
10129
  }
10063
- for (const [key, val] of Object.entries(item)) {
10064
- if (seen.has(key) || SKIP.has(key) || key === "@type") continue;
10130
+ for (const [key2, val] of Object.entries(item)) {
10131
+ if (seen.has(key2) || SKIP.has(key2) || key2 === "@type") continue;
10065
10132
  const rendered = renderValue(val);
10066
- if (rendered) lines.push(` ${key}: ${rendered}`);
10133
+ if (rendered) lines.push(` ${key2}: ${rendered}`);
10067
10134
  }
10068
10135
  lines.push("");
10069
10136
  }
@@ -10198,9 +10265,9 @@ function getResultCandidates(page) {
10198
10265
  );
10199
10266
  const seen = /* @__PURE__ */ new Set();
10200
10267
  return scored.map(({ element }) => element).filter((element) => {
10201
- const key = `${normalizeComparable(element.text || "")}|${normalizeUrlForMatch(element.href) || ""}`;
10202
- if (seen.has(key)) return false;
10203
- seen.add(key);
10268
+ const key2 = `${normalizeComparable(element.text || "")}|${normalizeUrlForMatch(element.href) || ""}`;
10269
+ if (seen.has(key2)) return false;
10270
+ seen.add(key2);
10204
10271
  return true;
10205
10272
  });
10206
10273
  }
@@ -10997,9 +11064,9 @@ function getCompactPrimaryResultLinks(page, options) {
10997
11064
  })).filter(({ score }) => score >= (listingLike ? 5 : 7)).sort(
10998
11065
  (a, b) => b.score - a.score || (a.element.index ?? Number.MAX_SAFE_INTEGER) - (b.element.index ?? Number.MAX_SAFE_INTEGER)
10999
11066
  ).map(({ element }) => element).filter((element) => {
11000
- const key = `${normalizeComparable(element.text)}|${normalizeUrlForMatch(element.href) || ""}`;
11001
- if (seen.has(key)) return false;
11002
- seen.add(key);
11067
+ const key2 = `${normalizeComparable(element.text)}|${normalizeUrlForMatch(element.href) || ""}`;
11068
+ if (seen.has(key2)) return false;
11069
+ seen.add(key2);
11003
11070
  return true;
11004
11071
  }).slice(0, max);
11005
11072
  }
@@ -11045,9 +11112,9 @@ function formatElement(element) {
11045
11112
  function uniqueElements(elements) {
11046
11113
  const seen = /* @__PURE__ */ new Set();
11047
11114
  return elements.filter((element) => {
11048
- const key = `${element.index ?? ""}|${element.type}|${elementLabel(element)}|${element.href ?? ""}`;
11049
- if (seen.has(key)) return false;
11050
- seen.add(key);
11115
+ const key2 = `${element.index ?? ""}|${element.type}|${elementLabel(element)}|${element.href ?? ""}`;
11116
+ if (seen.has(key2)) return false;
11117
+ seen.add(key2);
11051
11118
  return true;
11052
11119
  });
11053
11120
  }
@@ -12370,12 +12437,12 @@ function normalizeAgentHints(value) {
12370
12437
  return void 0;
12371
12438
  }
12372
12439
  const normalized = Object.fromEntries(
12373
- Object.entries(value).map(([key, hint]) => [key.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
12440
+ Object.entries(value).map(([key2, hint]) => [key2.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
12374
12441
  );
12375
12442
  return Object.keys(normalized).length > 0 ? normalized : void 0;
12376
12443
  }
12377
- function hasOwn(value, key) {
12378
- return Object.prototype.hasOwnProperty.call(value, key);
12444
+ function hasOwn(value, key2) {
12445
+ return Object.prototype.hasOwnProperty.call(value, key2);
12379
12446
  }
12380
12447
  function normalizeBookmarkMetadata(input) {
12381
12448
  const normalized = {};
@@ -12413,7 +12480,7 @@ const UNSORTED_ID = "unsorted";
12413
12480
  const ARCHIVE_FOLDER_NAME = "Archive";
12414
12481
  const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
12415
12482
  const SAVE_DEBOUNCE_MS$1 = 250;
12416
- let state$1 = null;
12483
+ let state$2 = null;
12417
12484
  const listeners = /* @__PURE__ */ new Set();
12418
12485
  function cloneState(current) {
12419
12486
  return {
@@ -12428,18 +12495,18 @@ function createPersistence() {
12428
12495
  return createDebouncedJsonPersistence({
12429
12496
  debounceMs: SAVE_DEBOUNCE_MS$1,
12430
12497
  filePath: getBookmarksPath(),
12431
- getValue: () => state$1,
12498
+ getValue: () => state$2,
12432
12499
  logLabel: "bookmarks"
12433
12500
  });
12434
12501
  }
12435
- let persistence$1 = null;
12502
+ let persistence$3 = null;
12436
12503
  function getPersistence() {
12437
- persistence$1 ??= createPersistence();
12438
- return persistence$1;
12504
+ persistence$3 ??= createPersistence();
12505
+ return persistence$3;
12439
12506
  }
12440
12507
  function load$1() {
12441
- if (state$1) return state$1;
12442
- state$1 = loadJsonFile({
12508
+ if (state$2) return state$2;
12509
+ state$2 = loadJsonFile({
12443
12510
  filePath: getBookmarksPath(),
12444
12511
  fallback: { folders: [], bookmarks: [] },
12445
12512
  parse: (raw) => {
@@ -12450,23 +12517,23 @@ function load$1() {
12450
12517
  };
12451
12518
  }
12452
12519
  });
12453
- return state$1;
12520
+ return state$2;
12454
12521
  }
12455
- function save() {
12522
+ function save$1() {
12456
12523
  getPersistence().schedule();
12457
12524
  }
12458
12525
  function assignDefinedBookmarkFields(bookmark, fields) {
12459
12526
  if (!fields) return;
12460
- for (const [key, value] of Object.entries(fields)) {
12527
+ for (const [key2, value] of Object.entries(fields)) {
12461
12528
  if (value === void 0) continue;
12462
- Object.assign(bookmark, { [key]: value });
12529
+ Object.assign(bookmark, { [key2]: value });
12463
12530
  }
12464
12531
  }
12465
- function emit() {
12466
- if (!state$1) return;
12467
- const snapshot = cloneState(state$1);
12532
+ function emit$2() {
12533
+ if (!state$2) return;
12534
+ const snapshot2 = cloneState(state$2);
12468
12535
  for (const listener of listeners) {
12469
- listener(snapshot);
12536
+ listener(snapshot2);
12470
12537
  }
12471
12538
  }
12472
12539
  function escapeBookmarkHtml(value) {
@@ -12483,7 +12550,7 @@ function getBookmarkDescription(bookmark) {
12483
12550
  bookmark.intent ? `Intent: ${bookmark.intent}` : "",
12484
12551
  bookmark.expectedContent ? `Expected content: ${bookmark.expectedContent}` : "",
12485
12552
  bookmark.keyFields?.length ? `Key fields: ${bookmark.keyFields.join(", ")}` : "",
12486
- bookmark.agentHints && Object.keys(bookmark.agentHints).length > 0 ? `Agent hints: ${Object.entries(bookmark.agentHints).map(([key, value]) => `${key}: ${value}`).join("; ")}` : ""
12553
+ bookmark.agentHints && Object.keys(bookmark.agentHints).length > 0 ? `Agent hints: ${Object.entries(bookmark.agentHints).map(([key2, value]) => `${key2}: ${value}`).join("; ")}` : ""
12487
12554
  ].filter(Boolean);
12488
12555
  return lines.join("\n");
12489
12556
  }
@@ -12586,28 +12653,28 @@ function subscribe(listener) {
12586
12653
  };
12587
12654
  }
12588
12655
  function clearAll() {
12589
- state$1 = { folders: [], bookmarks: [] };
12590
- save();
12591
- emit();
12656
+ state$2 = { folders: [], bookmarks: [] };
12657
+ save$1();
12658
+ emit$2();
12592
12659
  }
12593
12660
  function getBookmark(id) {
12594
12661
  load$1();
12595
- const bookmark = state$1.bookmarks.find((item) => item.id === id);
12662
+ const bookmark = state$2.bookmarks.find((item) => item.id === id);
12596
12663
  return bookmark ? { ...bookmark } : null;
12597
12664
  }
12598
12665
  function getBookmarkByUrl(url) {
12599
12666
  load$1();
12600
12667
  const normalized = url.trim();
12601
12668
  if (!normalized) return null;
12602
- const bookmark = [...state$1.bookmarks].reverse().find((item) => item.url === normalized);
12669
+ const bookmark = [...state$2.bookmarks].reverse().find((item) => item.url === normalized);
12603
12670
  return bookmark ? { ...bookmark } : null;
12604
12671
  }
12605
12672
  function getBookmarkByUrlInFolder(url, folderId) {
12606
12673
  load$1();
12607
12674
  const normalizedUrl = url.trim();
12608
12675
  if (!normalizedUrl) return null;
12609
- const targetFolderId = folderId && folderId !== UNSORTED_ID ? state$1.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
12610
- const bookmark = [...state$1.bookmarks].reverse().find(
12676
+ const targetFolderId = folderId && folderId !== UNSORTED_ID ? state$2.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
12677
+ const bookmark = [...state$2.bookmarks].reverse().find(
12611
12678
  (item) => item.url === normalizedUrl && item.folderId === targetFolderId
12612
12679
  );
12613
12680
  return bookmark ? { ...bookmark } : null;
@@ -12615,14 +12682,14 @@ function getBookmarkByUrlInFolder(url, folderId) {
12615
12682
  function getFolder(id) {
12616
12683
  load$1();
12617
12684
  if (!id || id === UNSORTED_ID) return null;
12618
- const folder = state$1.folders.find((item) => item.id === id);
12685
+ const folder = state$2.folders.find((item) => item.id === id);
12619
12686
  return folder ? { ...folder } : null;
12620
12687
  }
12621
12688
  function findFolderByName(name) {
12622
12689
  load$1();
12623
12690
  const normalized = name.trim().toLowerCase();
12624
12691
  if (!normalized || normalized === "unsorted") return null;
12625
- const folder = state$1.folders.find(
12692
+ const folder = state$2.folders.find(
12626
12693
  (item) => item.name.trim().toLowerCase() === normalized
12627
12694
  );
12628
12695
  return folder ? { ...folder } : null;
@@ -12630,7 +12697,7 @@ function findFolderByName(name) {
12630
12697
  function listFolderOverviews() {
12631
12698
  load$1();
12632
12699
  const counts = /* @__PURE__ */ new Map();
12633
- for (const bookmark of state$1.bookmarks) {
12700
+ for (const bookmark of state$2.bookmarks) {
12634
12701
  counts.set(bookmark.folderId, (counts.get(bookmark.folderId) ?? 0) + 1);
12635
12702
  }
12636
12703
  return [
@@ -12639,7 +12706,7 @@ function listFolderOverviews() {
12639
12706
  name: "Unsorted",
12640
12707
  count: counts.get(UNSORTED_ID) ?? 0
12641
12708
  },
12642
- ...state$1.folders.map((folder) => ({
12709
+ ...state$2.folders.map((folder) => ({
12643
12710
  id: folder.id,
12644
12711
  name: folder.name,
12645
12712
  summary: folder.summary,
@@ -12650,8 +12717,8 @@ function listFolderOverviews() {
12650
12717
  function searchBookmarks(query) {
12651
12718
  load$1();
12652
12719
  if (!query.trim()) return [];
12653
- return state$1.bookmarks.map((bookmark) => {
12654
- const folder = state$1.folders.find(
12720
+ return state$2.bookmarks.map((bookmark) => {
12721
+ const folder = state$2.folders.find(
12655
12722
  (item) => item.id === bookmark.folderId
12656
12723
  );
12657
12724
  const { matchedFields, score } = getBookmarkSearchMatch({
@@ -12689,9 +12756,9 @@ function createFolderWithSummary(name, summary) {
12689
12756
  summary: summary?.trim() || void 0,
12690
12757
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
12691
12758
  };
12692
- state$1.folders.push(folder);
12693
- save();
12694
- emit();
12759
+ state$2.folders.push(folder);
12760
+ save$1();
12761
+ emit$2();
12695
12762
  return folder;
12696
12763
  }
12697
12764
  function ensureFolder(name, summary) {
@@ -12720,7 +12787,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
12720
12787
  throw new Error("Bookmark URL cannot be empty");
12721
12788
  }
12722
12789
  const normalizedTitle = title.trim() || normalizedUrl;
12723
- const targetId = folderId && folderId !== UNSORTED_ID ? state$1.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
12790
+ const targetId = folderId && folderId !== UNSORTED_ID ? state$2.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
12724
12791
  const duplicatePolicy = options?.onDuplicate ?? "ask";
12725
12792
  const existing = getBookmarkByUrlInFolder(normalizedUrl, targetId);
12726
12793
  if (existing) {
@@ -12731,7 +12798,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
12731
12798
  };
12732
12799
  }
12733
12800
  if (duplicatePolicy === "update") {
12734
- const bookmark2 = state$1.bookmarks.find((item) => item.id === existing.id);
12801
+ const bookmark2 = state$2.bookmarks.find((item) => item.id === existing.id);
12735
12802
  if (!bookmark2) {
12736
12803
  return {
12737
12804
  status: "conflict",
@@ -12744,8 +12811,8 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
12744
12811
  }
12745
12812
  assignDefinedBookmarkFields(bookmark2, options?.extra);
12746
12813
  bookmark2.savedAt = (/* @__PURE__ */ new Date()).toISOString();
12747
- save();
12748
- emit();
12814
+ save$1();
12815
+ emit$2();
12749
12816
  return {
12750
12817
  status: "updated",
12751
12818
  bookmark: { ...bookmark2 }
@@ -12761,9 +12828,9 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
12761
12828
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
12762
12829
  ...options?.extra
12763
12830
  };
12764
- state$1.bookmarks.push(bookmark);
12765
- save();
12766
- emit();
12831
+ state$2.bookmarks.push(bookmark);
12832
+ save$1();
12833
+ emit$2();
12767
12834
  return {
12768
12835
  status: "created",
12769
12836
  bookmark
@@ -12771,18 +12838,18 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
12771
12838
  }
12772
12839
  function removeBookmark(id) {
12773
12840
  load$1();
12774
- const before = state$1.bookmarks.length;
12775
- state$1.bookmarks = state$1.bookmarks.filter((b) => b.id !== id);
12776
- if (state$1.bookmarks.length !== before) {
12777
- save();
12778
- emit();
12841
+ const before = state$2.bookmarks.length;
12842
+ state$2.bookmarks = state$2.bookmarks.filter((b) => b.id !== id);
12843
+ if (state$2.bookmarks.length !== before) {
12844
+ save$1();
12845
+ emit$2();
12779
12846
  return true;
12780
12847
  }
12781
12848
  return false;
12782
12849
  }
12783
12850
  function updateBookmark(id, updates) {
12784
12851
  load$1();
12785
- const bookmark = state$1.bookmarks.find((item) => item.id === id);
12852
+ const bookmark = state$2.bookmarks.find((item) => item.id === id);
12786
12853
  if (!bookmark) return null;
12787
12854
  const metadataUpdates = normalizeBookmarkMetadataUpdate({
12788
12855
  intent: updates.intent,
@@ -12799,7 +12866,7 @@ function updateBookmark(id, updates) {
12799
12866
  bookmark.note = trimmed || void 0;
12800
12867
  }
12801
12868
  if (typeof updates.folderId === "string") {
12802
- bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$1.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
12869
+ bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$2.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
12803
12870
  }
12804
12871
  if ("intent" in metadataUpdates) {
12805
12872
  bookmark.intent = metadataUpdates.intent;
@@ -12816,36 +12883,36 @@ function updateBookmark(id, updates) {
12816
12883
  if ("agentHints" in metadataUpdates) {
12817
12884
  bookmark.agentHints = metadataUpdates.agentHints;
12818
12885
  }
12819
- save();
12820
- emit();
12886
+ save$1();
12887
+ emit$2();
12821
12888
  return { ...bookmark };
12822
12889
  }
12823
12890
  function removeFolder(id, deleteContents = false) {
12824
12891
  load$1();
12825
- const exists = state$1.folders.some((f) => f.id === id);
12892
+ const exists = state$2.folders.some((f) => f.id === id);
12826
12893
  if (!exists) return false;
12827
12894
  if (deleteContents) {
12828
- state$1.bookmarks = state$1.bookmarks.filter((b) => b.folderId !== id);
12895
+ state$2.bookmarks = state$2.bookmarks.filter((b) => b.folderId !== id);
12829
12896
  } else {
12830
- state$1.bookmarks = state$1.bookmarks.map(
12897
+ state$2.bookmarks = state$2.bookmarks.map(
12831
12898
  (b) => b.folderId === id ? { ...b, folderId: UNSORTED_ID } : b
12832
12899
  );
12833
12900
  }
12834
- state$1.folders = state$1.folders.filter((f) => f.id !== id);
12835
- save();
12836
- emit();
12901
+ state$2.folders = state$2.folders.filter((f) => f.id !== id);
12902
+ save$1();
12903
+ emit$2();
12837
12904
  return true;
12838
12905
  }
12839
12906
  function renameFolder(id, newName, summary) {
12840
12907
  load$1();
12841
- const folder = state$1.folders.find((f) => f.id === id);
12908
+ const folder = state$2.folders.find((f) => f.id === id);
12842
12909
  if (!folder) return null;
12843
12910
  const trimmed = newName.trim();
12844
12911
  if (!trimmed) return null;
12845
12912
  folder.name = trimmed;
12846
12913
  folder.summary = summary?.trim() || void 0;
12847
- save();
12848
- emit();
12914
+ save$1();
12915
+ emit$2();
12849
12916
  return { ...folder };
12850
12917
  }
12851
12918
  function flushPersist$1() {
@@ -12856,7 +12923,7 @@ function importBookmarksFromHtml(content) {
12856
12923
  let skipped = 0;
12857
12924
  let errors = 0;
12858
12925
  load$1();
12859
- const existingUrls = new Set(state$1.bookmarks.map((b) => b.url));
12926
+ const existingUrls = new Set(state$2.bookmarks.map((b) => b.url));
12860
12927
  let currentFolderId = UNSORTED_ID;
12861
12928
  let currentFolderName = "Imported";
12862
12929
  const lines = content.split("\n");
@@ -12865,7 +12932,7 @@ function importBookmarksFromHtml(content) {
12865
12932
  const folderMatch = line.match(/<DT><H3[^>]*>([^<]+)<\/H3>/i);
12866
12933
  if (folderMatch) {
12867
12934
  currentFolderName = folderMatch[1].trim();
12868
- const existing = state$1.folders.find(
12935
+ const existing = state$2.folders.find(
12869
12936
  (f) => f.name.toLowerCase() === currentFolderName.toLowerCase()
12870
12937
  );
12871
12938
  if (existing) {
@@ -12876,7 +12943,7 @@ function importBookmarksFromHtml(content) {
12876
12943
  name: currentFolderName,
12877
12944
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
12878
12945
  };
12879
- state$1.folders.push(folder);
12946
+ state$2.folders.push(folder);
12880
12947
  currentFolderId = folder.id;
12881
12948
  }
12882
12949
  continue;
@@ -12897,7 +12964,7 @@ function importBookmarksFromHtml(content) {
12897
12964
  else errors++;
12898
12965
  continue;
12899
12966
  }
12900
- state$1.bookmarks.push({
12967
+ state$2.bookmarks.push({
12901
12968
  id: crypto$1.randomUUID(),
12902
12969
  url,
12903
12970
  title,
@@ -12909,8 +12976,8 @@ function importBookmarksFromHtml(content) {
12909
12976
  }
12910
12977
  }
12911
12978
  if (imported > 0) {
12912
- save();
12913
- emit();
12979
+ save$1();
12980
+ emit$2();
12914
12981
  }
12915
12982
  return { imported, skipped, errors };
12916
12983
  }
@@ -12923,14 +12990,14 @@ function importBookmarksFromJson(content) {
12923
12990
  const incomingFolders = Array.isArray(parsed?.folders) ? parsed.folders : [];
12924
12991
  const incomingBookmarks = Array.isArray(parsed?.bookmarks) ? parsed.bookmarks : [];
12925
12992
  load$1();
12926
- const existingUrls = new Set(state$1.bookmarks.map((b) => b.url));
12993
+ const existingUrls = new Set(state$2.bookmarks.map((b) => b.url));
12927
12994
  const folderIdMap = /* @__PURE__ */ new Map();
12928
12995
  for (const folder of incomingFolders) {
12929
12996
  if (!folder?.id || !folder?.name) {
12930
12997
  errors++;
12931
12998
  continue;
12932
12999
  }
12933
- const existing = state$1.folders.find(
13000
+ const existing = state$2.folders.find(
12934
13001
  (f) => f.name.toLowerCase() === folder.name.toLowerCase()
12935
13002
  );
12936
13003
  if (existing) {
@@ -12942,7 +13009,7 @@ function importBookmarksFromJson(content) {
12942
13009
  summary: folder.summary?.trim() || void 0,
12943
13010
  createdAt: folder.createdAt || (/* @__PURE__ */ new Date()).toISOString()
12944
13011
  };
12945
- state$1.folders.push(newFolder);
13012
+ state$2.folders.push(newFolder);
12946
13013
  folderIdMap.set(folder.id, newFolder.id);
12947
13014
  }
12948
13015
  }
@@ -12956,7 +13023,7 @@ function importBookmarksFromJson(content) {
12956
13023
  continue;
12957
13024
  }
12958
13025
  const mappedFolderId = bookmark.folderId ? folderIdMap.get(bookmark.folderId) ?? UNSORTED_ID : UNSORTED_ID;
12959
- state$1.bookmarks.push({
13026
+ state$2.bookmarks.push({
12960
13027
  id: crypto$1.randomUUID(),
12961
13028
  url: bookmark.url.trim(),
12962
13029
  title: typeof bookmark.title === "string" ? bookmark.title.trim() : bookmark.url,
@@ -12968,8 +13035,8 @@ function importBookmarksFromJson(content) {
12968
13035
  imported++;
12969
13036
  }
12970
13037
  if (imported > 0 || errors > 0) {
12971
- save();
12972
- emit();
13038
+ save$1();
13039
+ emit$2();
12973
13040
  }
12974
13041
  } catch {
12975
13042
  errors++;
@@ -12987,7 +13054,7 @@ async function captureLiveHighlightSnapshot(wc, savedHighlights = []) {
12987
13054
  savedHighlights.map((highlight) => normalizeText(highlight.text)).filter(Boolean)
12988
13055
  );
12989
13056
  try {
12990
- const snapshot = await wc.executeJavaScript(`(() => {
13057
+ const snapshot2 = await wc.executeJavaScript(`(() => {
12991
13058
  const selection = window.getSelection?.()?.toString().trim() || "";
12992
13059
  const pageHighlights = Array.from(
12993
13060
  document.querySelectorAll("mark.__vessel-highlight-text[data-vessel-highlight]")
@@ -13009,36 +13076,36 @@ async function captureLiveHighlightSnapshot(wc, savedHighlights = []) {
13009
13076
  };
13010
13077
  })()`, true);
13011
13078
  const seen = /* @__PURE__ */ new Set();
13012
- const pageHighlights = (snapshot.pageHighlights ?? []).map((highlight) => ({
13079
+ const pageHighlights = (snapshot2.pageHighlights ?? []).map((highlight) => ({
13013
13080
  text: normalizeText(highlight.text),
13014
13081
  color: highlight.color?.trim() || void 0
13015
13082
  })).filter((highlight) => highlight.text.length > 0).filter((highlight) => {
13016
- const key = `${highlight.text}\0${highlight.color ?? ""}`;
13017
- if (seen.has(key)) return false;
13018
- seen.add(key);
13083
+ const key2 = `${highlight.text}\0${highlight.color ?? ""}`;
13084
+ if (seen.has(key2)) return false;
13085
+ seen.add(key2);
13019
13086
  return true;
13020
13087
  }).map((highlight) => ({
13021
13088
  ...highlight,
13022
13089
  persisted: savedTexts.has(highlight.text)
13023
13090
  }));
13024
- const activeSelection = normalizeText(snapshot.activeSelection) || void 0;
13091
+ const activeSelection = normalizeText(snapshot2.activeSelection) || void 0;
13025
13092
  return { activeSelection, pageHighlights };
13026
13093
  } catch {
13027
13094
  return { pageHighlights: [] };
13028
13095
  }
13029
13096
  }
13030
- function formatLiveSelectionSection(snapshot) {
13097
+ function formatLiveSelectionSection(snapshot2) {
13031
13098
  const sections = [];
13032
- if (snapshot.activeSelection) {
13033
- const preview = snapshot.activeSelection.length > 400 ? `${snapshot.activeSelection.slice(0, 397)}...` : snapshot.activeSelection;
13099
+ if (snapshot2.activeSelection) {
13100
+ const preview = snapshot2.activeSelection.length > 400 ? `${snapshot2.activeSelection.slice(0, 397)}...` : snapshot2.activeSelection;
13034
13101
  sections.push(
13035
13102
  `## Active User Selection
13036
13103
  The user currently has this text selected on screen:
13037
13104
  - "${preview}"`
13038
13105
  );
13039
13106
  }
13040
- if (snapshot.pageHighlights.length > 0) {
13041
- const lines = snapshot.pageHighlights.map((highlight) => {
13107
+ if (snapshot2.pageHighlights.length > 0) {
13108
+ const lines = snapshot2.pageHighlights.map((highlight) => {
13042
13109
  const preview = highlight.text.length > 180 ? `${highlight.text.slice(0, 177)}...` : highlight.text;
13043
13110
  const details = [
13044
13111
  highlight.persisted ? "saved" : "visible only",
@@ -13364,17 +13431,17 @@ function sessionFileName(name) {
13364
13431
  function getSessionPath(name) {
13365
13432
  return path$1.join(ensureSessionsDir(), sessionFileName(name));
13366
13433
  }
13367
- function writeSessionFile(filePath, data) {
13434
+ function writeSessionFile(filePath2, data) {
13368
13435
  fs$1.writeFileSync(
13369
- filePath,
13436
+ filePath2,
13370
13437
  JSON.stringify({ version: SESSION_VERSION, ...data }, null, 2),
13371
13438
  { encoding: "utf-8", mode: 384 }
13372
13439
  );
13373
- fs$1.chmodSync(filePath, 384);
13440
+ fs$1.chmodSync(filePath2, 384);
13374
13441
  }
13375
- function readSessionFile(filePath) {
13442
+ function readSessionFile(filePath2) {
13376
13443
  try {
13377
- const raw = fs$1.readFileSync(filePath, "utf-8");
13444
+ const raw = fs$1.readFileSync(filePath2, "utf-8");
13378
13445
  const parsed = JSON.parse(raw);
13379
13446
  if (!parsed || typeof parsed.name !== "string") {
13380
13447
  return null;
@@ -13539,7 +13606,7 @@ async function saveNamedSession(tabManager, name) {
13539
13606
  const entries = await captureLocalStorageForOrigin(tabManager, origin);
13540
13607
  localStorage.push({ origin, entries });
13541
13608
  }
13542
- const snapshot = tabManager.snapshotSession(`Named session: ${normalizedName}`);
13609
+ const snapshot2 = tabManager.snapshotSession(`Named session: ${normalizedName}`);
13543
13610
  const domains = [...new Set(cookies.map((cookie) => cookie.domain))].sort();
13544
13611
  const now = (/* @__PURE__ */ new Date()).toISOString();
13545
13612
  const data = {
@@ -13551,7 +13618,7 @@ async function saveNamedSession(tabManager, name) {
13551
13618
  domains,
13552
13619
  cookies,
13553
13620
  localStorage,
13554
- snapshot
13621
+ snapshot: snapshot2
13555
13622
  };
13556
13623
  writeSessionFile(getSessionPath(normalizedName), data);
13557
13624
  return {
@@ -13595,9 +13662,9 @@ async function loadNamedSession(tabManager, name) {
13595
13662
  };
13596
13663
  }
13597
13664
  function deleteNamedSession(name) {
13598
- const filePath = getSessionPath(name);
13599
- if (!fs$1.existsSync(filePath)) return false;
13600
- fs$1.unlinkSync(filePath);
13665
+ const filePath2 = getSessionPath(name);
13666
+ if (!fs$1.existsSync(filePath2)) return false;
13667
+ fs$1.unlinkSync(filePath2);
13601
13668
  return true;
13602
13669
  }
13603
13670
  function isInvalidTextTargetQuery(rawQuery) {
@@ -13906,8 +13973,8 @@ function compactSearchLikeResult(text) {
13906
13973
  return limitText(cleaned, 16, 1400);
13907
13974
  }
13908
13975
  const summary = cleaned.slice(0, markerIndex).trim();
13909
- const snapshot = cleaned.slice(markerIndex + marker.length).trim();
13910
- return [summary, compactReadPageResult(snapshot)].filter(Boolean).join("\n\n");
13976
+ const snapshot2 = cleaned.slice(markerIndex + marker.length).trim();
13977
+ return [summary, compactReadPageResult(snapshot2)].filter(Boolean).join("\n\n");
13911
13978
  }
13912
13979
  function compactCurrentTabResult(text) {
13913
13980
  try {
@@ -14956,7 +15023,7 @@ async function inspectElement(wc, selector, limit = 8) {
14956
15023
  return lines.join("\n");
14957
15024
  }
14958
15025
  async function getLocaleSnapshot(wc) {
14959
- const snapshot = await executePageScript(
15026
+ const snapshot2 = await executePageScript(
14960
15027
  wc,
14961
15028
  `
14962
15029
  (function() {
@@ -14975,13 +15042,13 @@ async function getLocaleSnapshot(wc) {
14975
15042
  label: "locale snapshot"
14976
15043
  }
14977
15044
  );
14978
- if (!snapshot || snapshot === PAGE_SCRIPT_TIMEOUT || typeof snapshot !== "object") {
15045
+ if (!snapshot2 || snapshot2 === PAGE_SCRIPT_TIMEOUT || typeof snapshot2 !== "object") {
14979
15046
  return null;
14980
15047
  }
14981
15048
  return {
14982
- lang: typeof snapshot.lang === "string" ? snapshot.lang.trim() : "",
14983
- url: typeof snapshot.url === "string" ? snapshot.url : wc.getURL(),
14984
- title: typeof snapshot.title === "string" ? snapshot.title : wc.getTitle()
15049
+ lang: typeof snapshot2.lang === "string" ? snapshot2.lang.trim() : "",
15050
+ url: typeof snapshot2.url === "string" ? snapshot2.url : wc.getURL(),
15051
+ title: typeof snapshot2.title === "string" ? snapshot2.title : wc.getTitle()
14985
15052
  };
14986
15053
  }
14987
15054
  function primaryLanguageTag(value) {
@@ -14997,31 +15064,31 @@ function localeChanged(before, after) {
14997
15064
  const localeHint = /[?&](lang|locale|language|hl)=|\/(ja|jp|en|fr|de|es|it|ko|zh)(\/|$)/i;
14998
15065
  return before.url !== after.url && localeHint.test(after.url);
14999
15066
  }
15000
- async function restoreLocaleSnapshot(wc, snapshot) {
15001
- if (!snapshot || wc.isDestroyed()) return;
15067
+ async function restoreLocaleSnapshot(wc, snapshot2) {
15068
+ if (!snapshot2 || wc.isDestroyed()) return;
15002
15069
  try {
15003
15070
  if (typeof wc.canGoBack === "function" && wc.canGoBack()) {
15004
15071
  wc.goBack();
15005
15072
  await waitForLoad(wc, 3e3);
15006
15073
  const reverted = await getLocaleSnapshot(wc);
15007
- if (!localeChanged(snapshot, reverted)) {
15074
+ if (!localeChanged(snapshot2, reverted)) {
15008
15075
  return;
15009
15076
  }
15010
15077
  }
15011
15078
  } catch (err) {
15012
15079
  logger$c.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
15013
15080
  }
15014
- if (snapshot.url && snapshot.url !== wc.getURL()) {
15081
+ if (snapshot2.url && snapshot2.url !== wc.getURL()) {
15015
15082
  try {
15016
- assertSafeURL(snapshot.url);
15017
- await wc.loadURL(snapshot.url);
15083
+ assertSafeURL(snapshot2.url);
15084
+ await wc.loadURL(snapshot2.url);
15018
15085
  await waitForLoad(wc, 3e3);
15019
15086
  return;
15020
15087
  } catch (err) {
15021
15088
  logger$c.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
15022
15089
  }
15023
15090
  }
15024
- if (snapshot.url) {
15091
+ if (snapshot2.url) {
15025
15092
  try {
15026
15093
  await wc.reload();
15027
15094
  await waitForLoad(wc, 3e3);
@@ -15054,9 +15121,9 @@ function isAddToCartText(text) {
15054
15121
  }
15055
15122
  function recordCartClick(url, text) {
15056
15123
  recentCartClicks.set(url, { text, ts: Date.now() });
15057
- for (const [key, entry] of recentCartClicks) {
15124
+ for (const [key2, entry] of recentCartClicks) {
15058
15125
  if (Date.now() - entry.ts > CART_CLICK_COOLDOWN_MS) {
15059
- recentCartClicks.delete(key);
15126
+ recentCartClicks.delete(key2);
15060
15127
  }
15061
15128
  }
15062
15129
  }
@@ -15103,9 +15170,9 @@ function normalizeCartProductKey(url) {
15103
15170
  }
15104
15171
  }
15105
15172
  function pruneCartAddedProducts(now = Date.now()) {
15106
- for (const [key, entry] of cartAddedProducts) {
15173
+ for (const [key2, entry] of cartAddedProducts) {
15107
15174
  if (now - entry.ts > CART_ADDED_TTL_MS) {
15108
- cartAddedProducts.delete(key);
15175
+ cartAddedProducts.delete(key2);
15109
15176
  }
15110
15177
  }
15111
15178
  }
@@ -15131,7 +15198,7 @@ function isProductAlreadyInCart(url) {
15131
15198
  function getCartAddedSummary(url) {
15132
15199
  pruneCartAddedProducts();
15133
15200
  const origin = cartOrigin(url);
15134
- const items = Array.from(cartAddedProducts.entries()).filter(([key]) => !origin || key.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
15201
+ const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
15135
15202
  if (!items) return "";
15136
15203
  const count = items.split("\n").length;
15137
15204
  return `
@@ -16787,8 +16854,8 @@ async function submitForm(wc, args) {
16787
16854
  }
16788
16855
  return "Submitted form";
16789
16856
  }
16790
- async function pressKeyDirect(wc, key, index, selector) {
16791
- return pressKey(wc, { key, index, selector });
16857
+ async function pressKeyDirect(wc, key2, index, selector) {
16858
+ return pressKey(wc, { key: key2, index, selector });
16792
16859
  }
16793
16860
  async function submitFormDirect(wc, index, selector) {
16794
16861
  return submitForm(wc, { index, selector });
@@ -17154,8 +17221,8 @@ async function searchPage(wc, args) {
17154
17221
  return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
17155
17222
  }
17156
17223
  async function pressKey(wc, args) {
17157
- const key = typeof args.key === "string" ? args.key.trim() : "";
17158
- if (!key) return "Error: No key provided";
17224
+ const key2 = typeof args.key === "string" ? args.key.trim() : "";
17225
+ if (!key2) return "Error: No key provided";
17159
17226
  const selector = await resolveSelector(wc, args.index, args.selector);
17160
17227
  const focusResult = await executePageScript(
17161
17228
  wc,
@@ -17193,16 +17260,16 @@ async function pressKey(wc, args) {
17193
17260
  return focusResult.error;
17194
17261
  }
17195
17262
  wc.focus();
17196
- const normalizedKey = key.length === 1 ? key : key[0].toUpperCase() + key.slice(1);
17263
+ const normalizedKey = key2.length === 1 ? key2 : key2[0].toUpperCase() + key2.slice(1);
17197
17264
  const electronKeyCode = normalizedKey === "Enter" ? "Return" : normalizedKey === "ArrowUp" ? "Up" : normalizedKey === "ArrowDown" ? "Down" : normalizedKey === "ArrowLeft" ? "Left" : normalizedKey === "ArrowRight" ? "Right" : normalizedKey;
17198
17265
  wc.sendInputEvent({ type: "keyDown", keyCode: electronKeyCode });
17199
- if (key.length === 1) {
17200
- wc.sendInputEvent({ type: "char", keyCode: key });
17266
+ if (key2.length === 1) {
17267
+ wc.sendInputEvent({ type: "char", keyCode: key2 });
17201
17268
  }
17202
17269
  await sleep(16);
17203
17270
  wc.sendInputEvent({ type: "keyUp", keyCode: electronKeyCode });
17204
17271
  const label = "label" in focusResult && typeof focusResult.label === "string" ? focusResult.label : null;
17205
- return label ? `Pressed key: ${key} on ${label}` : `Pressed key: ${key}`;
17272
+ return label ? `Pressed key: ${key2} on ${label}` : `Pressed key: ${key2}`;
17206
17273
  }
17207
17274
  async function getPostActionState$1(ctx, name) {
17208
17275
  const tab = ctx.tabManager.getActiveTab();
@@ -17612,8 +17679,8 @@ async function executeAction(name, args, ctx) {
17612
17679
  if (!wc) return "Error: No active tab";
17613
17680
  const beforeUrl = wc.getURL();
17614
17681
  const result2 = await pressKey(wc, args);
17615
- const key = typeof args.key === "string" ? args.key.trim() : "";
17616
- if (key === "Enter") {
17682
+ const key2 = typeof args.key === "string" ? args.key.trim() : "";
17683
+ if (key2 === "Enter") {
17617
17684
  await waitForPotentialNavigation(wc, beforeUrl, 3e3);
17618
17685
  const afterUrl = wc.getURL();
17619
17686
  if (afterUrl !== beforeUrl) {
@@ -18718,17 +18785,17 @@ function escapeYaml(value) {
18718
18785
  }
18719
18786
  function renderFrontmatter(data) {
18720
18787
  const lines = ["---"];
18721
- for (const [key, value] of Object.entries(data)) {
18788
+ for (const [key2, value] of Object.entries(data)) {
18722
18789
  if (value == null) continue;
18723
18790
  if (Array.isArray(value)) {
18724
18791
  if (value.length === 0) continue;
18725
- lines.push(`${key}:`);
18792
+ lines.push(`${key2}:`);
18726
18793
  for (const item of value) {
18727
18794
  lines.push(` - ${escapeYaml(item)}`);
18728
18795
  }
18729
18796
  continue;
18730
18797
  }
18731
- lines.push(`${key}: ${escapeYaml(value)}`);
18798
+ lines.push(`${key2}: ${escapeYaml(value)}`);
18732
18799
  }
18733
18800
  lines.push("---", "");
18734
18801
  return lines.join("\n");
@@ -18781,11 +18848,11 @@ function parseFrontmatter(content) {
18781
18848
  activeArrayKey = "";
18782
18849
  const separatorIndex = trimmed.indexOf(":");
18783
18850
  if (separatorIndex === -1) continue;
18784
- const key = trimmed.slice(0, separatorIndex).trim();
18851
+ const key2 = trimmed.slice(0, separatorIndex).trim();
18785
18852
  const value = trimmed.slice(separatorIndex + 1).trim();
18786
- if (key === "title" && value) {
18853
+ if (key2 === "title" && value) {
18787
18854
  result.title = value.replace(/^["']|["']$/g, "");
18788
- } else if (key === "tags") {
18855
+ } else if (key2 === "tags") {
18789
18856
  activeArrayKey = "tags";
18790
18857
  if (value.startsWith("[") && value.endsWith("]")) {
18791
18858
  const inline = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
@@ -18971,9 +19038,9 @@ function capturePageToVault({
18971
19038
  if (page.excerpt.trim()) {
18972
19039
  bodyLines.push("## Excerpt", "", page.excerpt.trim(), "");
18973
19040
  }
18974
- const snapshot = trimContent(page.content);
18975
- if (snapshot) {
18976
- bodyLines.push("## Page Snapshot", "", snapshot, "");
19041
+ const snapshot2 = trimContent(page.content);
19042
+ if (snapshot2) {
19043
+ bodyLines.push("## Page Snapshot", "", snapshot2, "");
18977
19044
  }
18978
19045
  return writeMemoryNote({
18979
19046
  title: noteTitle,
@@ -19371,15 +19438,15 @@ Exception: ${result.exceptionDetails}`);
19371
19438
  value: zod.z.string().nullable().describe("Value to set, or null to remove the key")
19372
19439
  }
19373
19440
  },
19374
- async ({ type, key, value }) => {
19441
+ async ({ type, key: key2, value }) => {
19375
19442
  return withDevToolsAction(
19376
19443
  runtime2,
19377
19444
  tabManager,
19378
19445
  "devtools_set_storage",
19379
- { type, key, value: value ? value.slice(0, 100) : null },
19446
+ { type, key: key2, value: value ? value.slice(0, 100) : null },
19380
19447
  async () => {
19381
19448
  const session = getOrCreateSession(tabManager);
19382
- return session.setStorage(type, key, value);
19449
+ return session.setStorage(type, key2, value);
19383
19450
  }
19384
19451
  );
19385
19452
  }
@@ -19398,8 +19465,8 @@ Exception: ${result.exceptionDetails}`);
19398
19465
  {},
19399
19466
  async () => {
19400
19467
  const session = getOrCreateSession(tabManager);
19401
- const snapshot = await session.getPerformanceSnapshot();
19402
- return JSON.stringify(snapshot, null, 2);
19468
+ const snapshot2 = await session.getPerformanceSnapshot();
19469
+ return JSON.stringify(snapshot2, null, 2);
19403
19470
  }
19404
19471
  );
19405
19472
  }
@@ -19471,11 +19538,12 @@ function getOrCreateEncryptionKey(keyFilename) {
19471
19538
  const encryptedKey = fs$1.readFileSync(keyPath);
19472
19539
  return Buffer.from(electron.safeStorage.decryptString(encryptedKey), "utf-8");
19473
19540
  }
19474
- const key = crypto$2.randomBytes(32);
19541
+ const key2 = crypto$2.randomBytes(32);
19475
19542
  fs$1.mkdirSync(path$1.dirname(keyPath), { recursive: true });
19476
- const encrypted = electron.safeStorage.encryptString(key.toString("utf-8"));
19543
+ const encrypted = electron.safeStorage.encryptString(key2.toString("utf-8"));
19477
19544
  fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
19478
- return key;
19545
+ fs$1.chmodSync(keyPath, 384);
19546
+ return key2;
19479
19547
  }
19480
19548
  function createEncryptDecrypt(keyFilename) {
19481
19549
  let cachedKey = null;
@@ -19484,9 +19552,9 @@ function createEncryptDecrypt(keyFilename) {
19484
19552
  return cachedKey;
19485
19553
  }
19486
19554
  function encrypt2(plaintext) {
19487
- const key = getKey();
19555
+ const key2 = getKey();
19488
19556
  const iv = crypto$2.randomBytes(IV_LENGTH);
19489
- const cipher = crypto$2.createCipheriv(ALGORITHM, key, iv, {
19557
+ const cipher = crypto$2.createCipheriv(ALGORITHM, key2, iv, {
19490
19558
  authTagLength: AUTH_TAG_LENGTH
19491
19559
  });
19492
19560
  const encrypted = Buffer.concat([
@@ -19497,11 +19565,11 @@ function createEncryptDecrypt(keyFilename) {
19497
19565
  return Buffer.concat([iv, authTag, encrypted]);
19498
19566
  }
19499
19567
  function decrypt2(data) {
19500
- const key = getKey();
19568
+ const key2 = getKey();
19501
19569
  const iv = data.subarray(0, IV_LENGTH);
19502
19570
  const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
19503
19571
  const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
19504
- const decipher = crypto$2.createDecipheriv(ALGORITHM, key, iv, {
19572
+ const decipher = crypto$2.createDecipheriv(ALGORITHM, key2, iv, {
19505
19573
  authTagLength: AUTH_TAG_LENGTH
19506
19574
  });
19507
19575
  decipher.setAuthTag(authTag);
@@ -19548,15 +19616,21 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
19548
19616
  }
19549
19617
  return { loadVault: loadVault2, saveVault: saveVault2, resetCache };
19550
19618
  }
19551
- function domainMatches(pattern, hostname) {
19552
- const p = pattern.toLowerCase().trim();
19553
- const h = hostname.toLowerCase().trim();
19554
- if (p === h) return true;
19555
- if (p.startsWith("*.")) {
19556
- const suffix = p.slice(2);
19557
- return h === suffix || h.endsWith("." + suffix);
19619
+ function normalizeCredentialHost(value) {
19620
+ try {
19621
+ const parsed = new URL(value.includes("://") ? value : `https://${value}`);
19622
+ return parsed.hostname.toLowerCase().replace(/^www\./, "");
19623
+ } catch {
19624
+ const normalized = value.toLowerCase().trim().replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/.*$/, "");
19625
+ return normalized && !normalized.includes(" ") ? normalized : null;
19558
19626
  }
19559
- return false;
19627
+ }
19628
+ function domainMatches(pattern, hostname) {
19629
+ const isWildcard = pattern.trim().startsWith("*.");
19630
+ const p = normalizeCredentialHost(isWildcard ? pattern.slice(2) : pattern);
19631
+ const h = normalizeCredentialHost(hostname);
19632
+ if (!p || !h) return false;
19633
+ return isWildcard ? h.endsWith("." + p) : p === h;
19560
19634
  }
19561
19635
  function generateTotpCode(secret) {
19562
19636
  const epoch = Math.floor(Date.now() / 1e3);
@@ -19627,12 +19701,8 @@ function listEntries$1() {
19627
19701
  return loadVault$1().map(({ password, totpSecret, ...rest }) => rest);
19628
19702
  }
19629
19703
  function findEntriesForDomain(url) {
19630
- let hostname;
19631
- try {
19632
- hostname = new URL(url).hostname;
19633
- } catch {
19634
- return [];
19635
- }
19704
+ const hostname = normalizeCredentialHost(url);
19705
+ if (!hostname) return [];
19636
19706
  return loadVault$1().filter((e) => domainMatches(e.domainPattern, hostname)).map(({ password, totpSecret, ...rest }) => rest);
19637
19707
  }
19638
19708
  function addEntry(entry) {
@@ -19755,12 +19825,7 @@ const auditLog = createAuditLog(
19755
19825
  AUDIT_MAX_ENTRIES
19756
19826
  );
19757
19827
  function extractDomain(url) {
19758
- try {
19759
- const parsed = new URL(url.startsWith("http") ? url : `https://${url}`);
19760
- return parsed.hostname.toLowerCase();
19761
- } catch {
19762
- return url.toLowerCase().replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/.*$/, "");
19763
- }
19828
+ return normalizeCredentialHost(url) ?? "";
19764
19829
  }
19765
19830
  function listEntries() {
19766
19831
  return loadVault().map(({ password, totpSecret, ...rest }) => rest);
@@ -19938,13 +20003,14 @@ function getPersistentMcpAuthToken() {
19938
20003
  }
19939
20004
  function writeMcpAuthFile(endpoint, token) {
19940
20005
  try {
19941
- const filePath = getMcpAuthFilePath();
19942
- fs$1.mkdirSync(path$1.dirname(filePath), { recursive: true });
20006
+ const filePath2 = getMcpAuthFilePath();
20007
+ fs$1.mkdirSync(path$1.dirname(filePath2), { recursive: true });
19943
20008
  fs$1.writeFileSync(
19944
- filePath,
20009
+ filePath2,
19945
20010
  JSON.stringify({ endpoint, token, pid: process.pid }, null, 2) + "\n",
19946
20011
  { mode: 384 }
19947
20012
  );
20013
+ fs$1.chmodSync(filePath2, 384);
19948
20014
  } catch (err) {
19949
20015
  logger$9.warn("Failed to write auth file:", err);
19950
20016
  }
@@ -19959,10 +20025,10 @@ function clearMcpAuthFile() {
19959
20025
  return;
19960
20026
  }
19961
20027
  try {
19962
- const filePath = getMcpAuthFilePath();
19963
- fs$1.mkdirSync(path$1.dirname(filePath), { recursive: true });
20028
+ const filePath2 = getMcpAuthFilePath();
20029
+ fs$1.mkdirSync(path$1.dirname(filePath2), { recursive: true });
19964
20030
  fs$1.writeFileSync(
19965
- filePath,
20031
+ filePath2,
19966
20032
  JSON.stringify(
19967
20033
  { endpoint: "", token: existingToken, pid: null },
19968
20034
  null,
@@ -19970,10 +20036,18 @@ function clearMcpAuthFile() {
19970
20036
  ) + "\n",
19971
20037
  { mode: 384 }
19972
20038
  );
20039
+ fs$1.chmodSync(filePath2, 384);
19973
20040
  } catch (err) {
19974
20041
  logger$9.warn("Failed to clear auth file:", err);
19975
20042
  }
19976
20043
  }
20044
+ function regenerateMcpAuthToken() {
20045
+ const endpoint = getRuntimeHealth().mcp.endpoint;
20046
+ if (!httpServer || !endpoint) return null;
20047
+ mcpAuthToken = crypto$2.randomBytes(32).toString("hex");
20048
+ writeMcpAuthFile(endpoint, mcpAuthToken);
20049
+ return { endpoint };
20050
+ }
19977
20051
  function asTextResponse(text) {
19978
20052
  return { content: [{ type: "text", text }] };
19979
20053
  }
@@ -19999,6 +20073,11 @@ function asPromptResponse(text) {
19999
20073
  function isDangerousMcpAction(name) {
20000
20074
  return name === "close_tab" || isDangerousAction(name);
20001
20075
  }
20076
+ function requiresExplicitMcpApproval(name, args) {
20077
+ if (name === "delete_session" || name === "close_tab" || name === "load_session") return true;
20078
+ if (name === "remove_folder" && args.delete_contents === true) return true;
20079
+ return false;
20080
+ }
20002
20081
  function getActiveTabSummary(tabManager) {
20003
20082
  const activeTab = tabManager.getActiveTab();
20004
20083
  const activeTabId = tabManager.getActiveTabId();
@@ -20087,6 +20166,7 @@ async function withAction(runtime2, tabManager, name, args, executor) {
20087
20166
  args,
20088
20167
  tabId: tabManager.getActiveTabId(),
20089
20168
  dangerous: isDangerousMcpAction(name),
20169
+ requiresApproval: requiresExplicitMcpApproval(name, args),
20090
20170
  executor
20091
20171
  });
20092
20172
  const stateInfo = await getPostActionState(tabManager, name);
@@ -20978,19 +21058,19 @@ ${buildScopedContext(pageContent, mode)}`;
20978
21058
  selector: zod.z.string().optional().describe("CSS selector to focus first")
20979
21059
  }
20980
21060
  },
20981
- async ({ key, index, selector }) => {
21061
+ async ({ key: key2, index, selector }) => {
20982
21062
  const tab = tabManager.getActiveTab();
20983
21063
  if (!tab) return asNoActiveTabResponse();
20984
21064
  return withAction(
20985
21065
  runtime2,
20986
21066
  tabManager,
20987
21067
  "press_key",
20988
- { key, index, selector },
21068
+ { key: key2, index, selector },
20989
21069
  async () => {
20990
21070
  const wc = tab.view.webContents;
20991
21071
  const beforeUrl = wc.getURL();
20992
- const result = await pressKeyDirect(wc, key, index, selector);
20993
- if (key === "Enter") {
21072
+ const result = await pressKeyDirect(wc, key2, index, selector);
21073
+ if (key2 === "Enter") {
20994
21074
  await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
20995
21075
  const afterUrl = wc.getURL();
20996
21076
  if (afterUrl !== beforeUrl) {
@@ -21456,7 +21536,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
21456
21536
  )
21457
21537
  }
21458
21538
  },
21459
- async ({ index, selector, text, label, durationMs, persist, color }) => {
21539
+ async ({ index, selector, text, label, durationMs, persist: persist2, color }) => {
21460
21540
  const tab = tabManager.getActiveTab();
21461
21541
  if (!tab) return asNoActiveTabResponse();
21462
21542
  const normalizedText = normalizeLooseString(text);
@@ -21470,7 +21550,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
21470
21550
  text: normalizedText,
21471
21551
  label,
21472
21552
  durationMs,
21473
- persist,
21553
+ persist: persist2,
21474
21554
  color
21475
21555
  },
21476
21556
  async () => {
@@ -21484,7 +21564,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
21484
21564
  durationMs,
21485
21565
  color
21486
21566
  );
21487
- if (persist && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
21567
+ if (persist2 && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
21488
21568
  const url = normalizeUrl$1(wc.getURL());
21489
21569
  addHighlight(
21490
21570
  url,
@@ -24110,6 +24190,16 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
24110
24190
  return true;
24111
24191
  });
24112
24192
  }
24193
+ const trustedIpcSenderIds = /* @__PURE__ */ new Set();
24194
+ function registerTrustedIpcSender(wc) {
24195
+ trustedIpcSenderIds.add(wc.id);
24196
+ wc.once("destroyed", () => trustedIpcSenderIds.delete(wc.id));
24197
+ }
24198
+ function assertTrustedIpcSender(event) {
24199
+ if (!trustedIpcSenderIds.has(event.sender.id)) {
24200
+ throw new Error("Blocked IPC from untrusted renderer");
24201
+ }
24202
+ }
24113
24203
  function assertString(value, name) {
24114
24204
  if (typeof value !== "string") throw new Error(`${name} must be a string`);
24115
24205
  }
@@ -24149,7 +24239,7 @@ const PROFILE_FIELDS = [
24149
24239
  "postalCode",
24150
24240
  "country"
24151
24241
  ];
24152
- let state = null;
24242
+ let state$1 = null;
24153
24243
  function getFilePath() {
24154
24244
  return path.join(electron.app.getPath("userData"), "vessel-autofill.json");
24155
24245
  }
@@ -24174,8 +24264,8 @@ function normalizeStoredProfile(value) {
24174
24264
  return profile;
24175
24265
  }
24176
24266
  function load() {
24177
- if (state) return state;
24178
- state = loadJsonFile({
24267
+ if (state$1) return state$1;
24268
+ state$1 = loadJsonFile({
24179
24269
  filePath: getFilePath(),
24180
24270
  fallback: getDefaultState(),
24181
24271
  secure: true,
@@ -24186,12 +24276,12 @@ function load() {
24186
24276
  };
24187
24277
  }
24188
24278
  });
24189
- return state;
24279
+ return state$1;
24190
24280
  }
24191
- const persistence = createDebouncedJsonPersistence({
24281
+ const persistence$2 = createDebouncedJsonPersistence({
24192
24282
  debounceMs: SAVE_DEBOUNCE_MS,
24193
24283
  filePath: getFilePath(),
24194
- getValue: () => state,
24284
+ getValue: () => state$1,
24195
24285
  logLabel: "autofill",
24196
24286
  secure: true
24197
24287
  });
@@ -24211,7 +24301,7 @@ function addProfile(input) {
24211
24301
  updatedAt: now
24212
24302
  };
24213
24303
  s.profiles.push(profile);
24214
- persistence.schedule();
24304
+ persistence$2.schedule();
24215
24305
  return profile;
24216
24306
  }
24217
24307
  function updateProfile(id, updates) {
@@ -24223,7 +24313,7 @@ function updateProfile(id, updates) {
24223
24313
  ...updates,
24224
24314
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24225
24315
  };
24226
- persistence.schedule();
24316
+ persistence$2.schedule();
24227
24317
  return s.profiles[idx];
24228
24318
  }
24229
24319
  function deleteProfile(id) {
@@ -24231,11 +24321,11 @@ function deleteProfile(id) {
24231
24321
  const len = s.profiles.length;
24232
24322
  s.profiles = s.profiles.filter((p) => p.id !== id);
24233
24323
  if (s.profiles.length === len) return false;
24234
- persistence.schedule();
24324
+ persistence$2.schedule();
24235
24325
  return true;
24236
24326
  }
24237
24327
  function flushPersist() {
24238
- return persistence.flush();
24328
+ return persistence$2.flush();
24239
24329
  }
24240
24330
  const AUTOCOMPLETE_MAP = {
24241
24331
  "given-name": "firstName",
@@ -24364,12 +24454,12 @@ function matchField(el, profile) {
24364
24454
  if (inputType === "hidden" || inputType === "submit" || inputType === "button" || inputType === "file" || inputType === "image") return null;
24365
24455
  if (inputType === "password" || inputType === "checkbox" || inputType === "radio") return null;
24366
24456
  if (el.autocomplete) {
24367
- const key = el.autocomplete.replace(/section-\w+\s+/, "").replace(/^shipping\s+|^billing\s+/, "");
24368
- if (key === "name" || key === "additional-name") {
24457
+ const key2 = el.autocomplete.replace(/section-\w+\s+/, "").replace(/^shipping\s+|^billing\s+/, "");
24458
+ if (key2 === "name" || key2 === "additional-name") {
24369
24459
  const fullName = getFullName(profile);
24370
24460
  if (fullName) return mk(fullName, 100, "autocomplete", "fullName");
24371
24461
  }
24372
- const pk = AUTOCOMPLETE_MAP[key];
24462
+ const pk = AUTOCOMPLETE_MAP[key2];
24373
24463
  if (pk && profile[pk]) return mk(profile[pk], 100, "autocomplete", pk);
24374
24464
  }
24375
24465
  if (INPUT_TYPE_MAP[inputType]) {
@@ -24525,13 +24615,26 @@ function registerAutofillHandlers(windowState) {
24525
24615
  });
24526
24616
  }
24527
24617
  function registerPageDiffHandlers(windowState, sendToRendererViews) {
24528
- electron.ipcMain.handle(Channels.PAGE_DIFF_GET, () => {
24618
+ const pageEventBuckets = /* @__PURE__ */ new Map();
24619
+ const allowPageEvent = (webContentsId) => {
24620
+ const now = Date.now();
24621
+ const bucket = pageEventBuckets.get(webContentsId);
24622
+ if (!bucket || bucket.resetAt <= now) {
24623
+ pageEventBuckets.set(webContentsId, { count: 1, resetAt: now + 1e3 });
24624
+ return true;
24625
+ }
24626
+ bucket.count += 1;
24627
+ return bucket.count <= 20;
24628
+ };
24629
+ electron.ipcMain.handle(Channels.PAGE_DIFF_GET, (event) => {
24630
+ assertTrustedIpcSender(event);
24529
24631
  const activeTab = windowState.tabManager.getActiveTab();
24530
24632
  const wc = activeTab?.view.webContents;
24531
24633
  if (!wc) return null;
24532
24634
  return getLatestPageDiff(wc.getURL());
24533
24635
  });
24534
- electron.ipcMain.handle(Channels.PAGE_DIFF_HISTORY, () => {
24636
+ electron.ipcMain.handle(Channels.PAGE_DIFF_HISTORY, (event) => {
24637
+ assertTrustedIpcSender(event);
24535
24638
  try {
24536
24639
  if (!isPremiumActiveState(getPremiumState())) {
24537
24640
  return { error: "Premium required" };
@@ -24547,21 +24650,25 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
24547
24650
  electron.ipcMain.on(Channels.PAGE_DIFF_ACTIVITY, (event) => {
24548
24651
  const wc = event.sender;
24549
24652
  if (!wc || wc.isDestroyed()) return;
24653
+ if (!allowPageEvent(wc.id)) return;
24550
24654
  notePageMutationActivity(wc, sendToRendererViews);
24551
24655
  });
24552
24656
  electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
24553
24657
  const wc = event.sender;
24554
24658
  if (!wc || wc.isDestroyed()) return;
24659
+ if (!allowPageEvent(wc.id)) return;
24555
24660
  schedulePageSnapshotCapture(wc, sendToRendererViews);
24556
24661
  });
24557
24662
  }
24558
24663
  function registerVaultHandlers() {
24559
- electron.ipcMain.handle(Channels.VAULT_LIST, () => {
24664
+ electron.ipcMain.handle(Channels.VAULT_LIST, (event) => {
24665
+ assertTrustedIpcSender(event);
24560
24666
  return listEntries$1();
24561
24667
  });
24562
24668
  electron.ipcMain.handle(
24563
24669
  Channels.VAULT_ADD,
24564
- (_, entry) => {
24670
+ (event, entry) => {
24671
+ assertTrustedIpcSender(event);
24565
24672
  if (!entry || typeof entry !== "object") {
24566
24673
  throw new Error("Invalid vault entry");
24567
24674
  }
@@ -24586,7 +24693,8 @@ function registerVaultHandlers() {
24586
24693
  );
24587
24694
  electron.ipcMain.handle(
24588
24695
  Channels.VAULT_UPDATE,
24589
- (_, id, updates) => {
24696
+ (event, id, updates) => {
24697
+ assertTrustedIpcSender(event);
24590
24698
  assertString(id, "id");
24591
24699
  if (!updates || typeof updates !== "object") {
24592
24700
  throw new Error("Invalid updates");
@@ -24594,12 +24702,14 @@ function registerVaultHandlers() {
24594
24702
  return updateEntry$1(id, updates) !== null;
24595
24703
  }
24596
24704
  );
24597
- electron.ipcMain.handle(Channels.VAULT_REMOVE, (_, id) => {
24705
+ electron.ipcMain.handle(Channels.VAULT_REMOVE, (event, id) => {
24706
+ assertTrustedIpcSender(event);
24598
24707
  assertString(id, "id");
24599
24708
  trackVaultAction("credential_removed");
24600
24709
  return removeEntry$1(id);
24601
24710
  });
24602
- electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (_, limit) => {
24711
+ electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (event, limit) => {
24712
+ assertTrustedIpcSender(event);
24603
24713
  return readAuditLog$1(limit);
24604
24714
  });
24605
24715
  }
@@ -24618,17 +24728,20 @@ function normalizeTags(value) {
24618
24728
  return tags.length > 0 ? tags : void 0;
24619
24729
  }
24620
24730
  function registerHumanVaultHandlers() {
24621
- electron.ipcMain.handle(Channels.HUMAN_VAULT_LIST, (_, domain) => {
24731
+ electron.ipcMain.handle(Channels.HUMAN_VAULT_LIST, (event, domain) => {
24732
+ assertTrustedIpcSender(event);
24622
24733
  if (domain !== void 0) assertString(domain, "domain");
24623
24734
  return domain ? findForDomain(domain) : listEntries();
24624
24735
  });
24625
- electron.ipcMain.handle(Channels.HUMAN_VAULT_GET, (_, id) => {
24736
+ electron.ipcMain.handle(Channels.HUMAN_VAULT_GET, (event, id) => {
24737
+ assertTrustedIpcSender(event);
24626
24738
  assertString(id, "id");
24627
24739
  return getEntrySafe(id);
24628
24740
  });
24629
24741
  electron.ipcMain.handle(
24630
24742
  Channels.HUMAN_VAULT_SAVE,
24631
- (_, input) => {
24743
+ (event, input) => {
24744
+ assertTrustedIpcSender(event);
24632
24745
  if (!input || typeof input !== "object") {
24633
24746
  throw new Error("Invalid credential entry");
24634
24747
  }
@@ -24658,7 +24771,8 @@ function registerHumanVaultHandlers() {
24658
24771
  );
24659
24772
  electron.ipcMain.handle(
24660
24773
  Channels.HUMAN_VAULT_UPDATE,
24661
- (_, id, updates) => {
24774
+ (event, id, updates) => {
24775
+ assertTrustedIpcSender(event);
24662
24776
  assertString(id, "id");
24663
24777
  if (!updates || typeof updates !== "object") {
24664
24778
  throw new Error("Invalid updates");
@@ -24700,26 +24814,31 @@ function registerHumanVaultHandlers() {
24700
24814
  return safe;
24701
24815
  }
24702
24816
  );
24703
- electron.ipcMain.handle(Channels.HUMAN_VAULT_REMOVE, (_, id) => {
24817
+ electron.ipcMain.handle(Channels.HUMAN_VAULT_REMOVE, (event, id) => {
24818
+ assertTrustedIpcSender(event);
24704
24819
  assertString(id, "id");
24705
24820
  return removeEntry(id);
24706
24821
  });
24707
- electron.ipcMain.handle(Channels.HUMAN_VAULT_AUDIT_LOG, (_, limit) => {
24822
+ electron.ipcMain.handle(Channels.HUMAN_VAULT_AUDIT_LOG, (event, limit) => {
24823
+ assertTrustedIpcSender(event);
24708
24824
  return readAuditLog(limit);
24709
24825
  });
24710
24826
  }
24711
24827
  function registerWindowControlHandlers(mainWindow) {
24712
- electron.ipcMain.handle(Channels.WINDOW_MINIMIZE, () => {
24828
+ electron.ipcMain.handle(Channels.WINDOW_MINIMIZE, (event) => {
24829
+ assertTrustedIpcSender(event);
24713
24830
  mainWindow.minimize();
24714
24831
  });
24715
- electron.ipcMain.handle(Channels.WINDOW_MAXIMIZE, () => {
24832
+ electron.ipcMain.handle(Channels.WINDOW_MAXIMIZE, (event) => {
24833
+ assertTrustedIpcSender(event);
24716
24834
  if (mainWindow.isMaximized()) {
24717
24835
  mainWindow.unmaximize();
24718
24836
  } else {
24719
24837
  mainWindow.maximize();
24720
24838
  }
24721
24839
  });
24722
- electron.ipcMain.handle(Channels.WINDOW_CLOSE, () => {
24840
+ electron.ipcMain.handle(Channels.WINDOW_CLOSE, (event) => {
24841
+ assertTrustedIpcSender(event);
24723
24842
  mainWindow.close();
24724
24843
  });
24725
24844
  }
@@ -24857,6 +24976,102 @@ function installAdBlockingForSession(ses, tabManager) {
24857
24976
  callback(getAdBlockDecision(details));
24858
24977
  });
24859
24978
  }
24979
+ const filePath$1 = () => path$1.join(electron.app.getPath("userData"), "vessel-downloads.json");
24980
+ const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
24981
+ ".appimage",
24982
+ ".bat",
24983
+ ".cmd",
24984
+ ".command",
24985
+ ".desktop",
24986
+ ".exe",
24987
+ ".msi",
24988
+ ".ps1",
24989
+ ".scr",
24990
+ ".sh"
24991
+ ]);
24992
+ function hasMisleadingDoubleExtension(filename) {
24993
+ return /\.(pdf|docx?|xlsx?|pptx?|png|jpe?g|gif|txt|zip)\.(exe|msi|bat|cmd|ps1|sh|scr|appimage)$/i.test(filename);
24994
+ }
24995
+ function isExecutableDownload(savePath) {
24996
+ return EXECUTABLE_EXTENSIONS.has(path$1.extname(savePath).toLowerCase());
24997
+ }
24998
+ function executableWarningDetail(item) {
24999
+ return [
25000
+ "This file can run code on your computer. Only open it if you trust the source.",
25001
+ item.url ? `Source: ${item.url}` : null,
25002
+ item.mimeType ? `Type: ${item.mimeType}` : null,
25003
+ hasMisleadingDoubleExtension(item.filename) ? "Warning: this filename uses a misleading double extension." : null
25004
+ ].filter(Boolean).join("\n");
25005
+ }
25006
+ function parse(raw) {
25007
+ if (!raw || typeof raw !== "object") return { items: [] };
25008
+ const items = Array.isArray(raw.items) ? raw.items : [];
25009
+ return { items };
25010
+ }
25011
+ let state = loadJsonFile({ filePath: filePath$1(), fallback: { items: [] }, parse });
25012
+ const persistence$1 = createDebouncedJsonPersistence({
25013
+ debounceMs: 250,
25014
+ filePath: filePath$1(),
25015
+ getValue: () => state,
25016
+ logLabel: "downloads"
25017
+ });
25018
+ let broadcaster$1 = null;
25019
+ function persist() {
25020
+ state.items = state.items.slice(0, 200);
25021
+ persistence$1.schedule();
25022
+ }
25023
+ function emit$1() {
25024
+ broadcaster$1?.(Channels.DOWNLOADS_UPDATE, listDownloads());
25025
+ }
25026
+ function setDownloadBroadcaster(fn) {
25027
+ broadcaster$1 = fn;
25028
+ }
25029
+ function listDownloads() {
25030
+ return state.items.map((item) => ({ ...item }));
25031
+ }
25032
+ function upsertDownload(input) {
25033
+ const now = (/* @__PURE__ */ new Date()).toISOString();
25034
+ const existing = state.items.find((item) => item.savePath === input.savePath);
25035
+ if (existing) {
25036
+ Object.assign(existing, input, { updatedAt: now });
25037
+ persist();
25038
+ emit$1();
25039
+ return existing;
25040
+ }
25041
+ const record = { id: crypto$2.randomUUID(), ...input, startedAt: now, updatedAt: now };
25042
+ state.items = [record, ...state.items];
25043
+ persist();
25044
+ emit$1();
25045
+ return record;
25046
+ }
25047
+ function clearDownloads() {
25048
+ state.items = [];
25049
+ persist();
25050
+ emit$1();
25051
+ }
25052
+ async function openDownload(id) {
25053
+ const item = state.items.find((d) => d.id === id);
25054
+ if (!item || item.state !== "completed" || !fs$1.existsSync(item.savePath)) return false;
25055
+ if (isExecutableDownload(item.savePath)) {
25056
+ const result = electron.dialog.showMessageBoxSync({
25057
+ type: "warning",
25058
+ buttons: ["Cancel", "Open Anyway"],
25059
+ defaultId: 0,
25060
+ cancelId: 0,
25061
+ title: "Open executable download?",
25062
+ message: `Open ${item.filename}?`,
25063
+ detail: executableWarningDetail(item)
25064
+ });
25065
+ if (result !== 1) return false;
25066
+ }
25067
+ return await electron.shell.openPath(item.savePath) === "";
25068
+ }
25069
+ async function showDownloadInFolder(id) {
25070
+ const item = state.items.find((d) => d.id === id);
25071
+ if (!item || !fs$1.existsSync(item.savePath)) return false;
25072
+ electron.shell.showItemInFolder(item.savePath);
25073
+ return true;
25074
+ }
24860
25075
  const defaultDownloadViews = /* @__PURE__ */ new Set();
24861
25076
  let defaultDownloadHandlerInstalled = false;
24862
25077
  function resolveDownloadPath(downloadDir, filename) {
@@ -24899,21 +25114,26 @@ function installDownloadHandlerForSession(targetSession, chromeView) {
24899
25114
  const info = {
24900
25115
  filename,
24901
25116
  savePath,
25117
+ url: item.getURL(),
25118
+ mimeType: typeof item.getMimeType === "function" ? item.getMimeType() : void 0,
24902
25119
  totalBytes: item.getTotalBytes(),
24903
25120
  receivedBytes: 0,
24904
25121
  state: "progressing"
24905
25122
  };
24906
- send(Channels.DOWNLOAD_STARTED, info);
25123
+ const record = upsertDownload(info);
25124
+ send(Channels.DOWNLOAD_STARTED, { ...info, id: record.id, startedAt: record.startedAt, updatedAt: record.updatedAt });
24907
25125
  item.on("updated", (_event2, state2) => {
24908
25126
  info.receivedBytes = item.getReceivedBytes();
24909
25127
  info.totalBytes = item.getTotalBytes();
24910
25128
  info.state = state2 === "progressing" ? "progressing" : "interrupted";
24911
- send(Channels.DOWNLOAD_PROGRESS, info);
25129
+ const record2 = upsertDownload(info);
25130
+ send(Channels.DOWNLOAD_PROGRESS, { ...info, id: record2.id, startedAt: record2.startedAt, updatedAt: record2.updatedAt });
24912
25131
  });
24913
25132
  item.once("done", (_event2, state2) => {
24914
25133
  info.receivedBytes = item.getReceivedBytes();
24915
25134
  info.state = state2 === "completed" ? "completed" : "cancelled";
24916
- send(Channels.DOWNLOAD_DONE, info);
25135
+ const record2 = upsertDownload(info);
25136
+ send(Channels.DOWNLOAD_DONE, { ...info, id: record2.id, startedAt: record2.startedAt, updatedAt: record2.updatedAt });
24917
25137
  });
24918
25138
  });
24919
25139
  }
@@ -24943,9 +25163,9 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
24943
25163
  const sidebarUrl = rendererUrlFor("sidebar");
24944
25164
  const devtoolsUrl = rendererUrlFor("devtools");
24945
25165
  if (chromeUrl && sidebarUrl && devtoolsUrl) {
24946
- chromeView.webContents.loadURL(chromeUrl);
24947
- sidebarView.webContents.loadURL(sidebarUrl);
24948
- devtoolsPanelView.webContents.loadURL(devtoolsUrl);
25166
+ void loadTrustedAppURL(chromeView.webContents, chromeUrl);
25167
+ void loadTrustedAppURL(sidebarView.webContents, sidebarUrl);
25168
+ void loadTrustedAppURL(devtoolsPanelView.webContents, devtoolsUrl);
24949
25169
  } else {
24950
25170
  const rendererFile = resolveRendererFile();
24951
25171
  chromeView.webContents.loadFile(rendererFile, {
@@ -25171,7 +25391,7 @@ function loadPrivateRenderer(chromeView) {
25171
25391
  const url = new URL(devUrl);
25172
25392
  url.searchParams.set("view", "chrome");
25173
25393
  url.searchParams.set("private", "1");
25174
- chromeView.webContents.loadURL(url.toString());
25394
+ void loadTrustedAppURL(chromeView.webContents, url.toString());
25175
25395
  } else {
25176
25396
  chromeView.webContents.loadFile(resolveRendererFile(), {
25177
25397
  query: { view: "chrome", private: "1" }
@@ -25406,7 +25626,7 @@ function loadSecondaryRenderer(chromeView) {
25406
25626
  const url = new URL(devUrl);
25407
25627
  url.searchParams.set("view", "chrome");
25408
25628
  url.searchParams.set("secondary", "1");
25409
- chromeView.webContents.loadURL(url.toString());
25629
+ void loadTrustedAppURL(chromeView.webContents, url.toString());
25410
25630
  } else {
25411
25631
  chromeView.webContents.loadFile(resolveRendererFile(), {
25412
25632
  query: { view: "chrome", secondary: "1" }
@@ -25626,63 +25846,67 @@ function registerBookmarkHandlers() {
25626
25846
  });
25627
25847
  electron.ipcMain.handle(
25628
25848
  Channels.BOOKMARKS_EXPORT_HTML,
25629
- async (_, options) => {
25630
- const { canceled, filePath } = await electron.dialog.showSaveDialog({
25849
+ async (event, options) => {
25850
+ assertTrustedIpcSender(event);
25851
+ const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
25631
25852
  title: "Export Bookmarks",
25632
25853
  defaultPath: "vessel-bookmarks.html",
25633
25854
  filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
25634
25855
  });
25635
- if (canceled || !filePath) return null;
25856
+ if (canceled || !filePath2) return null;
25636
25857
  const content = exportBookmarksHtml({
25637
25858
  includeNotes: options?.includeNotes ?? false
25638
25859
  });
25639
- await fs.promises.writeFile(filePath, content, "utf-8");
25860
+ await fs.promises.writeFile(filePath2, content, "utf-8");
25640
25861
  trackBookmarkAction("export");
25641
25862
  return {
25642
- filePath,
25863
+ filePath: filePath2,
25643
25864
  count: getState().bookmarks.length
25644
25865
  };
25645
25866
  }
25646
25867
  );
25647
- electron.ipcMain.handle(Channels.BOOKMARKS_EXPORT_JSON, async () => {
25648
- const { canceled, filePath } = await electron.dialog.showSaveDialog({
25868
+ electron.ipcMain.handle(Channels.BOOKMARKS_EXPORT_JSON, async (event) => {
25869
+ assertTrustedIpcSender(event);
25870
+ const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
25649
25871
  title: "Export Vessel Bookmark Archive",
25650
25872
  defaultPath: "vessel-bookmarks.json",
25651
25873
  filters: [{ name: "Vessel Bookmark Archive", extensions: ["json"] }]
25652
25874
  });
25653
- if (canceled || !filePath) return null;
25875
+ if (canceled || !filePath2) return null;
25654
25876
  const content = exportBookmarksJson();
25655
- await fs.promises.writeFile(filePath, content, "utf-8");
25877
+ await fs.promises.writeFile(filePath2, content, "utf-8");
25656
25878
  trackBookmarkAction("export");
25657
25879
  return {
25658
- filePath,
25880
+ filePath: filePath2,
25659
25881
  count: getState().bookmarks.length
25660
25882
  };
25661
25883
  });
25662
25884
  electron.ipcMain.handle(
25663
25885
  Channels.FOLDER_EXPORT_HTML,
25664
- async (_, folderId, options) => {
25886
+ async (event, folderId, options) => {
25887
+ assertTrustedIpcSender(event);
25665
25888
  const folder = getFolder(folderId);
25666
25889
  if (!folder) return null;
25667
- const { canceled, filePath } = await electron.dialog.showSaveDialog({
25890
+ const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
25668
25891
  title: `Export ${folder.name}`,
25669
25892
  defaultPath: `vessel-bookmarks-${getSafeBookmarkExportName(folder.name)}.html`,
25670
25893
  filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
25671
25894
  });
25672
- if (canceled || !filePath) return null;
25895
+ if (canceled || !filePath2) return null;
25673
25896
  const result = exportBookmarkFolderHtml(folderId, {
25674
25897
  includeNotes: options?.includeNotes ?? true
25675
25898
  });
25676
25899
  if (!result) return null;
25677
- await fs.promises.writeFile(filePath, result.content, "utf-8");
25900
+ await fs.promises.writeFile(filePath2, result.content, "utf-8");
25678
25901
  trackBookmarkAction("export");
25679
25902
  return {
25680
- filePath,
25903
+ filePath: filePath2,
25681
25904
  count: result.count
25682
25905
  };
25683
25906
  }
25684
25907
  );
25685
- electron.ipcMain.handle(Channels.BOOKMARKS_IMPORT_HTML, async () => {
25908
+ electron.ipcMain.handle(Channels.BOOKMARKS_IMPORT_HTML, async (event) => {
25909
+ assertTrustedIpcSender(event);
25686
25910
  const { canceled, filePaths } = await electron.dialog.showOpenDialog({
25687
25911
  title: "Import Bookmarks",
25688
25912
  filters: [
@@ -25695,7 +25919,8 @@ function registerBookmarkHandlers() {
25695
25919
  trackBookmarkAction("import");
25696
25920
  return importBookmarksFromHtml(content);
25697
25921
  });
25698
- electron.ipcMain.handle(Channels.BOOKMARKS_IMPORT_JSON, async () => {
25922
+ electron.ipcMain.handle(Channels.BOOKMARKS_IMPORT_JSON, async (event) => {
25923
+ assertTrustedIpcSender(event);
25699
25924
  const { canceled, filePaths } = await electron.dialog.showOpenDialog({
25700
25925
  title: "Import Bookmarks",
25701
25926
  filters: [
@@ -25726,32 +25951,36 @@ function registerHistoryHandlers() {
25726
25951
  electron.ipcMain.handle(Channels.HISTORY_SEARCH, (_, query) => {
25727
25952
  return search(query);
25728
25953
  });
25729
- electron.ipcMain.handle(Channels.HISTORY_CLEAR, () => {
25954
+ electron.ipcMain.handle(Channels.HISTORY_CLEAR, (event) => {
25955
+ assertTrustedIpcSender(event);
25730
25956
  clearAll$1();
25731
25957
  });
25732
- electron.ipcMain.handle(Channels.HISTORY_EXPORT_HTML, async () => {
25733
- const { canceled, filePath } = await electron.dialog.showSaveDialog({
25958
+ electron.ipcMain.handle(Channels.HISTORY_EXPORT_HTML, async (event) => {
25959
+ assertTrustedIpcSender(event);
25960
+ const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
25734
25961
  title: "Export History",
25735
25962
  defaultPath: "vessel-history.html",
25736
25963
  filters: [{ name: "HTML", extensions: ["html"] }]
25737
25964
  });
25738
- if (canceled || !filePath) return null;
25965
+ if (canceled || !filePath2) return null;
25739
25966
  const content = exportHistoryHtml();
25740
- await fs.promises.writeFile(filePath, content, "utf-8");
25741
- return { filePath, count: getState$1().entries.length };
25967
+ await fs.promises.writeFile(filePath2, content, "utf-8");
25968
+ return { filePath: filePath2, count: getState$1().entries.length };
25742
25969
  });
25743
- electron.ipcMain.handle(Channels.HISTORY_EXPORT_JSON, async () => {
25744
- const { canceled, filePath } = await electron.dialog.showSaveDialog({
25970
+ electron.ipcMain.handle(Channels.HISTORY_EXPORT_JSON, async (event) => {
25971
+ assertTrustedIpcSender(event);
25972
+ const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
25745
25973
  title: "Export History",
25746
25974
  defaultPath: "vessel-history.json",
25747
25975
  filters: [{ name: "JSON", extensions: ["json"] }]
25748
25976
  });
25749
- if (canceled || !filePath) return null;
25977
+ if (canceled || !filePath2) return null;
25750
25978
  const content = exportHistoryJson();
25751
- await fs.promises.writeFile(filePath, content, "utf-8");
25752
- return { filePath, count: getState$1().entries.length };
25979
+ await fs.promises.writeFile(filePath2, content, "utf-8");
25980
+ return { filePath: filePath2, count: getState$1().entries.length };
25753
25981
  });
25754
- electron.ipcMain.handle(Channels.HISTORY_IMPORT, async () => {
25982
+ electron.ipcMain.handle(Channels.HISTORY_IMPORT, async (event) => {
25983
+ assertTrustedIpcSender(event);
25755
25984
  const { canceled, filePaths } = await electron.dialog.showOpenDialog({
25756
25985
  title: "Import History",
25757
25986
  filters: [
@@ -25760,9 +25989,9 @@ function registerHistoryHandlers() {
25760
25989
  properties: ["openFile"]
25761
25990
  });
25762
25991
  if (canceled || filePaths.length === 0) return null;
25763
- const filePath = filePaths[0];
25764
- const content = await fs.promises.readFile(filePath, "utf-8");
25765
- const result = filePath.endsWith(".json") ? importHistoryFromJson(content) : importHistoryFromHtml(content);
25992
+ const filePath2 = filePaths[0];
25993
+ const content = await fs.promises.readFile(filePath2, "utf-8");
25994
+ const result = filePath2.endsWith(".json") ? importHistoryFromJson(content) : importHistoryFromHtml(content);
25766
25995
  return result;
25767
25996
  });
25768
25997
  }
@@ -25962,7 +26191,8 @@ function buildCertificateDetailsHtml(state2) {
25962
26191
  </html>`;
25963
26192
  }
25964
26193
  function registerSecurityHandlers(tabManager) {
25965
- electron.ipcMain.handle(Channels.SECURITY_SHOW_DETAILS, async (_, state2) => {
26194
+ electron.ipcMain.handle(Channels.SECURITY_SHOW_DETAILS, async (event, state2) => {
26195
+ assertTrustedIpcSender(event);
25966
26196
  const domain = (() => {
25967
26197
  try {
25968
26198
  return new URL(state2.url).hostname || state2.url;
@@ -25983,13 +26213,15 @@ function registerSecurityHandlers(tabManager) {
25983
26213
  spellcheck: false
25984
26214
  }
25985
26215
  });
25986
- void win.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(content)}`);
26216
+ void loadInternalDataURL(win.webContents, `data:text/html;charset=utf-8,${encodeURIComponent(content)}`);
25987
26217
  });
25988
- electron.ipcMain.handle(Channels.SECURITY_PROCEED_ANYWAY, (_, tabId) => {
26218
+ electron.ipcMain.handle(Channels.SECURITY_PROCEED_ANYWAY, (event, tabId) => {
26219
+ assertTrustedIpcSender(event);
25989
26220
  assertString(tabId, "tabId");
25990
26221
  tabManager.proceedAnyway(tabId);
25991
26222
  });
25992
- electron.ipcMain.handle(Channels.SECURITY_GO_BACK_TO_SAFETY, (_, tabId) => {
26223
+ electron.ipcMain.handle(Channels.SECURITY_GO_BACK_TO_SAFETY, (event, tabId) => {
26224
+ assertTrustedIpcSender(event);
25993
26225
  assertString(tabId, "tabId");
25994
26226
  tabManager.goBackToSafety(tabId);
25995
26227
  });
@@ -25997,6 +26229,7 @@ function registerSecurityHandlers(tabManager) {
25997
26229
  const logger$5 = createLogger("CodexIPC");
25998
26230
  function registerCodexHandlers() {
25999
26231
  electron.ipcMain.handle(Channels.CODEX_START_AUTH, async (event) => {
26232
+ assertTrustedIpcSender(event);
26000
26233
  const wc = event.sender;
26001
26234
  if (!wc || wc.isDestroyed()) {
26002
26235
  return {
@@ -26027,15 +26260,190 @@ function registerCodexHandlers() {
26027
26260
  };
26028
26261
  }
26029
26262
  });
26030
- electron.ipcMain.handle(Channels.CODEX_CANCEL_AUTH, () => {
26263
+ electron.ipcMain.handle(Channels.CODEX_CANCEL_AUTH, (event) => {
26264
+ assertTrustedIpcSender(event);
26031
26265
  cancelCodexOAuth();
26032
26266
  return { ok: true };
26033
26267
  });
26034
- electron.ipcMain.handle(Channels.CODEX_DISCONNECT, () => {
26268
+ electron.ipcMain.handle(Channels.CODEX_DISCONNECT, (event) => {
26269
+ assertTrustedIpcSender(event);
26035
26270
  clearStoredCodexTokens();
26036
26271
  return { ok: true };
26037
26272
  });
26038
26273
  }
26274
+ const filePath = () => path$1.join(electron.app.getPath("userData"), "vessel-permissions.json");
26275
+ const ALLOWED_PERMISSION_TYPES = /* @__PURE__ */ new Set([
26276
+ "clipboard-read",
26277
+ "fullscreen",
26278
+ "geolocation",
26279
+ "media",
26280
+ "midiSysex",
26281
+ "notifications",
26282
+ "pointerLock"
26283
+ ]);
26284
+ function parseOrigin(value) {
26285
+ try {
26286
+ const origin = new URL(value).origin;
26287
+ return origin === "null" ? null : origin;
26288
+ } catch {
26289
+ return null;
26290
+ }
26291
+ }
26292
+ function isPermissionRecord(value) {
26293
+ if (!value || typeof value !== "object") return false;
26294
+ const record = value;
26295
+ return typeof record.origin === "string" && parseOrigin(record.origin) === record.origin && typeof record.permission === "string" && ALLOWED_PERMISSION_TYPES.has(record.permission) && (record.decision === "allow" || record.decision === "deny") && typeof record.updatedAt === "string";
26296
+ }
26297
+ let records = loadJsonFile({
26298
+ filePath: filePath(),
26299
+ fallback: [],
26300
+ parse: (raw) => Array.isArray(raw) ? raw.filter(isPermissionRecord) : []
26301
+ });
26302
+ const persistence = createDebouncedJsonPersistence({ debounceMs: 250, filePath: filePath(), getValue: () => records, logLabel: "permissions" });
26303
+ const sessionDecisions = /* @__PURE__ */ new Map();
26304
+ let broadcaster = null;
26305
+ function key(origin, permission) {
26306
+ return `${origin}
26307
+ ${permission}`;
26308
+ }
26309
+ function snapshot() {
26310
+ return records.map((record) => ({ ...record }));
26311
+ }
26312
+ function emit() {
26313
+ broadcaster?.(Channels.PERMISSIONS_GET, snapshot());
26314
+ }
26315
+ function save(origin, permission, decision) {
26316
+ const k = key(origin, permission);
26317
+ const existing = records.find((r) => key(r.origin, r.permission) === k);
26318
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
26319
+ if (existing) Object.assign(existing, { decision, updatedAt });
26320
+ else records.unshift({ origin, permission, decision, updatedAt });
26321
+ persistence.schedule();
26322
+ emit();
26323
+ }
26324
+ function listPermissions() {
26325
+ return snapshot();
26326
+ }
26327
+ function clearPermissions() {
26328
+ records = [];
26329
+ sessionDecisions.clear();
26330
+ persistence.schedule();
26331
+ emit();
26332
+ }
26333
+ function clearPermissionsForOrigin(origin) {
26334
+ if (!parseOrigin(origin)) return;
26335
+ records = records.filter((record) => record.origin !== origin);
26336
+ for (const storedKey of sessionDecisions.keys()) {
26337
+ if (storedKey.startsWith(`${origin}
26338
+ `)) sessionDecisions.delete(storedKey);
26339
+ }
26340
+ persistence.schedule();
26341
+ emit();
26342
+ }
26343
+ function setPermissionBroadcaster(fn) {
26344
+ broadcaster = fn;
26345
+ }
26346
+ function installPermissionHandler() {
26347
+ electron.session.defaultSession.setPermissionRequestHandler((webContents, permission, callback, details) => {
26348
+ if (!ALLOWED_PERMISSION_TYPES.has(permission)) {
26349
+ callback(false);
26350
+ return;
26351
+ }
26352
+ const origin = parseOrigin(details.requestingUrl || webContents.getURL());
26353
+ if (!origin) {
26354
+ callback(false);
26355
+ return;
26356
+ }
26357
+ const k = key(origin, permission);
26358
+ const existing = records.find((r) => r.origin === origin && r.permission === permission);
26359
+ if (existing) {
26360
+ callback(existing.decision === "allow");
26361
+ return;
26362
+ }
26363
+ const sessionDecision = sessionDecisions.get(k);
26364
+ if (sessionDecision) {
26365
+ callback(sessionDecision === "allow");
26366
+ return;
26367
+ }
26368
+ const result = electron.dialog.showMessageBoxSync({
26369
+ type: "question",
26370
+ buttons: ["Deny", "Allow Once", "Allow Until Quit", "Always Allow"],
26371
+ defaultId: 0,
26372
+ cancelId: 0,
26373
+ title: "Site permission request",
26374
+ message: `${origin} wants to use ${permission}`,
26375
+ detail: "Temporary choices are safer for camera, microphone, location, and clipboard access. Persistent choices can be cleared in Settings > Privacy."
26376
+ });
26377
+ if (result === 1) {
26378
+ callback(true);
26379
+ return;
26380
+ }
26381
+ if (result === 2) {
26382
+ sessionDecisions.set(k, "allow");
26383
+ callback(true);
26384
+ return;
26385
+ }
26386
+ if (result === 3) {
26387
+ save(origin, permission, "allow");
26388
+ callback(true);
26389
+ return;
26390
+ }
26391
+ save(origin, permission, "deny");
26392
+ callback(false);
26393
+ });
26394
+ }
26395
+ const GITHUB_LATEST_RELEASE_API_URL = "https://api.github.com/repos/unmodeled-tyler/quanta-vessel-browser/releases/latest";
26396
+ const RELEASES_URL = "https://github.com/unmodeled-tyler/quanta-vessel-browser/releases/latest";
26397
+ function normalizeVersion(version) {
26398
+ return version.replace(/^v/i, "").split(/[.-]/).slice(0, 3).map((part) => {
26399
+ const n = Number.parseInt(part, 10);
26400
+ return Number.isFinite(n) ? n : 0;
26401
+ });
26402
+ }
26403
+ function compareVersions(a, b) {
26404
+ const av = normalizeVersion(a);
26405
+ const bv = normalizeVersion(b);
26406
+ for (let i = 0; i < 3; i += 1) {
26407
+ if ((av[i] ?? 0) > (bv[i] ?? 0)) return 1;
26408
+ if ((av[i] ?? 0) < (bv[i] ?? 0)) return -1;
26409
+ }
26410
+ return 0;
26411
+ }
26412
+ async function checkForUpdates() {
26413
+ const currentVersion = electron.app.getVersion();
26414
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
26415
+ try {
26416
+ const response = await fetch(GITHUB_LATEST_RELEASE_API_URL, {
26417
+ headers: { accept: "application/vnd.github+json", "user-agent": `Vessel/${currentVersion}` }
26418
+ });
26419
+ if (!response.ok) {
26420
+ throw new Error(`GitHub Releases responded with ${response.status}`);
26421
+ }
26422
+ const body = await response.json();
26423
+ const latestVersion = typeof body.tag_name === "string" ? body.tag_name : null;
26424
+ if (!latestVersion) throw new Error("GitHub release response did not include a tag name");
26425
+ const releaseUrl = typeof body.html_url === "string" && body.html_url.startsWith("https://github.com/") ? body.html_url : RELEASES_URL;
26426
+ return {
26427
+ currentVersion,
26428
+ latestVersion,
26429
+ updateAvailable: compareVersions(latestVersion, currentVersion) > 0,
26430
+ checkedAt,
26431
+ releaseUrl
26432
+ };
26433
+ } catch (error) {
26434
+ return {
26435
+ currentVersion,
26436
+ latestVersion: null,
26437
+ updateAvailable: false,
26438
+ checkedAt,
26439
+ releaseUrl: RELEASES_URL,
26440
+ error: error instanceof Error ? error.message : "Update check failed"
26441
+ };
26442
+ }
26443
+ }
26444
+ async function openUpdateDownload() {
26445
+ await openExternalAllowlisted(RELEASES_URL, { hosts: ["github.com"] });
26446
+ }
26039
26447
  let activeChatProvider = null;
26040
26448
  const logger$4 = createLogger("IPC");
26041
26449
  const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
@@ -26069,10 +26477,18 @@ async function togglePictureInPicture(tabManager) {
26069
26477
  }
26070
26478
  function registerIpcHandlers(windowState, runtime2) {
26071
26479
  const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
26072
- electron.ipcMain.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
26480
+ registerTrustedIpcSender(chromeView.webContents);
26481
+ registerTrustedIpcSender(sidebarView.webContents);
26482
+ registerTrustedIpcSender(devtoolsPanelView.webContents);
26483
+ const requireTrusted = (event) => {
26484
+ assertTrustedIpcSender(event);
26485
+ };
26486
+ electron.ipcMain.handle(Channels.OPEN_PRIVATE_WINDOW, (event) => {
26487
+ requireTrusted(event);
26073
26488
  createPrivateWindow();
26074
26489
  });
26075
- electron.ipcMain.handle(Channels.OPEN_NEW_WINDOW, () => {
26490
+ electron.ipcMain.handle(Channels.OPEN_NEW_WINDOW, (event) => {
26491
+ requireTrusted(event);
26076
26492
  createSecondaryWindow();
26077
26493
  });
26078
26494
  electron.ipcMain.handle(Channels.IS_PRIVATE_MODE, () => false);
@@ -26344,14 +26760,15 @@ function registerIpcHandlers(windowState, runtime2) {
26344
26760
  const originalUrl = activeTab.readerOriginalUrl;
26345
26761
  activeTab.setReaderMode(false);
26346
26762
  if (originalUrl) {
26347
- activeTab.view.webContents.loadURL(originalUrl);
26763
+ void loadPermittedNavigationURL(activeTab.view.webContents, originalUrl);
26348
26764
  }
26349
26765
  } else {
26350
26766
  const originalUrl = activeTab.state.url;
26351
26767
  const content = await extractContent(activeTab.view.webContents);
26352
26768
  const html = generateReaderHTML(content);
26353
26769
  activeTab.setReaderMode(true, originalUrl);
26354
- activeTab.view.webContents.loadURL(
26770
+ void loadInternalDataURL(
26771
+ activeTab.view.webContents,
26355
26772
  `data:text/html;charset=utf-8,${encodeURIComponent(html)}`
26356
26773
  );
26357
26774
  }
@@ -26433,18 +26850,23 @@ function registerIpcHandlers(windowState, runtime2) {
26433
26850
  return getRendererSettings();
26434
26851
  });
26435
26852
  electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, () => getRuntimeHealth());
26436
- electron.ipcMain.handle(Channels.SETTINGS_SET, async (_, key, value) => {
26437
- assertString(key, "key");
26438
- if (!SETTABLE_KEYS.has(key)) {
26439
- throw new Error(`Unknown setting key: ${key}`);
26853
+ electron.ipcMain.handle(Channels.MCP_REGENERATE_TOKEN, (event) => {
26854
+ requireTrusted(event);
26855
+ return regenerateMcpAuthToken();
26856
+ });
26857
+ electron.ipcMain.handle(Channels.SETTINGS_SET, async (event, key2, value) => {
26858
+ requireTrusted(event);
26859
+ assertString(key2, "key");
26860
+ if (!SETTABLE_KEYS.has(key2)) {
26861
+ throw new Error(`Unknown setting key: ${key2}`);
26440
26862
  }
26441
- const settingsKey = key;
26863
+ const settingsKey = key2;
26442
26864
  const updatedSettings = setSetting(settingsKey, value);
26443
- trackSettingChanged(key);
26444
- if (key === "approvalMode") {
26865
+ trackSettingChanged(key2);
26866
+ if (key2 === "approvalMode") {
26445
26867
  runtime2.setApprovalMode(value);
26446
26868
  }
26447
- if (key === "mcpPort") {
26869
+ if (key2 === "mcpPort") {
26448
26870
  await stopMcpServer();
26449
26871
  await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
26450
26872
  }
@@ -26453,11 +26875,18 @@ function registerIpcHandlers(windowState, runtime2) {
26453
26875
  return rendererSettings;
26454
26876
  });
26455
26877
  electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () => runtime2.getState());
26456
- electron.ipcMain.handle(Channels.AGENT_PAUSE, () => runtime2.pause());
26457
- electron.ipcMain.handle(Channels.AGENT_RESUME, () => runtime2.resume());
26878
+ electron.ipcMain.handle(Channels.AGENT_PAUSE, (event) => {
26879
+ requireTrusted(event);
26880
+ return runtime2.pause();
26881
+ });
26882
+ electron.ipcMain.handle(Channels.AGENT_RESUME, (event) => {
26883
+ requireTrusted(event);
26884
+ return runtime2.resume();
26885
+ });
26458
26886
  electron.ipcMain.handle(
26459
26887
  Channels.AGENT_SET_APPROVAL_MODE,
26460
- (_, mode) => {
26888
+ (event, mode) => {
26889
+ requireTrusted(event);
26461
26890
  assertString(mode, "mode");
26462
26891
  if (!VALID_APPROVAL_MODES.includes(mode)) {
26463
26892
  throw new Error(`Invalid approval mode: ${mode}`);
@@ -26469,7 +26898,10 @@ function registerIpcHandlers(windowState, runtime2) {
26469
26898
  );
26470
26899
  electron.ipcMain.handle(
26471
26900
  Channels.AGENT_APPROVAL_RESOLVE,
26472
- (_, approvalId, approved) => runtime2.resolveApproval(approvalId, approved)
26901
+ (event, approvalId, approved) => {
26902
+ requireTrusted(event);
26903
+ return runtime2.resolveApproval(approvalId, approved);
26904
+ }
26473
26905
  );
26474
26906
  electron.ipcMain.handle(
26475
26907
  Channels.AGENT_CHECKPOINT_CREATE,
@@ -26493,7 +26925,10 @@ function registerIpcHandlers(windowState, runtime2) {
26493
26925
  );
26494
26926
  electron.ipcMain.handle(
26495
26927
  Channels.AGENT_SESSION_RESTORE,
26496
- (_, snapshot) => runtime2.restoreSession(snapshot)
26928
+ (event, snapshot2) => {
26929
+ requireTrusted(event);
26930
+ return runtime2.restoreSession(snapshot2);
26931
+ }
26497
26932
  );
26498
26933
  registerBookmarkHandlers();
26499
26934
  electron.ipcMain.handle(Channels.HIGHLIGHT_CAPTURE, async () => {
@@ -26613,17 +27048,20 @@ function registerIpcHandlers(windowState, runtime2) {
26613
27048
  electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, () => {
26614
27049
  return getInstalledKits();
26615
27050
  });
26616
- electron.ipcMain.handle(Channels.AUTOMATION_INSTALL_FROM_FILE, async () => {
27051
+ electron.ipcMain.handle(Channels.AUTOMATION_INSTALL_FROM_FILE, async (event) => {
27052
+ requireTrusted(event);
26617
27053
  return await installKitFromFile();
26618
27054
  });
26619
- electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, (_event, id) => {
27055
+ electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, (event, id) => {
27056
+ requireTrusted(event);
26620
27057
  assertString(id, "id");
26621
27058
  return uninstallKit(id, getScheduledKitIds());
26622
27059
  });
26623
27060
  registerScheduleHandlers(windowState, runtime2, sendToRendererViews);
26624
27061
  registerAutofillHandlers(windowState);
26625
27062
  registerPageDiffHandlers(windowState, sendToRendererViews);
26626
- electron.ipcMain.handle(Channels.CLEAR_BROWSING_DATA, async (_, options) => {
27063
+ electron.ipcMain.handle(Channels.CLEAR_BROWSING_DATA, async (event, options) => {
27064
+ requireTrusted(event);
26627
27065
  const { cache, cookies, history, localStorage: clearLs, timeRange } = options;
26628
27066
  if (cache) {
26629
27067
  await electron.session.defaultSession.clearCache();
@@ -26638,6 +27076,38 @@ function registerIpcHandlers(windowState, runtime2) {
26638
27076
  clearByTimeRange(timeRange);
26639
27077
  }
26640
27078
  });
27079
+ setDownloadBroadcaster(sendToRendererViews);
27080
+ setPermissionBroadcaster(sendToRendererViews);
27081
+ electron.ipcMain.handle(Channels.DOWNLOADS_GET, () => listDownloads());
27082
+ electron.ipcMain.handle(Channels.DOWNLOADS_CLEAR, (event) => {
27083
+ requireTrusted(event);
27084
+ clearDownloads();
27085
+ return true;
27086
+ });
27087
+ electron.ipcMain.handle(Channels.DOWNLOADS_OPEN, (event, id) => {
27088
+ requireTrusted(event);
27089
+ return openDownload(id);
27090
+ });
27091
+ electron.ipcMain.handle(Channels.DOWNLOADS_SHOW_IN_FOLDER, (event, id) => {
27092
+ requireTrusted(event);
27093
+ return showDownloadInFolder(id);
27094
+ });
27095
+ electron.ipcMain.handle(Channels.PERMISSIONS_GET, () => listPermissions());
27096
+ electron.ipcMain.handle(Channels.PERMISSIONS_CLEAR, (event) => {
27097
+ requireTrusted(event);
27098
+ clearPermissions();
27099
+ return true;
27100
+ });
27101
+ electron.ipcMain.handle(Channels.PERMISSIONS_CLEAR_ORIGIN, (event, origin) => {
27102
+ requireTrusted(event);
27103
+ clearPermissionsForOrigin(origin);
27104
+ return true;
27105
+ });
27106
+ electron.ipcMain.handle(Channels.UPDATES_CHECK, () => checkForUpdates());
27107
+ electron.ipcMain.handle(Channels.UPDATES_OPEN_DOWNLOAD, (event) => {
27108
+ requireTrusted(event);
27109
+ return openUpdateDownload();
27110
+ });
26641
27111
  electron.ipcMain.handle(Channels.TAB_TOGGLE_PIP, async () => {
26642
27112
  return togglePictureInPicture(tabManager);
26643
27113
  });
@@ -27033,9 +27503,9 @@ function clone(value) {
27033
27503
  function summarizeArgs(args) {
27034
27504
  const entries = Object.entries(args).filter(([, value]) => value != null);
27035
27505
  if (entries.length === 0) return "No arguments";
27036
- return entries.map(([key, value]) => {
27506
+ return entries.map(([key2, value]) => {
27037
27507
  const rendered = typeof value === "string" ? value : JSON.stringify(value);
27038
- return `${key}=${String(rendered).slice(0, 120)}`;
27508
+ return `${key2}=${String(rendered).slice(0, 120)}`;
27039
27509
  }).join(", ");
27040
27510
  }
27041
27511
  function summarizeText(value) {
@@ -27070,6 +27540,7 @@ class AgentRuntime {
27070
27540
  this.state = this.loadPersistedState();
27071
27541
  onMcpStatusChange(() => this.emit());
27072
27542
  }
27543
+ tabManager;
27073
27544
  state;
27074
27545
  updateListener = null;
27075
27546
  pendingResolvers = /* @__PURE__ */ new Map();
@@ -27081,11 +27552,11 @@ class AgentRuntime {
27081
27552
  }
27082
27553
  }
27083
27554
  getState() {
27084
- const snapshot = clone(this.state);
27085
- snapshot.mcpStatus = getMcpStatus();
27086
- snapshot.canUndo = this.canUndo();
27087
- snapshot.undoInfo = this.getUndoInfo();
27088
- return snapshot;
27555
+ const snapshot2 = clone(this.state);
27556
+ snapshot2.mcpStatus = getMcpStatus();
27557
+ snapshot2.canUndo = this.canUndo();
27558
+ snapshot2.undoInfo = this.getUndoInfo();
27559
+ return snapshot2;
27089
27560
  }
27090
27561
  onTabStateChanged() {
27091
27562
  this.captureSession();
@@ -27121,13 +27592,13 @@ class AgentRuntime {
27121
27592
  return this.getState();
27122
27593
  }
27123
27594
  createCheckpoint(name, note) {
27124
- const snapshot = this.captureSession(note);
27595
+ const snapshot2 = this.captureSession(note);
27125
27596
  const checkpoint = {
27126
27597
  id: crypto$2.randomUUID(),
27127
27598
  name: name?.trim() || `Checkpoint ${this.state.checkpoints.length + 1}`,
27128
27599
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
27129
27600
  note: note?.trim() || void 0,
27130
- snapshot
27601
+ snapshot: snapshot2
27131
27602
  };
27132
27603
  this.state.checkpoints = [...this.state.checkpoints, checkpoint].slice(
27133
27604
  -20
@@ -27161,26 +27632,26 @@ class AgentRuntime {
27161
27632
  return { actionName: latest.actionName, capturedAt: latest.capturedAt };
27162
27633
  }
27163
27634
  undoLastAction() {
27164
- const snapshot = this.undoSnapshots.at(-1);
27165
- if (!snapshot) return null;
27635
+ const snapshot2 = this.undoSnapshots.at(-1);
27636
+ if (!snapshot2) return null;
27166
27637
  try {
27167
- this.tabManager.restoreSession(snapshot.snapshot);
27638
+ this.tabManager.restoreSession(snapshot2.snapshot);
27168
27639
  this.undoSnapshots.pop();
27169
27640
  } catch (error) {
27170
27641
  logger$3.error("Failed to restore undo snapshot", error);
27171
27642
  return null;
27172
27643
  }
27173
- this.captureSession(`Undid ${snapshot.actionName}`);
27174
- return snapshot.actionName;
27644
+ this.captureSession(`Undid ${snapshot2.actionName}`);
27645
+ return snapshot2.actionName;
27175
27646
  }
27176
27647
  captureSession(note) {
27177
- const snapshot = this.tabManager.snapshotSession(note);
27178
- this.state.session = snapshot;
27648
+ const snapshot2 = this.tabManager.snapshotSession(note);
27649
+ this.state.session = snapshot2;
27179
27650
  this.emit();
27180
- return clone(snapshot);
27651
+ return clone(snapshot2);
27181
27652
  }
27182
- restoreSession(snapshot) {
27183
- const target = snapshot || this.state.session;
27653
+ restoreSession(snapshot2) {
27654
+ const target = snapshot2 || this.state.session;
27184
27655
  if (!target) {
27185
27656
  return this.captureSession("No saved session to restore");
27186
27657
  }
@@ -27322,6 +27793,7 @@ ${progress}
27322
27793
  args = {},
27323
27794
  tabId = null,
27324
27795
  dangerous = false,
27796
+ requiresApproval = false,
27325
27797
  undoable,
27326
27798
  executor
27327
27799
  }) {
@@ -27341,7 +27813,7 @@ ${progress}
27341
27813
  streamId: transcriptStreamId,
27342
27814
  mode: "replace"
27343
27815
  });
27344
- const approvalReason = this.getApprovalReason(dangerous);
27816
+ const approvalReason = this.getApprovalReason(dangerous, requiresApproval);
27345
27817
  if (approvalReason) {
27346
27818
  this.publishTranscript({
27347
27819
  source,
@@ -27419,8 +27891,8 @@ ${progress}
27419
27891
  capturedAt: (/* @__PURE__ */ new Date()).toISOString()
27420
27892
  };
27421
27893
  }
27422
- pushUndoSnapshot(snapshot) {
27423
- this.undoSnapshots = [...this.undoSnapshots, snapshot].slice(
27894
+ pushUndoSnapshot(snapshot2) {
27895
+ this.undoSnapshots = [...this.undoSnapshots, snapshot2].slice(
27424
27896
  -10
27425
27897
  );
27426
27898
  }
@@ -27564,10 +28036,13 @@ ${progress}
27564
28036
  )
27565
28037
  };
27566
28038
  }
27567
- getApprovalReason(dangerous) {
28039
+ getApprovalReason(dangerous, requiresApproval) {
27568
28040
  if (this.state.supervisor.paused) {
27569
28041
  return "Agent execution is paused";
27570
28042
  }
28043
+ if (requiresApproval) {
28044
+ return "Approval required: high-risk action";
28045
+ }
27571
28046
  if (this.state.supervisor.approvalMode === "manual") {
27572
28047
  return "Approval required: ask every time mode";
27573
28048
  }
@@ -28061,6 +28536,7 @@ async function bootstrap() {
28061
28536
  sidebarView.webContents.send(Channels.HISTORY_UPDATE, state2);
28062
28537
  });
28063
28538
  installDownloadHandler(chromeView);
28539
+ installPermissionHandler();
28064
28540
  startBackgroundRevalidation();
28065
28541
  startTelemetry();
28066
28542
  const initializeChromeRenderer = () => {