@quanta-intellect/vessel-browser 0.1.134 → 0.1.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/main/index.js
CHANGED
|
@@ -4,11 +4,11 @@ const fs$1 = require("node:fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const crypto$1 = require("crypto");
|
|
7
|
+
const zod = require("zod");
|
|
7
8
|
const Anthropic = require("@anthropic-ai/sdk");
|
|
8
9
|
const OpenAI = require("openai");
|
|
9
10
|
const crypto$2 = require("node:crypto");
|
|
10
11
|
const http = require("http");
|
|
11
|
-
const zod = require("zod");
|
|
12
12
|
const path$1 = require("node:path");
|
|
13
13
|
const node_module = require("node:module");
|
|
14
14
|
const http$1 = require("node:http");
|
|
@@ -111,10 +111,10 @@ const defaults = {
|
|
|
111
111
|
expiresAt: ""
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
|
-
const SAVE_DEBOUNCE_MS$
|
|
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 = `
|
|
@@ -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(
|
|
@@ -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) {
|
|
@@ -9328,7 +9470,7 @@ function createLocalPkceOAuthFlow(config) {
|
|
|
9328
9470
|
isInProgress: () => activeFlow !== null
|
|
9329
9471
|
};
|
|
9330
9472
|
}
|
|
9331
|
-
const logger$
|
|
9473
|
+
const logger$q = createLogger("CodexOAuth");
|
|
9332
9474
|
const ISSUER = "https://auth.openai.com";
|
|
9333
9475
|
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
9334
9476
|
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
@@ -9456,7 +9598,7 @@ async function refreshAccessToken(tokens) {
|
|
|
9456
9598
|
}
|
|
9457
9599
|
const codexOAuth = createLocalPkceOAuthFlow({
|
|
9458
9600
|
name: "Codex",
|
|
9459
|
-
logger: logger$
|
|
9601
|
+
logger: logger$q,
|
|
9460
9602
|
preferredPorts: [PREFERRED_PORT$1, FALLBACK_PORT$1],
|
|
9461
9603
|
timeoutMs: AUTH_TIMEOUT_MS$1,
|
|
9462
9604
|
callbackPath: () => "/auth/callback",
|
|
@@ -9480,7 +9622,7 @@ async function startCodexOAuth(onStatus) {
|
|
|
9480
9622
|
function cancelCodexOAuth() {
|
|
9481
9623
|
codexOAuth.cancel();
|
|
9482
9624
|
}
|
|
9483
|
-
const logger$
|
|
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
|
}
|
|
@@ -20031,7 +20180,6 @@ function installAdBlockingForSession(ses, tabManager) {
|
|
|
20031
20180
|
);
|
|
20032
20181
|
});
|
|
20033
20182
|
}
|
|
20034
|
-
const filePath$1 = () => path$1.join(electron.app.getPath("userData"), "vessel-downloads.json");
|
|
20035
20183
|
const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
20036
20184
|
".appimage",
|
|
20037
20185
|
".bat",
|
|
@@ -20044,6 +20192,7 @@ const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
20044
20192
|
".scr",
|
|
20045
20193
|
".sh"
|
|
20046
20194
|
]);
|
|
20195
|
+
const DOWNLOADS_FALLBACK = { items: [] };
|
|
20047
20196
|
function hasMisleadingDoubleExtension(filename) {
|
|
20048
20197
|
return /\.(pdf|docx?|xlsx?|pptx?|png|jpe?g|gif|txt|zip)\.(exe|msi|bat|cmd|ps1|sh|scr|appimage)$/i.test(filename);
|
|
20049
20198
|
}
|
|
@@ -20058,23 +20207,14 @@ function executableWarningDetail(item) {
|
|
|
20058
20207
|
hasMisleadingDoubleExtension(item.filename) ? "Warning: this filename uses a misleading double extension." : null
|
|
20059
20208
|
].filter(Boolean).join("\n");
|
|
20060
20209
|
}
|
|
20061
|
-
|
|
20062
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
const persistence$2 = createDebouncedJsonPersistence({
|
|
20068
|
-
debounceMs: 250,
|
|
20069
|
-
filePath: filePath$1(),
|
|
20070
|
-
getValue: () => state$2,
|
|
20071
|
-
logLabel: "downloads"
|
|
20210
|
+
const store = new PersistentState({
|
|
20211
|
+
filename: "vessel-downloads.json",
|
|
20212
|
+
fallback: DOWNLOADS_FALLBACK,
|
|
20213
|
+
parse: (raw) => parseArrayStateWithFallback(DownloadRecordSchema, raw, "items", DOWNLOADS_FALLBACK, "downloads"),
|
|
20214
|
+
logLabel: "downloads",
|
|
20215
|
+
debounceMs: 250
|
|
20072
20216
|
});
|
|
20073
20217
|
let broadcaster$1 = null;
|
|
20074
|
-
function persist() {
|
|
20075
|
-
state$2.items = state$2.items.slice(0, 200);
|
|
20076
|
-
persistence$2.schedule();
|
|
20077
|
-
}
|
|
20078
20218
|
function emit$1() {
|
|
20079
20219
|
broadcaster$1?.(Channels.DOWNLOADS_UPDATE, listDownloads());
|
|
20080
20220
|
}
|
|
@@ -20082,30 +20222,33 @@ function setDownloadBroadcaster(fn) {
|
|
|
20082
20222
|
broadcaster$1 = fn;
|
|
20083
20223
|
}
|
|
20084
20224
|
function listDownloads() {
|
|
20085
|
-
return
|
|
20225
|
+
return store.getState().items.map((item) => ({ ...item }));
|
|
20086
20226
|
}
|
|
20087
20227
|
function upsertDownload(input) {
|
|
20088
20228
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20089
|
-
const
|
|
20090
|
-
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20229
|
+
const result = store.mutate((s) => {
|
|
20230
|
+
const existing = s.items.find((item) => item.savePath === input.savePath);
|
|
20231
|
+
if (existing) {
|
|
20232
|
+
Object.assign(existing, input, { updatedAt: now });
|
|
20233
|
+
return existing;
|
|
20234
|
+
} else {
|
|
20235
|
+
const record = { id: crypto$2.randomUUID(), ...input, startedAt: now, updatedAt: now };
|
|
20236
|
+
s.items = [record, ...s.items];
|
|
20237
|
+
s.items = s.items.slice(0, 200);
|
|
20238
|
+
return record;
|
|
20239
|
+
}
|
|
20240
|
+
});
|
|
20099
20241
|
emit$1();
|
|
20100
|
-
return
|
|
20242
|
+
return result;
|
|
20101
20243
|
}
|
|
20102
20244
|
function clearDownloads() {
|
|
20103
|
-
|
|
20104
|
-
|
|
20245
|
+
store.mutate((s) => {
|
|
20246
|
+
s.items = [];
|
|
20247
|
+
});
|
|
20105
20248
|
emit$1();
|
|
20106
20249
|
}
|
|
20107
20250
|
async function openDownload(id) {
|
|
20108
|
-
const item =
|
|
20251
|
+
const item = store.getState().items.find((d) => d.id === id);
|
|
20109
20252
|
if (!item || item.state !== "completed" || !fs$1.existsSync(item.savePath)) return false;
|
|
20110
20253
|
if (isExecutableDownload(item.savePath)) {
|
|
20111
20254
|
const result = electron.dialog.showMessageBoxSync({
|
|
@@ -20122,7 +20265,7 @@ async function openDownload(id) {
|
|
|
20122
20265
|
return await electron.shell.openPath(item.savePath) === "";
|
|
20123
20266
|
}
|
|
20124
20267
|
async function showDownloadInFolder(id) {
|
|
20125
|
-
const item =
|
|
20268
|
+
const item = store.getState().items.find((d) => d.id === id);
|
|
20126
20269
|
if (!item || !fs$1.existsSync(item.savePath)) return false;
|
|
20127
20270
|
electron.shell.showItemInFolder(item.savePath);
|
|
20128
20271
|
return true;
|
|
@@ -21505,6 +21648,32 @@ function compactProviderHistory(history = []) {
|
|
|
21505
21648
|
const omitted = normalized.slice(0, normalized.length - recent.length);
|
|
21506
21649
|
return omitted.length > 0 ? [summarizeOmittedHistory(omitted), ...recent] : recent;
|
|
21507
21650
|
}
|
|
21651
|
+
const AIQuerySchema = zod.z.string().min(1);
|
|
21652
|
+
const AIMessageSchema = zod.z.object({
|
|
21653
|
+
role: zod.z.enum(["user", "assistant"]),
|
|
21654
|
+
content: zod.z.string()
|
|
21655
|
+
});
|
|
21656
|
+
const AIHistorySchema = zod.z.array(AIMessageSchema).optional();
|
|
21657
|
+
const ReasoningEffortSchema = zod.z.enum(["off", "low", "medium", "high", "max"]);
|
|
21658
|
+
const ProviderConfigSchema = zod.z.object({
|
|
21659
|
+
id: zod.z.enum([
|
|
21660
|
+
"anthropic",
|
|
21661
|
+
"openai",
|
|
21662
|
+
"openai_codex",
|
|
21663
|
+
"openrouter",
|
|
21664
|
+
"ollama",
|
|
21665
|
+
"llama_cpp",
|
|
21666
|
+
"mistral",
|
|
21667
|
+
"xai",
|
|
21668
|
+
"google",
|
|
21669
|
+
"custom"
|
|
21670
|
+
]),
|
|
21671
|
+
apiKey: zod.z.string(),
|
|
21672
|
+
hasApiKey: zod.z.boolean().optional(),
|
|
21673
|
+
model: zod.z.string(),
|
|
21674
|
+
baseUrl: zod.z.string().optional(),
|
|
21675
|
+
reasoningEffort: ReasoningEffortSchema.optional()
|
|
21676
|
+
});
|
|
21508
21677
|
let activeChatProvider = null;
|
|
21509
21678
|
function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResearchOrchestrator) {
|
|
21510
21679
|
onAIStreamIdle(() => {
|
|
@@ -21512,10 +21681,12 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
|
|
|
21512
21681
|
});
|
|
21513
21682
|
electron.ipcMain.handle(Channels.AI_QUERY, async (event, query, history) => {
|
|
21514
21683
|
assertTrustedIpcSender(event);
|
|
21684
|
+
const validatedQuery = parseIpc(AIQuerySchema, query, "query");
|
|
21685
|
+
const validatedHistory = history !== void 0 ? parseIpc(AIHistorySchema, history, "history") : void 0;
|
|
21515
21686
|
const settings2 = loadSettings();
|
|
21516
21687
|
const chatConfig = settings2.chatProvider;
|
|
21517
21688
|
if (!chatConfig) {
|
|
21518
|
-
sendToRendererViews(Channels.AI_STREAM_START,
|
|
21689
|
+
sendToRendererViews(Channels.AI_STREAM_START, validatedQuery);
|
|
21519
21690
|
sendToRendererViews(
|
|
21520
21691
|
Channels.AI_STREAM_CHUNK,
|
|
21521
21692
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
@@ -21526,20 +21697,20 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
|
|
|
21526
21697
|
if (!tryBeginAIStream("manual")) {
|
|
21527
21698
|
return { accepted: false, reason: "busy" };
|
|
21528
21699
|
}
|
|
21529
|
-
sendToRendererViews(Channels.AI_STREAM_START,
|
|
21700
|
+
sendToRendererViews(Channels.AI_STREAM_START, validatedQuery);
|
|
21530
21701
|
(async () => {
|
|
21531
21702
|
try {
|
|
21532
21703
|
activeChatProvider = createProvider(chatConfig);
|
|
21533
21704
|
const activeTab = tabManager.getActiveTab();
|
|
21534
21705
|
await handleAIQuery(
|
|
21535
|
-
|
|
21706
|
+
validatedQuery,
|
|
21536
21707
|
activeChatProvider,
|
|
21537
21708
|
activeTab?.view.webContents,
|
|
21538
21709
|
(chunk) => sendToRendererViews(Channels.AI_STREAM_CHUNK, chunk),
|
|
21539
21710
|
() => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
|
|
21540
21711
|
tabManager,
|
|
21541
21712
|
runtime2,
|
|
21542
|
-
compactProviderHistory(
|
|
21713
|
+
compactProviderHistory(validatedHistory),
|
|
21543
21714
|
getResearchOrchestrator()
|
|
21544
21715
|
);
|
|
21545
21716
|
} catch (err) {
|
|
@@ -21561,12 +21732,8 @@ function registerAIHandlers(tabManager, runtime2, sendToRendererViews, getResear
|
|
|
21561
21732
|
electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (event, config) => {
|
|
21562
21733
|
assertTrustedIpcSender(event);
|
|
21563
21734
|
try {
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
}
|
|
21567
|
-
return await fetchProviderModels(
|
|
21568
|
-
config
|
|
21569
|
-
);
|
|
21735
|
+
const validatedConfig = parseIpc(ProviderConfigSchema, config, "providerConfig");
|
|
21736
|
+
return await fetchProviderModels(validatedConfig);
|
|
21570
21737
|
} catch (err) {
|
|
21571
21738
|
return errorResult(getErrorMessage(err), { models: [] });
|
|
21572
21739
|
}
|
|
@@ -23822,7 +23989,7 @@ function domainMatches(pattern, hostname) {
|
|
|
23822
23989
|
return isWildcard ? h.endsWith("." + p) : p === h;
|
|
23823
23990
|
}
|
|
23824
23991
|
function generateTotpCode(secret) {
|
|
23825
|
-
const epoch =
|
|
23992
|
+
const epoch = unixNow();
|
|
23826
23993
|
const counter = Math.floor(epoch / 30);
|
|
23827
23994
|
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
23828
23995
|
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
@@ -23867,7 +24034,8 @@ function createAuditLog(filename, maxEntries) {
|
|
|
23867
24034
|
});
|
|
23868
24035
|
fs$1.chmodSync(auditPath, 384);
|
|
23869
24036
|
}
|
|
23870
|
-
} catch {
|
|
24037
|
+
} catch (err) {
|
|
24038
|
+
logger$e.warn("Failed to trim audit log:", err);
|
|
23871
24039
|
}
|
|
23872
24040
|
} catch (err) {
|
|
23873
24041
|
logger$e.error("Failed to write audit log:", err);
|
|
@@ -25436,7 +25604,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25436
25604
|
)
|
|
25437
25605
|
}
|
|
25438
25606
|
},
|
|
25439
|
-
async ({ index, selector, text, label, durationMs, persist
|
|
25607
|
+
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
25440
25608
|
const tab = tabManager.getActiveTab();
|
|
25441
25609
|
if (!tab) return asNoActiveTabResponse();
|
|
25442
25610
|
const normalizedText = normalizeLooseString(text);
|
|
@@ -25450,7 +25618,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25450
25618
|
text: normalizedText,
|
|
25451
25619
|
label,
|
|
25452
25620
|
durationMs,
|
|
25453
|
-
persist
|
|
25621
|
+
persist,
|
|
25454
25622
|
color
|
|
25455
25623
|
},
|
|
25456
25624
|
async () => {
|
|
@@ -25464,7 +25632,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25464
25632
|
durationMs,
|
|
25465
25633
|
color
|
|
25466
25634
|
);
|
|
25467
|
-
if (
|
|
25635
|
+
if (persist && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
|
|
25468
25636
|
const url = normalizeUrl$1(wc.getURL());
|
|
25469
25637
|
addHighlight(
|
|
25470
25638
|
url,
|
|
@@ -27984,6 +28152,25 @@ function registerSystemHandlers(windowState2, sendToRendererViews) {
|
|
|
27984
28152
|
return togglePictureInPicture(tabManager);
|
|
27985
28153
|
});
|
|
27986
28154
|
}
|
|
28155
|
+
const FolderNameSchema = zod.z.string().min(1);
|
|
28156
|
+
const BookmarkUrlSchema = zod.z.string().min(1);
|
|
28157
|
+
const BookmarkIdSchema = zod.z.string().min(1);
|
|
28158
|
+
const OptionalStringSchema = zod.z.string().optional();
|
|
28159
|
+
const OptionalStringArraySchema = zod.z.array(zod.z.string()).optional();
|
|
28160
|
+
const OptionalRecordSchema = zod.z.record(zod.z.string(), zod.z.string()).optional();
|
|
28161
|
+
const OptionalBooleanSchema = zod.z.boolean().optional();
|
|
28162
|
+
const BookmarkUpdateSchema = zod.z.object({
|
|
28163
|
+
title: zod.z.string().optional(),
|
|
28164
|
+
note: zod.z.string().optional(),
|
|
28165
|
+
folderId: zod.z.string().optional(),
|
|
28166
|
+
intent: zod.z.string().optional(),
|
|
28167
|
+
expectedContent: zod.z.string().optional(),
|
|
28168
|
+
keyFields: zod.z.array(zod.z.string()).optional(),
|
|
28169
|
+
agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional()
|
|
28170
|
+
});
|
|
28171
|
+
const ExportOptionsSchema = zod.z.object({
|
|
28172
|
+
includeNotes: zod.z.boolean().optional()
|
|
28173
|
+
}).optional();
|
|
27987
28174
|
function getSafeBookmarkExportName(name) {
|
|
27988
28175
|
const safeName = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
27989
28176
|
return safeName || "folder";
|
|
@@ -27997,23 +28184,33 @@ function registerBookmarkHandlers() {
|
|
|
27997
28184
|
Channels.FOLDER_CREATE,
|
|
27998
28185
|
(event, name, summary) => {
|
|
27999
28186
|
assertTrustedIpcSender(event);
|
|
28187
|
+
const validatedName = parseIpc(FolderNameSchema, name, "name");
|
|
28188
|
+
const validatedSummary = parseIpc(OptionalStringSchema, summary, "summary");
|
|
28000
28189
|
trackBookmarkAction("folder_create");
|
|
28001
|
-
return createFolderWithSummary(
|
|
28190
|
+
return createFolderWithSummary(validatedName, validatedSummary);
|
|
28002
28191
|
}
|
|
28003
28192
|
);
|
|
28004
28193
|
electron.ipcMain.handle(
|
|
28005
28194
|
Channels.BOOKMARK_SAVE,
|
|
28006
28195
|
(event, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
|
|
28007
28196
|
assertTrustedIpcSender(event);
|
|
28197
|
+
const validatedUrl = parseIpc(BookmarkUrlSchema, url, "url");
|
|
28198
|
+
const validatedTitle = parseIpc(zod.z.string(), title, "title");
|
|
28199
|
+
const validatedFolderId = parseIpc(OptionalStringSchema, folderId, "folderId");
|
|
28200
|
+
const validatedNote = parseIpc(OptionalStringSchema, note, "note");
|
|
28201
|
+
const validatedIntent = parseIpc(OptionalStringSchema, intent, "intent");
|
|
28202
|
+
const validatedExpectedContent = parseIpc(OptionalStringSchema, expectedContent, "expectedContent");
|
|
28203
|
+
const validatedKeyFields = parseIpc(OptionalStringArraySchema, keyFields, "keyFields");
|
|
28204
|
+
const validatedAgentHints = parseIpc(OptionalRecordSchema, agentHints, "agentHints");
|
|
28008
28205
|
trackBookmarkAction("save");
|
|
28009
|
-
const result = saveBookmarkWithPolicy(
|
|
28206
|
+
const result = saveBookmarkWithPolicy(validatedUrl, validatedTitle, validatedFolderId, validatedNote, {
|
|
28010
28207
|
onDuplicate: "update",
|
|
28011
28208
|
extra: {
|
|
28012
28209
|
...normalizeBookmarkMetadata({
|
|
28013
|
-
intent,
|
|
28014
|
-
expectedContent,
|
|
28015
|
-
keyFields,
|
|
28016
|
-
agentHints
|
|
28210
|
+
intent: validatedIntent,
|
|
28211
|
+
expectedContent: validatedExpectedContent,
|
|
28212
|
+
keyFields: validatedKeyFields,
|
|
28213
|
+
agentHints: validatedAgentHints
|
|
28017
28214
|
})
|
|
28018
28215
|
}
|
|
28019
28216
|
});
|
|
@@ -28027,19 +28224,23 @@ function registerBookmarkHandlers() {
|
|
|
28027
28224
|
Channels.BOOKMARK_UPDATE,
|
|
28028
28225
|
(event, id, updates) => {
|
|
28029
28226
|
assertTrustedIpcSender(event);
|
|
28227
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28228
|
+
const validatedUpdates = parseIpc(BookmarkUpdateSchema, updates, "updates");
|
|
28030
28229
|
trackBookmarkAction("save");
|
|
28031
|
-
return updateBookmark(
|
|
28230
|
+
return updateBookmark(validatedId, validatedUpdates);
|
|
28032
28231
|
}
|
|
28033
28232
|
);
|
|
28034
28233
|
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (event, id) => {
|
|
28035
28234
|
assertTrustedIpcSender(event);
|
|
28235
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28036
28236
|
trackBookmarkAction("remove");
|
|
28037
|
-
return removeBookmark(
|
|
28237
|
+
return removeBookmark(validatedId);
|
|
28038
28238
|
});
|
|
28039
28239
|
electron.ipcMain.handle(
|
|
28040
28240
|
Channels.BOOKMARKS_EXPORT_HTML,
|
|
28041
28241
|
async (event, options) => {
|
|
28042
28242
|
assertTrustedIpcSender(event);
|
|
28243
|
+
const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
|
|
28043
28244
|
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
28044
28245
|
title: "Export Bookmarks",
|
|
28045
28246
|
defaultPath: "vessel-bookmarks.html",
|
|
@@ -28047,7 +28248,7 @@ function registerBookmarkHandlers() {
|
|
|
28047
28248
|
});
|
|
28048
28249
|
if (canceled || !filePath2) return null;
|
|
28049
28250
|
const content = exportBookmarksHtml({
|
|
28050
|
-
includeNotes:
|
|
28251
|
+
includeNotes: validatedOptions?.includeNotes ?? false
|
|
28051
28252
|
});
|
|
28052
28253
|
await fs.promises.writeFile(filePath2, content, "utf-8");
|
|
28053
28254
|
trackBookmarkAction("export");
|
|
@@ -28077,7 +28278,9 @@ function registerBookmarkHandlers() {
|
|
|
28077
28278
|
Channels.FOLDER_EXPORT_HTML,
|
|
28078
28279
|
async (event, folderId, options) => {
|
|
28079
28280
|
assertTrustedIpcSender(event);
|
|
28080
|
-
const
|
|
28281
|
+
const validatedFolderId = parseIpc(BookmarkIdSchema, folderId, "folderId");
|
|
28282
|
+
const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
|
|
28283
|
+
const folder = getFolder(validatedFolderId);
|
|
28081
28284
|
if (!folder) return null;
|
|
28082
28285
|
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
28083
28286
|
title: `Export ${folder.name}`,
|
|
@@ -28085,8 +28288,8 @@ function registerBookmarkHandlers() {
|
|
|
28085
28288
|
filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
|
|
28086
28289
|
});
|
|
28087
28290
|
if (canceled || !filePath2) return null;
|
|
28088
|
-
const result = exportBookmarkFolderHtml(
|
|
28089
|
-
includeNotes:
|
|
28291
|
+
const result = exportBookmarkFolderHtml(validatedFolderId, {
|
|
28292
|
+
includeNotes: validatedOptions?.includeNotes ?? true
|
|
28090
28293
|
});
|
|
28091
28294
|
if (!result) return null;
|
|
28092
28295
|
await fs.promises.writeFile(filePath2, result.content, "utf-8");
|
|
@@ -28127,14 +28330,23 @@ function registerBookmarkHandlers() {
|
|
|
28127
28330
|
});
|
|
28128
28331
|
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (event, id, deleteContents) => {
|
|
28129
28332
|
assertTrustedIpcSender(event);
|
|
28333
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28334
|
+
const validatedDeleteContents = parseIpc(
|
|
28335
|
+
OptionalBooleanSchema,
|
|
28336
|
+
deleteContents,
|
|
28337
|
+
"deleteContents"
|
|
28338
|
+
);
|
|
28130
28339
|
trackBookmarkAction("folder_remove");
|
|
28131
|
-
return removeFolder(
|
|
28340
|
+
return removeFolder(validatedId, validatedDeleteContents ?? false);
|
|
28132
28341
|
});
|
|
28133
28342
|
electron.ipcMain.handle(
|
|
28134
28343
|
Channels.FOLDER_RENAME,
|
|
28135
28344
|
(event, id, newName, summary) => {
|
|
28136
28345
|
assertTrustedIpcSender(event);
|
|
28137
|
-
|
|
28346
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28347
|
+
const validatedName = parseIpc(FolderNameSchema, newName, "newName");
|
|
28348
|
+
const validatedSummary = parseIpc(OptionalStringSchema, summary, "summary");
|
|
28349
|
+
return renameFolder(validatedId, validatedName, validatedSummary);
|
|
28138
28350
|
}
|
|
28139
28351
|
);
|
|
28140
28352
|
}
|
|
@@ -28399,6 +28611,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
28399
28611
|
return result;
|
|
28400
28612
|
});
|
|
28401
28613
|
}
|
|
28614
|
+
const SessionNameSchema = zod.z.string().min(1);
|
|
28402
28615
|
function registerSessionHandlers(tabManager) {
|
|
28403
28616
|
electron.ipcMain.handle(Channels.SESSION_LIST, (event) => {
|
|
28404
28617
|
assertTrustedIpcSender(event);
|
|
@@ -28406,18 +28619,18 @@ function registerSessionHandlers(tabManager) {
|
|
|
28406
28619
|
});
|
|
28407
28620
|
electron.ipcMain.handle(Channels.SESSION_SAVE, async (event, name) => {
|
|
28408
28621
|
assertTrustedIpcSender(event);
|
|
28409
|
-
|
|
28410
|
-
return await saveNamedSession(tabManager,
|
|
28622
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28623
|
+
return await saveNamedSession(tabManager, validatedName);
|
|
28411
28624
|
});
|
|
28412
28625
|
electron.ipcMain.handle(Channels.SESSION_LOAD, async (event, name) => {
|
|
28413
28626
|
assertTrustedIpcSender(event);
|
|
28414
|
-
|
|
28415
|
-
return await loadNamedSession(tabManager,
|
|
28627
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28628
|
+
return await loadNamedSession(tabManager, validatedName);
|
|
28416
28629
|
});
|
|
28417
28630
|
electron.ipcMain.handle(Channels.SESSION_DELETE, (event, name) => {
|
|
28418
28631
|
assertTrustedIpcSender(event);
|
|
28419
|
-
|
|
28420
|
-
return deleteNamedSession(
|
|
28632
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28633
|
+
return deleteNamedSession(validatedName);
|
|
28421
28634
|
});
|
|
28422
28635
|
}
|
|
28423
28636
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -30507,7 +30720,8 @@ ${progress}
|
|
|
30507
30720
|
const raw = fs$1.readFileSync(getRuntimeStatePath(), "utf-8");
|
|
30508
30721
|
const parsed = JSON.parse(raw);
|
|
30509
30722
|
return sanitizePersistence(parsed);
|
|
30510
|
-
} catch {
|
|
30723
|
+
} catch (err) {
|
|
30724
|
+
logger$3.warn("Failed to load persisted runtime state, starting fresh:", err);
|
|
30511
30725
|
return sanitizePersistence(null);
|
|
30512
30726
|
}
|
|
30513
30727
|
}
|
|
@@ -30766,7 +30980,7 @@ function findIconBase64() {
|
|
|
30766
30980
|
return "";
|
|
30767
30981
|
}
|
|
30768
30982
|
function buildSplashHTML(iconSrc) {
|
|
30769
|
-
const imgTag = iconSrc ? `<img class="logo" src="${iconSrc}" alt="" />` : `<div class="logo-fallback">V</div>`;
|
|
30983
|
+
const imgTag = iconSrc ? `<img class="logo" src="${escapeHtml(iconSrc)}" alt="" />` : `<div class="logo-fallback">V</div>`;
|
|
30770
30984
|
return `<!DOCTYPE html>
|
|
30771
30985
|
<html>
|
|
30772
30986
|
<head>
|
|
@@ -30906,7 +31120,8 @@ function createSplashWindow() {
|
|
|
30906
31120
|
splash.once("closed", () => {
|
|
30907
31121
|
try {
|
|
30908
31122
|
fs$1.rmSync(tmpDir, { recursive: true, force: true });
|
|
30909
|
-
} catch {
|
|
31123
|
+
} catch (err) {
|
|
31124
|
+
logger$1.debug("Failed to clean up splash temp dir:", err);
|
|
30910
31125
|
}
|
|
30911
31126
|
});
|
|
30912
31127
|
fs$1.writeFileSync(tmpPath, html, "utf-8");
|