@quanta-intellect/vessel-browser 0.1.133 → 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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
356
|
-
})).catch((err) => logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
1020
|
+
logger$x.warn("Failed to retrieve page source:", err);
|
|
1021
1021
|
return;
|
|
1022
1022
|
}
|
|
1023
1023
|
const escaped = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1147,7 +1147,7 @@ class Tab {
|
|
|
1147
1147
|
document.addEventListener('mouseup', window.__vesselHighlightHandler);
|
|
1148
1148
|
}
|
|
1149
1149
|
})()
|
|
1150
|
-
`).catch((err) => logger$
|
|
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$
|
|
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
|
|
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
|
|
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
|
|
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$
|
|
1265
|
-
})).catch((err) => logger$
|
|
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
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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
|
-
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
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
|
-
|
|
1342
|
-
return { 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
|
|
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
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1499
|
+
store$2.mutate((s) => {
|
|
1500
|
+
s.highlights.push(highlight);
|
|
1501
|
+
});
|
|
1364
1502
|
return highlight;
|
|
1365
1503
|
}
|
|
1366
1504
|
function removeHighlight(id) {
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
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
|
|
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
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
|
1395
|
-
|
|
1396
|
-
|
|
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
|
|
1399
|
-
emit
|
|
1552
|
+
store$2.save();
|
|
1553
|
+
store$2.emit();
|
|
1400
1554
|
}
|
|
1401
1555
|
return removed;
|
|
1402
1556
|
}
|
|
1403
1557
|
function flushPersist$4() {
|
|
1404
|
-
return
|
|
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 = `
|
|
@@ -1535,7 +1689,7 @@ function applyTextRangeMatch(match, solidColor, bgColor, fullText) {
|
|
|
1535
1689
|
try {
|
|
1536
1690
|
var mark = markTextSegment(segments[i], solidColor, bgColor, fullText);
|
|
1537
1691
|
if (mark) marks.unshift(mark);
|
|
1538
|
-
} catch (_e) {}
|
|
1692
|
+
} catch (_e) { /* text segment couldn't be marked — it may overlap other highlights */ }
|
|
1539
1693
|
}
|
|
1540
1694
|
return marks;
|
|
1541
1695
|
}
|
|
@@ -1875,7 +2029,7 @@ async function highlightBatchOnPage(wc, entries) {
|
|
|
1875
2029
|
el.style.setProperty('outline-color', c.solid, 'important');
|
|
1876
2030
|
el.style.setProperty('box-shadow', '0 0 8px ' + c.glow, 'important');
|
|
1877
2031
|
}
|
|
1878
|
-
} catch (_e) {}
|
|
2032
|
+
} catch (_e) { /* selector may not exist on current page */ }
|
|
1879
2033
|
}
|
|
1880
2034
|
}
|
|
1881
2035
|
})()
|
|
@@ -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
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
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
|
-
|
|
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:
|
|
2204
|
+
entries: s.entries.slice(safeOffset, safeOffset + safeLimit),
|
|
2074
2205
|
offset: safeOffset,
|
|
2075
2206
|
limit: safeLimit,
|
|
2076
|
-
total:
|
|
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
|
-
|
|
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
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
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
|
-
|
|
2111
|
-
if (!query.trim()) return
|
|
2245
|
+
const s = store$1.getState();
|
|
2246
|
+
if (!query.trim()) return s.entries.slice(0, limit);
|
|
2112
2247
|
const normalized = query.toLowerCase();
|
|
2113
|
-
return
|
|
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
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
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
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
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 =
|
|
2188
|
-
|
|
2189
|
-
const
|
|
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
|
|
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
|
-
|
|
2208
|
-
(
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
-
|
|
2225
|
-
const existingUrls = new Set(
|
|
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
|
-
|
|
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
|
-
|
|
2245
|
-
(
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3601
|
+
(err) => logger$v.warn("Failed to update highlight color:", err)
|
|
3460
3602
|
);
|
|
3461
3603
|
});
|
|
3462
3604
|
}
|
|
3463
3605
|
} catch (err) {
|
|
3464
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
4329
|
+
persistence$4.schedule();
|
|
4188
4330
|
return snapshot2;
|
|
4189
4331
|
}
|
|
4190
4332
|
function flushPersist$2() {
|
|
4191
|
-
return persistence$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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(
|
|
@@ -5673,6 +5815,7 @@ const PRELOAD_EXTRACTION_SCRIPT = String.raw`
|
|
|
5673
5815
|
}
|
|
5674
5816
|
}
|
|
5675
5817
|
} catch (_error) {
|
|
5818
|
+
// Structured extraction unavailable, fall through to direct extraction
|
|
5676
5819
|
}
|
|
5677
5820
|
return null;
|
|
5678
5821
|
})()
|
|
@@ -6312,9 +6455,9 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
6312
6455
|
} else if (parsed && typeof parsed === "object") {
|
|
6313
6456
|
jsonLd.push(parsed);
|
|
6314
6457
|
}
|
|
6315
|
-
} catch (_e) {}
|
|
6458
|
+
} catch (_e) { /* skip malformed JSON-LD block */ }
|
|
6316
6459
|
});
|
|
6317
|
-
} catch (_e) {}
|
|
6460
|
+
} catch (_e) { /* no JSON-LD scripts found or querySelectorAll failed */ }
|
|
6318
6461
|
|
|
6319
6462
|
// Extract meta tags as fallback
|
|
6320
6463
|
var metaTags = {};
|
|
@@ -6327,8 +6470,7 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
6327
6470
|
});
|
|
6328
6471
|
var canonical = document.querySelector('link[rel="canonical"]');
|
|
6329
6472
|
if (canonical && canonical.getAttribute("href")) metaTags["canonical"] = canonical.getAttribute("href");
|
|
6330
|
-
} catch (_e) {}
|
|
6331
|
-
|
|
6473
|
+
} catch (_e) { /* meta tag extraction failed — non-critical */ }
|
|
6332
6474
|
return {
|
|
6333
6475
|
title: document.title,
|
|
6334
6476
|
content: getCleanBodyText(),
|
|
@@ -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$
|
|
6536
|
+
logger$t.warn(detail);
|
|
6395
6537
|
} else {
|
|
6396
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
8844
|
+
logger$r.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
8703
8845
|
} catch (err) {
|
|
8704
|
-
logger$
|
|
8846
|
+
logger$r.warn("Failed to serialize debug payload:", err);
|
|
8705
8847
|
}
|
|
8706
8848
|
}
|
|
8707
8849
|
function formatOpenAICompatErrorMessage(providerId, message) {
|
|
@@ -9141,6 +9283,9 @@ class OpenAICompatProvider {
|
|
|
9141
9283
|
this.abortController?.abort();
|
|
9142
9284
|
}
|
|
9143
9285
|
}
|
|
9286
|
+
function escapeHtml(value) {
|
|
9287
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9288
|
+
}
|
|
9144
9289
|
async function openExternalAllowlisted(url, rule) {
|
|
9145
9290
|
const parsed = new URL(url);
|
|
9146
9291
|
const schemes = rule.schemes ?? ["https:"];
|
|
@@ -9325,7 +9470,7 @@ function createLocalPkceOAuthFlow(config) {
|
|
|
9325
9470
|
isInProgress: () => activeFlow !== null
|
|
9326
9471
|
};
|
|
9327
9472
|
}
|
|
9328
|
-
const logger$
|
|
9473
|
+
const logger$q = createLogger("CodexOAuth");
|
|
9329
9474
|
const ISSUER = "https://auth.openai.com";
|
|
9330
9475
|
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
9331
9476
|
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
@@ -9451,12 +9596,9 @@ async function refreshAccessToken(tokens) {
|
|
|
9451
9596
|
};
|
|
9452
9597
|
return refreshedTokens;
|
|
9453
9598
|
}
|
|
9454
|
-
function escapeHtml$1(text) {
|
|
9455
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
9456
|
-
}
|
|
9457
9599
|
const codexOAuth = createLocalPkceOAuthFlow({
|
|
9458
9600
|
name: "Codex",
|
|
9459
|
-
logger: logger$
|
|
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",
|
|
@@ -9465,7 +9607,7 @@ const codexOAuth = createLocalPkceOAuthFlow({
|
|
|
9465
9607
|
buildAuthorizeUrl: ({ callbackUrl, pkce, state: state2 }) => buildAuthorizeUrl(callbackUrl, pkce, state2),
|
|
9466
9608
|
exchangeCode: ({ code, callbackUrl, codeVerifier }) => exchangeCodeForTokens(code, callbackUrl, codeVerifier),
|
|
9467
9609
|
successHtml: (tokens) => {
|
|
9468
|
-
const label = escapeHtml
|
|
9610
|
+
const label = escapeHtml(tokens.accountEmail || tokens.accountId);
|
|
9469
9611
|
return `<!DOCTYPE html>
|
|
9470
9612
|
<html><head><meta charset="utf-8"><title>Vessel — Signed In</title>
|
|
9471
9613
|
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#111;color:#eee}</style></head>
|
|
@@ -9480,7 +9622,7 @@ async function startCodexOAuth(onStatus) {
|
|
|
9480
9622
|
function cancelCodexOAuth() {
|
|
9481
9623
|
codexOAuth.cancel();
|
|
9482
9624
|
}
|
|
9483
|
-
const logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
11169
|
+
getValue: () => state$2,
|
|
11025
11170
|
logLabel: "bookmarks"
|
|
11026
11171
|
});
|
|
11027
11172
|
}
|
|
11028
|
-
let persistence$
|
|
11173
|
+
let persistence$2 = null;
|
|
11029
11174
|
function getPersistence() {
|
|
11030
|
-
persistence$
|
|
11031
|
-
return persistence$
|
|
11175
|
+
persistence$2 ??= createPersistence();
|
|
11176
|
+
return persistence$2;
|
|
11032
11177
|
}
|
|
11033
11178
|
function load$1() {
|
|
11034
|
-
if (state$
|
|
11035
|
-
state$
|
|
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$
|
|
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$
|
|
11060
|
-
const snapshot2 = cloneState(state$
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
11067
11212
|
}
|
|
11068
11213
|
function toNetscapeTimestamp(value) {
|
|
11069
|
-
if (!value) return
|
|
11214
|
+
if (!value) return unixNow();
|
|
11070
11215
|
const time = Date.parse(value);
|
|
11071
|
-
return Number.isNaN(time) ?
|
|
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 =
|
|
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 =
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
11203
|
-
const bookmark = [...state$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
11371
|
-
state$
|
|
11372
|
-
if (state$
|
|
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$
|
|
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$
|
|
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$
|
|
11566
|
+
const exists = state$2.folders.some((f) => f.id === id);
|
|
11422
11567
|
if (!exists) return false;
|
|
11423
11568
|
if (deleteContents) {
|
|
11424
|
-
state$
|
|
11569
|
+
state$2.bookmarks = state$2.bookmarks.filter((b) => b.folderId !== id);
|
|
11425
11570
|
} else {
|
|
11426
|
-
state$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
}
|
|
@@ -17844,60 +17993,7 @@ WARNING: This page shows no results. You likely clicked a filter or category lin
|
|
|
17844
17993
|
}
|
|
17845
17994
|
return "";
|
|
17846
17995
|
}
|
|
17847
|
-
const KNOWN_TOOLS =
|
|
17848
|
-
"current_tab",
|
|
17849
|
-
"list_tabs",
|
|
17850
|
-
"switch_tab",
|
|
17851
|
-
"create_tab",
|
|
17852
|
-
"navigate",
|
|
17853
|
-
"go_back",
|
|
17854
|
-
"go_forward",
|
|
17855
|
-
"reload",
|
|
17856
|
-
"click",
|
|
17857
|
-
"inspect_element",
|
|
17858
|
-
"type_text",
|
|
17859
|
-
"select_option",
|
|
17860
|
-
"submit_form",
|
|
17861
|
-
"press_key",
|
|
17862
|
-
"scroll",
|
|
17863
|
-
"hover",
|
|
17864
|
-
"focus",
|
|
17865
|
-
"set_ad_blocking",
|
|
17866
|
-
"dismiss_popup",
|
|
17867
|
-
"clear_overlays",
|
|
17868
|
-
"read_page",
|
|
17869
|
-
"screenshot",
|
|
17870
|
-
"wait_for",
|
|
17871
|
-
"create_checkpoint",
|
|
17872
|
-
"restore_checkpoint",
|
|
17873
|
-
"save_session",
|
|
17874
|
-
"load_session",
|
|
17875
|
-
"list_sessions",
|
|
17876
|
-
"delete_session",
|
|
17877
|
-
"list_bookmarks",
|
|
17878
|
-
"search_bookmarks",
|
|
17879
|
-
"create_bookmark_folder",
|
|
17880
|
-
"save_bookmark",
|
|
17881
|
-
"organize_bookmark",
|
|
17882
|
-
"archive_bookmark",
|
|
17883
|
-
"open_bookmark",
|
|
17884
|
-
"highlight",
|
|
17885
|
-
"clear_highlights",
|
|
17886
|
-
"flow_start",
|
|
17887
|
-
"flow_advance",
|
|
17888
|
-
"flow_status",
|
|
17889
|
-
"flow_end",
|
|
17890
|
-
"suggest",
|
|
17891
|
-
"fill_form",
|
|
17892
|
-
"login",
|
|
17893
|
-
"search",
|
|
17894
|
-
"paginate",
|
|
17895
|
-
"accept_cookies",
|
|
17896
|
-
"extract_table",
|
|
17897
|
-
"scroll_to_element",
|
|
17898
|
-
"metrics",
|
|
17899
|
-
"wait_for_navigation"
|
|
17900
|
-
]);
|
|
17996
|
+
const KNOWN_TOOLS = new Set(TOOL_DEFINITIONS.map((d) => d.name));
|
|
17901
17997
|
async function executeAction(name, args, ctx) {
|
|
17902
17998
|
name = normalizeToolAlias(name);
|
|
17903
17999
|
if (ctx.tabId && ctx._tabMutex) {
|
|
@@ -20084,7 +20180,6 @@ function installAdBlockingForSession(ses, tabManager) {
|
|
|
20084
20180
|
);
|
|
20085
20181
|
});
|
|
20086
20182
|
}
|
|
20087
|
-
const filePath$1 = () => path$1.join(electron.app.getPath("userData"), "vessel-downloads.json");
|
|
20088
20183
|
const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
20089
20184
|
".appimage",
|
|
20090
20185
|
".bat",
|
|
@@ -20097,6 +20192,7 @@ const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
20097
20192
|
".scr",
|
|
20098
20193
|
".sh"
|
|
20099
20194
|
]);
|
|
20195
|
+
const DOWNLOADS_FALLBACK = { items: [] };
|
|
20100
20196
|
function hasMisleadingDoubleExtension(filename) {
|
|
20101
20197
|
return /\.(pdf|docx?|xlsx?|pptx?|png|jpe?g|gif|txt|zip)\.(exe|msi|bat|cmd|ps1|sh|scr|appimage)$/i.test(filename);
|
|
20102
20198
|
}
|
|
@@ -20111,23 +20207,14 @@ function executableWarningDetail(item) {
|
|
|
20111
20207
|
hasMisleadingDoubleExtension(item.filename) ? "Warning: this filename uses a misleading double extension." : null
|
|
20112
20208
|
].filter(Boolean).join("\n");
|
|
20113
20209
|
}
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20119
|
-
|
|
20120
|
-
const persistence$2 = createDebouncedJsonPersistence({
|
|
20121
|
-
debounceMs: 250,
|
|
20122
|
-
filePath: filePath$1(),
|
|
20123
|
-
getValue: () => state$2,
|
|
20124
|
-
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
|
|
20125
20216
|
});
|
|
20126
20217
|
let broadcaster$1 = null;
|
|
20127
|
-
function persist() {
|
|
20128
|
-
state$2.items = state$2.items.slice(0, 200);
|
|
20129
|
-
persistence$2.schedule();
|
|
20130
|
-
}
|
|
20131
20218
|
function emit$1() {
|
|
20132
20219
|
broadcaster$1?.(Channels.DOWNLOADS_UPDATE, listDownloads());
|
|
20133
20220
|
}
|
|
@@ -20135,30 +20222,33 @@ function setDownloadBroadcaster(fn) {
|
|
|
20135
20222
|
broadcaster$1 = fn;
|
|
20136
20223
|
}
|
|
20137
20224
|
function listDownloads() {
|
|
20138
|
-
return
|
|
20225
|
+
return store.getState().items.map((item) => ({ ...item }));
|
|
20139
20226
|
}
|
|
20140
20227
|
function upsertDownload(input) {
|
|
20141
20228
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20142
|
-
const
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
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
|
+
});
|
|
20152
20241
|
emit$1();
|
|
20153
|
-
return
|
|
20242
|
+
return result;
|
|
20154
20243
|
}
|
|
20155
20244
|
function clearDownloads() {
|
|
20156
|
-
|
|
20157
|
-
|
|
20245
|
+
store.mutate((s) => {
|
|
20246
|
+
s.items = [];
|
|
20247
|
+
});
|
|
20158
20248
|
emit$1();
|
|
20159
20249
|
}
|
|
20160
20250
|
async function openDownload(id) {
|
|
20161
|
-
const item =
|
|
20251
|
+
const item = store.getState().items.find((d) => d.id === id);
|
|
20162
20252
|
if (!item || item.state !== "completed" || !fs$1.existsSync(item.savePath)) return false;
|
|
20163
20253
|
if (isExecutableDownload(item.savePath)) {
|
|
20164
20254
|
const result = electron.dialog.showMessageBoxSync({
|
|
@@ -20175,7 +20265,7 @@ async function openDownload(id) {
|
|
|
20175
20265
|
return await electron.shell.openPath(item.savePath) === "";
|
|
20176
20266
|
}
|
|
20177
20267
|
async function showDownloadInFolder(id) {
|
|
20178
|
-
const item =
|
|
20268
|
+
const item = store.getState().items.find((d) => d.id === id);
|
|
20179
20269
|
if (!item || !fs$1.existsSync(item.savePath)) return false;
|
|
20180
20270
|
electron.shell.showItemInFolder(item.savePath);
|
|
20181
20271
|
return true;
|
|
@@ -21558,6 +21648,32 @@ function compactProviderHistory(history = []) {
|
|
|
21558
21648
|
const omitted = normalized.slice(0, normalized.length - recent.length);
|
|
21559
21649
|
return omitted.length > 0 ? [summarizeOmittedHistory(omitted), ...recent] : recent;
|
|
21560
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
|
+
});
|
|
21561
21677
|
let activeChatProvider = null;
|
|
21562
21678
|
function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResearchOrchestrator) {
|
|
21563
21679
|
onAIStreamIdle(() => {
|
|
@@ -21565,10 +21681,12 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
|
|
|
21565
21681
|
});
|
|
21566
21682
|
electron.ipcMain.handle(Channels.AI_QUERY, async (event, query, history) => {
|
|
21567
21683
|
assertTrustedIpcSender(event);
|
|
21684
|
+
const validatedQuery = parseIpc(AIQuerySchema, query, "query");
|
|
21685
|
+
const validatedHistory = history !== void 0 ? parseIpc(AIHistorySchema, history, "history") : void 0;
|
|
21568
21686
|
const settings2 = loadSettings();
|
|
21569
21687
|
const chatConfig = settings2.chatProvider;
|
|
21570
21688
|
if (!chatConfig) {
|
|
21571
|
-
sendToRendererViews(Channels.AI_STREAM_START,
|
|
21689
|
+
sendToRendererViews(Channels.AI_STREAM_START, validatedQuery);
|
|
21572
21690
|
sendToRendererViews(
|
|
21573
21691
|
Channels.AI_STREAM_CHUNK,
|
|
21574
21692
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
@@ -21579,20 +21697,20 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
|
|
|
21579
21697
|
if (!tryBeginAIStream("manual")) {
|
|
21580
21698
|
return { accepted: false, reason: "busy" };
|
|
21581
21699
|
}
|
|
21582
|
-
sendToRendererViews(Channels.AI_STREAM_START,
|
|
21700
|
+
sendToRendererViews(Channels.AI_STREAM_START, validatedQuery);
|
|
21583
21701
|
(async () => {
|
|
21584
21702
|
try {
|
|
21585
21703
|
activeChatProvider = createProvider(chatConfig);
|
|
21586
21704
|
const activeTab = tabManager.getActiveTab();
|
|
21587
21705
|
await handleAIQuery(
|
|
21588
|
-
|
|
21706
|
+
validatedQuery,
|
|
21589
21707
|
activeChatProvider,
|
|
21590
21708
|
activeTab?.view.webContents,
|
|
21591
21709
|
(chunk) => sendToRendererViews(Channels.AI_STREAM_CHUNK, chunk),
|
|
21592
21710
|
() => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
|
|
21593
21711
|
tabManager,
|
|
21594
21712
|
runtime2,
|
|
21595
|
-
compactProviderHistory(
|
|
21713
|
+
compactProviderHistory(validatedHistory),
|
|
21596
21714
|
getResearchOrchestrator()
|
|
21597
21715
|
);
|
|
21598
21716
|
} catch (err) {
|
|
@@ -21614,12 +21732,8 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
|
|
|
21614
21732
|
electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (event, config) => {
|
|
21615
21733
|
assertTrustedIpcSender(event);
|
|
21616
21734
|
try {
|
|
21617
|
-
|
|
21618
|
-
|
|
21619
|
-
}
|
|
21620
|
-
return await fetchProviderModels(
|
|
21621
|
-
config
|
|
21622
|
-
);
|
|
21735
|
+
const validatedConfig = parseIpc(ProviderConfigSchema, config, "providerConfig");
|
|
21736
|
+
return await fetchProviderModels(validatedConfig);
|
|
21623
21737
|
} catch (err) {
|
|
21624
21738
|
return errorResult(getErrorMessage(err), { models: [] });
|
|
21625
21739
|
}
|
|
@@ -21717,9 +21831,6 @@ function renderReaderContent(page) {
|
|
|
21717
21831
|
}
|
|
21718
21832
|
return source.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean).map((block) => `<p>${escapeHtml(block).replace(/\n/g, "<br>")}</p>`).join("\n");
|
|
21719
21833
|
}
|
|
21720
|
-
function escapeHtml(str) {
|
|
21721
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
21722
|
-
}
|
|
21723
21834
|
function registerContentHandlers(windowState2) {
|
|
21724
21835
|
const { tabManager } = windowState2;
|
|
21725
21836
|
electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async (event) => {
|
|
@@ -23518,6 +23629,7 @@ function registerBookmarkTools(server, tabManager, runtime2) {
|
|
|
23518
23629
|
return `Opened bookmark "${bookmark.title}" in new tab ${createdId}`;
|
|
23519
23630
|
}
|
|
23520
23631
|
const activeId = tabManager.getActiveTabId();
|
|
23632
|
+
if (!activeId) return "No active tab to open bookmark in";
|
|
23521
23633
|
const activeTab = tabManager.getActiveTab();
|
|
23522
23634
|
tabManager.navigateTab(activeId, bookmark.url);
|
|
23523
23635
|
if (activeTab) {
|
|
@@ -23877,7 +23989,7 @@ function domainMatches(pattern, hostname) {
|
|
|
23877
23989
|
return isWildcard ? h.endsWith("." + p) : p === h;
|
|
23878
23990
|
}
|
|
23879
23991
|
function generateTotpCode(secret) {
|
|
23880
|
-
const epoch =
|
|
23992
|
+
const epoch = unixNow();
|
|
23881
23993
|
const counter = Math.floor(epoch / 30);
|
|
23882
23994
|
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
23883
23995
|
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
@@ -23922,7 +24034,8 @@ function createAuditLog(filename, maxEntries) {
|
|
|
23922
24034
|
});
|
|
23923
24035
|
fs$1.chmodSync(auditPath, 384);
|
|
23924
24036
|
}
|
|
23925
|
-
} catch {
|
|
24037
|
+
} catch (err) {
|
|
24038
|
+
logger$e.warn("Failed to trim audit log:", err);
|
|
23926
24039
|
}
|
|
23927
24040
|
} catch (err) {
|
|
23928
24041
|
logger$e.error("Failed to write audit log:", err);
|
|
@@ -24008,9 +24121,12 @@ async function requestConsent(request) {
|
|
|
24008
24121
|
if (sessionTrustedDomains.has(domain)) {
|
|
24009
24122
|
return { approved: true, trustForSession: true };
|
|
24010
24123
|
}
|
|
24011
|
-
const
|
|
24124
|
+
const parentWindow = electron.BrowserWindow.getFocusedWindow() ?? electron.BrowserWindow.getAllWindows()[0];
|
|
24125
|
+
if (!parentWindow) {
|
|
24126
|
+
return { approved: false, trustForSession: false };
|
|
24127
|
+
}
|
|
24012
24128
|
const { response } = await electron.dialog.showMessageBox(
|
|
24013
|
-
|
|
24129
|
+
parentWindow,
|
|
24014
24130
|
{
|
|
24015
24131
|
type: "question",
|
|
24016
24132
|
title: "Agent Credential Access",
|
|
@@ -24643,6 +24759,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
24643
24759
|
}
|
|
24644
24760
|
return withAction(runtime2, tabManager, "navigate", { url }, async () => {
|
|
24645
24761
|
const id = tabManager.getActiveTabId();
|
|
24762
|
+
if (!id) return asNoActiveTabResponse();
|
|
24646
24763
|
const navError = tabManager.navigateTab(id, url, postBody);
|
|
24647
24764
|
if (navError) return navError;
|
|
24648
24765
|
const { httpStatus } = await waitForLoadWithStatus(
|
|
@@ -24766,7 +24883,9 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
24766
24883
|
return "No previous page in history";
|
|
24767
24884
|
}
|
|
24768
24885
|
const beforeUrl = tab.view.webContents.getURL();
|
|
24769
|
-
tabManager.
|
|
24886
|
+
const backId = tabManager.getActiveTabId();
|
|
24887
|
+
if (!backId) return asNoActiveTabResponse();
|
|
24888
|
+
tabManager.goBack(backId);
|
|
24770
24889
|
await waitForLoad(tab.view.webContents);
|
|
24771
24890
|
const afterUrl = tab.view.webContents.getURL();
|
|
24772
24891
|
return afterUrl !== beforeUrl ? `Went back to ${afterUrl}` : `Back action completed but page stayed on ${afterUrl}`;
|
|
@@ -24787,7 +24906,9 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
24787
24906
|
return "No forward page in history";
|
|
24788
24907
|
}
|
|
24789
24908
|
const beforeUrl = tab.view.webContents.getURL();
|
|
24790
|
-
tabManager.
|
|
24909
|
+
const forwardId = tabManager.getActiveTabId();
|
|
24910
|
+
if (!forwardId) return asNoActiveTabResponse();
|
|
24911
|
+
tabManager.goForward(forwardId);
|
|
24791
24912
|
await waitForLoad(tab.view.webContents);
|
|
24792
24913
|
const afterUrl = tab.view.webContents.getURL();
|
|
24793
24914
|
return afterUrl !== beforeUrl ? `Went forward to ${afterUrl}` : `Forward action completed but page stayed on ${afterUrl}`;
|
|
@@ -24804,7 +24925,9 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
24804
24925
|
const tab = tabManager.getActiveTab();
|
|
24805
24926
|
if (!tab) return asNoActiveTabResponse();
|
|
24806
24927
|
return withAction(runtime2, tabManager, "reload", {}, async () => {
|
|
24807
|
-
tabManager.
|
|
24928
|
+
const reloadId = tabManager.getActiveTabId();
|
|
24929
|
+
if (!reloadId) return asNoActiveTabResponse();
|
|
24930
|
+
tabManager.reloadTab(reloadId);
|
|
24808
24931
|
await waitForLoad(tab.view.webContents);
|
|
24809
24932
|
return `Reloaded ${tab.view.webContents.getURL()}`;
|
|
24810
24933
|
});
|
|
@@ -25481,7 +25604,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25481
25604
|
)
|
|
25482
25605
|
}
|
|
25483
25606
|
},
|
|
25484
|
-
async ({ index, selector, text, label, durationMs, persist
|
|
25607
|
+
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
25485
25608
|
const tab = tabManager.getActiveTab();
|
|
25486
25609
|
if (!tab) return asNoActiveTabResponse();
|
|
25487
25610
|
const normalizedText = normalizeLooseString(text);
|
|
@@ -25495,7 +25618,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25495
25618
|
text: normalizedText,
|
|
25496
25619
|
label,
|
|
25497
25620
|
durationMs,
|
|
25498
|
-
persist
|
|
25621
|
+
persist,
|
|
25499
25622
|
color
|
|
25500
25623
|
},
|
|
25501
25624
|
async () => {
|
|
@@ -25509,7 +25632,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25509
25632
|
durationMs,
|
|
25510
25633
|
color
|
|
25511
25634
|
);
|
|
25512
|
-
if (
|
|
25635
|
+
if (persist && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
|
|
25513
25636
|
const url = normalizeUrl$1(wc.getURL());
|
|
25514
25637
|
addHighlight(
|
|
25515
25638
|
url,
|
|
@@ -26114,6 +26237,7 @@ ${results.join("\n")}`;
|
|
|
26114
26237
|
const steps = [];
|
|
26115
26238
|
if (url) {
|
|
26116
26239
|
const id = tabManager.getActiveTabId();
|
|
26240
|
+
if (!id) return asNoActiveTabResponse();
|
|
26117
26241
|
tabManager.navigateTab(id, url);
|
|
26118
26242
|
await waitForLoad(wc);
|
|
26119
26243
|
steps.push(`Navigated to ${wc.getURL()}`);
|
|
@@ -28028,6 +28152,25 @@ function registerSystemHandlers(windowState2, sendToRendererViews) {
|
|
|
28028
28152
|
return togglePictureInPicture(tabManager);
|
|
28029
28153
|
});
|
|
28030
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();
|
|
28031
28174
|
function getSafeBookmarkExportName(name) {
|
|
28032
28175
|
const safeName = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
28033
28176
|
return safeName || "folder";
|
|
@@ -28041,23 +28184,33 @@ function registerBookmarkHandlers() {
|
|
|
28041
28184
|
Channels.FOLDER_CREATE,
|
|
28042
28185
|
(event, name, summary) => {
|
|
28043
28186
|
assertTrustedIpcSender(event);
|
|
28187
|
+
const validatedName = parseIpc(FolderNameSchema, name, "name");
|
|
28188
|
+
const validatedSummary = parseIpc(OptionalStringSchema, summary, "summary");
|
|
28044
28189
|
trackBookmarkAction("folder_create");
|
|
28045
|
-
return createFolderWithSummary(
|
|
28190
|
+
return createFolderWithSummary(validatedName, validatedSummary);
|
|
28046
28191
|
}
|
|
28047
28192
|
);
|
|
28048
28193
|
electron.ipcMain.handle(
|
|
28049
28194
|
Channels.BOOKMARK_SAVE,
|
|
28050
28195
|
(event, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
|
|
28051
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");
|
|
28052
28205
|
trackBookmarkAction("save");
|
|
28053
|
-
const result = saveBookmarkWithPolicy(
|
|
28206
|
+
const result = saveBookmarkWithPolicy(validatedUrl, validatedTitle, validatedFolderId, validatedNote, {
|
|
28054
28207
|
onDuplicate: "update",
|
|
28055
28208
|
extra: {
|
|
28056
28209
|
...normalizeBookmarkMetadata({
|
|
28057
|
-
intent,
|
|
28058
|
-
expectedContent,
|
|
28059
|
-
keyFields,
|
|
28060
|
-
agentHints
|
|
28210
|
+
intent: validatedIntent,
|
|
28211
|
+
expectedContent: validatedExpectedContent,
|
|
28212
|
+
keyFields: validatedKeyFields,
|
|
28213
|
+
agentHints: validatedAgentHints
|
|
28061
28214
|
})
|
|
28062
28215
|
}
|
|
28063
28216
|
});
|
|
@@ -28071,19 +28224,23 @@ function registerBookmarkHandlers() {
|
|
|
28071
28224
|
Channels.BOOKMARK_UPDATE,
|
|
28072
28225
|
(event, id, updates) => {
|
|
28073
28226
|
assertTrustedIpcSender(event);
|
|
28227
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28228
|
+
const validatedUpdates = parseIpc(BookmarkUpdateSchema, updates, "updates");
|
|
28074
28229
|
trackBookmarkAction("save");
|
|
28075
|
-
return updateBookmark(
|
|
28230
|
+
return updateBookmark(validatedId, validatedUpdates);
|
|
28076
28231
|
}
|
|
28077
28232
|
);
|
|
28078
28233
|
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (event, id) => {
|
|
28079
28234
|
assertTrustedIpcSender(event);
|
|
28235
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28080
28236
|
trackBookmarkAction("remove");
|
|
28081
|
-
return removeBookmark(
|
|
28237
|
+
return removeBookmark(validatedId);
|
|
28082
28238
|
});
|
|
28083
28239
|
electron.ipcMain.handle(
|
|
28084
28240
|
Channels.BOOKMARKS_EXPORT_HTML,
|
|
28085
28241
|
async (event, options) => {
|
|
28086
28242
|
assertTrustedIpcSender(event);
|
|
28243
|
+
const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
|
|
28087
28244
|
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
28088
28245
|
title: "Export Bookmarks",
|
|
28089
28246
|
defaultPath: "vessel-bookmarks.html",
|
|
@@ -28091,7 +28248,7 @@ function registerBookmarkHandlers() {
|
|
|
28091
28248
|
});
|
|
28092
28249
|
if (canceled || !filePath2) return null;
|
|
28093
28250
|
const content = exportBookmarksHtml({
|
|
28094
|
-
includeNotes:
|
|
28251
|
+
includeNotes: validatedOptions?.includeNotes ?? false
|
|
28095
28252
|
});
|
|
28096
28253
|
await fs.promises.writeFile(filePath2, content, "utf-8");
|
|
28097
28254
|
trackBookmarkAction("export");
|
|
@@ -28121,7 +28278,9 @@ function registerBookmarkHandlers() {
|
|
|
28121
28278
|
Channels.FOLDER_EXPORT_HTML,
|
|
28122
28279
|
async (event, folderId, options) => {
|
|
28123
28280
|
assertTrustedIpcSender(event);
|
|
28124
|
-
const
|
|
28281
|
+
const validatedFolderId = parseIpc(BookmarkIdSchema, folderId, "folderId");
|
|
28282
|
+
const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
|
|
28283
|
+
const folder = getFolder(validatedFolderId);
|
|
28125
28284
|
if (!folder) return null;
|
|
28126
28285
|
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
28127
28286
|
title: `Export ${folder.name}`,
|
|
@@ -28129,8 +28288,8 @@ function registerBookmarkHandlers() {
|
|
|
28129
28288
|
filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
|
|
28130
28289
|
});
|
|
28131
28290
|
if (canceled || !filePath2) return null;
|
|
28132
|
-
const result = exportBookmarkFolderHtml(
|
|
28133
|
-
includeNotes:
|
|
28291
|
+
const result = exportBookmarkFolderHtml(validatedFolderId, {
|
|
28292
|
+
includeNotes: validatedOptions?.includeNotes ?? true
|
|
28134
28293
|
});
|
|
28135
28294
|
if (!result) return null;
|
|
28136
28295
|
await fs.promises.writeFile(filePath2, result.content, "utf-8");
|
|
@@ -28171,14 +28330,23 @@ function registerBookmarkHandlers() {
|
|
|
28171
28330
|
});
|
|
28172
28331
|
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (event, id, deleteContents) => {
|
|
28173
28332
|
assertTrustedIpcSender(event);
|
|
28333
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28334
|
+
const validatedDeleteContents = parseIpc(
|
|
28335
|
+
OptionalBooleanSchema,
|
|
28336
|
+
deleteContents,
|
|
28337
|
+
"deleteContents"
|
|
28338
|
+
);
|
|
28174
28339
|
trackBookmarkAction("folder_remove");
|
|
28175
|
-
return removeFolder(
|
|
28340
|
+
return removeFolder(validatedId, validatedDeleteContents ?? false);
|
|
28176
28341
|
});
|
|
28177
28342
|
electron.ipcMain.handle(
|
|
28178
28343
|
Channels.FOLDER_RENAME,
|
|
28179
28344
|
(event, id, newName, summary) => {
|
|
28180
28345
|
assertTrustedIpcSender(event);
|
|
28181
|
-
|
|
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);
|
|
28182
28350
|
}
|
|
28183
28351
|
);
|
|
28184
28352
|
}
|
|
@@ -28443,6 +28611,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
28443
28611
|
return result;
|
|
28444
28612
|
});
|
|
28445
28613
|
}
|
|
28614
|
+
const SessionNameSchema = zod.z.string().min(1);
|
|
28446
28615
|
function registerSessionHandlers(tabManager) {
|
|
28447
28616
|
electron.ipcMain.handle(Channels.SESSION_LIST, (event) => {
|
|
28448
28617
|
assertTrustedIpcSender(event);
|
|
@@ -28450,18 +28619,18 @@ function registerSessionHandlers(tabManager) {
|
|
|
28450
28619
|
});
|
|
28451
28620
|
electron.ipcMain.handle(Channels.SESSION_SAVE, async (event, name) => {
|
|
28452
28621
|
assertTrustedIpcSender(event);
|
|
28453
|
-
|
|
28454
|
-
return await saveNamedSession(tabManager,
|
|
28622
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28623
|
+
return await saveNamedSession(tabManager, validatedName);
|
|
28455
28624
|
});
|
|
28456
28625
|
electron.ipcMain.handle(Channels.SESSION_LOAD, async (event, name) => {
|
|
28457
28626
|
assertTrustedIpcSender(event);
|
|
28458
|
-
|
|
28459
|
-
return await loadNamedSession(tabManager,
|
|
28627
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28628
|
+
return await loadNamedSession(tabManager, validatedName);
|
|
28460
28629
|
});
|
|
28461
28630
|
electron.ipcMain.handle(Channels.SESSION_DELETE, (event, name) => {
|
|
28462
28631
|
assertTrustedIpcSender(event);
|
|
28463
|
-
|
|
28464
|
-
return deleteNamedSession(
|
|
28632
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28633
|
+
return deleteNamedSession(validatedName);
|
|
28465
28634
|
});
|
|
28466
28635
|
}
|
|
28467
28636
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -30551,7 +30720,8 @@ ${progress}
|
|
|
30551
30720
|
const raw = fs$1.readFileSync(getRuntimeStatePath(), "utf-8");
|
|
30552
30721
|
const parsed = JSON.parse(raw);
|
|
30553
30722
|
return sanitizePersistence(parsed);
|
|
30554
|
-
} catch {
|
|
30723
|
+
} catch (err) {
|
|
30724
|
+
logger$3.warn("Failed to load persisted runtime state, starting fresh:", err);
|
|
30555
30725
|
return sanitizePersistence(null);
|
|
30556
30726
|
}
|
|
30557
30727
|
}
|
|
@@ -30810,7 +30980,7 @@ function findIconBase64() {
|
|
|
30810
30980
|
return "";
|
|
30811
30981
|
}
|
|
30812
30982
|
function buildSplashHTML(iconSrc) {
|
|
30813
|
-
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>`;
|
|
30814
30984
|
return `<!DOCTYPE html>
|
|
30815
30985
|
<html>
|
|
30816
30986
|
<head>
|
|
@@ -30950,7 +31120,8 @@ function createSplashWindow() {
|
|
|
30950
31120
|
splash.once("closed", () => {
|
|
30951
31121
|
try {
|
|
30952
31122
|
fs$1.rmSync(tmpDir, { recursive: true, force: true });
|
|
30953
|
-
} catch {
|
|
31123
|
+
} catch (err) {
|
|
31124
|
+
logger$1.debug("Failed to clean up splash temp dir:", err);
|
|
30954
31125
|
}
|
|
30955
31126
|
});
|
|
30956
31127
|
fs$1.writeFileSync(tmpPath, html, "utf-8");
|