@quanta-intellect/vessel-browser 0.1.134 → 0.1.136

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
@@ -4,11 +4,11 @@ const fs$1 = require("node:fs");
4
4
  const path = require("path");
5
5
  const fs = require("fs");
6
6
  const crypto$1 = require("crypto");
7
+ const zod = require("zod");
7
8
  const Anthropic = require("@anthropic-ai/sdk");
8
9
  const OpenAI = require("openai");
9
10
  const crypto$2 = require("node:crypto");
10
11
  const http = require("http");
11
- const zod = require("zod");
12
12
  const path$1 = require("node:path");
13
13
  const node_module = require("node:module");
14
14
  const http$1 = require("node:http");
@@ -111,10 +111,10 @@ const defaults = {
111
111
  expiresAt: ""
112
112
  }
113
113
  };
114
- const SAVE_DEBOUNCE_MS$6 = 150;
114
+ const SAVE_DEBOUNCE_MS$4 = 150;
115
115
  const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
116
116
  const CODEX_TOKENS_FILENAME = "vessel-codex-tokens";
117
- const logger$x = createLogger("Settings");
117
+ const logger$y = createLogger("Settings");
118
118
  const SETTABLE_KEYS = new Set(Object.keys(defaults));
119
119
  let settings = null;
120
120
  let settingsIssues = [];
@@ -139,7 +139,7 @@ function canUseSafeStorage$1() {
139
139
  try {
140
140
  return electron.safeStorage.isEncryptionAvailable();
141
141
  } catch (err) {
142
- logger$x.warn("safeStorage.isEncryptionAvailable() failed, assuming unavailable:", err);
142
+ logger$y.warn("safeStorage.isEncryptionAvailable() failed, assuming unavailable:", err);
143
143
  return false;
144
144
  }
145
145
  }
@@ -148,7 +148,7 @@ function writePrivateFile(filePath2, data) {
148
148
  try {
149
149
  fs.chmodSync(filePath2, 384);
150
150
  } catch (err) {
151
- logger$x.debug("Could not chmod private file (non-POSIX filesystem):", err);
151
+ logger$y.debug("Could not chmod private file (non-POSIX filesystem):", err);
152
152
  }
153
153
  }
154
154
  function assertSafeStorageAvailable() {
@@ -167,7 +167,7 @@ function readStoredProviderSecret() {
167
167
  }
168
168
  } catch (err) {
169
169
  if (!isMissingFileError(err)) {
170
- logger$x.warn("Could not read stored provider secret:", err);
170
+ logger$y.warn("Could not read stored provider secret:", err);
171
171
  }
172
172
  }
173
173
  return null;
@@ -185,7 +185,7 @@ function clearStoredProviderSecret() {
185
185
  fs.unlinkSync(getChatProviderSecretPath());
186
186
  } catch (err) {
187
187
  if (!isMissingFileError(err)) {
188
- logger$x.warn("Could not delete provider secret file:", err);
188
+ logger$y.warn("Could not delete provider secret file:", err);
189
189
  }
190
190
  }
191
191
  }
@@ -203,7 +203,7 @@ function readStoredCodexTokens() {
203
203
  }
204
204
  } catch (err) {
205
205
  if (!isMissingFileError(err)) {
206
- logger$x.warn("Could not read stored Codex tokens:", err);
206
+ logger$y.warn("Could not read stored Codex tokens:", err);
207
207
  }
208
208
  }
209
209
  return null;
@@ -221,7 +221,7 @@ function clearStoredCodexTokens() {
221
221
  fs.unlinkSync(getCodexTokensPath());
222
222
  } catch (err) {
223
223
  if (!isMissingFileError(err)) {
224
- logger$x.warn("Could not delete Codex token file:", err);
224
+ logger$y.warn("Could not delete Codex token file:", err);
225
225
  }
226
226
  }
227
227
  }
@@ -352,8 +352,8 @@ function persistNow() {
352
352
  { encoding: "utf-8", mode: 384 }
353
353
  )
354
354
  ).then(() => fs.promises.chmod(getSettingsPath(), 384).catch((err) => {
355
- logger$x.warn("Failed to chmod settings file:", err);
356
- })).catch((err) => logger$x.error("Failed to save settings:", err));
355
+ logger$y.warn("Failed to chmod settings file:", err);
356
+ })).catch((err) => logger$y.error("Failed to save settings:", err));
357
357
  }
358
358
  function saveSettings() {
359
359
  saveDirty = true;
@@ -363,7 +363,7 @@ function saveSettings() {
363
363
  if (saveDirty) {
364
364
  void persistNow();
365
365
  }
366
- }, SAVE_DEBOUNCE_MS$6);
366
+ }, SAVE_DEBOUNCE_MS$4);
367
367
  }
368
368
  function setSetting(key2, value) {
369
369
  loadSettings();
@@ -532,7 +532,7 @@ const urlSafety = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
532
532
  }, Symbol.toStringTag, { value: "Module" }));
533
533
  const MAX_CUSTOM_HISTORY = 50;
534
534
  const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
535
- const logger$w = createLogger("Tab");
535
+ const logger$x = createLogger("Tab");
536
536
  const sessionCertExceptions = /* @__PURE__ */ new WeakMap();
537
537
  const sessionsWithVerifyProc = /* @__PURE__ */ new WeakSet();
538
538
  const CERT_VERIFY_TRUST = 0;
@@ -598,7 +598,7 @@ class Tab {
598
598
  guardedLoadURL(url, options) {
599
599
  const blockReason = this.getNavigationBlockReason(url);
600
600
  if (blockReason) {
601
- logger$w.warn(blockReason);
601
+ logger$x.warn(blockReason);
602
602
  return blockReason;
603
603
  }
604
604
  void this.view.webContents.loadURL(url, options);
@@ -682,7 +682,7 @@ class Tab {
682
682
  wc.setWindowOpenHandler(({ url, disposition }) => {
683
683
  const error = this.getNavigationBlockReason(url);
684
684
  if (error) {
685
- logger$w.warn(error);
685
+ logger$x.warn(error);
686
686
  return { action: "deny" };
687
687
  }
688
688
  this.onOpenUrl?.({
@@ -696,7 +696,7 @@ class Tab {
696
696
  const error = this.getNavigationBlockReason(url);
697
697
  if (!error) return;
698
698
  event.preventDefault();
699
- logger$w.warn(`${context}: ${error}`);
699
+ logger$x.warn(`${context}: ${error}`);
700
700
  };
701
701
  wc.on("will-navigate", (event, url) => {
702
702
  blockNavigation(event, url, "Blocked top-level navigation");
@@ -780,7 +780,7 @@ class Tab {
780
780
  ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
781
781
  ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
782
782
  ::-webkit-scrollbar-corner { background: transparent; }
783
- `).catch((err) => logger$w.warn("Failed to inject scrollbar CSS:", err));
783
+ `).catch((err) => logger$x.warn("Failed to inject scrollbar CSS:", err));
784
784
  });
785
785
  wc.on("page-favicon-updated", (_, favicons) => {
786
786
  this._state.favicon = favicons[0] || "";
@@ -816,7 +816,7 @@ class Tab {
816
816
  ).then((highlightedText) => {
817
817
  this.buildContextMenu(wc, params, highlightedText.trim());
818
818
  }).catch((err) => {
819
- logger$w.warn("Failed to inspect highlighted text for context menu:", err);
819
+ logger$x.warn("Failed to inspect highlighted text for context menu:", err);
820
820
  this.buildContextMenu(wc, params, "");
821
821
  });
822
822
  });
@@ -1017,7 +1017,7 @@ class Tab {
1017
1017
  "document.documentElement.outerHTML"
1018
1018
  );
1019
1019
  } catch (err) {
1020
- logger$w.warn("Failed to retrieve page source:", err);
1020
+ logger$x.warn("Failed to retrieve page source:", err);
1021
1021
  return;
1022
1022
  }
1023
1023
  const escaped = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1147,7 +1147,7 @@ class Tab {
1147
1147
  document.addEventListener('mouseup', window.__vesselHighlightHandler);
1148
1148
  }
1149
1149
  })()
1150
- `).catch((err) => logger$w.warn("Failed to inject highlight listener:", err));
1150
+ `).catch((err) => logger$x.warn("Failed to inject highlight listener:", err));
1151
1151
  } else {
1152
1152
  void wc.executeJavaScript(`
1153
1153
  (function() {
@@ -1158,7 +1158,7 @@ class Tab {
1158
1158
  delete window.__vesselHighlightHandler;
1159
1159
  }
1160
1160
  })()
1161
- `).catch((err) => logger$w.warn("Failed to remove highlight listener:", err));
1161
+ `).catch((err) => logger$x.warn("Failed to remove highlight listener:", err));
1162
1162
  }
1163
1163
  }
1164
1164
  get webContentsId() {
@@ -1195,7 +1195,87 @@ const SEARCH_ENGINE_PRESETS = {
1195
1195
  ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
1196
1196
  kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
1197
1197
  };
1198
- const logger$v = createLogger("JsonPersistence");
1198
+ const DownloadStateSchema = zod.z.enum([
1199
+ "progressing",
1200
+ "completed",
1201
+ "cancelled",
1202
+ "interrupted"
1203
+ ]);
1204
+ const DownloadRecordSchema = zod.z.object({
1205
+ id: zod.z.string(),
1206
+ filename: zod.z.string(),
1207
+ savePath: zod.z.string(),
1208
+ url: zod.z.string().optional(),
1209
+ mimeType: zod.z.string().optional(),
1210
+ totalBytes: zod.z.number(),
1211
+ receivedBytes: zod.z.number(),
1212
+ state: DownloadStateSchema,
1213
+ startedAt: zod.z.string(),
1214
+ updatedAt: zod.z.string()
1215
+ });
1216
+ zod.z.object({
1217
+ items: zod.z.array(DownloadRecordSchema)
1218
+ });
1219
+ const HistoryEntrySchema = zod.z.object({
1220
+ url: zod.z.string(),
1221
+ title: zod.z.string(),
1222
+ visitedAt: zod.z.string()
1223
+ });
1224
+ zod.z.object({
1225
+ entries: zod.z.array(HistoryEntrySchema)
1226
+ });
1227
+ const HistoryImportEntrySchema = zod.z.object({
1228
+ url: zod.z.string(),
1229
+ title: zod.z.string().optional(),
1230
+ visitedAt: zod.z.string().optional()
1231
+ });
1232
+ const HistoryImportStateSchema = zod.z.object({
1233
+ entries: zod.z.array(HistoryImportEntrySchema)
1234
+ });
1235
+ const HighlightColorSchema = zod.z.enum([
1236
+ "yellow",
1237
+ "red",
1238
+ "green",
1239
+ "blue",
1240
+ "purple",
1241
+ "orange"
1242
+ ]);
1243
+ const HighlightSourceSchema = zod.z.enum(["agent", "user"]);
1244
+ const StoredHighlightSchema = zod.z.object({
1245
+ id: zod.z.string(),
1246
+ url: zod.z.string(),
1247
+ selector: zod.z.string().optional(),
1248
+ text: zod.z.string().optional(),
1249
+ label: zod.z.string().optional(),
1250
+ color: HighlightColorSchema.optional(),
1251
+ source: HighlightSourceSchema.optional(),
1252
+ createdAt: zod.z.string()
1253
+ });
1254
+ zod.z.object({
1255
+ highlights: zod.z.array(StoredHighlightSchema)
1256
+ });
1257
+ function parseArrayStateWithFallback(itemSchema, data, field, fallback, label) {
1258
+ if (!data || typeof data !== "object") return fallback;
1259
+ const rawItems = data[field];
1260
+ if (!Array.isArray(rawItems)) return fallback;
1261
+ const items = [];
1262
+ let invalid = 0;
1263
+ for (const item of rawItems) {
1264
+ const result = itemSchema.safeParse(item);
1265
+ if (result.success) {
1266
+ items.push(result.data);
1267
+ } else {
1268
+ invalid++;
1269
+ }
1270
+ }
1271
+ if (invalid > 0) {
1272
+ console.warn(
1273
+ `[persistence] ${label} dropped ${invalid} invalid ${field} item${invalid === 1 ? "" : "s"}`
1274
+ );
1275
+ }
1276
+ return { ...fallback, [field]: items };
1277
+ }
1278
+ const logger$w = createLogger("JsonPersistence");
1199
1279
  function canUseSafeStorage() {
1200
1280
  try {
1201
1281
  return electron.safeStorage.isEncryptionAvailable();
@@ -1218,14 +1298,15 @@ function encodeStoredData(payload, secure) {
1218
1298
  function loadJsonFile({
1219
1299
  filePath: filePath2,
1220
1300
  fallback,
1221
- parse: parse2,
1301
+ parse,
1222
1302
  secure = false
1223
1303
  }) {
1224
1304
  try {
1225
1305
  const raw = fs.readFileSync(filePath2);
1226
1306
  const decoded = decodeStoredData(raw, secure);
1227
- return parse2(JSON.parse(decoded));
1228
- } catch {
1307
+ return parse(JSON.parse(decoded));
1308
+ } catch (err) {
1309
+ logger$w.warn(`Failed to load ${filePath2}, using fallback:`, err);
1229
1310
  return fallback;
1230
1311
  }
1231
1312
  }
@@ -1261,8 +1342,8 @@ function createDebouncedJsonPersistence({
1261
1342
  typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
1262
1343
  )
1263
1344
  ).then(() => fs.promises.chmod(filePath2, 384).catch((err) => {
1264
- logger$v.warn(`Failed to chmod ${logLabel}:`, err);
1265
- })).catch((err) => logger$v.error(`Failed to save ${logLabel}:`, err));
1345
+ logger$w.warn(`Failed to chmod ${logLabel}:`, err);
1346
+ })).catch((err) => logger$w.error(`Failed to save ${logLabel}:`, err));
1266
1347
  };
1267
1348
  const schedule = () => {
1268
1349
  saveDirty2 = true;
@@ -1284,50 +1365,109 @@ function createDebouncedJsonPersistence({
1284
1365
  flush: flush2
1285
1366
  };
1286
1367
  }
1287
- let state$5 = null;
1288
- const listeners$2 = /* @__PURE__ */ new Set();
1289
- const SAVE_DEBOUNCE_MS$5 = 250;
1290
- function getHighlightsPath() {
1291
- return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
1292
- }
1293
- function createPersistence$1() {
1294
- return createDebouncedJsonPersistence({
1295
- debounceMs: SAVE_DEBOUNCE_MS$5,
1296
- filePath: getHighlightsPath(),
1297
- getValue: () => state$5,
1298
- logLabel: "highlights",
1299
- resetOnSchedule: true
1300
- });
1301
- }
1302
- let persistence$7 = null;
1303
- function getPersistence$1() {
1304
- persistence$7 ??= createPersistence$1();
1305
- return persistence$7;
1306
- }
1307
- function load$4() {
1308
- if (state$5) return state$5;
1309
- state$5 = loadJsonFile({
1310
- filePath: getHighlightsPath(),
1311
- fallback: { highlights: [] },
1312
- parse: (raw) => {
1313
- const parsed = raw;
1314
- return {
1315
- highlights: Array.isArray(parsed.highlights) ? parsed.highlights : []
1316
- };
1368
+ class PersistentState {
1369
+ state = null;
1370
+ listeners = /* @__PURE__ */ new Set();
1371
+ persistence = null;
1372
+ config;
1373
+ constructor(config) {
1374
+ this.config = {
1375
+ debounceMs: 250,
1376
+ resetOnSchedule: false,
1377
+ secure: false,
1378
+ snapshot: (s) => s,
1379
+ ...config
1380
+ };
1381
+ }
1382
+ // --- State access ---
1383
+ /** Get the current state, loading from disk if needed. */
1384
+ getState() {
1385
+ if (this.state) return this.state;
1386
+ this.state = loadJsonFile({
1387
+ filePath: this.getFilePath(),
1388
+ fallback: this.config.fallback,
1389
+ parse: this.config.parse,
1390
+ secure: this.config.secure
1391
+ });
1392
+ return this.state;
1393
+ }
1394
+ /**
1395
+ * Update state via a mutator function.
1396
+ * The mutator receives the current state and should mutate it in place.
1397
+ * Does NOT automatically save or emit — call save() and emit() as needed.
1398
+ */
1399
+ update(mutator) {
1400
+ const s = this.getState();
1401
+ mutator(s);
1402
+ }
1403
+ /**
1404
+ * Mutate state and apply persistence/notification as one operation.
1405
+ * The mutator may return a value, such as the item it created or removed.
1406
+ */
1407
+ mutate(mutator, options = {}) {
1408
+ const result = mutator(this.getState());
1409
+ if (options.save ?? true) {
1410
+ this.save();
1317
1411
  }
1318
- });
1319
- return state$5;
1320
- }
1321
- function save$3() {
1322
- getPersistence$1().schedule();
1323
- }
1324
- function emit$4() {
1325
- if (!state$5) return;
1326
- const snapshot2 = { highlights: [...state$5.highlights] };
1327
- for (const listener of listeners$2) {
1328
- listener(snapshot2);
1412
+ if (options.emit ?? true) {
1413
+ this.emit();
1414
+ }
1415
+ return result;
1416
+ }
1417
+ // --- Persistence ---
1418
+ /** Get the file path for this state's JSON file. */
1419
+ getFilePath() {
1420
+ return path.join(electron.app.getPath("userData"), this.config.filename);
1421
+ }
1422
+ /** Schedule a debounced write to disk. */
1423
+ save() {
1424
+ this.getPersistence().schedule();
1425
+ }
1426
+ /** Flush any pending write to disk immediately. */
1427
+ flushPersist() {
1428
+ return this.getPersistence().flush();
1429
+ }
1430
+ // --- Change notification ---
1431
+ /** Subscribe to state changes. Returns an unsubscribe function. */
1432
+ subscribe(listener) {
1433
+ this.listeners.add(listener);
1434
+ return () => {
1435
+ this.listeners.delete(listener);
1436
+ };
1437
+ }
1438
+ /** Emit the current state to all subscribers. No-op if state hasn't been loaded. */
1439
+ emit() {
1440
+ if (!this.state) return;
1441
+ const snapshot2 = this.config.snapshot(this.state);
1442
+ for (const listener of this.listeners) {
1443
+ listener(snapshot2);
1444
+ }
1445
+ }
1446
+ // --- Private ---
1447
+ getPersistence() {
1448
+ if (!this.persistence) {
1449
+ this.persistence = createDebouncedJsonPersistence({
1450
+ debounceMs: this.config.debounceMs,
1451
+ filePath: this.getFilePath(),
1452
+ getValue: () => this.state,
1453
+ logLabel: this.config.logLabel,
1454
+ resetOnSchedule: this.config.resetOnSchedule,
1455
+ secure: this.config.secure
1456
+ });
1457
+ }
1458
+ return this.persistence;
1329
1459
  }
1330
1460
  }
1461
+ const HIGHLIGHTS_FALLBACK = { highlights: [] };
1462
+ const store$2 = new PersistentState({
1463
+ filename: "vessel-highlights.json",
1464
+ fallback: HIGHLIGHTS_FALLBACK,
1465
+ parse: (raw) => parseArrayStateWithFallback(StoredHighlightSchema, raw, "highlights", HIGHLIGHTS_FALLBACK, "highlights"),
1466
+ logLabel: "highlights",
1467
+ debounceMs: 250,
1468
+ resetOnSchedule: true,
1469
+ snapshot: (s) => ({ highlights: [...s.highlights] })
1470
+ });
1331
1471
  function normalizeUrl$1(rawUrl) {
1332
1472
  try {
1333
1473
  const parsed = new URL(rawUrl);
@@ -1338,16 +1478,14 @@ function normalizeUrl$1(rawUrl) {
1338
1478
  }
1339
1479
  }
1340
1480
  function getState$2() {
1341
- load$4();
1342
- return { highlights: [...state$5.highlights] };
1481
+ const s = store$2.getState();
1482
+ return { highlights: [...s.highlights] };
1343
1483
  }
1344
1484
  function getHighlightsForUrl(url) {
1345
- load$4();
1346
1485
  const normalized = normalizeUrl$1(url);
1347
- return state$5.highlights.filter((h) => h.url === normalized);
1486
+ return store$2.getState().highlights.filter((h) => h.url === normalized);
1348
1487
  }
1349
1488
  function addHighlight(url, selector, text, label, color, source) {
1350
- load$4();
1351
1489
  const highlight = {
1352
1490
  id: crypto$1.randomUUID(),
1353
1491
  url: normalizeUrl$1(url),
@@ -1358,50 +1496,66 @@ function addHighlight(url, selector, text, label, color, source) {
1358
1496
  source: source || void 0,
1359
1497
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1360
1498
  };
1361
- state$5.highlights.push(highlight);
1362
- save$3();
1363
- emit$4();
1499
+ store$2.mutate((s) => {
1500
+ s.highlights.push(highlight);
1501
+ });
1364
1502
  return highlight;
1365
1503
  }
1366
1504
  function removeHighlight(id) {
1367
- load$4();
1368
- const index = state$5.highlights.findIndex((h) => h.id === id);
1369
- if (index === -1) return null;
1370
- const [removed] = state$5.highlights.splice(index, 1);
1371
- save$3();
1372
- emit$4();
1505
+ const removed = store$2.mutate((s) => {
1506
+ const index = s.highlights.findIndex((h) => h.id === id);
1507
+ if (index === -1) return null;
1508
+ const [removed2] = s.highlights.splice(index, 1);
1509
+ return removed2;
1510
+ }, {
1511
+ save: false,
1512
+ emit: false
1513
+ });
1514
+ if (removed) {
1515
+ store$2.save();
1516
+ store$2.emit();
1517
+ }
1373
1518
  return removed;
1374
1519
  }
1375
1520
  function findHighlightByText(url, text) {
1376
- load$4();
1377
1521
  const normalized = normalizeUrl$1(url);
1378
- return state$5.highlights.find(
1522
+ return store$2.getState().highlights.find(
1379
1523
  (h) => h.url === normalized && h.text && h.text === text
1380
1524
  ) ?? null;
1381
1525
  }
1382
1526
  function updateHighlightColor(id, color) {
1383
- load$4();
1384
- const highlight = state$5.highlights.find((h) => h.id === id);
1385
- if (!highlight) return null;
1386
- highlight.color = color;
1387
- save$3();
1388
- emit$4();
1527
+ const highlight = store$2.mutate((s) => {
1528
+ const item = s.highlights.find((h) => h.id === id) ?? null;
1529
+ if (item) item.color = color;
1530
+ return item;
1531
+ }, {
1532
+ save: false,
1533
+ emit: false
1534
+ });
1535
+ if (highlight) {
1536
+ store$2.save();
1537
+ store$2.emit();
1538
+ }
1389
1539
  return highlight;
1390
1540
  }
1391
1541
  function clearHighlightsForUrl(url) {
1392
- load$4();
1393
1542
  const normalized = normalizeUrl$1(url);
1394
- const before = state$5.highlights.length;
1395
- state$5.highlights = state$5.highlights.filter((h) => h.url !== normalized);
1396
- const removed = before - state$5.highlights.length;
1543
+ const removed = store$2.mutate((s) => {
1544
+ const before = s.highlights.length;
1545
+ s.highlights = s.highlights.filter((h) => h.url !== normalized);
1546
+ return before - s.highlights.length;
1547
+ }, {
1548
+ save: false,
1549
+ emit: false
1550
+ });
1397
1551
  if (removed > 0) {
1398
- save$3();
1399
- emit$4();
1552
+ store$2.save();
1553
+ store$2.emit();
1400
1554
  }
1401
1555
  return removed;
1402
1556
  }
1403
1557
  function flushPersist$4() {
1404
- return getPersistence$1().flush();
1558
+ return store$2.flushPersist();
1405
1559
  }
1406
1560
  const SKIP_TAGS_JS = "var SKIP_TAGS = {SCRIPT:1,STYLE:1,NOSCRIPT:1,TEMPLATE:1,IFRAME:1,SVG:1};";
1407
1561
  const CONTENT_ROOTS_JS = `
@@ -2025,114 +2179,94 @@ function persistHighlight(url, text) {
2025
2179
  return { success: true, text: capped, id: highlight.id };
2026
2180
  }
2027
2181
  const MAX_HISTORY_ENTRIES = 5e3;
2028
- const SAVE_DEBOUNCE_MS$4 = 250;
2029
- let state$4 = null;
2030
- const listeners$1 = /* @__PURE__ */ new Set();
2031
- function getHistoryPath() {
2032
- return path.join(electron.app.getPath("userData"), "vessel-history.json");
2033
- }
2034
- function load$3() {
2035
- if (state$4) return state$4;
2036
- state$4 = loadJsonFile({
2037
- filePath: getHistoryPath(),
2038
- fallback: { entries: [] },
2039
- parse: (raw) => {
2040
- const parsed = raw;
2041
- return {
2042
- entries: Array.isArray(parsed.entries) ? parsed.entries : []
2043
- };
2044
- }
2045
- });
2046
- return state$4;
2047
- }
2048
- const persistence$6 = createDebouncedJsonPersistence({
2049
- debounceMs: SAVE_DEBOUNCE_MS$4,
2050
- filePath: getHistoryPath(),
2051
- getValue: () => state$4,
2052
- logLabel: "history"
2053
- });
2054
- function save$2() {
2055
- persistence$6.schedule();
2056
- }
2057
- function emit$3() {
2058
- if (!state$4) return;
2059
- const snapshot2 = listEntries$2();
2060
- for (const listener of listeners$1) {
2061
- listener(snapshot2);
2182
+ const HISTORY_FALLBACK = { entries: [] };
2183
+ const store$1 = new PersistentState({
2184
+ filename: "vessel-history.json",
2185
+ fallback: HISTORY_FALLBACK,
2186
+ parse: (raw) => parseArrayStateWithFallback(HistoryEntrySchema, raw, "entries", HISTORY_FALLBACK, "history"),
2187
+ logLabel: "history",
2188
+ debounceMs: 250,
2189
+ snapshot: (s) => {
2190
+ const entries = s.entries.slice(0, 200);
2191
+ return {
2192
+ entries,
2193
+ offset: 0,
2194
+ limit: entries.length,
2195
+ total: s.entries.length
2196
+ };
2062
2197
  }
2063
- }
2064
- function getState$1() {
2065
- load$3();
2066
- return { entries: [...state$4.entries] };
2067
- }
2198
+ });
2068
2199
  function listEntries$2(offset = 0, limit = 200) {
2069
- load$3();
2200
+ const s = store$1.getState();
2070
2201
  const safeOffset = Math.max(0, Math.floor(offset));
2071
2202
  const safeLimit = Math.max(1, Math.min(500, Math.floor(limit)));
2072
2203
  return {
2073
- entries: state$4.entries.slice(safeOffset, safeOffset + safeLimit),
2204
+ entries: s.entries.slice(safeOffset, safeOffset + safeLimit),
2074
2205
  offset: safeOffset,
2075
2206
  limit: safeLimit,
2076
- total: state$4.entries.length
2207
+ total: s.entries.length
2077
2208
  };
2078
2209
  }
2210
+ function getState$1() {
2211
+ const s = store$1.getState();
2212
+ return { entries: [...s.entries] };
2213
+ }
2079
2214
  function subscribe$1(listener) {
2080
- listeners$1.add(listener);
2081
- return () => {
2082
- listeners$1.delete(listener);
2083
- };
2215
+ return store$1.subscribe(listener);
2084
2216
  }
2085
2217
  function addEntry$1(url, title) {
2086
2218
  if (!url || url === "about:blank") return;
2087
- load$3();
2088
- const last = state$4.entries[0];
2089
- if (last && last.url === url) {
2090
- if (title && title !== last.title) {
2091
- last.title = title;
2092
- save$2();
2093
- emit$3();
2219
+ const changed = store$1.mutate((s) => {
2220
+ const last = s.entries[0];
2221
+ if (last && last.url === url) {
2222
+ if (title && title !== last.title) {
2223
+ last.title = title;
2224
+ return true;
2225
+ }
2226
+ return false;
2094
2227
  }
2095
- return;
2096
- }
2097
- const entry = {
2098
- url,
2099
- title: title || url,
2100
- visitedAt: (/* @__PURE__ */ new Date()).toISOString()
2101
- };
2102
- state$4.entries.unshift(entry);
2103
- if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
2104
- state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
2228
+ const entry = {
2229
+ url,
2230
+ title: title || url,
2231
+ visitedAt: (/* @__PURE__ */ new Date()).toISOString()
2232
+ };
2233
+ s.entries.unshift(entry);
2234
+ if (s.entries.length > MAX_HISTORY_ENTRIES) {
2235
+ s.entries = s.entries.slice(0, MAX_HISTORY_ENTRIES);
2236
+ }
2237
+ return true;
2238
+ }, { save: false, emit: false });
2239
+ if (changed) {
2240
+ store$1.save();
2241
+ store$1.emit();
2105
2242
  }
2106
- save$2();
2107
- emit$3();
2108
2243
  }
2109
2244
  function search(query, limit = 50) {
2110
- load$3();
2111
- if (!query.trim()) return state$4.entries.slice(0, limit);
2245
+ const s = store$1.getState();
2246
+ if (!query.trim()) return s.entries.slice(0, limit);
2112
2247
  const normalized = query.toLowerCase();
2113
- return state$4.entries.filter(
2248
+ return s.entries.filter(
2114
2249
  (e) => e.url.toLowerCase().includes(normalized) || e.title.toLowerCase().includes(normalized)
2115
2250
  ).slice(0, limit);
2116
2251
  }
2117
2252
  function clearAll$1() {
2118
- state$4 = { entries: [] };
2119
- save$2();
2120
- emit$3();
2253
+ store$1.mutate((s) => {
2254
+ s.entries = [];
2255
+ });
2121
2256
  }
2122
2257
  function clearByTimeRange(timeRange) {
2123
- load$3();
2124
2258
  if (timeRange === "all") {
2125
2259
  clearAll$1();
2126
2260
  return;
2127
2261
  }
2128
2262
  const now = Date.now();
2129
2263
  const cutoff = new Date(now - timeRangeToMs(timeRange));
2130
- state$4.entries = state$4.entries.filter((entry) => {
2131
- const visitedAt = new Date(entry.visitedAt).getTime();
2132
- return Number.isNaN(visitedAt) || visitedAt < cutoff.getTime();
2264
+ store$1.mutate((s) => {
2265
+ s.entries = s.entries.filter((entry) => {
2266
+ const visitedAt = new Date(entry.visitedAt).getTime();
2267
+ return Number.isNaN(visitedAt) || visitedAt < cutoff.getTime();
2268
+ });
2133
2269
  });
2134
- save$2();
2135
- emit$3();
2136
2270
  }
2137
2271
  function timeRangeToMs(range) {
2138
2272
  switch (range) {
@@ -2183,12 +2317,13 @@ function importHistoryFromJson(content) {
2183
2317
  let skipped = 0;
2184
2318
  let errors = 0;
2185
2319
  try {
2186
- const parsed = JSON.parse(content);
2187
- const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
2188
- load$3();
2189
- const existingUrls = new Set(state$4.entries.map((e) => e.url));
2320
+ const parsed = HistoryImportStateSchema.safeParse(JSON.parse(content));
2321
+ const entries = parsed.success ? parsed.data.entries : [];
2322
+ if (!parsed.success) errors++;
2323
+ const s = store$1.getState();
2324
+ const existingUrls = new Set(s.entries.map((e) => e.url));
2190
2325
  for (const entry of entries) {
2191
- if (!entry?.url || typeof entry.url !== "string") {
2326
+ if (!entry.url) {
2192
2327
  errors++;
2193
2328
  continue;
2194
2329
  }
@@ -2196,22 +2331,29 @@ function importHistoryFromJson(content) {
2196
2331
  skipped++;
2197
2332
  continue;
2198
2333
  }
2199
- state$4.entries.push({
2200
- url: entry.url,
2201
- title: typeof entry.title === "string" ? entry.title : entry.url,
2202
- visitedAt: typeof entry.visitedAt === "string" ? entry.visitedAt : (/* @__PURE__ */ new Date()).toISOString()
2203
- });
2204
2334
  existingUrls.add(entry.url);
2205
2335
  imported++;
2206
2336
  }
2207
- state$4.entries.sort(
2208
- (a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
2209
- );
2210
- if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
2211
- state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
2337
+ if (imported > 0) {
2338
+ store$1.mutate((state2) => {
2339
+ const urlSet = new Set(state2.entries.map((e) => e.url));
2340
+ for (const entry of entries) {
2341
+ if (!entry.url || urlSet.has(entry.url)) continue;
2342
+ state2.entries.push({
2343
+ url: entry.url,
2344
+ title: entry.title || entry.url,
2345
+ visitedAt: entry.visitedAt || (/* @__PURE__ */ new Date()).toISOString()
2346
+ });
2347
+ urlSet.add(entry.url);
2348
+ }
2349
+ state2.entries.sort(
2350
+ (a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
2351
+ );
2352
+ if (state2.entries.length > MAX_HISTORY_ENTRIES) {
2353
+ state2.entries = state2.entries.slice(0, MAX_HISTORY_ENTRIES);
2354
+ }
2355
+ });
2212
2356
  }
2213
- save$2();
2214
- emit$3();
2215
2357
  } catch {
2216
2358
  errors++;
2217
2359
  }
@@ -2221,9 +2363,10 @@ function importHistoryFromHtml(content) {
2221
2363
  let imported = 0;
2222
2364
  let skipped = 0;
2223
2365
  let errors = 0;
2224
- load$3();
2225
- const existingUrls = new Set(state$4.entries.map((e) => e.url));
2366
+ const s = store$1.getState();
2367
+ const existingUrls = new Set(s.entries.map((e) => e.url));
2226
2368
  const hrefRegex = /<A\s+[^>]*HREF="([^"]+)"[^>]*>([^<]*)<\/A>/gi;
2369
+ const newEntries = [];
2227
2370
  let match;
2228
2371
  while ((match = hrefRegex.exec(content)) !== null) {
2229
2372
  const url = match[1];
@@ -2233,26 +2376,25 @@ function importHistoryFromHtml(content) {
2233
2376
  else errors++;
2234
2377
  continue;
2235
2378
  }
2236
- state$4.entries.push({
2237
- url,
2238
- title,
2239
- visitedAt: (/* @__PURE__ */ new Date()).toISOString()
2240
- });
2379
+ newEntries.push({ url, title, visitedAt: (/* @__PURE__ */ new Date()).toISOString() });
2241
2380
  existingUrls.add(url);
2242
2381
  imported++;
2243
2382
  }
2244
- state$4.entries.sort(
2245
- (a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
2246
- );
2247
- if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
2248
- state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
2383
+ if (newEntries.length > 0) {
2384
+ store$1.mutate((state2) => {
2385
+ state2.entries.push(...newEntries);
2386
+ state2.entries.sort(
2387
+ (a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
2388
+ );
2389
+ if (state2.entries.length > MAX_HISTORY_ENTRIES) {
2390
+ state2.entries = state2.entries.slice(0, MAX_HISTORY_ENTRIES);
2391
+ }
2392
+ });
2249
2393
  }
2250
- save$2();
2251
- emit$3();
2252
2394
  return { imported, skipped, errors };
2253
2395
  }
2254
2396
  function flushPersist$3() {
2255
- return persistence$6.flush();
2397
+ return store$1.flushPersist();
2256
2398
  }
2257
2399
  const MAX_CONSOLE_ENTRIES = 500;
2258
2400
  const MAX_NETWORK_ENTRIES = 200;
@@ -2954,7 +3096,7 @@ function destroySession(tabId) {
2954
3096
  sessions.delete(tabId);
2955
3097
  }
2956
3098
  }
2957
- const logger$u = createLogger("TabManager");
3099
+ const logger$v = createLogger("TabManager");
2958
3100
  function sanitizeFilename(title, ext) {
2959
3101
  const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
2960
3102
  const escapedExt = ext.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3373,7 +3515,7 @@ class TabManager {
3373
3515
  }));
3374
3516
  if (entries.length > 0) {
3375
3517
  void highlightBatchOnPage(wc, entries).catch(
3376
- (err) => logger$u.warn("Failed to batch highlight:", err)
3518
+ (err) => logger$v.warn("Failed to batch highlight:", err)
3377
3519
  );
3378
3520
  }
3379
3521
  }
@@ -3395,12 +3537,12 @@ class TabManager {
3395
3537
  const result = await captureSelectionHighlight(wc);
3396
3538
  if (result.success && result.text) {
3397
3539
  await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
3398
- (err) => logger$u.warn("Failed to capture highlight:", err)
3540
+ (err) => logger$v.warn("Failed to capture highlight:", err)
3399
3541
  );
3400
3542
  }
3401
3543
  this.highlightCaptureCallback?.(result);
3402
3544
  } catch (err) {
3403
- logger$u.warn("Failed to capture highlight from page:", err);
3545
+ logger$v.warn("Failed to capture highlight from page:", err);
3404
3546
  this.highlightCaptureCallback?.({
3405
3547
  success: false,
3406
3548
  message: "Could not capture selection"
@@ -3425,7 +3567,7 @@ class TabManager {
3425
3567
  void this.removeHighlightMarksForText(wc, text);
3426
3568
  }
3427
3569
  } catch (err) {
3428
- logger$u.warn("Failed to remove highlight from matching tab:", err);
3570
+ logger$v.warn("Failed to remove highlight from matching tab:", err);
3429
3571
  }
3430
3572
  }
3431
3573
  this.highlightCaptureCallback?.({
@@ -3456,12 +3598,12 @@ class TabManager {
3456
3598
  void 0,
3457
3599
  color
3458
3600
  ).catch(
3459
- (err) => logger$u.warn("Failed to update highlight color:", err)
3601
+ (err) => logger$v.warn("Failed to update highlight color:", err)
3460
3602
  );
3461
3603
  });
3462
3604
  }
3463
3605
  } catch (err) {
3464
- logger$u.warn("Failed to iterate highlights for color change:", err);
3606
+ logger$v.warn("Failed to iterate highlights for color change:", err);
3465
3607
  }
3466
3608
  }
3467
3609
  this.highlightCaptureCallback?.({
@@ -3516,7 +3658,7 @@ class TabManager {
3516
3658
  });
3517
3659
  })()`
3518
3660
  ).catch(
3519
- (err) => logger$u.warn("Failed to remove highlight marks:", err)
3661
+ (err) => logger$v.warn("Failed to remove highlight marks:", err)
3520
3662
  );
3521
3663
  }
3522
3664
  broadcastState(meta = { persistSession: false }) {
@@ -4155,7 +4297,7 @@ function load$2() {
4155
4297
  });
4156
4298
  return snapshots;
4157
4299
  }
4158
- const persistence$5 = createDebouncedJsonPersistence({
4300
+ const persistence$4 = createDebouncedJsonPersistence({
4159
4301
  debounceMs: SAVE_DEBOUNCE_MS$3,
4160
4302
  filePath: getFilePath$1(),
4161
4303
  getValue: () => snapshots,
@@ -4184,11 +4326,11 @@ function saveSnapshot(rawUrl, title, textContent, headings) {
4184
4326
  };
4185
4327
  s.delete(key2);
4186
4328
  s.set(key2, snapshot2);
4187
- persistence$5.schedule();
4329
+ persistence$4.schedule();
4188
4330
  return snapshot2;
4189
4331
  }
4190
4332
  function flushPersist$2() {
4191
- return persistence$5.flush();
4333
+ return persistence$4.flush();
4192
4334
  }
4193
4335
  const SEARCH_ENGINE_HOSTS = [
4194
4336
  "google.",
@@ -4768,7 +4910,7 @@ async function readJsonResponse(response, fallback, onError) {
4768
4910
  return fallback;
4769
4911
  }
4770
4912
  }
4771
- const logger$t = createLogger("Premium");
4913
+ const logger$u = createLogger("Premium");
4772
4914
  const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
4773
4915
  const FREE_TOOL_ITERATION_LIMIT = 50;
4774
4916
  const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -4944,7 +5086,7 @@ async function verifySubscription$1(identifier) {
4944
5086
  });
4945
5087
  if (!res.ok) {
4946
5088
  const detail = await readApiErrorDetail(res);
4947
- logger$t.warn(
5089
+ logger$u.warn(
4948
5090
  "Verification API returned a non-OK status:",
4949
5091
  res.status,
4950
5092
  detail
@@ -4963,7 +5105,7 @@ async function verifySubscription$1(identifier) {
4963
5105
  setSetting("premium", updated);
4964
5106
  return updated;
4965
5107
  } catch (err) {
4966
- logger$t.warn("Verification failed:", err);
5108
+ logger$u.warn("Verification failed:", err);
4967
5109
  return current;
4968
5110
  }
4969
5111
  }
@@ -4984,7 +5126,7 @@ async function requestActivationCode(email) {
4984
5126
  const data = await readJsonResponse(
4985
5127
  res,
4986
5128
  {},
4987
- (msg) => logger$t.warn("Failed to parse premium activation response:", msg)
5129
+ (msg) => logger$u.warn("Failed to parse premium activation response:", msg)
4988
5130
  );
4989
5131
  if (!res.ok || !data.challengeToken) {
4990
5132
  return errorResult(data.error || `HTTP ${res.status}`);
@@ -5029,7 +5171,7 @@ async function verifyActivationCode(email, code, challengeToken) {
5029
5171
  const data = await readJsonResponse(
5030
5172
  res,
5031
5173
  {},
5032
- (msg) => logger$t.warn("Failed to parse premium verification response:", msg)
5174
+ (msg) => logger$u.warn("Failed to parse premium verification response:", msg)
5033
5175
  );
5034
5176
  if (!res.ok) {
5035
5177
  return errorResult(data.error || `HTTP ${res.status}`, {
@@ -5626,7 +5768,7 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
5626
5768
  const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
5627
5769
  const MUTATION_SETTLE_AFTER_MS = 1500;
5628
5770
  const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
5629
- const logger$s = createLogger("Extractor");
5771
+ const logger$t = createLogger("Extractor");
5630
5772
  const EXTRACTION_CACHE_TTL_MS = 1500;
5631
5773
  const MAX_EXTRACTION_CACHE_ENTRIES = 50;
5632
5774
  const extractionCache = new BoundedCache(
@@ -6391,9 +6533,9 @@ async function executeScript(webContents, script, options = {}) {
6391
6533
  const message = err instanceof Error ? err.message : String(err);
6392
6534
  const detail = `Failed to execute page script${label} on ${url}: ${message}`;
6393
6535
  if (options.warnOnFailure) {
6394
- logger$s.warn(detail);
6536
+ logger$t.warn(detail);
6395
6537
  } else {
6396
- logger$s.debug(detail);
6538
+ logger$t.debug(detail);
6397
6539
  }
6398
6540
  return null;
6399
6541
  } finally {
@@ -6502,7 +6644,7 @@ async function estimateExtractionTimeout(webContents) {
6502
6644
  return EXTRACT_TIMEOUT_BASE_MS + extra;
6503
6645
  }
6504
6646
  } catch (err) {
6505
- logger$s.warn("Failed to estimate extraction timeout, using base timeout:", err);
6647
+ logger$t.warn("Failed to estimate extraction timeout, using base timeout:", err);
6506
6648
  }
6507
6649
  return EXTRACT_TIMEOUT_BASE_MS;
6508
6650
  }
@@ -6673,7 +6815,7 @@ function loadHistory() {
6673
6815
  }
6674
6816
  return recentPageDiffBursts;
6675
6817
  }
6676
- const persistence$4 = createDebouncedJsonPersistence({
6818
+ const persistence$3 = createDebouncedJsonPersistence({
6677
6819
  debounceMs: SAVE_DEBOUNCE_MS$2,
6678
6820
  filePath: getHistoryFilePath(),
6679
6821
  getValue: () => recentPageDiffBursts,
@@ -6703,7 +6845,7 @@ function getPageDiffBursts(rawUrl) {
6703
6845
  } else {
6704
6846
  history.delete(key2);
6705
6847
  }
6706
- persistence$4.schedule();
6848
+ persistence$3.schedule();
6707
6849
  }
6708
6850
  return bursts.slice().reverse();
6709
6851
  }
@@ -6724,7 +6866,7 @@ function enrichWithBurstHistory(key2, diff) {
6724
6866
  now: Date.parse(detectedAt)
6725
6867
  });
6726
6868
  history.set(key2, bursts);
6727
- persistence$4.schedule();
6869
+ persistence$3.schedule();
6728
6870
  const recentBursts = bursts.slice(-5);
6729
6871
  return {
6730
6872
  ...diff,
@@ -7470,7 +7612,7 @@ function isClickReadLoop(names) {
7470
7612
  return clickReadPairs >= 2;
7471
7613
  }
7472
7614
  const TERMINAL_TOOL_RESULT = "__VESSEL_TERMINAL_TOOL_RESULT__";
7473
- const logger$r = createLogger("PromptCache");
7615
+ const logger$s = createLogger("PromptCache");
7474
7616
  function shortHash(value) {
7475
7617
  return crypto$2.createHash("sha256").update(value).digest("hex").slice(0, 12);
7476
7618
  }
@@ -7522,7 +7664,7 @@ function logOpenAIPromptCacheUsage(usage, context) {
7522
7664
  const details = record.prompt_tokens_details;
7523
7665
  const cachedTokens = details && typeof details === "object" ? numericField(details, "cached_tokens") : null;
7524
7666
  if (promptTokens === null && cachedTokens === null) return;
7525
- logger$r.debug("OpenAI prompt cache usage", {
7667
+ logger$s.debug("OpenAI prompt cache usage", {
7526
7668
  model: context.model,
7527
7669
  mode: context.mode,
7528
7670
  promptTokens,
@@ -7538,7 +7680,7 @@ function logAnthropicPromptCacheUsage(usage, context) {
7538
7680
  if (inputTokens === null && cacheCreationTokens === null && cacheReadTokens === null) {
7539
7681
  return;
7540
7682
  }
7541
- logger$r.debug("Anthropic prompt cache usage", {
7683
+ logger$s.debug("Anthropic prompt cache usage", {
7542
7684
  model: context.model,
7543
7685
  mode: context.mode,
7544
7686
  inputTokens,
@@ -8431,7 +8573,7 @@ function recoverNarratedActionToolCalls(text, availableToolNames) {
8431
8573
  }
8432
8574
  return recovered;
8433
8575
  }
8434
- const logger$q = createLogger("OpenAIProvider");
8576
+ const logger$r = createLogger("OpenAIProvider");
8435
8577
  function shouldDebugAgentLoop() {
8436
8578
  const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
8437
8579
  return value === "1" || value === "true";
@@ -8699,9 +8841,9 @@ function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage)
8699
8841
  function logAgentLoopDebug(payload) {
8700
8842
  if (!shouldDebugAgentLoop()) return;
8701
8843
  try {
8702
- logger$q.info(`[agent-debug] ${JSON.stringify(payload)}`);
8844
+ logger$r.info(`[agent-debug] ${JSON.stringify(payload)}`);
8703
8845
  } catch (err) {
8704
- logger$q.warn("Failed to serialize debug payload:", err);
8846
+ logger$r.warn("Failed to serialize debug payload:", err);
8705
8847
  }
8706
8848
  }
8707
8849
  function formatOpenAICompatErrorMessage(providerId, message) {
@@ -9328,7 +9470,7 @@ function createLocalPkceOAuthFlow(config) {
9328
9470
  isInProgress: () => activeFlow !== null
9329
9471
  };
9330
9472
  }
9331
- const logger$p = createLogger("CodexOAuth");
9473
+ const logger$q = createLogger("CodexOAuth");
9332
9474
  const ISSUER = "https://auth.openai.com";
9333
9475
  const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
9334
9476
  const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
@@ -9456,7 +9598,7 @@ async function refreshAccessToken(tokens) {
9456
9598
  }
9457
9599
  const codexOAuth = createLocalPkceOAuthFlow({
9458
9600
  name: "Codex",
9459
- logger: logger$p,
9601
+ logger: logger$q,
9460
9602
  preferredPorts: [PREFERRED_PORT$1, FALLBACK_PORT$1],
9461
9603
  timeoutMs: AUTH_TIMEOUT_MS$1,
9462
9604
  callbackPath: () => "/auth/callback",
@@ -9480,7 +9622,7 @@ async function startCodexOAuth(onStatus) {
9480
9622
  function cancelCodexOAuth() {
9481
9623
  codexOAuth.cancel();
9482
9624
  }
9483
- const logger$o = createLogger("CodexProvider");
9625
+ const logger$p = createLogger("CodexProvider");
9484
9626
  const REFRESH_WINDOW_MS = 5 * 60 * 1e3;
9485
9627
  const CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api/codex";
9486
9628
  const CODEX_CLIENT_VERSION = "0.129.0";
@@ -9545,7 +9687,7 @@ class CodexProvider {
9545
9687
  async ensureFreshTokens() {
9546
9688
  if (Date.now() < this.tokens.expiresAt - REFRESH_WINDOW_MS) return;
9547
9689
  try {
9548
- logger$o.info("Refreshing Codex access token");
9690
+ logger$p.info("Refreshing Codex access token");
9549
9691
  const fresh = await refreshAccessToken(this.tokens);
9550
9692
  this.tokens = fresh;
9551
9693
  writeStoredCodexTokens(fresh);
@@ -9693,7 +9835,7 @@ class CodexProvider {
9693
9835
  } catch (err) {
9694
9836
  if (err.name !== "AbortError") {
9695
9837
  const msg = err instanceof Error ? err.message : String(err);
9696
- logger$o.error("Codex streamQuery error:", err);
9838
+ logger$p.error("Codex streamQuery error:", err);
9697
9839
  onChunk(`
9698
9840
 
9699
9841
  [Error: ${msg}]`);
@@ -9761,7 +9903,7 @@ class CodexProvider {
9761
9903
  } catch (err) {
9762
9904
  if (err.name !== "AbortError") {
9763
9905
  const msg = err instanceof Error ? err.message : String(err);
9764
- logger$o.error("Codex streamAgentQuery error:", err);
9906
+ logger$p.error("Codex streamAgentQuery error:", err);
9765
9907
  onChunk(`
9766
9908
 
9767
9909
  [Error: ${msg}]`);
@@ -10995,14 +11137,17 @@ function normalizeBookmarkMetadataUpdate(input) {
10995
11137
  }
10996
11138
  return normalized;
10997
11139
  }
10998
- const logger$n = createLogger("BookmarkManager");
11140
+ function unixNow() {
11141
+ return Math.floor(Date.now() / 1e3);
11142
+ }
11143
+ const logger$o = createLogger("BookmarkManager");
10999
11144
  const UNSORTED_ID = "unsorted";
11000
11145
  const ARCHIVE_FOLDER_NAME = "Archive";
11001
11146
  const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
11002
11147
  const SAVE_DEBOUNCE_MS$1 = 250;
11003
11148
  const DEFAULT_BOOKMARK_SEARCH_LIMIT = 50;
11004
11149
  const MAX_BOOKMARK_SEARCH_LIMIT = 200;
11005
- let state$3 = null;
11150
+ let state$2 = null;
11006
11151
  const listeners = /* @__PURE__ */ new Set();
11007
11152
  function cloneState(current) {
11008
11153
  return {
@@ -11012,7 +11157,7 @@ function cloneState(current) {
11012
11157
  }
11013
11158
  function getFolderMap() {
11014
11159
  load$1();
11015
- return new Map(state$3.folders.map((folder) => [folder.id, folder]));
11160
+ return new Map(state$2.folders.map((folder) => [folder.id, folder]));
11016
11161
  }
11017
11162
  function getBookmarksPath() {
11018
11163
  return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
@@ -11021,18 +11166,18 @@ function createPersistence() {
11021
11166
  return createDebouncedJsonPersistence({
11022
11167
  debounceMs: SAVE_DEBOUNCE_MS$1,
11023
11168
  filePath: getBookmarksPath(),
11024
- getValue: () => state$3,
11169
+ getValue: () => state$2,
11025
11170
  logLabel: "bookmarks"
11026
11171
  });
11027
11172
  }
11028
- let persistence$3 = null;
11173
+ let persistence$2 = null;
11029
11174
  function getPersistence() {
11030
- persistence$3 ??= createPersistence();
11031
- return persistence$3;
11175
+ persistence$2 ??= createPersistence();
11176
+ return persistence$2;
11032
11177
  }
11033
11178
  function load$1() {
11034
- if (state$3) return state$3;
11035
- state$3 = loadJsonFile({
11179
+ if (state$2) return state$2;
11180
+ state$2 = loadJsonFile({
11036
11181
  filePath: getBookmarksPath(),
11037
11182
  fallback: { folders: [], bookmarks: [] },
11038
11183
  parse: (raw) => {
@@ -11043,7 +11188,7 @@ function load$1() {
11043
11188
  };
11044
11189
  }
11045
11190
  });
11046
- return state$3;
11191
+ return state$2;
11047
11192
  }
11048
11193
  function save$1() {
11049
11194
  getPersistence().schedule();
@@ -11056,8 +11201,8 @@ function assignDefinedBookmarkFields(bookmark, fields) {
11056
11201
  }
11057
11202
  }
11058
11203
  function emit$2() {
11059
- if (!state$3) return;
11060
- const snapshot2 = cloneState(state$3);
11204
+ if (!state$2) return;
11205
+ const snapshot2 = cloneState(state$2);
11061
11206
  for (const listener of listeners) {
11062
11207
  listener(snapshot2);
11063
11208
  }
@@ -11066,9 +11211,9 @@ function escapeBookmarkHtml(value) {
11066
11211
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
11067
11212
  }
11068
11213
  function toNetscapeTimestamp(value) {
11069
- if (!value) return Math.floor(Date.now() / 1e3);
11214
+ if (!value) return unixNow();
11070
11215
  const time = Date.parse(value);
11071
- return Number.isNaN(time) ? Math.floor(Date.now() / 1e3) : Math.floor(time / 1e3);
11216
+ return Number.isNaN(time) ? unixNow() : Math.floor(time / 1e3);
11072
11217
  }
11073
11218
  function getBookmarkDescription(bookmark) {
11074
11219
  const lines = [
@@ -11099,7 +11244,7 @@ function exportBookmarksHtml(options = {}) {
11099
11244
  const resolvedOptions = {
11100
11245
  includeNotes: options.includeNotes ?? false
11101
11246
  };
11102
- const now = Math.floor(Date.now() / 1e3);
11247
+ const now = unixNow();
11103
11248
  const folders = [
11104
11249
  { id: UNSORTED_ID, name: "Vessel Bookmarks", createdAt: "", summary: "" },
11105
11250
  ...current.folders
@@ -11140,7 +11285,7 @@ function exportBookmarkFolderHtml(folderId, options = {}) {
11140
11285
  const resolvedOptions = {
11141
11286
  includeNotes: options.includeNotes ?? false
11142
11287
  };
11143
- const now = Math.floor(Date.now() / 1e3);
11288
+ const now = unixNow();
11144
11289
  const items = current.bookmarks.filter(
11145
11290
  (bookmark) => bookmark.folderId === folder.id
11146
11291
  );
@@ -11179,28 +11324,28 @@ function subscribe(listener) {
11179
11324
  };
11180
11325
  }
11181
11326
  function clearAll() {
11182
- state$3 = { folders: [], bookmarks: [] };
11327
+ state$2 = { folders: [], bookmarks: [] };
11183
11328
  save$1();
11184
11329
  emit$2();
11185
11330
  }
11186
11331
  function getBookmark(id) {
11187
11332
  load$1();
11188
- const bookmark = state$3.bookmarks.find((item) => item.id === id);
11333
+ const bookmark = state$2.bookmarks.find((item) => item.id === id);
11189
11334
  return bookmark ? { ...bookmark } : null;
11190
11335
  }
11191
11336
  function getBookmarkByUrl(url) {
11192
11337
  load$1();
11193
11338
  const normalized = url.trim();
11194
11339
  if (!normalized) return null;
11195
- const bookmark = [...state$3.bookmarks].reverse().find((item) => item.url === normalized);
11340
+ const bookmark = [...state$2.bookmarks].reverse().find((item) => item.url === normalized);
11196
11341
  return bookmark ? { ...bookmark } : null;
11197
11342
  }
11198
11343
  function getBookmarkByUrlInFolder(url, folderId) {
11199
11344
  load$1();
11200
11345
  const normalizedUrl = url.trim();
11201
11346
  if (!normalizedUrl) return null;
11202
- const targetFolderId = folderId && folderId !== UNSORTED_ID ? state$3.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
11203
- const bookmark = [...state$3.bookmarks].reverse().find(
11347
+ const targetFolderId = folderId && folderId !== UNSORTED_ID ? state$2.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
11348
+ const bookmark = [...state$2.bookmarks].reverse().find(
11204
11349
  (item) => item.url === normalizedUrl && item.folderId === targetFolderId
11205
11350
  );
11206
11351
  return bookmark ? { ...bookmark } : null;
@@ -11208,14 +11353,14 @@ function getBookmarkByUrlInFolder(url, folderId) {
11208
11353
  function getFolder(id) {
11209
11354
  load$1();
11210
11355
  if (!id || id === UNSORTED_ID) return null;
11211
- const folder = state$3.folders.find((item) => item.id === id);
11356
+ const folder = state$2.folders.find((item) => item.id === id);
11212
11357
  return folder ? { ...folder } : null;
11213
11358
  }
11214
11359
  function findFolderByName(name) {
11215
11360
  load$1();
11216
11361
  const normalized = name.trim().toLowerCase();
11217
11362
  if (!normalized || normalized === "unsorted") return null;
11218
- const folder = state$3.folders.find(
11363
+ const folder = state$2.folders.find(
11219
11364
  (item) => item.name.trim().toLowerCase() === normalized
11220
11365
  );
11221
11366
  return folder ? { ...folder } : null;
@@ -11223,7 +11368,7 @@ function findFolderByName(name) {
11223
11368
  function listFolderOverviews() {
11224
11369
  load$1();
11225
11370
  const counts = /* @__PURE__ */ new Map();
11226
- for (const bookmark of state$3.bookmarks) {
11371
+ for (const bookmark of state$2.bookmarks) {
11227
11372
  counts.set(bookmark.folderId, (counts.get(bookmark.folderId) ?? 0) + 1);
11228
11373
  }
11229
11374
  return [
@@ -11232,7 +11377,7 @@ function listFolderOverviews() {
11232
11377
  name: "Unsorted",
11233
11378
  count: counts.get(UNSORTED_ID) ?? 0
11234
11379
  },
11235
- ...state$3.folders.map((folder) => ({
11380
+ ...state$2.folders.map((folder) => ({
11236
11381
  id: folder.id,
11237
11382
  name: folder.name,
11238
11383
  summary: folder.summary,
@@ -11248,7 +11393,7 @@ function searchBookmarks(query, limit = DEFAULT_BOOKMARK_SEARCH_LIMIT) {
11248
11393
  1,
11249
11394
  Math.min(MAX_BOOKMARK_SEARCH_LIMIT, Math.floor(limit))
11250
11395
  );
11251
- return state$3.bookmarks.map((bookmark) => {
11396
+ return state$2.bookmarks.map((bookmark) => {
11252
11397
  const folder = foldersById.get(bookmark.folderId);
11253
11398
  const { matchedFields, score } = getBookmarkSearchMatch({
11254
11399
  query,
@@ -11285,7 +11430,7 @@ function createFolderWithSummary(name, summary) {
11285
11430
  summary: summary?.trim() || void 0,
11286
11431
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
11287
11432
  };
11288
- state$3.folders.push(folder);
11433
+ state$2.folders.push(folder);
11289
11434
  save$1();
11290
11435
  emit$2();
11291
11436
  return folder;
@@ -11316,7 +11461,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
11316
11461
  throw new Error("Bookmark URL cannot be empty");
11317
11462
  }
11318
11463
  const normalizedTitle = title.trim() || normalizedUrl;
11319
- const targetId = folderId && folderId !== UNSORTED_ID ? state$3.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
11464
+ const targetId = folderId && folderId !== UNSORTED_ID ? state$2.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
11320
11465
  const duplicatePolicy = options?.onDuplicate ?? "ask";
11321
11466
  const existing = getBookmarkByUrlInFolder(normalizedUrl, targetId);
11322
11467
  if (existing) {
@@ -11327,7 +11472,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
11327
11472
  };
11328
11473
  }
11329
11474
  if (duplicatePolicy === "update") {
11330
- const bookmark2 = state$3.bookmarks.find((item) => item.id === existing.id);
11475
+ const bookmark2 = state$2.bookmarks.find((item) => item.id === existing.id);
11331
11476
  if (!bookmark2) {
11332
11477
  return {
11333
11478
  status: "conflict",
@@ -11357,7 +11502,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
11357
11502
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
11358
11503
  ...options?.extra
11359
11504
  };
11360
- state$3.bookmarks.push(bookmark);
11505
+ state$2.bookmarks.push(bookmark);
11361
11506
  save$1();
11362
11507
  emit$2();
11363
11508
  return {
@@ -11367,9 +11512,9 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
11367
11512
  }
11368
11513
  function removeBookmark(id) {
11369
11514
  load$1();
11370
- const before = state$3.bookmarks.length;
11371
- state$3.bookmarks = state$3.bookmarks.filter((b) => b.id !== id);
11372
- if (state$3.bookmarks.length !== before) {
11515
+ const before = state$2.bookmarks.length;
11516
+ state$2.bookmarks = state$2.bookmarks.filter((b) => b.id !== id);
11517
+ if (state$2.bookmarks.length !== before) {
11373
11518
  save$1();
11374
11519
  emit$2();
11375
11520
  return true;
@@ -11378,7 +11523,7 @@ function removeBookmark(id) {
11378
11523
  }
11379
11524
  function updateBookmark(id, updates) {
11380
11525
  load$1();
11381
- const bookmark = state$3.bookmarks.find((item) => item.id === id);
11526
+ const bookmark = state$2.bookmarks.find((item) => item.id === id);
11382
11527
  if (!bookmark) return null;
11383
11528
  const metadataUpdates = normalizeBookmarkMetadataUpdate({
11384
11529
  intent: updates.intent,
@@ -11395,7 +11540,7 @@ function updateBookmark(id, updates) {
11395
11540
  bookmark.note = trimmed || void 0;
11396
11541
  }
11397
11542
  if (typeof updates.folderId === "string") {
11398
- bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$3.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
11543
+ bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$2.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
11399
11544
  }
11400
11545
  if ("intent" in metadataUpdates) {
11401
11546
  bookmark.intent = metadataUpdates.intent;
@@ -11418,23 +11563,23 @@ function updateBookmark(id, updates) {
11418
11563
  }
11419
11564
  function removeFolder(id, deleteContents = false) {
11420
11565
  load$1();
11421
- const exists = state$3.folders.some((f) => f.id === id);
11566
+ const exists = state$2.folders.some((f) => f.id === id);
11422
11567
  if (!exists) return false;
11423
11568
  if (deleteContents) {
11424
- state$3.bookmarks = state$3.bookmarks.filter((b) => b.folderId !== id);
11569
+ state$2.bookmarks = state$2.bookmarks.filter((b) => b.folderId !== id);
11425
11570
  } else {
11426
- state$3.bookmarks = state$3.bookmarks.map(
11571
+ state$2.bookmarks = state$2.bookmarks.map(
11427
11572
  (b) => b.folderId === id ? { ...b, folderId: UNSORTED_ID } : b
11428
11573
  );
11429
11574
  }
11430
- state$3.folders = state$3.folders.filter((f) => f.id !== id);
11575
+ state$2.folders = state$2.folders.filter((f) => f.id !== id);
11431
11576
  save$1();
11432
11577
  emit$2();
11433
11578
  return true;
11434
11579
  }
11435
11580
  function renameFolder(id, newName, summary) {
11436
11581
  load$1();
11437
- const folder = state$3.folders.find((f) => f.id === id);
11582
+ const folder = state$2.folders.find((f) => f.id === id);
11438
11583
  if (!folder) return null;
11439
11584
  const trimmed = newName.trim();
11440
11585
  if (!trimmed) return null;
@@ -11452,7 +11597,7 @@ function importBookmarksFromHtml(content) {
11452
11597
  let skipped = 0;
11453
11598
  let errors = 0;
11454
11599
  load$1();
11455
- const existingUrls = new Set(state$3.bookmarks.map((b) => b.url));
11600
+ const existingUrls = new Set(state$2.bookmarks.map((b) => b.url));
11456
11601
  let currentFolderId = UNSORTED_ID;
11457
11602
  let currentFolderName = "Imported";
11458
11603
  const lines = content.split("\n");
@@ -11461,7 +11606,7 @@ function importBookmarksFromHtml(content) {
11461
11606
  const folderMatch = line.match(/<DT><H3[^>]*>([^<]+)<\/H3>/i);
11462
11607
  if (folderMatch) {
11463
11608
  currentFolderName = folderMatch[1].trim();
11464
- const existing = state$3.folders.find(
11609
+ const existing = state$2.folders.find(
11465
11610
  (f) => f.name.toLowerCase() === currentFolderName.toLowerCase()
11466
11611
  );
11467
11612
  if (existing) {
@@ -11472,7 +11617,7 @@ function importBookmarksFromHtml(content) {
11472
11617
  name: currentFolderName,
11473
11618
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
11474
11619
  };
11475
- state$3.folders.push(folder);
11620
+ state$2.folders.push(folder);
11476
11621
  currentFolderId = folder.id;
11477
11622
  }
11478
11623
  continue;
@@ -11493,7 +11638,7 @@ function importBookmarksFromHtml(content) {
11493
11638
  else errors++;
11494
11639
  continue;
11495
11640
  }
11496
- state$3.bookmarks.push({
11641
+ state$2.bookmarks.push({
11497
11642
  id: crypto$1.randomUUID(),
11498
11643
  url,
11499
11644
  title,
@@ -11519,14 +11664,14 @@ function importBookmarksFromJson(content) {
11519
11664
  const incomingFolders = Array.isArray(parsed?.folders) ? parsed.folders : [];
11520
11665
  const incomingBookmarks = Array.isArray(parsed?.bookmarks) ? parsed.bookmarks : [];
11521
11666
  load$1();
11522
- const existingUrls = new Set(state$3.bookmarks.map((b) => b.url));
11667
+ const existingUrls = new Set(state$2.bookmarks.map((b) => b.url));
11523
11668
  const folderIdMap = /* @__PURE__ */ new Map();
11524
11669
  for (const folder of incomingFolders) {
11525
11670
  if (!folder?.id || !folder?.name) {
11526
11671
  errors++;
11527
11672
  continue;
11528
11673
  }
11529
- const existing = state$3.folders.find(
11674
+ const existing = state$2.folders.find(
11530
11675
  (f) => f.name.toLowerCase() === folder.name.toLowerCase()
11531
11676
  );
11532
11677
  if (existing) {
@@ -11538,7 +11683,7 @@ function importBookmarksFromJson(content) {
11538
11683
  summary: folder.summary?.trim() || void 0,
11539
11684
  createdAt: folder.createdAt || (/* @__PURE__ */ new Date()).toISOString()
11540
11685
  };
11541
- state$3.folders.push(newFolder);
11686
+ state$2.folders.push(newFolder);
11542
11687
  folderIdMap.set(folder.id, newFolder.id);
11543
11688
  }
11544
11689
  }
@@ -11552,7 +11697,7 @@ function importBookmarksFromJson(content) {
11552
11697
  continue;
11553
11698
  }
11554
11699
  const mappedFolderId = bookmark.folderId ? folderIdMap.get(bookmark.folderId) ?? UNSORTED_ID : UNSORTED_ID;
11555
- state$3.bookmarks.push({
11700
+ state$2.bookmarks.push({
11556
11701
  id: crypto$1.randomUUID(),
11557
11702
  url: bookmark.url.trim(),
11558
11703
  title: typeof bookmark.title === "string" ? bookmark.title.trim() : bookmark.url,
@@ -11568,7 +11713,7 @@ function importBookmarksFromJson(content) {
11568
11713
  emit$2();
11569
11714
  }
11570
11715
  } catch (err) {
11571
- logger$n.warn("Failed to import bookmarks from JSON:", err);
11716
+ logger$o.warn("Failed to import bookmarks from JSON:", err);
11572
11717
  errors++;
11573
11718
  }
11574
11719
  return { imported, skipped, errors };
@@ -11973,7 +12118,7 @@ async function resolveSelector(wc, index, selector) {
11973
12118
  if (extractedSelector) return extractedSelector;
11974
12119
  return null;
11975
12120
  }
11976
- const logger$m = createLogger("LinkValidation");
12121
+ const logger$n = createLogger("LinkValidation");
11977
12122
  const DEAD_STATUS_CODES = /* @__PURE__ */ new Set([404, 410, 451]);
11978
12123
  const HEAD_FALLBACK_STATUS_CODES = /* @__PURE__ */ new Set([400, 403, 404, 405, 406, 500, 501]);
11979
12124
  function isHttpUrl(value) {
@@ -11997,7 +12142,7 @@ async function requestUrl(url, method, timeoutMs) {
11997
12142
  }
11998
12143
  });
11999
12144
  await response.body?.cancel().catch((err) => {
12000
- logger$m.debug("Failed to cancel response body:", err);
12145
+ logger$n.debug("Failed to cancel response body:", err);
12001
12146
  });
12002
12147
  return response;
12003
12148
  } finally {
@@ -12061,7 +12206,7 @@ function formatDeadLinkMessage(label, result) {
12061
12206
  const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
12062
12207
  return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
12063
12208
  }
12064
- const logger$l = createLogger("Screenshot");
12209
+ const logger$m = createLogger("Screenshot");
12065
12210
  const SCREENSHOT_RETRY_COUNT = 3;
12066
12211
  const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
12067
12212
  async function captureScreenshot(wc) {
@@ -12083,7 +12228,7 @@ async function captureScreenshot(wc) {
12083
12228
  }
12084
12229
  }
12085
12230
  } catch (err) {
12086
- logger$l.debug(
12231
+ logger$m.debug(
12087
12232
  `capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
12088
12233
  getErrorMessage(err)
12089
12234
  );
@@ -12091,6 +12236,7 @@ async function captureScreenshot(wc) {
12091
12236
  }
12092
12237
  return errorResult("Page image was empty after 3 attempts");
12093
12238
  }
12239
+ const logger$l = createLogger("Sessions");
12094
12240
  const SESSION_VERSION = 1;
12095
12241
  function getSessionsDir() {
12096
12242
  return path$1.join(electron.app.getPath("userData"), "named-sessions");
@@ -12155,7 +12301,8 @@ function readSessionFile(filePath2) {
12155
12301
  capturedAt: (/* @__PURE__ */ new Date()).toISOString()
12156
12302
  }
12157
12303
  };
12158
- } catch {
12304
+ } catch (err) {
12305
+ logger$l.warn(`Failed to read session file ${filePath2}:`, err);
12159
12306
  return null;
12160
12307
  }
12161
12308
  }
@@ -12237,7 +12384,8 @@ async function captureLocalStorageForOrigin(tabManager, origin) {
12237
12384
  )
12238
12385
  );
12239
12386
  }
12240
- } catch {
12387
+ } catch (err) {
12388
+ logger$l.debug(`Failed to capture localStorage for origin ${origin}:`, err);
12241
12389
  return {};
12242
12390
  }
12243
12391
  return {};
@@ -12327,7 +12475,8 @@ async function loadNamedSession(tabManager, name) {
12327
12475
  for (const cookie of saved.cookies) {
12328
12476
  try {
12329
12477
  await electron.session.defaultSession.cookies.set(cookieSetDetails(cookie));
12330
- } catch {
12478
+ } catch (err) {
12479
+ logger$l.debug(`Skipping cookie ${cookie.name} for ${cookie.domain}:`, err);
12331
12480
  continue;
12332
12481
  }
12333
12482
  }
@@ -20031,7 +20180,6 @@ function installAdBlockingForSession(ses, tabManager) {
20031
20180
  );
20032
20181
  });
20033
20182
  }
20034
- const filePath$1 = () => path$1.join(electron.app.getPath("userData"), "vessel-downloads.json");
20035
20183
  const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
20036
20184
  ".appimage",
20037
20185
  ".bat",
@@ -20044,6 +20192,7 @@ const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
20044
20192
  ".scr",
20045
20193
  ".sh"
20046
20194
  ]);
20195
+ const DOWNLOADS_FALLBACK = { items: [] };
20047
20196
  function hasMisleadingDoubleExtension(filename) {
20048
20197
  return /\.(pdf|docx?|xlsx?|pptx?|png|jpe?g|gif|txt|zip)\.(exe|msi|bat|cmd|ps1|sh|scr|appimage)$/i.test(filename);
20049
20198
  }
@@ -20058,23 +20207,14 @@ function executableWarningDetail(item) {
20058
20207
  hasMisleadingDoubleExtension(item.filename) ? "Warning: this filename uses a misleading double extension." : null
20059
20208
  ].filter(Boolean).join("\n");
20060
20209
  }
20061
- function parse(raw) {
20062
- if (!raw || typeof raw !== "object") return { items: [] };
20063
- const items = Array.isArray(raw.items) ? raw.items : [];
20064
- return { items };
20065
- }
20066
- const state$2 = loadJsonFile({ filePath: filePath$1(), fallback: { items: [] }, parse });
20067
- const persistence$2 = createDebouncedJsonPersistence({
20068
- debounceMs: 250,
20069
- filePath: filePath$1(),
20070
- getValue: () => state$2,
20071
- logLabel: "downloads"
20210
+ const store = new PersistentState({
20211
+ filename: "vessel-downloads.json",
20212
+ fallback: DOWNLOADS_FALLBACK,
20213
+ parse: (raw) => parseArrayStateWithFallback(DownloadRecordSchema, raw, "items", DOWNLOADS_FALLBACK, "downloads"),
20214
+ logLabel: "downloads",
20215
+ debounceMs: 250
20072
20216
  });
20073
20217
  let broadcaster$1 = null;
20074
- function persist() {
20075
- state$2.items = state$2.items.slice(0, 200);
20076
- persistence$2.schedule();
20077
- }
20078
20218
  function emit$1() {
20079
20219
  broadcaster$1?.(Channels.DOWNLOADS_UPDATE, listDownloads());
20080
20220
  }
@@ -20082,30 +20222,33 @@ function setDownloadBroadcaster(fn) {
20082
20222
  broadcaster$1 = fn;
20083
20223
  }
20084
20224
  function listDownloads() {
20085
- return state$2.items.map((item) => ({ ...item }));
20225
+ return store.getState().items.map((item) => ({ ...item }));
20086
20226
  }
20087
20227
  function upsertDownload(input) {
20088
20228
  const now = (/* @__PURE__ */ new Date()).toISOString();
20089
- const existing = state$2.items.find((item) => item.savePath === input.savePath);
20090
- if (existing) {
20091
- Object.assign(existing, input, { updatedAt: now });
20092
- persist();
20093
- emit$1();
20094
- return existing;
20095
- }
20096
- const record = { id: crypto$2.randomUUID(), ...input, startedAt: now, updatedAt: now };
20097
- state$2.items = [record, ...state$2.items];
20098
- persist();
20229
+ const result = store.mutate((s) => {
20230
+ const existing = s.items.find((item) => item.savePath === input.savePath);
20231
+ if (existing) {
20232
+ Object.assign(existing, input, { updatedAt: now });
20233
+ return existing;
20234
+ } else {
20235
+ const record = { id: crypto$2.randomUUID(), ...input, startedAt: now, updatedAt: now };
20236
+ s.items = [record, ...s.items];
20237
+ s.items = s.items.slice(0, 200);
20238
+ return record;
20239
+ }
20240
+ });
20099
20241
  emit$1();
20100
- return record;
20242
+ return result;
20101
20243
  }
20102
20244
  function clearDownloads() {
20103
- state$2.items = [];
20104
- persist();
20245
+ store.mutate((s) => {
20246
+ s.items = [];
20247
+ });
20105
20248
  emit$1();
20106
20249
  }
20107
20250
  async function openDownload(id) {
20108
- const item = state$2.items.find((d) => d.id === id);
20251
+ const item = store.getState().items.find((d) => d.id === id);
20109
20252
  if (!item || item.state !== "completed" || !fs$1.existsSync(item.savePath)) return false;
20110
20253
  if (isExecutableDownload(item.savePath)) {
20111
20254
  const result = electron.dialog.showMessageBoxSync({
@@ -20122,7 +20265,7 @@ async function openDownload(id) {
20122
20265
  return await electron.shell.openPath(item.savePath) === "";
20123
20266
  }
20124
20267
  async function showDownloadInFolder(id) {
20125
- const item = state$2.items.find((d) => d.id === id);
20268
+ const item = store.getState().items.find((d) => d.id === id);
20126
20269
  if (!item || !fs$1.existsSync(item.savePath)) return false;
20127
20270
  electron.shell.showItemInFolder(item.savePath);
20128
20271
  return true;
@@ -21505,6 +21648,32 @@ function compactProviderHistory(history = []) {
21505
21648
  const omitted = normalized.slice(0, normalized.length - recent.length);
21506
21649
  return omitted.length > 0 ? [summarizeOmittedHistory(omitted), ...recent] : recent;
21507
21650
  }
21651
+ const AIQuerySchema = zod.z.string().min(1);
21652
+ const AIMessageSchema = zod.z.object({
21653
+ role: zod.z.enum(["user", "assistant"]),
21654
+ content: zod.z.string()
21655
+ });
21656
+ const AIHistorySchema = zod.z.array(AIMessageSchema).optional();
21657
+ const ReasoningEffortSchema = zod.z.enum(["off", "low", "medium", "high", "max"]);
21658
+ const ProviderConfigSchema = zod.z.object({
21659
+ id: zod.z.enum([
21660
+ "anthropic",
21661
+ "openai",
21662
+ "openai_codex",
21663
+ "openrouter",
21664
+ "ollama",
21665
+ "llama_cpp",
21666
+ "mistral",
21667
+ "xai",
21668
+ "google",
21669
+ "custom"
21670
+ ]),
21671
+ apiKey: zod.z.string(),
21672
+ hasApiKey: zod.z.boolean().optional(),
21673
+ model: zod.z.string(),
21674
+ baseUrl: zod.z.string().optional(),
21675
+ reasoningEffort: ReasoningEffortSchema.optional()
21676
+ });
21508
21677
  let activeChatProvider = null;
21509
21678
  function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResearchOrchestrator) {
21510
21679
  onAIStreamIdle(() => {
@@ -21512,10 +21681,12 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
21512
21681
  });
21513
21682
  electron.ipcMain.handle(Channels.AI_QUERY, async (event, query, history) => {
21514
21683
  assertTrustedIpcSender(event);
21684
+ const validatedQuery = parseIpc(AIQuerySchema, query, "query");
21685
+ const validatedHistory = history !== void 0 ? parseIpc(AIHistorySchema, history, "history") : void 0;
21515
21686
  const settings2 = loadSettings();
21516
21687
  const chatConfig = settings2.chatProvider;
21517
21688
  if (!chatConfig) {
21518
- sendToRendererViews(Channels.AI_STREAM_START, query);
21689
+ sendToRendererViews(Channels.AI_STREAM_START, validatedQuery);
21519
21690
  sendToRendererViews(
21520
21691
  Channels.AI_STREAM_CHUNK,
21521
21692
  "Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
@@ -21526,20 +21697,20 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
21526
21697
  if (!tryBeginAIStream("manual")) {
21527
21698
  return { accepted: false, reason: "busy" };
21528
21699
  }
21529
- sendToRendererViews(Channels.AI_STREAM_START, query);
21700
+ sendToRendererViews(Channels.AI_STREAM_START, validatedQuery);
21530
21701
  (async () => {
21531
21702
  try {
21532
21703
  activeChatProvider = createProvider(chatConfig);
21533
21704
  const activeTab = tabManager.getActiveTab();
21534
21705
  await handleAIQuery(
21535
- query,
21706
+ validatedQuery,
21536
21707
  activeChatProvider,
21537
21708
  activeTab?.view.webContents,
21538
21709
  (chunk) => sendToRendererViews(Channels.AI_STREAM_CHUNK, chunk),
21539
21710
  () => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
21540
21711
  tabManager,
21541
21712
  runtime2,
21542
- compactProviderHistory(history),
21713
+ compactProviderHistory(validatedHistory),
21543
21714
  getResearchOrchestrator()
21544
21715
  );
21545
21716
  } catch (err) {
@@ -21561,12 +21732,8 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
21561
21732
  electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (event, config) => {
21562
21733
  assertTrustedIpcSender(event);
21563
21734
  try {
21564
- if (!config || typeof config !== "object" || !("id" in config)) {
21565
- return errorResult("Invalid provider configuration", { models: [] });
21566
- }
21567
- return await fetchProviderModels(
21568
- config
21569
- );
21735
+ const validatedConfig = parseIpc(ProviderConfigSchema, config, "providerConfig");
21736
+ return await fetchProviderModels(validatedConfig);
21570
21737
  } catch (err) {
21571
21738
  return errorResult(getErrorMessage(err), { models: [] });
21572
21739
  }
@@ -23822,7 +23989,7 @@ function domainMatches(pattern, hostname) {
23822
23989
  return isWildcard ? h.endsWith("." + p) : p === h;
23823
23990
  }
23824
23991
  function generateTotpCode(secret) {
23825
- const epoch = Math.floor(Date.now() / 1e3);
23992
+ const epoch = unixNow();
23826
23993
  const counter = Math.floor(epoch / 30);
23827
23994
  const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
23828
23995
  const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
@@ -23867,7 +24034,8 @@ function createAuditLog(filename, maxEntries) {
23867
24034
  });
23868
24035
  fs$1.chmodSync(auditPath, 384);
23869
24036
  }
23870
- } catch {
24037
+ } catch (err) {
24038
+ logger$e.warn("Failed to trim audit log:", err);
23871
24039
  }
23872
24040
  } catch (err) {
23873
24041
  logger$e.error("Failed to write audit log:", err);
@@ -25436,7 +25604,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
25436
25604
  )
25437
25605
  }
25438
25606
  },
25439
- async ({ index, selector, text, label, durationMs, persist: persist2, color }) => {
25607
+ async ({ index, selector, text, label, durationMs, persist, color }) => {
25440
25608
  const tab = tabManager.getActiveTab();
25441
25609
  if (!tab) return asNoActiveTabResponse();
25442
25610
  const normalizedText = normalizeLooseString(text);
@@ -25450,7 +25618,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
25450
25618
  text: normalizedText,
25451
25619
  label,
25452
25620
  durationMs,
25453
- persist: persist2,
25621
+ persist,
25454
25622
  color
25455
25623
  },
25456
25624
  async () => {
@@ -25464,7 +25632,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
25464
25632
  durationMs,
25465
25633
  color
25466
25634
  );
25467
- if (persist2 && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
25635
+ if (persist && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
25468
25636
  const url = normalizeUrl$1(wc.getURL());
25469
25637
  addHighlight(
25470
25638
  url,
@@ -27984,6 +28152,25 @@ function registerSystemHandlers(windowState2, sendToRendererViews) {
27984
28152
  return togglePictureInPicture(tabManager);
27985
28153
  });
27986
28154
  }
28155
+ const FolderNameSchema = zod.z.string().min(1);
28156
+ const BookmarkUrlSchema = zod.z.string().min(1);
28157
+ const BookmarkIdSchema = zod.z.string().min(1);
28158
+ const OptionalStringSchema = zod.z.string().optional();
28159
+ const OptionalStringArraySchema = zod.z.array(zod.z.string()).optional();
28160
+ const OptionalRecordSchema = zod.z.record(zod.z.string(), zod.z.string()).optional();
28161
+ const OptionalBooleanSchema = zod.z.boolean().optional();
28162
+ const BookmarkUpdateSchema = zod.z.object({
28163
+ title: zod.z.string().optional(),
28164
+ note: zod.z.string().optional(),
28165
+ folderId: zod.z.string().optional(),
28166
+ intent: zod.z.string().optional(),
28167
+ expectedContent: zod.z.string().optional(),
28168
+ keyFields: zod.z.array(zod.z.string()).optional(),
28169
+ agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional()
28170
+ });
28171
+ const ExportOptionsSchema = zod.z.object({
28172
+ includeNotes: zod.z.boolean().optional()
28173
+ }).optional();
27987
28174
  function getSafeBookmarkExportName(name) {
27988
28175
  const safeName = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
27989
28176
  return safeName || "folder";
@@ -27997,23 +28184,33 @@ function registerBookmarkHandlers() {
27997
28184
  Channels.FOLDER_CREATE,
27998
28185
  (event, name, summary) => {
27999
28186
  assertTrustedIpcSender(event);
28187
+ const validatedName = parseIpc(FolderNameSchema, name, "name");
28188
+ const validatedSummary = parseIpc(OptionalStringSchema, summary, "summary");
28000
28189
  trackBookmarkAction("folder_create");
28001
- return createFolderWithSummary(name, summary);
28190
+ return createFolderWithSummary(validatedName, validatedSummary);
28002
28191
  }
28003
28192
  );
28004
28193
  electron.ipcMain.handle(
28005
28194
  Channels.BOOKMARK_SAVE,
28006
28195
  (event, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
28007
28196
  assertTrustedIpcSender(event);
28197
+ const validatedUrl = parseIpc(BookmarkUrlSchema, url, "url");
28198
+ const validatedTitle = parseIpc(zod.z.string(), title, "title");
28199
+ const validatedFolderId = parseIpc(OptionalStringSchema, folderId, "folderId");
28200
+ const validatedNote = parseIpc(OptionalStringSchema, note, "note");
28201
+ const validatedIntent = parseIpc(OptionalStringSchema, intent, "intent");
28202
+ const validatedExpectedContent = parseIpc(OptionalStringSchema, expectedContent, "expectedContent");
28203
+ const validatedKeyFields = parseIpc(OptionalStringArraySchema, keyFields, "keyFields");
28204
+ const validatedAgentHints = parseIpc(OptionalRecordSchema, agentHints, "agentHints");
28008
28205
  trackBookmarkAction("save");
28009
- const result = saveBookmarkWithPolicy(url, title, folderId, note, {
28206
+ const result = saveBookmarkWithPolicy(validatedUrl, validatedTitle, validatedFolderId, validatedNote, {
28010
28207
  onDuplicate: "update",
28011
28208
  extra: {
28012
28209
  ...normalizeBookmarkMetadata({
28013
- intent,
28014
- expectedContent,
28015
- keyFields,
28016
- agentHints
28210
+ intent: validatedIntent,
28211
+ expectedContent: validatedExpectedContent,
28212
+ keyFields: validatedKeyFields,
28213
+ agentHints: validatedAgentHints
28017
28214
  })
28018
28215
  }
28019
28216
  });
@@ -28027,19 +28224,23 @@ function registerBookmarkHandlers() {
28027
28224
  Channels.BOOKMARK_UPDATE,
28028
28225
  (event, id, updates) => {
28029
28226
  assertTrustedIpcSender(event);
28227
+ const validatedId = parseIpc(BookmarkIdSchema, id, "id");
28228
+ const validatedUpdates = parseIpc(BookmarkUpdateSchema, updates, "updates");
28030
28229
  trackBookmarkAction("save");
28031
- return updateBookmark(id, updates);
28230
+ return updateBookmark(validatedId, validatedUpdates);
28032
28231
  }
28033
28232
  );
28034
28233
  electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (event, id) => {
28035
28234
  assertTrustedIpcSender(event);
28235
+ const validatedId = parseIpc(BookmarkIdSchema, id, "id");
28036
28236
  trackBookmarkAction("remove");
28037
- return removeBookmark(id);
28237
+ return removeBookmark(validatedId);
28038
28238
  });
28039
28239
  electron.ipcMain.handle(
28040
28240
  Channels.BOOKMARKS_EXPORT_HTML,
28041
28241
  async (event, options) => {
28042
28242
  assertTrustedIpcSender(event);
28243
+ const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
28043
28244
  const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
28044
28245
  title: "Export Bookmarks",
28045
28246
  defaultPath: "vessel-bookmarks.html",
@@ -28047,7 +28248,7 @@ function registerBookmarkHandlers() {
28047
28248
  });
28048
28249
  if (canceled || !filePath2) return null;
28049
28250
  const content = exportBookmarksHtml({
28050
- includeNotes: options?.includeNotes ?? false
28251
+ includeNotes: validatedOptions?.includeNotes ?? false
28051
28252
  });
28052
28253
  await fs.promises.writeFile(filePath2, content, "utf-8");
28053
28254
  trackBookmarkAction("export");
@@ -28077,7 +28278,9 @@ function registerBookmarkHandlers() {
28077
28278
  Channels.FOLDER_EXPORT_HTML,
28078
28279
  async (event, folderId, options) => {
28079
28280
  assertTrustedIpcSender(event);
28080
- const folder = getFolder(folderId);
28281
+ const validatedFolderId = parseIpc(BookmarkIdSchema, folderId, "folderId");
28282
+ const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
28283
+ const folder = getFolder(validatedFolderId);
28081
28284
  if (!folder) return null;
28082
28285
  const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
28083
28286
  title: `Export ${folder.name}`,
@@ -28085,8 +28288,8 @@ function registerBookmarkHandlers() {
28085
28288
  filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
28086
28289
  });
28087
28290
  if (canceled || !filePath2) return null;
28088
- const result = exportBookmarkFolderHtml(folderId, {
28089
- includeNotes: options?.includeNotes ?? true
28291
+ const result = exportBookmarkFolderHtml(validatedFolderId, {
28292
+ includeNotes: validatedOptions?.includeNotes ?? true
28090
28293
  });
28091
28294
  if (!result) return null;
28092
28295
  await fs.promises.writeFile(filePath2, result.content, "utf-8");
@@ -28127,14 +28330,23 @@ function registerBookmarkHandlers() {
28127
28330
  });
28128
28331
  electron.ipcMain.handle(Channels.FOLDER_REMOVE, (event, id, deleteContents) => {
28129
28332
  assertTrustedIpcSender(event);
28333
+ const validatedId = parseIpc(BookmarkIdSchema, id, "id");
28334
+ const validatedDeleteContents = parseIpc(
28335
+ OptionalBooleanSchema,
28336
+ deleteContents,
28337
+ "deleteContents"
28338
+ );
28130
28339
  trackBookmarkAction("folder_remove");
28131
- return removeFolder(id, deleteContents ?? false);
28340
+ return removeFolder(validatedId, validatedDeleteContents ?? false);
28132
28341
  });
28133
28342
  electron.ipcMain.handle(
28134
28343
  Channels.FOLDER_RENAME,
28135
28344
  (event, id, newName, summary) => {
28136
28345
  assertTrustedIpcSender(event);
28137
- return renameFolder(id, newName, summary);
28346
+ const validatedId = parseIpc(BookmarkIdSchema, id, "id");
28347
+ const validatedName = parseIpc(FolderNameSchema, newName, "newName");
28348
+ const validatedSummary = parseIpc(OptionalStringSchema, summary, "summary");
28349
+ return renameFolder(validatedId, validatedName, validatedSummary);
28138
28350
  }
28139
28351
  );
28140
28352
  }
@@ -28399,6 +28611,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
28399
28611
  return result;
28400
28612
  });
28401
28613
  }
28614
+ const SessionNameSchema = zod.z.string().min(1);
28402
28615
  function registerSessionHandlers(tabManager) {
28403
28616
  electron.ipcMain.handle(Channels.SESSION_LIST, (event) => {
28404
28617
  assertTrustedIpcSender(event);
@@ -28406,18 +28619,18 @@ function registerSessionHandlers(tabManager) {
28406
28619
  });
28407
28620
  electron.ipcMain.handle(Channels.SESSION_SAVE, async (event, name) => {
28408
28621
  assertTrustedIpcSender(event);
28409
- assertString(name, "name");
28410
- return await saveNamedSession(tabManager, name);
28622
+ const validatedName = parseIpc(SessionNameSchema, name, "name");
28623
+ return await saveNamedSession(tabManager, validatedName);
28411
28624
  });
28412
28625
  electron.ipcMain.handle(Channels.SESSION_LOAD, async (event, name) => {
28413
28626
  assertTrustedIpcSender(event);
28414
- assertString(name, "name");
28415
- return await loadNamedSession(tabManager, name);
28627
+ const validatedName = parseIpc(SessionNameSchema, name, "name");
28628
+ return await loadNamedSession(tabManager, validatedName);
28416
28629
  });
28417
28630
  electron.ipcMain.handle(Channels.SESSION_DELETE, (event, name) => {
28418
28631
  assertTrustedIpcSender(event);
28419
- assertString(name, "name");
28420
- return deleteNamedSession(name);
28632
+ const validatedName = parseIpc(SessionNameSchema, name, "name");
28633
+ return deleteNamedSession(validatedName);
28421
28634
  });
28422
28635
  }
28423
28636
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -30507,7 +30720,8 @@ ${progress}
30507
30720
  const raw = fs$1.readFileSync(getRuntimeStatePath(), "utf-8");
30508
30721
  const parsed = JSON.parse(raw);
30509
30722
  return sanitizePersistence(parsed);
30510
- } catch {
30723
+ } catch (err) {
30724
+ logger$3.warn("Failed to load persisted runtime state, starting fresh:", err);
30511
30725
  return sanitizePersistence(null);
30512
30726
  }
30513
30727
  }
@@ -30766,7 +30980,7 @@ function findIconBase64() {
30766
30980
  return "";
30767
30981
  }
30768
30982
  function buildSplashHTML(iconSrc) {
30769
- const imgTag = iconSrc ? `<img class="logo" src="${iconSrc}" alt="" />` : `<div class="logo-fallback">V</div>`;
30983
+ const imgTag = iconSrc ? `<img class="logo" src="${escapeHtml(iconSrc)}" alt="" />` : `<div class="logo-fallback">V</div>`;
30770
30984
  return `<!DOCTYPE html>
30771
30985
  <html>
30772
30986
  <head>
@@ -30906,7 +31120,8 @@ function createSplashWindow() {
30906
31120
  splash.once("closed", () => {
30907
31121
  try {
30908
31122
  fs$1.rmSync(tmpDir, { recursive: true, force: true });
30909
- } catch {
31123
+ } catch (err) {
31124
+ logger$1.debug("Failed to clean up splash temp dir:", err);
30910
31125
  }
30911
31126
  });
30912
31127
  fs$1.writeFileSync(tmpPath, html, "utf-8");