@quanta-intellect/vessel-browser 0.1.134 → 0.1.137
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 +1492 -1247
- package/out/preload/content-script.js +35 -25
- package/out/renderer/assets/{index-CWy6khUL.css → index-CdUTXTU4.css} +12 -0
- package/out/renderer/assets/{index-3HMjZNhK.js → index-k2scA5OB.js} +33 -11
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
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
|
}
|
|
@@ -21980,429 +22147,72 @@ function registerAgentRuntimeHandlers(runtime2, chromeView, sidebarView, sendToR
|
|
|
21980
22147
|
}
|
|
21981
22148
|
);
|
|
21982
22149
|
}
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
21986
|
-
const PAGE_CONTENT_LIMIT = 6e3;
|
|
21987
|
-
const DEFAULT_LIST_LIMIT = 50;
|
|
21988
|
-
const DEFAULT_SEARCH_LIMIT = 20;
|
|
21989
|
-
function getVaultRoot() {
|
|
21990
|
-
const configured = loadSettings().obsidianVaultPath.trim();
|
|
21991
|
-
if (!configured) {
|
|
21992
|
-
throw new Error(
|
|
21993
|
-
"Obsidian not configured. Set vault path in Vessel settings to use memory capture."
|
|
21994
|
-
);
|
|
21995
|
-
}
|
|
21996
|
-
return path$1.resolve(configured);
|
|
22150
|
+
function asTextResponse$1(text) {
|
|
22151
|
+
return { content: [{ type: "text", text }] };
|
|
21997
22152
|
}
|
|
21998
|
-
|
|
21999
|
-
|
|
22000
|
-
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22153
|
+
const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
|
|
22154
|
+
"devtools_execute_js",
|
|
22155
|
+
"devtools_modify_dom",
|
|
22156
|
+
"devtools_set_storage"
|
|
22157
|
+
]);
|
|
22158
|
+
let stateListener = null;
|
|
22159
|
+
const activityLog = [];
|
|
22160
|
+
const MAX_ACTIVITY_ENTRIES = 100;
|
|
22161
|
+
let activityCounter = 0;
|
|
22162
|
+
function setDevToolsPanelListener(listener) {
|
|
22163
|
+
stateListener = listener;
|
|
22005
22164
|
}
|
|
22006
|
-
function
|
|
22007
|
-
const
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
|
|
22012
|
-
|
|
22013
|
-
|
|
22014
|
-
throw new Error("Vault note folders cannot traverse outside the vault.");
|
|
22015
|
-
}
|
|
22016
|
-
return segments.join(path$1.sep);
|
|
22165
|
+
function getDevToolsPanelState(tabId) {
|
|
22166
|
+
const session = tabId ? getSession(tabId) : void 0;
|
|
22167
|
+
return {
|
|
22168
|
+
console: session?.getConsoleLogs() ?? [],
|
|
22169
|
+
network: session?.getNetworkLog() ?? [],
|
|
22170
|
+
errors: session?.getErrors() ?? [],
|
|
22171
|
+
activity: activityLog
|
|
22172
|
+
};
|
|
22017
22173
|
}
|
|
22018
|
-
function
|
|
22019
|
-
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22174
|
+
function broadcastState(tabManager) {
|
|
22175
|
+
if (!stateListener) return;
|
|
22176
|
+
const tabId = tabManager.getActiveTabId();
|
|
22177
|
+
stateListener(getDevToolsPanelState(tabId));
|
|
22178
|
+
}
|
|
22179
|
+
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
22180
|
+
const activityEntry = {
|
|
22181
|
+
id: ++activityCounter,
|
|
22182
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22183
|
+
tool: name,
|
|
22184
|
+
args: JSON.stringify(args).slice(0, 200),
|
|
22185
|
+
result: "",
|
|
22186
|
+
durationMs: 0,
|
|
22187
|
+
status: "running"
|
|
22188
|
+
};
|
|
22189
|
+
activityLog.push(activityEntry);
|
|
22190
|
+
if (activityLog.length > MAX_ACTIVITY_ENTRIES) {
|
|
22191
|
+
activityLog.splice(0, activityLog.length - MAX_ACTIVITY_ENTRIES);
|
|
22025
22192
|
}
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22034
|
-
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
|
|
22038
|
-
|
|
22039
|
-
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
|
|
22043
|
-
|
|
22044
|
-
|
|
22045
|
-
|
|
22046
|
-
|
|
22047
|
-
|
|
22048
|
-
|
|
22049
|
-
}
|
|
22050
|
-
lines.push("---", "");
|
|
22051
|
-
return lines.join("\n");
|
|
22052
|
-
}
|
|
22053
|
-
function slugify(value) {
|
|
22054
|
-
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
22055
|
-
return normalized || "note";
|
|
22056
|
-
}
|
|
22057
|
-
function buildUniqueNotePath(dir, title) {
|
|
22058
|
-
const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
22059
|
-
const slug = slugify(title);
|
|
22060
|
-
const base = `${datePrefix}-${slug}`;
|
|
22061
|
-
let candidate = `${base}.md`;
|
|
22062
|
-
let counter = 2;
|
|
22063
|
-
while (fs$1.existsSync(path$1.join(dir, candidate))) {
|
|
22064
|
-
candidate = `${base}-${counter}.md`;
|
|
22065
|
-
counter += 1;
|
|
22066
|
-
}
|
|
22067
|
-
return path$1.join(dir, candidate);
|
|
22068
|
-
}
|
|
22069
|
-
function trimContent(content, limit = PAGE_CONTENT_LIMIT) {
|
|
22070
|
-
const cleaned = content.trim();
|
|
22071
|
-
if (cleaned.length <= limit) return cleaned;
|
|
22072
|
-
return `${cleaned.slice(0, limit)}
|
|
22073
|
-
|
|
22074
|
-
[Truncated]`;
|
|
22075
|
-
}
|
|
22076
|
-
function parseFrontmatter(content) {
|
|
22077
|
-
if (!content.startsWith("---\n")) {
|
|
22078
|
-
return { body: content, tags: [] };
|
|
22079
|
-
}
|
|
22080
|
-
const closingIndex = content.indexOf("\n---\n", 4);
|
|
22081
|
-
if (closingIndex === -1) {
|
|
22082
|
-
return { body: content, tags: [] };
|
|
22083
|
-
}
|
|
22084
|
-
const raw = content.slice(4, closingIndex);
|
|
22085
|
-
const body = content.slice(closingIndex + 5);
|
|
22086
|
-
const lines = raw.split("\n");
|
|
22087
|
-
const result = { tags: [] };
|
|
22088
|
-
let activeArrayKey = "";
|
|
22089
|
-
for (const line of lines) {
|
|
22090
|
-
const trimmed = line.trim();
|
|
22091
|
-
if (!trimmed) continue;
|
|
22092
|
-
if (trimmed.startsWith("- ") && activeArrayKey === "tags") {
|
|
22093
|
-
result.tags.push(
|
|
22094
|
-
trimmed.slice(2).trim().replace(/^["']|["']$/g, "")
|
|
22095
|
-
);
|
|
22096
|
-
continue;
|
|
22097
|
-
}
|
|
22098
|
-
activeArrayKey = "";
|
|
22099
|
-
const separatorIndex = trimmed.indexOf(":");
|
|
22100
|
-
if (separatorIndex === -1) continue;
|
|
22101
|
-
const key2 = trimmed.slice(0, separatorIndex).trim();
|
|
22102
|
-
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
22103
|
-
if (key2 === "title" && value) {
|
|
22104
|
-
result.title = value.replace(/^["']|["']$/g, "");
|
|
22105
|
-
} else if (key2 === "tags") {
|
|
22106
|
-
activeArrayKey = "tags";
|
|
22107
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
22108
|
-
const inline = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
22109
|
-
result.tags.push(...inline);
|
|
22110
|
-
activeArrayKey = "";
|
|
22111
|
-
}
|
|
22112
|
-
}
|
|
22113
|
-
}
|
|
22114
|
-
return { body, title: result.title, tags: result.tags };
|
|
22115
|
-
}
|
|
22116
|
-
function collectMarkdownFiles(dir) {
|
|
22117
|
-
const entries = fs$1.readdirSync(dir, { withFileTypes: true });
|
|
22118
|
-
const files = [];
|
|
22119
|
-
for (const entry of entries) {
|
|
22120
|
-
const absolutePath = path$1.join(dir, entry.name);
|
|
22121
|
-
if (entry.isDirectory()) {
|
|
22122
|
-
files.push(...collectMarkdownFiles(absolutePath));
|
|
22123
|
-
continue;
|
|
22124
|
-
}
|
|
22125
|
-
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
22126
|
-
files.push(absolutePath);
|
|
22127
|
-
}
|
|
22128
|
-
}
|
|
22129
|
-
return files;
|
|
22130
|
-
}
|
|
22131
|
-
function toSummary(absolutePath, vaultRoot) {
|
|
22132
|
-
const stats = fs$1.statSync(absolutePath);
|
|
22133
|
-
const relativePath = path$1.relative(vaultRoot, absolutePath).split(path$1.sep).join("/");
|
|
22134
|
-
const raw = fs$1.readFileSync(absolutePath, "utf-8");
|
|
22135
|
-
const parsed = parseFrontmatter(raw);
|
|
22136
|
-
const headingMatch = parsed.body.match(/^#\s+(.+)$/m);
|
|
22137
|
-
const title = parsed.title || headingMatch?.[1]?.trim() || path$1.basename(absolutePath, ".md");
|
|
22138
|
-
return {
|
|
22139
|
-
title,
|
|
22140
|
-
absolutePath,
|
|
22141
|
-
relativePath,
|
|
22142
|
-
modifiedAt: stats.mtime.toISOString(),
|
|
22143
|
-
tags: parsed.tags
|
|
22144
|
-
};
|
|
22145
|
-
}
|
|
22146
|
-
function renderBookmarkLinkBlock(bookmark, note) {
|
|
22147
|
-
const lines = [
|
|
22148
|
-
"## Linked Bookmark",
|
|
22149
|
-
"",
|
|
22150
|
-
`- Bookmark ID: \`${bookmark.id}\``,
|
|
22151
|
-
`- Title: ${bookmark.title || bookmark.url}`,
|
|
22152
|
-
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
22153
|
-
`- Saved At: ${bookmark.savedAt}`
|
|
22154
|
-
];
|
|
22155
|
-
if (note?.trim()) {
|
|
22156
|
-
lines.push("", "### Context", "", note.trim());
|
|
22157
|
-
}
|
|
22158
|
-
return `${lines.join("\n")}
|
|
22159
|
-
`;
|
|
22160
|
-
}
|
|
22161
|
-
function writeMemoryNote({
|
|
22162
|
-
title,
|
|
22163
|
-
body,
|
|
22164
|
-
folder,
|
|
22165
|
-
tags = [],
|
|
22166
|
-
frontmatter = {}
|
|
22167
|
-
}) {
|
|
22168
|
-
const vaultRoot = getVaultRoot();
|
|
22169
|
-
const relativeFolder = normalizeFolder(folder, DEFAULT_NOTE_FOLDER);
|
|
22170
|
-
const targetDir = path$1.join(vaultRoot, relativeFolder);
|
|
22171
|
-
fs$1.mkdirSync(targetDir, { recursive: true });
|
|
22172
|
-
const absolutePath = buildUniqueNotePath(targetDir, title);
|
|
22173
|
-
const relativePath = path$1.relative(vaultRoot, absolutePath);
|
|
22174
|
-
const content = [
|
|
22175
|
-
renderFrontmatter({
|
|
22176
|
-
title,
|
|
22177
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22178
|
-
tags,
|
|
22179
|
-
...frontmatter
|
|
22180
|
-
}),
|
|
22181
|
-
body.trim(),
|
|
22182
|
-
""
|
|
22183
|
-
].join("\n");
|
|
22184
|
-
fs$1.writeFileSync(absolutePath, content, "utf-8");
|
|
22185
|
-
return {
|
|
22186
|
-
title,
|
|
22187
|
-
absolutePath,
|
|
22188
|
-
relativePath: relativePath.split(path$1.sep).join("/")
|
|
22189
|
-
};
|
|
22190
|
-
}
|
|
22191
|
-
function appendToMemoryNote({
|
|
22192
|
-
notePath,
|
|
22193
|
-
content,
|
|
22194
|
-
heading
|
|
22195
|
-
}) {
|
|
22196
|
-
const vaultRoot = getVaultRoot();
|
|
22197
|
-
const relativePath = normalizeNotePath(notePath);
|
|
22198
|
-
const absolutePath = assertInsideVault(
|
|
22199
|
-
path$1.join(vaultRoot, relativePath),
|
|
22200
|
-
vaultRoot
|
|
22201
|
-
);
|
|
22202
|
-
if (!fs$1.existsSync(absolutePath)) {
|
|
22203
|
-
throw new Error(
|
|
22204
|
-
`Memory note not found: ${relativePath.split(path$1.sep).join("/")}`
|
|
22205
|
-
);
|
|
22206
|
-
}
|
|
22207
|
-
const current = fs$1.readFileSync(absolutePath, "utf-8").trimEnd();
|
|
22208
|
-
const nextParts = [current, ""];
|
|
22209
|
-
if (heading?.trim()) {
|
|
22210
|
-
nextParts.push(`## ${heading.trim()}`, "");
|
|
22211
|
-
}
|
|
22212
|
-
nextParts.push(content.trim(), "");
|
|
22213
|
-
fs$1.writeFileSync(absolutePath, nextParts.join("\n"), "utf-8");
|
|
22214
|
-
return {
|
|
22215
|
-
title: path$1.basename(absolutePath, ".md"),
|
|
22216
|
-
absolutePath,
|
|
22217
|
-
relativePath: relativePath.split(path$1.sep).join("/")
|
|
22218
|
-
};
|
|
22219
|
-
}
|
|
22220
|
-
function listMemoryNotes({
|
|
22221
|
-
folder,
|
|
22222
|
-
limit = DEFAULT_LIST_LIMIT
|
|
22223
|
-
} = {}) {
|
|
22224
|
-
const vaultRoot = getVaultRoot();
|
|
22225
|
-
const relativeFolder = normalizeFolder(folder, "");
|
|
22226
|
-
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
22227
|
-
if (!fs$1.existsSync(targetDir)) {
|
|
22228
|
-
return [];
|
|
22229
|
-
}
|
|
22230
|
-
return collectMarkdownFiles(targetDir).map((absolutePath) => toSummary(absolutePath, vaultRoot)).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
22231
|
-
}
|
|
22232
|
-
function searchMemoryNotes({
|
|
22233
|
-
query,
|
|
22234
|
-
folder,
|
|
22235
|
-
tags = [],
|
|
22236
|
-
limit = DEFAULT_SEARCH_LIMIT
|
|
22237
|
-
}) {
|
|
22238
|
-
const loweredQuery = query.trim().toLowerCase();
|
|
22239
|
-
if (!loweredQuery) {
|
|
22240
|
-
throw new Error("A non-empty memory search query is required.");
|
|
22241
|
-
}
|
|
22242
|
-
const vaultRoot = getVaultRoot();
|
|
22243
|
-
const relativeFolder = normalizeFolder(folder, "");
|
|
22244
|
-
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
22245
|
-
if (!fs$1.existsSync(targetDir)) {
|
|
22246
|
-
return [];
|
|
22247
|
-
}
|
|
22248
|
-
const loweredTags = tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean);
|
|
22249
|
-
return collectMarkdownFiles(targetDir).map((absolutePath) => {
|
|
22250
|
-
const raw = fs$1.readFileSync(absolutePath, "utf-8");
|
|
22251
|
-
const parsed = parseFrontmatter(raw);
|
|
22252
|
-
const summary = toSummary(absolutePath, vaultRoot);
|
|
22253
|
-
const haystack = `${summary.title}
|
|
22254
|
-
${summary.relativePath}
|
|
22255
|
-
${parsed.body}`.toLowerCase();
|
|
22256
|
-
const hasQuery = haystack.includes(loweredQuery);
|
|
22257
|
-
const hasTags = loweredTags.length === 0 || loweredTags.every(
|
|
22258
|
-
(tag) => summary.tags.some((noteTag) => noteTag.toLowerCase() === tag)
|
|
22259
|
-
);
|
|
22260
|
-
return hasQuery && hasTags ? summary : null;
|
|
22261
|
-
}).filter((item) => item !== null).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
22262
|
-
}
|
|
22263
|
-
function capturePageToVault({
|
|
22264
|
-
page,
|
|
22265
|
-
title,
|
|
22266
|
-
folder,
|
|
22267
|
-
summary,
|
|
22268
|
-
note,
|
|
22269
|
-
tags = []
|
|
22270
|
-
}) {
|
|
22271
|
-
const noteTitle = title?.trim() || page.title.trim() || page.url;
|
|
22272
|
-
const bodyLines = [
|
|
22273
|
-
`# ${noteTitle}`,
|
|
22274
|
-
"",
|
|
22275
|
-
`Source: [${page.title || page.url}](${page.url})`,
|
|
22276
|
-
`Captured: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
22277
|
-
];
|
|
22278
|
-
if (page.byline) {
|
|
22279
|
-
bodyLines.push(`Byline: ${page.byline}`);
|
|
22280
|
-
}
|
|
22281
|
-
bodyLines.push("");
|
|
22282
|
-
if (summary?.trim()) {
|
|
22283
|
-
bodyLines.push("## Summary", "", summary.trim(), "");
|
|
22284
|
-
}
|
|
22285
|
-
if (note?.trim()) {
|
|
22286
|
-
bodyLines.push("## Research Note", "", note.trim(), "");
|
|
22287
|
-
}
|
|
22288
|
-
if (page.excerpt.trim()) {
|
|
22289
|
-
bodyLines.push("## Excerpt", "", page.excerpt.trim(), "");
|
|
22290
|
-
}
|
|
22291
|
-
const snapshot2 = trimContent(page.content);
|
|
22292
|
-
if (snapshot2) {
|
|
22293
|
-
bodyLines.push("## Page Snapshot", "", snapshot2, "");
|
|
22294
|
-
}
|
|
22295
|
-
return writeMemoryNote({
|
|
22296
|
-
title: noteTitle,
|
|
22297
|
-
body: bodyLines.join("\n"),
|
|
22298
|
-
folder: folder || DEFAULT_PAGE_FOLDER,
|
|
22299
|
-
tags,
|
|
22300
|
-
frontmatter: {
|
|
22301
|
-
source_url: page.url,
|
|
22302
|
-
source_title: page.title || page.url
|
|
22303
|
-
}
|
|
22304
|
-
});
|
|
22305
|
-
}
|
|
22306
|
-
function linkBookmarkToMemory({
|
|
22307
|
-
bookmark,
|
|
22308
|
-
notePath,
|
|
22309
|
-
title,
|
|
22310
|
-
folder,
|
|
22311
|
-
note,
|
|
22312
|
-
tags = []
|
|
22313
|
-
}) {
|
|
22314
|
-
if (notePath?.trim()) {
|
|
22315
|
-
return appendToMemoryNote({
|
|
22316
|
-
notePath,
|
|
22317
|
-
heading: "Linked Bookmark",
|
|
22318
|
-
content: [
|
|
22319
|
-
`- Bookmark ID: \`${bookmark.id}\``,
|
|
22320
|
-
`- Title: ${bookmark.title || bookmark.url}`,
|
|
22321
|
-
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
22322
|
-
`- Saved At: ${bookmark.savedAt}`,
|
|
22323
|
-
note?.trim() ? `- Note: ${note.trim()}` : ""
|
|
22324
|
-
].filter(Boolean).join("\n")
|
|
22325
|
-
});
|
|
22326
|
-
}
|
|
22327
|
-
const noteTitle = title?.trim() || bookmark.title || bookmark.url;
|
|
22328
|
-
return writeMemoryNote({
|
|
22329
|
-
title: noteTitle,
|
|
22330
|
-
body: renderBookmarkLinkBlock(bookmark, note),
|
|
22331
|
-
folder: folder || DEFAULT_BOOKMARK_FOLDER,
|
|
22332
|
-
tags,
|
|
22333
|
-
frontmatter: {
|
|
22334
|
-
bookmark_id: bookmark.id,
|
|
22335
|
-
source_url: bookmark.url,
|
|
22336
|
-
source_title: bookmark.title || bookmark.url
|
|
22337
|
-
}
|
|
22338
|
-
});
|
|
22339
|
-
}
|
|
22340
|
-
function asTextResponse$1(text) {
|
|
22341
|
-
return { content: [{ type: "text", text }] };
|
|
22342
|
-
}
|
|
22343
|
-
const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
|
|
22344
|
-
"devtools_execute_js",
|
|
22345
|
-
"devtools_modify_dom",
|
|
22346
|
-
"devtools_set_storage"
|
|
22347
|
-
]);
|
|
22348
|
-
let stateListener = null;
|
|
22349
|
-
const activityLog = [];
|
|
22350
|
-
const MAX_ACTIVITY_ENTRIES = 100;
|
|
22351
|
-
let activityCounter = 0;
|
|
22352
|
-
function setDevToolsPanelListener(listener) {
|
|
22353
|
-
stateListener = listener;
|
|
22354
|
-
}
|
|
22355
|
-
function getDevToolsPanelState(tabId) {
|
|
22356
|
-
const session = tabId ? getSession(tabId) : void 0;
|
|
22357
|
-
return {
|
|
22358
|
-
console: session?.getConsoleLogs() ?? [],
|
|
22359
|
-
network: session?.getNetworkLog() ?? [],
|
|
22360
|
-
errors: session?.getErrors() ?? [],
|
|
22361
|
-
activity: activityLog
|
|
22362
|
-
};
|
|
22363
|
-
}
|
|
22364
|
-
function broadcastState(tabManager) {
|
|
22365
|
-
if (!stateListener) return;
|
|
22366
|
-
const tabId = tabManager.getActiveTabId();
|
|
22367
|
-
stateListener(getDevToolsPanelState(tabId));
|
|
22368
|
-
}
|
|
22369
|
-
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
22370
|
-
const activityEntry = {
|
|
22371
|
-
id: ++activityCounter,
|
|
22372
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22373
|
-
tool: name,
|
|
22374
|
-
args: JSON.stringify(args).slice(0, 200),
|
|
22375
|
-
result: "",
|
|
22376
|
-
durationMs: 0,
|
|
22377
|
-
status: "running"
|
|
22378
|
-
};
|
|
22379
|
-
activityLog.push(activityEntry);
|
|
22380
|
-
if (activityLog.length > MAX_ACTIVITY_ENTRIES) {
|
|
22381
|
-
activityLog.splice(0, activityLog.length - MAX_ACTIVITY_ENTRIES);
|
|
22382
|
-
}
|
|
22383
|
-
broadcastState(tabManager);
|
|
22384
|
-
const startTime = Date.now();
|
|
22385
|
-
try {
|
|
22386
|
-
const result = await runtime2.runControlledAction({
|
|
22387
|
-
source: "mcp",
|
|
22388
|
-
name,
|
|
22389
|
-
args,
|
|
22390
|
-
tabId: tabManager.getActiveTabId(),
|
|
22391
|
-
dangerous: DANGEROUS_DEVTOOLS_ACTIONS.has(name),
|
|
22392
|
-
executor
|
|
22393
|
-
});
|
|
22394
|
-
activityEntry.status = "completed";
|
|
22395
|
-
activityEntry.result = result.slice(0, 200);
|
|
22396
|
-
activityEntry.durationMs = Date.now() - startTime;
|
|
22397
|
-
broadcastState(tabManager);
|
|
22398
|
-
return asTextResponse$1(result);
|
|
22399
|
-
} catch (error) {
|
|
22400
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
22401
|
-
activityEntry.status = "failed";
|
|
22402
|
-
activityEntry.result = message.slice(0, 200);
|
|
22403
|
-
activityEntry.durationMs = Date.now() - startTime;
|
|
22404
|
-
broadcastState(tabManager);
|
|
22405
|
-
return asTextResponse$1(`Error: ${message}`);
|
|
22193
|
+
broadcastState(tabManager);
|
|
22194
|
+
const startTime = Date.now();
|
|
22195
|
+
try {
|
|
22196
|
+
const result = await runtime2.runControlledAction({
|
|
22197
|
+
source: "mcp",
|
|
22198
|
+
name,
|
|
22199
|
+
args,
|
|
22200
|
+
tabId: tabManager.getActiveTabId(),
|
|
22201
|
+
dangerous: DANGEROUS_DEVTOOLS_ACTIONS.has(name),
|
|
22202
|
+
executor
|
|
22203
|
+
});
|
|
22204
|
+
activityEntry.status = "completed";
|
|
22205
|
+
activityEntry.result = result.slice(0, 200);
|
|
22206
|
+
activityEntry.durationMs = Date.now() - startTime;
|
|
22207
|
+
broadcastState(tabManager);
|
|
22208
|
+
return asTextResponse$1(result);
|
|
22209
|
+
} catch (error) {
|
|
22210
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
22211
|
+
activityEntry.status = "failed";
|
|
22212
|
+
activityEntry.result = message.slice(0, 200);
|
|
22213
|
+
activityEntry.durationMs = Date.now() - startTime;
|
|
22214
|
+
broadcastState(tabManager);
|
|
22215
|
+
return asTextResponse$1(`Error: ${message}`);
|
|
22406
22216
|
}
|
|
22407
22217
|
}
|
|
22408
22218
|
function registerDevTools(server, tabManager, runtime2) {
|
|
@@ -22441,334 +22251,709 @@ function registerDevTools(server, tabManager, runtime2) {
|
|
|
22441
22251
|
title: "DevTools: Clear Console Logs",
|
|
22442
22252
|
description: "Clear the captured console log buffer for the active tab."
|
|
22443
22253
|
},
|
|
22444
|
-
async () => {
|
|
22254
|
+
async () => {
|
|
22255
|
+
return withDevToolsAction(
|
|
22256
|
+
runtime2,
|
|
22257
|
+
tabManager,
|
|
22258
|
+
"devtools_console_clear",
|
|
22259
|
+
{},
|
|
22260
|
+
async () => {
|
|
22261
|
+
const session = getOrCreateSession(tabManager);
|
|
22262
|
+
const count = session.clearConsoleLogs();
|
|
22263
|
+
return `Cleared ${count} console entries.`;
|
|
22264
|
+
}
|
|
22265
|
+
);
|
|
22266
|
+
}
|
|
22267
|
+
);
|
|
22268
|
+
server.registerTool(
|
|
22269
|
+
"vessel_devtools_network_log",
|
|
22270
|
+
{
|
|
22271
|
+
title: "DevTools: Get Network Log",
|
|
22272
|
+
description: "Get captured network requests/responses from the active tab. Returns method, URL, status, timing, headers, and size. Automatically starts capturing on first use.",
|
|
22273
|
+
inputSchema: {
|
|
22274
|
+
url_pattern: zod.z.string().optional().describe("Filter by URL pattern (regex or substring match)"),
|
|
22275
|
+
method: zod.z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)"),
|
|
22276
|
+
status_min: zod.z.number().optional().describe("Minimum HTTP status code (e.g., 400 for errors)"),
|
|
22277
|
+
status_max: zod.z.number().optional().describe("Maximum HTTP status code"),
|
|
22278
|
+
limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
|
|
22279
|
+
}
|
|
22280
|
+
},
|
|
22281
|
+
async ({ url_pattern, method, status_min, status_max, limit }) => {
|
|
22282
|
+
return withDevToolsAction(
|
|
22283
|
+
runtime2,
|
|
22284
|
+
tabManager,
|
|
22285
|
+
"devtools_network_log",
|
|
22286
|
+
{ url_pattern, method, status_min, status_max, limit },
|
|
22287
|
+
async () => {
|
|
22288
|
+
const session = getOrCreateSession(tabManager);
|
|
22289
|
+
await session.ensureNetworkDomain();
|
|
22290
|
+
const entries = session.getNetworkLog({
|
|
22291
|
+
urlPattern: url_pattern,
|
|
22292
|
+
method,
|
|
22293
|
+
statusRange: status_min != null || status_max != null ? { min: status_min, max: status_max } : void 0,
|
|
22294
|
+
limit
|
|
22295
|
+
});
|
|
22296
|
+
if (entries.length === 0) {
|
|
22297
|
+
return "No network requests captured yet. Network monitoring is now active — new requests will be captured as they occur.";
|
|
22298
|
+
}
|
|
22299
|
+
return JSON.stringify(entries, null, 2);
|
|
22300
|
+
}
|
|
22301
|
+
);
|
|
22302
|
+
}
|
|
22303
|
+
);
|
|
22304
|
+
server.registerTool(
|
|
22305
|
+
"vessel_devtools_network_response_body",
|
|
22306
|
+
{
|
|
22307
|
+
title: "DevTools: Get Network Response Body",
|
|
22308
|
+
description: "Get the response body for a specific network request by its request ID. Use vessel_devtools_network_log first to find the request ID.",
|
|
22309
|
+
inputSchema: {
|
|
22310
|
+
request_id: zod.z.string().describe("The requestId from a network log entry")
|
|
22311
|
+
}
|
|
22312
|
+
},
|
|
22313
|
+
async ({ request_id }) => {
|
|
22314
|
+
return withDevToolsAction(
|
|
22315
|
+
runtime2,
|
|
22316
|
+
tabManager,
|
|
22317
|
+
"devtools_network_response_body",
|
|
22318
|
+
{ request_id },
|
|
22319
|
+
async () => {
|
|
22320
|
+
const session = getOrCreateSession(tabManager);
|
|
22321
|
+
const result = await session.getNetworkResponseBody(request_id);
|
|
22322
|
+
if ("error" in result) return `Error: ${result.error}`;
|
|
22323
|
+
if (result.base64Encoded) {
|
|
22324
|
+
return `[Base64-encoded body, ${result.body.length} chars. Likely binary content.]`;
|
|
22325
|
+
}
|
|
22326
|
+
const body = result.body;
|
|
22327
|
+
return body.length > 2e4 ? body.slice(0, 2e4) + `
|
|
22328
|
+
... [truncated, total ${body.length} chars]` : body;
|
|
22329
|
+
}
|
|
22330
|
+
);
|
|
22331
|
+
}
|
|
22332
|
+
);
|
|
22333
|
+
server.registerTool(
|
|
22334
|
+
"vessel_devtools_network_clear",
|
|
22335
|
+
{
|
|
22336
|
+
title: "DevTools: Clear Network Log",
|
|
22337
|
+
description: "Clear the captured network request buffer for the active tab."
|
|
22338
|
+
},
|
|
22339
|
+
async () => {
|
|
22340
|
+
return withDevToolsAction(
|
|
22341
|
+
runtime2,
|
|
22342
|
+
tabManager,
|
|
22343
|
+
"devtools_network_clear",
|
|
22344
|
+
{},
|
|
22345
|
+
async () => {
|
|
22346
|
+
const session = getOrCreateSession(tabManager);
|
|
22347
|
+
const count = session.clearNetworkLog();
|
|
22348
|
+
return `Cleared ${count} network entries.`;
|
|
22349
|
+
}
|
|
22350
|
+
);
|
|
22351
|
+
}
|
|
22352
|
+
);
|
|
22353
|
+
server.registerTool(
|
|
22354
|
+
"vessel_devtools_query_dom",
|
|
22355
|
+
{
|
|
22356
|
+
title: "DevTools: Query DOM",
|
|
22357
|
+
description: "Query the DOM of the active tab using a CSS selector. Returns matching elements with their attributes, node type, and optionally their HTML content. Limited to 50 results.",
|
|
22358
|
+
inputSchema: {
|
|
22359
|
+
selector: zod.z.string().describe("CSS selector to query"),
|
|
22360
|
+
include_html: zod.z.boolean().optional().describe("Include outerHTML of matched elements (default: false)")
|
|
22361
|
+
}
|
|
22362
|
+
},
|
|
22363
|
+
async ({ selector, include_html }) => {
|
|
22364
|
+
return withDevToolsAction(
|
|
22365
|
+
runtime2,
|
|
22366
|
+
tabManager,
|
|
22367
|
+
"devtools_query_dom",
|
|
22368
|
+
{ selector, include_html },
|
|
22369
|
+
async () => {
|
|
22370
|
+
const session = getOrCreateSession(tabManager);
|
|
22371
|
+
const nodes = await session.queryDom(selector, {
|
|
22372
|
+
includeHtml: include_html
|
|
22373
|
+
});
|
|
22374
|
+
if (nodes.length === 0) {
|
|
22375
|
+
return `No elements found matching "${selector}"`;
|
|
22376
|
+
}
|
|
22377
|
+
return JSON.stringify(nodes, null, 2);
|
|
22378
|
+
}
|
|
22379
|
+
);
|
|
22380
|
+
}
|
|
22381
|
+
);
|
|
22382
|
+
server.registerTool(
|
|
22383
|
+
"vessel_devtools_get_styles",
|
|
22384
|
+
{
|
|
22385
|
+
title: "DevTools: Get Computed Styles",
|
|
22386
|
+
description: "Get computed CSS styles for an element matching a CSS selector. Optionally filter to specific properties.",
|
|
22387
|
+
inputSchema: {
|
|
22388
|
+
selector: zod.z.string().describe("CSS selector for the target element"),
|
|
22389
|
+
properties: zod.z.array(zod.z.string()).optional().describe(
|
|
22390
|
+
'Specific CSS properties to return (e.g., ["color", "font-size", "display"]). Omit for all properties.'
|
|
22391
|
+
)
|
|
22392
|
+
}
|
|
22393
|
+
},
|
|
22394
|
+
async ({ selector, properties }) => {
|
|
22395
|
+
return withDevToolsAction(
|
|
22396
|
+
runtime2,
|
|
22397
|
+
tabManager,
|
|
22398
|
+
"devtools_get_styles",
|
|
22399
|
+
{ selector, properties },
|
|
22400
|
+
async () => {
|
|
22401
|
+
const session = getOrCreateSession(tabManager);
|
|
22402
|
+
const styles = await session.getComputedStyles(selector, properties);
|
|
22403
|
+
if (styles.length === 0) {
|
|
22404
|
+
return `No computed styles found for "${selector}"`;
|
|
22405
|
+
}
|
|
22406
|
+
return JSON.stringify(styles, null, 2);
|
|
22407
|
+
}
|
|
22408
|
+
);
|
|
22409
|
+
}
|
|
22410
|
+
);
|
|
22411
|
+
server.registerTool(
|
|
22412
|
+
"vessel_devtools_modify_dom",
|
|
22413
|
+
{
|
|
22414
|
+
title: "DevTools: Modify DOM Attribute",
|
|
22415
|
+
description: "Set or remove an HTML attribute on an element matching a CSS selector. This is a dangerous action that modifies the page.",
|
|
22416
|
+
inputSchema: {
|
|
22417
|
+
selector: zod.z.string().describe("CSS selector for the target element"),
|
|
22418
|
+
attribute: zod.z.string().describe("Attribute name to set or remove"),
|
|
22419
|
+
value: zod.z.string().nullable().describe("Attribute value to set, or null to remove the attribute")
|
|
22420
|
+
}
|
|
22421
|
+
},
|
|
22422
|
+
async ({ selector, attribute, value }) => {
|
|
22423
|
+
return withDevToolsAction(
|
|
22424
|
+
runtime2,
|
|
22425
|
+
tabManager,
|
|
22426
|
+
"devtools_modify_dom",
|
|
22427
|
+
{ selector, attribute, value },
|
|
22428
|
+
async () => {
|
|
22429
|
+
const session = getOrCreateSession(tabManager);
|
|
22430
|
+
return session.modifyDomAttribute(selector, attribute, value);
|
|
22431
|
+
}
|
|
22432
|
+
);
|
|
22433
|
+
}
|
|
22434
|
+
);
|
|
22435
|
+
server.registerTool(
|
|
22436
|
+
"vessel_devtools_execute_js",
|
|
22437
|
+
{
|
|
22438
|
+
title: "DevTools: Execute JavaScript",
|
|
22439
|
+
description: "Execute a JavaScript expression in the context of the active tab's page via the Runtime.evaluate CDP method. Supports async/await. This is a dangerous action — it can modify page state.",
|
|
22440
|
+
inputSchema: {
|
|
22441
|
+
expression: zod.z.string().describe("JavaScript expression to evaluate in the page context")
|
|
22442
|
+
}
|
|
22443
|
+
},
|
|
22444
|
+
async ({ expression }) => {
|
|
22445
22445
|
return withDevToolsAction(
|
|
22446
22446
|
runtime2,
|
|
22447
22447
|
tabManager,
|
|
22448
|
-
"
|
|
22449
|
-
{},
|
|
22448
|
+
"devtools_execute_js",
|
|
22449
|
+
{ expression: expression.slice(0, 200) },
|
|
22450
22450
|
async () => {
|
|
22451
22451
|
const session = getOrCreateSession(tabManager);
|
|
22452
|
-
const
|
|
22453
|
-
|
|
22452
|
+
const result = await session.executeJs(expression);
|
|
22453
|
+
const parts = [`[${result.type}] ${result.result}`];
|
|
22454
|
+
if (result.exceptionDetails) {
|
|
22455
|
+
parts.push(`
|
|
22456
|
+
Exception: ${result.exceptionDetails}`);
|
|
22457
|
+
}
|
|
22458
|
+
return parts.join("");
|
|
22454
22459
|
}
|
|
22455
22460
|
);
|
|
22456
22461
|
}
|
|
22457
22462
|
);
|
|
22458
22463
|
server.registerTool(
|
|
22459
|
-
"
|
|
22464
|
+
"vessel_devtools_get_storage",
|
|
22460
22465
|
{
|
|
22461
|
-
title: "DevTools: Get
|
|
22462
|
-
description: "
|
|
22466
|
+
title: "DevTools: Get Storage",
|
|
22467
|
+
description: "Read browser storage for the active tab's origin. Supports localStorage, sessionStorage, cookies, and IndexedDB database listing.",
|
|
22463
22468
|
inputSchema: {
|
|
22464
|
-
|
|
22465
|
-
method: zod.z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)"),
|
|
22466
|
-
status_min: zod.z.number().optional().describe("Minimum HTTP status code (e.g., 400 for errors)"),
|
|
22467
|
-
status_max: zod.z.number().optional().describe("Maximum HTTP status code"),
|
|
22468
|
-
limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
|
|
22469
|
+
type: zod.z.enum(["localStorage", "sessionStorage", "cookie", "indexedDB"]).describe("Storage type to read")
|
|
22469
22470
|
}
|
|
22470
22471
|
},
|
|
22471
|
-
async ({
|
|
22472
|
+
async ({ type }) => {
|
|
22472
22473
|
return withDevToolsAction(
|
|
22473
22474
|
runtime2,
|
|
22474
22475
|
tabManager,
|
|
22475
|
-
"
|
|
22476
|
-
{
|
|
22476
|
+
"devtools_get_storage",
|
|
22477
|
+
{ type },
|
|
22477
22478
|
async () => {
|
|
22478
22479
|
const session = getOrCreateSession(tabManager);
|
|
22479
|
-
await session.
|
|
22480
|
-
const
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
statusRange: status_min != null || status_max != null ? { min: status_min, max: status_max } : void 0,
|
|
22484
|
-
limit
|
|
22485
|
-
});
|
|
22486
|
-
if (entries.length === 0) {
|
|
22487
|
-
return "No network requests captured yet. Network monitoring is now active — new requests will be captured as they occur.";
|
|
22480
|
+
const data = await session.getStorage(type);
|
|
22481
|
+
const count = Object.keys(data.entries).length;
|
|
22482
|
+
if (count === 0) {
|
|
22483
|
+
return `No ${type} entries found for ${data.origin}`;
|
|
22488
22484
|
}
|
|
22489
|
-
return JSON.stringify(
|
|
22485
|
+
return JSON.stringify(data, null, 2);
|
|
22490
22486
|
}
|
|
22491
22487
|
);
|
|
22492
22488
|
}
|
|
22493
22489
|
);
|
|
22494
22490
|
server.registerTool(
|
|
22495
|
-
"
|
|
22491
|
+
"vessel_devtools_set_storage",
|
|
22496
22492
|
{
|
|
22497
|
-
title: "DevTools:
|
|
22498
|
-
description: "
|
|
22493
|
+
title: "DevTools: Set Storage",
|
|
22494
|
+
description: "Set or remove a key in localStorage or sessionStorage for the active tab. This is a dangerous action that modifies page state.",
|
|
22499
22495
|
inputSchema: {
|
|
22500
|
-
|
|
22496
|
+
type: zod.z.enum(["localStorage", "sessionStorage"]).describe("Storage type to modify"),
|
|
22497
|
+
key: zod.z.string().describe("Storage key"),
|
|
22498
|
+
value: zod.z.string().nullable().describe("Value to set, or null to remove the key")
|
|
22501
22499
|
}
|
|
22502
22500
|
},
|
|
22503
|
-
async ({
|
|
22501
|
+
async ({ type, key: key2, value }) => {
|
|
22504
22502
|
return withDevToolsAction(
|
|
22505
22503
|
runtime2,
|
|
22506
22504
|
tabManager,
|
|
22507
|
-
"
|
|
22508
|
-
{
|
|
22505
|
+
"devtools_set_storage",
|
|
22506
|
+
{ type, key: key2, value: value ? value.slice(0, 100) : null },
|
|
22509
22507
|
async () => {
|
|
22510
22508
|
const session = getOrCreateSession(tabManager);
|
|
22511
|
-
|
|
22512
|
-
if ("error" in result) return `Error: ${result.error}`;
|
|
22513
|
-
if (result.base64Encoded) {
|
|
22514
|
-
return `[Base64-encoded body, ${result.body.length} chars. Likely binary content.]`;
|
|
22515
|
-
}
|
|
22516
|
-
const body = result.body;
|
|
22517
|
-
return body.length > 2e4 ? body.slice(0, 2e4) + `
|
|
22518
|
-
... [truncated, total ${body.length} chars]` : body;
|
|
22509
|
+
return session.setStorage(type, key2, value);
|
|
22519
22510
|
}
|
|
22520
22511
|
);
|
|
22521
22512
|
}
|
|
22522
22513
|
);
|
|
22523
22514
|
server.registerTool(
|
|
22524
|
-
"
|
|
22515
|
+
"vessel_devtools_performance",
|
|
22525
22516
|
{
|
|
22526
|
-
title: "DevTools:
|
|
22527
|
-
description: "
|
|
22517
|
+
title: "DevTools: Performance Snapshot",
|
|
22518
|
+
description: "Get a performance snapshot for the active tab including navigation timing, paint metrics, memory usage, and resource loading statistics."
|
|
22528
22519
|
},
|
|
22529
22520
|
async () => {
|
|
22530
22521
|
return withDevToolsAction(
|
|
22531
22522
|
runtime2,
|
|
22532
22523
|
tabManager,
|
|
22533
|
-
"
|
|
22524
|
+
"devtools_performance",
|
|
22534
22525
|
{},
|
|
22535
22526
|
async () => {
|
|
22536
22527
|
const session = getOrCreateSession(tabManager);
|
|
22537
|
-
const
|
|
22538
|
-
return
|
|
22528
|
+
const snapshot2 = await session.getPerformanceSnapshot();
|
|
22529
|
+
return JSON.stringify(snapshot2, null, 2);
|
|
22539
22530
|
}
|
|
22540
22531
|
);
|
|
22541
22532
|
}
|
|
22542
22533
|
);
|
|
22543
22534
|
server.registerTool(
|
|
22544
|
-
"
|
|
22535
|
+
"vessel_devtools_get_errors",
|
|
22545
22536
|
{
|
|
22546
|
-
title: "DevTools:
|
|
22547
|
-
description: "
|
|
22537
|
+
title: "DevTools: Get Errors",
|
|
22538
|
+
description: "Get captured JavaScript errors and unhandled promise rejections from the active tab. Automatically starts capturing on first use.",
|
|
22548
22539
|
inputSchema: {
|
|
22549
|
-
|
|
22550
|
-
|
|
22540
|
+
type: zod.z.enum(["exception", "unhandled-rejection"]).optional().describe("Filter by error type"),
|
|
22541
|
+
limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
|
|
22551
22542
|
}
|
|
22552
22543
|
},
|
|
22553
|
-
async ({
|
|
22544
|
+
async ({ type, limit }) => {
|
|
22554
22545
|
return withDevToolsAction(
|
|
22555
22546
|
runtime2,
|
|
22556
22547
|
tabManager,
|
|
22557
|
-
"
|
|
22558
|
-
{
|
|
22548
|
+
"devtools_get_errors",
|
|
22549
|
+
{ type, limit },
|
|
22559
22550
|
async () => {
|
|
22560
22551
|
const session = getOrCreateSession(tabManager);
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
return `No elements found matching "${selector}"`;
|
|
22552
|
+
await session.ensureErrorCapture();
|
|
22553
|
+
const entries = session.getErrors({ type, limit });
|
|
22554
|
+
if (entries.length === 0) {
|
|
22555
|
+
return "No errors captured yet. Error monitoring is now active — exceptions and unhandled rejections will be captured as they occur.";
|
|
22566
22556
|
}
|
|
22567
|
-
return JSON.stringify(
|
|
22557
|
+
return JSON.stringify(entries, null, 2);
|
|
22568
22558
|
}
|
|
22569
22559
|
);
|
|
22570
22560
|
}
|
|
22571
22561
|
);
|
|
22572
22562
|
server.registerTool(
|
|
22573
|
-
"
|
|
22563
|
+
"vessel_devtools_clear_errors",
|
|
22574
22564
|
{
|
|
22575
|
-
title: "DevTools:
|
|
22576
|
-
description: "
|
|
22577
|
-
inputSchema: {
|
|
22578
|
-
selector: zod.z.string().describe("CSS selector for the target element"),
|
|
22579
|
-
properties: zod.z.array(zod.z.string()).optional().describe(
|
|
22580
|
-
'Specific CSS properties to return (e.g., ["color", "font-size", "display"]). Omit for all properties.'
|
|
22581
|
-
)
|
|
22582
|
-
}
|
|
22565
|
+
title: "DevTools: Clear Errors",
|
|
22566
|
+
description: "Clear the captured error buffer for the active tab."
|
|
22583
22567
|
},
|
|
22584
|
-
async (
|
|
22568
|
+
async () => {
|
|
22585
22569
|
return withDevToolsAction(
|
|
22586
22570
|
runtime2,
|
|
22587
22571
|
tabManager,
|
|
22588
|
-
"
|
|
22589
|
-
{
|
|
22572
|
+
"devtools_clear_errors",
|
|
22573
|
+
{},
|
|
22590
22574
|
async () => {
|
|
22591
22575
|
const session = getOrCreateSession(tabManager);
|
|
22592
|
-
const
|
|
22593
|
-
|
|
22594
|
-
return `No computed styles found for "${selector}"`;
|
|
22595
|
-
}
|
|
22596
|
-
return JSON.stringify(styles, null, 2);
|
|
22576
|
+
const count = session.clearErrors();
|
|
22577
|
+
return `Cleared ${count} error entries.`;
|
|
22597
22578
|
}
|
|
22598
22579
|
);
|
|
22599
22580
|
}
|
|
22600
|
-
);
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22581
|
+
);
|
|
22582
|
+
}
|
|
22583
|
+
const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
|
|
22584
|
+
const DEFAULT_NOTE_FOLDER = "Vessel/Research";
|
|
22585
|
+
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
22586
|
+
const PAGE_CONTENT_LIMIT = 6e3;
|
|
22587
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
22588
|
+
const DEFAULT_SEARCH_LIMIT = 20;
|
|
22589
|
+
const { access: access$1, mkdir: mkdir$1, readFile: readFile$1, readdir: readdir$1, stat, writeFile: writeFile$1 } = fs$1.promises;
|
|
22590
|
+
function getVaultRoot() {
|
|
22591
|
+
const configured = loadSettings().obsidianVaultPath.trim();
|
|
22592
|
+
if (!configured) {
|
|
22593
|
+
throw new Error(
|
|
22594
|
+
"Obsidian not configured. Set vault path in Vessel settings to use memory capture."
|
|
22595
|
+
);
|
|
22596
|
+
}
|
|
22597
|
+
return path$1.resolve(configured);
|
|
22598
|
+
}
|
|
22599
|
+
function assertInsideVault(targetPath, vaultRoot) {
|
|
22600
|
+
const resolved = path$1.resolve(targetPath);
|
|
22601
|
+
const relative = path$1.relative(vaultRoot, resolved);
|
|
22602
|
+
if (relative.startsWith("..") || path$1.isAbsolute(relative)) {
|
|
22603
|
+
throw new Error("Resolved note path is outside the configured vault.");
|
|
22604
|
+
}
|
|
22605
|
+
return resolved;
|
|
22606
|
+
}
|
|
22607
|
+
function normalizeFolder(folder, fallback) {
|
|
22608
|
+
const raw = (folder?.trim() || fallback).replace(/\\/g, "/");
|
|
22609
|
+
if (!raw) return fallback;
|
|
22610
|
+
if (path$1.isAbsolute(raw)) {
|
|
22611
|
+
throw new Error("Vault note folders must be relative to the vault root.");
|
|
22612
|
+
}
|
|
22613
|
+
const segments = raw.split("/").filter(Boolean);
|
|
22614
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
22615
|
+
throw new Error("Vault note folders cannot traverse outside the vault.");
|
|
22616
|
+
}
|
|
22617
|
+
return segments.join(path$1.sep);
|
|
22618
|
+
}
|
|
22619
|
+
function normalizeNotePath(notePath) {
|
|
22620
|
+
const raw = notePath.trim().replace(/\\/g, "/");
|
|
22621
|
+
if (!raw) {
|
|
22622
|
+
throw new Error("A note path is required.");
|
|
22623
|
+
}
|
|
22624
|
+
if (path$1.isAbsolute(raw)) {
|
|
22625
|
+
throw new Error("Note paths must be relative to the vault root.");
|
|
22626
|
+
}
|
|
22627
|
+
const segments = raw.split("/").filter(Boolean);
|
|
22628
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
22629
|
+
throw new Error("Note paths cannot traverse outside the vault.");
|
|
22630
|
+
}
|
|
22631
|
+
const normalized = segments.join(path$1.sep);
|
|
22632
|
+
return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
22633
|
+
}
|
|
22634
|
+
function escapeYaml(value) {
|
|
22635
|
+
return JSON.stringify(value);
|
|
22636
|
+
}
|
|
22637
|
+
function renderFrontmatter(data) {
|
|
22638
|
+
const lines = ["---"];
|
|
22639
|
+
for (const [key2, value] of Object.entries(data)) {
|
|
22640
|
+
if (value == null) continue;
|
|
22641
|
+
if (Array.isArray(value)) {
|
|
22642
|
+
if (value.length === 0) continue;
|
|
22643
|
+
lines.push(`${key2}:`);
|
|
22644
|
+
for (const item of value) {
|
|
22645
|
+
lines.push(` - ${escapeYaml(item)}`);
|
|
22646
|
+
}
|
|
22647
|
+
continue;
|
|
22648
|
+
}
|
|
22649
|
+
lines.push(`${key2}: ${escapeYaml(value)}`);
|
|
22650
|
+
}
|
|
22651
|
+
lines.push("---", "");
|
|
22652
|
+
return lines.join("\n");
|
|
22653
|
+
}
|
|
22654
|
+
function slugify(value) {
|
|
22655
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
22656
|
+
return normalized || "note";
|
|
22657
|
+
}
|
|
22658
|
+
async function pathExists$1(filePath2) {
|
|
22659
|
+
try {
|
|
22660
|
+
await access$1(filePath2);
|
|
22661
|
+
return true;
|
|
22662
|
+
} catch {
|
|
22663
|
+
return false;
|
|
22664
|
+
}
|
|
22665
|
+
}
|
|
22666
|
+
async function buildUniqueNotePath(dir, title) {
|
|
22667
|
+
const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
22668
|
+
const slug = slugify(title);
|
|
22669
|
+
const base = `${datePrefix}-${slug}`;
|
|
22670
|
+
let candidate = `${base}.md`;
|
|
22671
|
+
let counter = 2;
|
|
22672
|
+
while (await pathExists$1(path$1.join(dir, candidate))) {
|
|
22673
|
+
candidate = `${base}-${counter}.md`;
|
|
22674
|
+
counter += 1;
|
|
22675
|
+
}
|
|
22676
|
+
return path$1.join(dir, candidate);
|
|
22677
|
+
}
|
|
22678
|
+
function trimContent(content, limit = PAGE_CONTENT_LIMIT) {
|
|
22679
|
+
const cleaned = content.trim();
|
|
22680
|
+
if (cleaned.length <= limit) return cleaned;
|
|
22681
|
+
return `${cleaned.slice(0, limit)}
|
|
22682
|
+
|
|
22683
|
+
[Truncated]`;
|
|
22684
|
+
}
|
|
22685
|
+
function parseFrontmatter(content) {
|
|
22686
|
+
if (!content.startsWith("---\n")) {
|
|
22687
|
+
return { body: content, tags: [] };
|
|
22688
|
+
}
|
|
22689
|
+
const closingIndex = content.indexOf("\n---\n", 4);
|
|
22690
|
+
if (closingIndex === -1) {
|
|
22691
|
+
return { body: content, tags: [] };
|
|
22692
|
+
}
|
|
22693
|
+
const raw = content.slice(4, closingIndex);
|
|
22694
|
+
const body = content.slice(closingIndex + 5);
|
|
22695
|
+
const lines = raw.split("\n");
|
|
22696
|
+
const result = { tags: [] };
|
|
22697
|
+
let activeArrayKey = "";
|
|
22698
|
+
for (const line of lines) {
|
|
22699
|
+
const trimmed = line.trim();
|
|
22700
|
+
if (!trimmed) continue;
|
|
22701
|
+
if (trimmed.startsWith("- ") && activeArrayKey === "tags") {
|
|
22702
|
+
result.tags.push(
|
|
22703
|
+
trimmed.slice(2).trim().replace(/^["']|["']$/g, "")
|
|
22704
|
+
);
|
|
22705
|
+
continue;
|
|
22706
|
+
}
|
|
22707
|
+
activeArrayKey = "";
|
|
22708
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
22709
|
+
if (separatorIndex === -1) continue;
|
|
22710
|
+
const key2 = trimmed.slice(0, separatorIndex).trim();
|
|
22711
|
+
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
22712
|
+
if (key2 === "title" && value) {
|
|
22713
|
+
result.title = value.replace(/^["']|["']$/g, "");
|
|
22714
|
+
} else if (key2 === "tags") {
|
|
22715
|
+
activeArrayKey = "tags";
|
|
22716
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
22717
|
+
const inline = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
22718
|
+
result.tags.push(...inline);
|
|
22719
|
+
activeArrayKey = "";
|
|
22610
22720
|
}
|
|
22611
|
-
},
|
|
22612
|
-
async ({ selector, attribute, value }) => {
|
|
22613
|
-
return withDevToolsAction(
|
|
22614
|
-
runtime2,
|
|
22615
|
-
tabManager,
|
|
22616
|
-
"devtools_modify_dom",
|
|
22617
|
-
{ selector, attribute, value },
|
|
22618
|
-
async () => {
|
|
22619
|
-
const session = getOrCreateSession(tabManager);
|
|
22620
|
-
return session.modifyDomAttribute(selector, attribute, value);
|
|
22621
|
-
}
|
|
22622
|
-
);
|
|
22623
22721
|
}
|
|
22624
|
-
|
|
22625
|
-
|
|
22626
|
-
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22722
|
+
}
|
|
22723
|
+
return { body, title: result.title, tags: result.tags };
|
|
22724
|
+
}
|
|
22725
|
+
async function collectMarkdownFiles(dir) {
|
|
22726
|
+
const entries = await readdir$1(dir, { withFileTypes: true });
|
|
22727
|
+
const nestedFiles = await Promise.all(
|
|
22728
|
+
entries.map(async (entry) => {
|
|
22729
|
+
const absolutePath = path$1.join(dir, entry.name);
|
|
22730
|
+
if (entry.isDirectory()) {
|
|
22731
|
+
return collectMarkdownFiles(absolutePath);
|
|
22632
22732
|
}
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
return withDevToolsAction(
|
|
22636
|
-
runtime2,
|
|
22637
|
-
tabManager,
|
|
22638
|
-
"devtools_execute_js",
|
|
22639
|
-
{ expression: expression.slice(0, 200) },
|
|
22640
|
-
async () => {
|
|
22641
|
-
const session = getOrCreateSession(tabManager);
|
|
22642
|
-
const result = await session.executeJs(expression);
|
|
22643
|
-
const parts = [`[${result.type}] ${result.result}`];
|
|
22644
|
-
if (result.exceptionDetails) {
|
|
22645
|
-
parts.push(`
|
|
22646
|
-
Exception: ${result.exceptionDetails}`);
|
|
22647
|
-
}
|
|
22648
|
-
return parts.join("");
|
|
22649
|
-
}
|
|
22650
|
-
);
|
|
22651
|
-
}
|
|
22652
|
-
);
|
|
22653
|
-
server.registerTool(
|
|
22654
|
-
"vessel_devtools_get_storage",
|
|
22655
|
-
{
|
|
22656
|
-
title: "DevTools: Get Storage",
|
|
22657
|
-
description: "Read browser storage for the active tab's origin. Supports localStorage, sessionStorage, cookies, and IndexedDB database listing.",
|
|
22658
|
-
inputSchema: {
|
|
22659
|
-
type: zod.z.enum(["localStorage", "sessionStorage", "cookie", "indexedDB"]).describe("Storage type to read")
|
|
22733
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
22734
|
+
return [absolutePath];
|
|
22660
22735
|
}
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
return withDevToolsAction(
|
|
22664
|
-
runtime2,
|
|
22665
|
-
tabManager,
|
|
22666
|
-
"devtools_get_storage",
|
|
22667
|
-
{ type },
|
|
22668
|
-
async () => {
|
|
22669
|
-
const session = getOrCreateSession(tabManager);
|
|
22670
|
-
const data = await session.getStorage(type);
|
|
22671
|
-
const count = Object.keys(data.entries).length;
|
|
22672
|
-
if (count === 0) {
|
|
22673
|
-
return `No ${type} entries found for ${data.origin}`;
|
|
22674
|
-
}
|
|
22675
|
-
return JSON.stringify(data, null, 2);
|
|
22676
|
-
}
|
|
22677
|
-
);
|
|
22678
|
-
}
|
|
22736
|
+
return [];
|
|
22737
|
+
})
|
|
22679
22738
|
);
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
|
|
22697
|
-
|
|
22698
|
-
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
|
|
22702
|
-
}
|
|
22739
|
+
return nestedFiles.flat();
|
|
22740
|
+
}
|
|
22741
|
+
async function toSummary(absolutePath, vaultRoot, raw) {
|
|
22742
|
+
const stats = await stat(absolutePath);
|
|
22743
|
+
const relativePath = path$1.relative(vaultRoot, absolutePath).split(path$1.sep).join("/");
|
|
22744
|
+
const noteContent = raw ?? await readFile$1(absolutePath, "utf-8");
|
|
22745
|
+
const parsed = parseFrontmatter(noteContent);
|
|
22746
|
+
const headingMatch = parsed.body.match(/^#\s+(.+)$/m);
|
|
22747
|
+
const title = parsed.title || headingMatch?.[1]?.trim() || path$1.basename(absolutePath, ".md");
|
|
22748
|
+
return {
|
|
22749
|
+
title,
|
|
22750
|
+
absolutePath,
|
|
22751
|
+
relativePath,
|
|
22752
|
+
modifiedAt: stats.mtime.toISOString(),
|
|
22753
|
+
tags: parsed.tags
|
|
22754
|
+
};
|
|
22755
|
+
}
|
|
22756
|
+
function renderBookmarkLinkBlock(bookmark, note) {
|
|
22757
|
+
const lines = [
|
|
22758
|
+
"## Linked Bookmark",
|
|
22759
|
+
"",
|
|
22760
|
+
`- Bookmark ID: \`${bookmark.id}\``,
|
|
22761
|
+
`- Title: ${bookmark.title || bookmark.url}`,
|
|
22762
|
+
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
22763
|
+
`- Saved At: ${bookmark.savedAt}`
|
|
22764
|
+
];
|
|
22765
|
+
if (note?.trim()) {
|
|
22766
|
+
lines.push("", "### Context", "", note.trim());
|
|
22767
|
+
}
|
|
22768
|
+
return `${lines.join("\n")}
|
|
22769
|
+
`;
|
|
22770
|
+
}
|
|
22771
|
+
async function writeMemoryNote({
|
|
22772
|
+
title,
|
|
22773
|
+
body,
|
|
22774
|
+
folder,
|
|
22775
|
+
tags = [],
|
|
22776
|
+
frontmatter = {}
|
|
22777
|
+
}) {
|
|
22778
|
+
const vaultRoot = getVaultRoot();
|
|
22779
|
+
const relativeFolder = normalizeFolder(folder, DEFAULT_NOTE_FOLDER);
|
|
22780
|
+
const targetDir = path$1.join(vaultRoot, relativeFolder);
|
|
22781
|
+
await mkdir$1(targetDir, { recursive: true });
|
|
22782
|
+
const absolutePath = await buildUniqueNotePath(targetDir, title);
|
|
22783
|
+
const relativePath = path$1.relative(vaultRoot, absolutePath);
|
|
22784
|
+
const content = [
|
|
22785
|
+
renderFrontmatter({
|
|
22786
|
+
title,
|
|
22787
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22788
|
+
tags,
|
|
22789
|
+
...frontmatter
|
|
22790
|
+
}),
|
|
22791
|
+
body.trim(),
|
|
22792
|
+
""
|
|
22793
|
+
].join("\n");
|
|
22794
|
+
await writeFile$1(absolutePath, content, "utf-8");
|
|
22795
|
+
return {
|
|
22796
|
+
title,
|
|
22797
|
+
absolutePath,
|
|
22798
|
+
relativePath: relativePath.split(path$1.sep).join("/")
|
|
22799
|
+
};
|
|
22800
|
+
}
|
|
22801
|
+
async function appendToMemoryNote({
|
|
22802
|
+
notePath,
|
|
22803
|
+
content,
|
|
22804
|
+
heading
|
|
22805
|
+
}) {
|
|
22806
|
+
const vaultRoot = getVaultRoot();
|
|
22807
|
+
const relativePath = normalizeNotePath(notePath);
|
|
22808
|
+
const absolutePath = assertInsideVault(
|
|
22809
|
+
path$1.join(vaultRoot, relativePath),
|
|
22810
|
+
vaultRoot
|
|
22703
22811
|
);
|
|
22704
|
-
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
|
|
22708
|
-
|
|
22709
|
-
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
|
|
22718
|
-
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
|
|
22722
|
-
|
|
22812
|
+
if (!await pathExists$1(absolutePath)) {
|
|
22813
|
+
throw new Error(
|
|
22814
|
+
`Memory note not found: ${relativePath.split(path$1.sep).join("/")}`
|
|
22815
|
+
);
|
|
22816
|
+
}
|
|
22817
|
+
const current = (await readFile$1(absolutePath, "utf-8")).trimEnd();
|
|
22818
|
+
const nextParts = [current, ""];
|
|
22819
|
+
if (heading?.trim()) {
|
|
22820
|
+
nextParts.push(`## ${heading.trim()}`, "");
|
|
22821
|
+
}
|
|
22822
|
+
nextParts.push(content.trim(), "");
|
|
22823
|
+
await writeFile$1(absolutePath, nextParts.join("\n"), "utf-8");
|
|
22824
|
+
return {
|
|
22825
|
+
title: path$1.basename(absolutePath, ".md"),
|
|
22826
|
+
absolutePath,
|
|
22827
|
+
relativePath: relativePath.split(path$1.sep).join("/")
|
|
22828
|
+
};
|
|
22829
|
+
}
|
|
22830
|
+
async function listMemoryNotes({
|
|
22831
|
+
folder,
|
|
22832
|
+
limit = DEFAULT_LIST_LIMIT
|
|
22833
|
+
} = {}) {
|
|
22834
|
+
const vaultRoot = getVaultRoot();
|
|
22835
|
+
const relativeFolder = normalizeFolder(folder, "");
|
|
22836
|
+
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
22837
|
+
if (!await pathExists$1(targetDir)) {
|
|
22838
|
+
return [];
|
|
22839
|
+
}
|
|
22840
|
+
const notes = await Promise.all(
|
|
22841
|
+
(await collectMarkdownFiles(targetDir)).map(
|
|
22842
|
+
(absolutePath) => toSummary(absolutePath, vaultRoot)
|
|
22843
|
+
)
|
|
22723
22844
|
);
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
22748
|
-
|
|
22845
|
+
return notes.sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
22846
|
+
}
|
|
22847
|
+
async function searchMemoryNotes({
|
|
22848
|
+
query,
|
|
22849
|
+
folder,
|
|
22850
|
+
tags = [],
|
|
22851
|
+
limit = DEFAULT_SEARCH_LIMIT
|
|
22852
|
+
}) {
|
|
22853
|
+
const loweredQuery = query.trim().toLowerCase();
|
|
22854
|
+
if (!loweredQuery) {
|
|
22855
|
+
throw new Error("A non-empty memory search query is required.");
|
|
22856
|
+
}
|
|
22857
|
+
const vaultRoot = getVaultRoot();
|
|
22858
|
+
const relativeFolder = normalizeFolder(folder, "");
|
|
22859
|
+
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
22860
|
+
if (!await pathExists$1(targetDir)) {
|
|
22861
|
+
return [];
|
|
22862
|
+
}
|
|
22863
|
+
const loweredTags = tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean);
|
|
22864
|
+
const matches = await Promise.all(
|
|
22865
|
+
(await collectMarkdownFiles(targetDir)).map(async (absolutePath) => {
|
|
22866
|
+
const raw = await readFile$1(absolutePath, "utf-8");
|
|
22867
|
+
const parsed = parseFrontmatter(raw);
|
|
22868
|
+
const summary = await toSummary(absolutePath, vaultRoot, raw);
|
|
22869
|
+
const haystack = `${summary.title}
|
|
22870
|
+
${summary.relativePath}
|
|
22871
|
+
${parsed.body}`.toLowerCase();
|
|
22872
|
+
const hasQuery = haystack.includes(loweredQuery);
|
|
22873
|
+
const hasTags = loweredTags.length === 0 || loweredTags.every(
|
|
22874
|
+
(tag) => summary.tags.some((noteTag) => noteTag.toLowerCase() === tag)
|
|
22749
22875
|
);
|
|
22750
|
-
|
|
22876
|
+
return hasQuery && hasTags ? summary : null;
|
|
22877
|
+
})
|
|
22751
22878
|
);
|
|
22752
|
-
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
|
|
22879
|
+
return matches.filter((item) => item !== null).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
22880
|
+
}
|
|
22881
|
+
async function capturePageToVault({
|
|
22882
|
+
page,
|
|
22883
|
+
title,
|
|
22884
|
+
folder,
|
|
22885
|
+
summary,
|
|
22886
|
+
note,
|
|
22887
|
+
tags = []
|
|
22888
|
+
}) {
|
|
22889
|
+
const noteTitle = title?.trim() || page.title.trim() || page.url;
|
|
22890
|
+
const bodyLines = [
|
|
22891
|
+
`# ${noteTitle}`,
|
|
22892
|
+
"",
|
|
22893
|
+
`Source: [${page.title || page.url}](${page.url})`,
|
|
22894
|
+
`Captured: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
22895
|
+
];
|
|
22896
|
+
if (page.byline) {
|
|
22897
|
+
bodyLines.push(`Byline: ${page.byline}`);
|
|
22898
|
+
}
|
|
22899
|
+
bodyLines.push("");
|
|
22900
|
+
if (summary?.trim()) {
|
|
22901
|
+
bodyLines.push("## Summary", "", summary.trim(), "");
|
|
22902
|
+
}
|
|
22903
|
+
if (note?.trim()) {
|
|
22904
|
+
bodyLines.push("## Research Note", "", note.trim(), "");
|
|
22905
|
+
}
|
|
22906
|
+
if (page.excerpt.trim()) {
|
|
22907
|
+
bodyLines.push("## Excerpt", "", page.excerpt.trim(), "");
|
|
22908
|
+
}
|
|
22909
|
+
const snapshot2 = trimContent(page.content);
|
|
22910
|
+
if (snapshot2) {
|
|
22911
|
+
bodyLines.push("## Page Snapshot", "", snapshot2, "");
|
|
22912
|
+
}
|
|
22913
|
+
return await writeMemoryNote({
|
|
22914
|
+
title: noteTitle,
|
|
22915
|
+
body: bodyLines.join("\n"),
|
|
22916
|
+
folder: folder || DEFAULT_PAGE_FOLDER,
|
|
22917
|
+
tags,
|
|
22918
|
+
frontmatter: {
|
|
22919
|
+
source_url: page.url,
|
|
22920
|
+
source_title: page.title || page.url
|
|
22770
22921
|
}
|
|
22771
|
-
);
|
|
22922
|
+
});
|
|
22923
|
+
}
|
|
22924
|
+
async function linkBookmarkToMemory({
|
|
22925
|
+
bookmark,
|
|
22926
|
+
notePath,
|
|
22927
|
+
title,
|
|
22928
|
+
folder,
|
|
22929
|
+
note,
|
|
22930
|
+
tags = []
|
|
22931
|
+
}) {
|
|
22932
|
+
if (notePath?.trim()) {
|
|
22933
|
+
return await appendToMemoryNote({
|
|
22934
|
+
notePath,
|
|
22935
|
+
heading: "Linked Bookmark",
|
|
22936
|
+
content: [
|
|
22937
|
+
`- Bookmark ID: \`${bookmark.id}\``,
|
|
22938
|
+
`- Title: ${bookmark.title || bookmark.url}`,
|
|
22939
|
+
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
22940
|
+
`- Saved At: ${bookmark.savedAt}`,
|
|
22941
|
+
note?.trim() ? `- Note: ${note.trim()}` : ""
|
|
22942
|
+
].filter(Boolean).join("\n")
|
|
22943
|
+
});
|
|
22944
|
+
}
|
|
22945
|
+
const noteTitle = title?.trim() || bookmark.title || bookmark.url;
|
|
22946
|
+
return await writeMemoryNote({
|
|
22947
|
+
title: noteTitle,
|
|
22948
|
+
body: renderBookmarkLinkBlock(bookmark, note),
|
|
22949
|
+
folder: folder || DEFAULT_BOOKMARK_FOLDER,
|
|
22950
|
+
tags,
|
|
22951
|
+
frontmatter: {
|
|
22952
|
+
bookmark_id: bookmark.id,
|
|
22953
|
+
source_url: bookmark.url,
|
|
22954
|
+
source_title: bookmark.title || bookmark.url
|
|
22955
|
+
}
|
|
22956
|
+
});
|
|
22772
22957
|
}
|
|
22773
22958
|
const logger$f = createLogger("MCP");
|
|
22774
22959
|
function asTextResponse(text) {
|
|
@@ -23485,95 +23670,246 @@ function registerBookmarkTools(server, tabManager, runtime2) {
|
|
|
23485
23670
|
)
|
|
23486
23671
|
}
|
|
23487
23672
|
},
|
|
23488
|
-
async ({ folder_id, delete_contents }) => {
|
|
23673
|
+
async ({ folder_id, delete_contents }) => {
|
|
23674
|
+
return withAction(
|
|
23675
|
+
runtime2,
|
|
23676
|
+
tabManager,
|
|
23677
|
+
"remove_bookmark_folder",
|
|
23678
|
+
{ folder_id, delete_contents },
|
|
23679
|
+
async () => {
|
|
23680
|
+
const removed = removeFolder(
|
|
23681
|
+
folder_id,
|
|
23682
|
+
delete_contents
|
|
23683
|
+
);
|
|
23684
|
+
if (!removed) return `Folder ${folder_id} not found`;
|
|
23685
|
+
return composeFolderAwareResponse$1(
|
|
23686
|
+
delete_contents ? `Removed folder ${folder_id} and deleted its bookmarks.` : `Removed folder ${folder_id}. Bookmarks moved to Unsorted.`
|
|
23687
|
+
);
|
|
23688
|
+
}
|
|
23689
|
+
);
|
|
23690
|
+
}
|
|
23691
|
+
);
|
|
23692
|
+
server.registerTool(
|
|
23693
|
+
"folder_rename",
|
|
23694
|
+
{
|
|
23695
|
+
title: "Rename Bookmark Folder",
|
|
23696
|
+
description: "Rename an existing bookmark folder.",
|
|
23697
|
+
inputSchema: {
|
|
23698
|
+
folder_id: zod.z.string().describe("ID of the folder to rename"),
|
|
23699
|
+
new_name: zod.z.string().describe("New name for the folder"),
|
|
23700
|
+
summary: zod.z.string().optional().describe("Optional one-sentence summary for the folder")
|
|
23701
|
+
}
|
|
23702
|
+
},
|
|
23703
|
+
async ({ folder_id, new_name, summary }) => {
|
|
23704
|
+
return withAction(
|
|
23705
|
+
runtime2,
|
|
23706
|
+
tabManager,
|
|
23707
|
+
"rename_bookmark_folder",
|
|
23708
|
+
{ folder_id, new_name, summary },
|
|
23709
|
+
async () => {
|
|
23710
|
+
const existing = findFolderByName(new_name);
|
|
23711
|
+
if (existing && existing.id !== folder_id) {
|
|
23712
|
+
return composeFolderAwareResponse$1(
|
|
23713
|
+
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
23714
|
+
);
|
|
23715
|
+
}
|
|
23716
|
+
const folder = renameFolder(
|
|
23717
|
+
folder_id,
|
|
23718
|
+
new_name,
|
|
23719
|
+
summary
|
|
23720
|
+
);
|
|
23721
|
+
return folder ? composeFolderAwareResponse$1(`Renamed folder to "${folder.name}"`) : `Folder ${folder_id} not found`;
|
|
23722
|
+
}
|
|
23723
|
+
);
|
|
23724
|
+
}
|
|
23725
|
+
);
|
|
23726
|
+
server.registerTool(
|
|
23727
|
+
"memory_link_bookmark",
|
|
23728
|
+
{
|
|
23729
|
+
title: "Link Bookmark To Memory",
|
|
23730
|
+
description: "Create a note for a bookmark or append bookmark details into an existing memory note.",
|
|
23731
|
+
inputSchema: {
|
|
23732
|
+
bookmark_id: zod.z.string().describe("Bookmark ID to link"),
|
|
23733
|
+
note_path: zod.z.string().optional().describe("Existing relative note path to append into"),
|
|
23734
|
+
title: zod.z.string().optional().describe("Optional title when creating a new note"),
|
|
23735
|
+
folder: zod.z.string().optional().describe("Relative folder when creating a new note"),
|
|
23736
|
+
note: zod.z.string().optional().describe(
|
|
23737
|
+
"Optional rationale or breadcrumb to store with the bookmark"
|
|
23738
|
+
),
|
|
23739
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags when creating a new note")
|
|
23740
|
+
}
|
|
23741
|
+
},
|
|
23742
|
+
async ({ bookmark_id, note_path, title, folder, note, tags }) => {
|
|
23743
|
+
return withAction(
|
|
23744
|
+
runtime2,
|
|
23745
|
+
tabManager,
|
|
23746
|
+
"memory_link_bookmark",
|
|
23747
|
+
{ bookmark_id, note_path, title, folder, tags },
|
|
23748
|
+
async () => {
|
|
23749
|
+
const bookmark = getBookmark(bookmark_id);
|
|
23750
|
+
if (!bookmark) {
|
|
23751
|
+
return `Bookmark ${bookmark_id} not found`;
|
|
23752
|
+
}
|
|
23753
|
+
const saved = await linkBookmarkToMemory({
|
|
23754
|
+
bookmark,
|
|
23755
|
+
notePath: note_path,
|
|
23756
|
+
title,
|
|
23757
|
+
folder,
|
|
23758
|
+
note,
|
|
23759
|
+
tags
|
|
23760
|
+
});
|
|
23761
|
+
return `Linked bookmark "${bookmark.title}" to memory note ${saved.relativePath}`;
|
|
23762
|
+
}
|
|
23763
|
+
);
|
|
23764
|
+
}
|
|
23765
|
+
);
|
|
23766
|
+
}
|
|
23767
|
+
function registerMemoryTools(server, tabManager, runtime2) {
|
|
23768
|
+
server.registerTool(
|
|
23769
|
+
"memory_note_create",
|
|
23770
|
+
{
|
|
23771
|
+
title: "Create Memory Note",
|
|
23772
|
+
description: "Write a markdown note into the configured Obsidian vault for research notes, breadcrumbs, or synthesis.",
|
|
23773
|
+
inputSchema: {
|
|
23774
|
+
title: zod.z.string().describe("Title of the note"),
|
|
23775
|
+
body: zod.z.string().describe("Markdown body for the note"),
|
|
23776
|
+
folder: zod.z.string().optional().describe(
|
|
23777
|
+
"Relative folder inside the vault (default: Vessel/Research)"
|
|
23778
|
+
),
|
|
23779
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
23780
|
+
}
|
|
23781
|
+
},
|
|
23782
|
+
async ({ title, body, folder, tags }) => {
|
|
23783
|
+
return withAction(
|
|
23784
|
+
runtime2,
|
|
23785
|
+
tabManager,
|
|
23786
|
+
"memory_note_create",
|
|
23787
|
+
{ title, folder, tags },
|
|
23788
|
+
async () => {
|
|
23789
|
+
const saved = await writeMemoryNote({ title, body, folder, tags });
|
|
23790
|
+
return `Saved memory note "${saved.title}" to ${saved.relativePath}`;
|
|
23791
|
+
}
|
|
23792
|
+
);
|
|
23793
|
+
}
|
|
23794
|
+
);
|
|
23795
|
+
server.registerTool(
|
|
23796
|
+
"memory_append",
|
|
23797
|
+
{
|
|
23798
|
+
title: "Append Memory Note",
|
|
23799
|
+
description: "Append markdown content to an existing note in the configured Obsidian vault.",
|
|
23800
|
+
inputSchema: {
|
|
23801
|
+
note_path: zod.z.string().describe("Relative path to an existing note inside the vault"),
|
|
23802
|
+
content: zod.z.string().describe("Markdown content to append"),
|
|
23803
|
+
heading: zod.z.string().optional().describe("Optional section heading to add before the content")
|
|
23804
|
+
}
|
|
23805
|
+
},
|
|
23806
|
+
async ({ note_path, content, heading }) => {
|
|
23807
|
+
return withAction(
|
|
23808
|
+
runtime2,
|
|
23809
|
+
tabManager,
|
|
23810
|
+
"memory_note_append",
|
|
23811
|
+
{ note_path, heading },
|
|
23812
|
+
async () => {
|
|
23813
|
+
const saved = await appendToMemoryNote({
|
|
23814
|
+
notePath: note_path,
|
|
23815
|
+
content,
|
|
23816
|
+
heading
|
|
23817
|
+
});
|
|
23818
|
+
return `Appended memory note at ${saved.relativePath}`;
|
|
23819
|
+
}
|
|
23820
|
+
);
|
|
23821
|
+
}
|
|
23822
|
+
);
|
|
23823
|
+
server.registerTool(
|
|
23824
|
+
"memory_list",
|
|
23825
|
+
{
|
|
23826
|
+
title: "List Memory Notes",
|
|
23827
|
+
description: "List recent markdown notes in the configured Obsidian vault.",
|
|
23828
|
+
inputSchema: {
|
|
23829
|
+
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
23830
|
+
limit: zod.z.number().int().positive().max(200).optional().describe("Maximum number of notes to return")
|
|
23831
|
+
}
|
|
23832
|
+
},
|
|
23833
|
+
async ({ folder, limit }) => {
|
|
23489
23834
|
return withAction(
|
|
23490
23835
|
runtime2,
|
|
23491
23836
|
tabManager,
|
|
23492
|
-
"
|
|
23493
|
-
{
|
|
23837
|
+
"memory_note_list",
|
|
23838
|
+
{ folder, limit },
|
|
23494
23839
|
async () => {
|
|
23495
|
-
const
|
|
23496
|
-
|
|
23497
|
-
|
|
23498
|
-
|
|
23499
|
-
|
|
23500
|
-
|
|
23501
|
-
|
|
23502
|
-
);
|
|
23840
|
+
const notes = await listMemoryNotes({ folder, limit });
|
|
23841
|
+
if (notes.length === 0) {
|
|
23842
|
+
return "No memory notes found.";
|
|
23843
|
+
}
|
|
23844
|
+
return notes.map(
|
|
23845
|
+
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
23846
|
+
).join("\n");
|
|
23503
23847
|
}
|
|
23504
23848
|
);
|
|
23505
23849
|
}
|
|
23506
23850
|
);
|
|
23507
23851
|
server.registerTool(
|
|
23508
|
-
"
|
|
23852
|
+
"memory_search",
|
|
23509
23853
|
{
|
|
23510
|
-
title: "
|
|
23511
|
-
description: "
|
|
23854
|
+
title: "Search Memory Notes",
|
|
23855
|
+
description: "Search markdown notes in the configured Obsidian vault by title, path, body, and optional tags.",
|
|
23512
23856
|
inputSchema: {
|
|
23513
|
-
|
|
23514
|
-
|
|
23515
|
-
|
|
23857
|
+
query: zod.z.string().describe("Search query"),
|
|
23858
|
+
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
23859
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags that matching notes must contain"),
|
|
23860
|
+
limit: zod.z.number().int().positive().max(100).optional().describe("Maximum number of matching notes to return")
|
|
23516
23861
|
}
|
|
23517
23862
|
},
|
|
23518
|
-
async ({
|
|
23863
|
+
async ({ query, folder, tags, limit }) => {
|
|
23519
23864
|
return withAction(
|
|
23520
23865
|
runtime2,
|
|
23521
23866
|
tabManager,
|
|
23522
|
-
"
|
|
23523
|
-
{
|
|
23867
|
+
"memory_note_search",
|
|
23868
|
+
{ query, folder, tags, limit },
|
|
23524
23869
|
async () => {
|
|
23525
|
-
const
|
|
23526
|
-
if (
|
|
23527
|
-
return
|
|
23528
|
-
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
23529
|
-
);
|
|
23870
|
+
const notes = await searchMemoryNotes({ query, folder, tags, limit });
|
|
23871
|
+
if (notes.length === 0) {
|
|
23872
|
+
return `No memory notes matched "${query}".`;
|
|
23530
23873
|
}
|
|
23531
|
-
|
|
23532
|
-
|
|
23533
|
-
|
|
23534
|
-
summary
|
|
23535
|
-
);
|
|
23536
|
-
return folder ? composeFolderAwareResponse$1(`Renamed folder to "${folder.name}"`) : `Folder ${folder_id} not found`;
|
|
23874
|
+
return notes.map(
|
|
23875
|
+
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
23876
|
+
).join("\n");
|
|
23537
23877
|
}
|
|
23538
23878
|
);
|
|
23539
23879
|
}
|
|
23540
23880
|
);
|
|
23541
23881
|
server.registerTool(
|
|
23542
|
-
"
|
|
23882
|
+
"memory_page_capture",
|
|
23543
23883
|
{
|
|
23544
|
-
title: "
|
|
23545
|
-
description: "
|
|
23884
|
+
title: "Capture Page To Memory",
|
|
23885
|
+
description: "Capture the current page into the configured Obsidian vault as a markdown note with URL, excerpt, and content snapshot.",
|
|
23546
23886
|
inputSchema: {
|
|
23547
|
-
|
|
23548
|
-
|
|
23549
|
-
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
"Optional rationale or breadcrumb to store with the bookmark"
|
|
23553
|
-
),
|
|
23554
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags when creating a new note")
|
|
23887
|
+
title: zod.z.string().optional().describe("Optional note title override"),
|
|
23888
|
+
folder: zod.z.string().optional().describe("Relative folder inside the vault (default: Vessel/Pages)"),
|
|
23889
|
+
summary: zod.z.string().optional().describe("Optional summary written into the note"),
|
|
23890
|
+
note: zod.z.string().optional().describe("Optional research note or breadcrumb"),
|
|
23891
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
23555
23892
|
}
|
|
23556
23893
|
},
|
|
23557
|
-
async ({
|
|
23894
|
+
async ({ title, folder, summary, note, tags }) => {
|
|
23895
|
+
const tab = tabManager.getActiveTab();
|
|
23896
|
+
if (!tab) return asNoActiveTabResponse();
|
|
23558
23897
|
return withAction(
|
|
23559
23898
|
runtime2,
|
|
23560
23899
|
tabManager,
|
|
23561
|
-
"
|
|
23562
|
-
{
|
|
23900
|
+
"memory_page_capture",
|
|
23901
|
+
{ title, folder, tags },
|
|
23563
23902
|
async () => {
|
|
23564
|
-
const
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
}
|
|
23568
|
-
const saved = linkBookmarkToMemory({
|
|
23569
|
-
bookmark,
|
|
23570
|
-
notePath: note_path,
|
|
23903
|
+
const page = await extractContent$1(tab.view.webContents);
|
|
23904
|
+
const saved = await capturePageToVault({
|
|
23905
|
+
page,
|
|
23571
23906
|
title,
|
|
23572
23907
|
folder,
|
|
23908
|
+
summary,
|
|
23573
23909
|
note,
|
|
23574
23910
|
tags
|
|
23575
23911
|
});
|
|
23576
|
-
return `
|
|
23912
|
+
return `Captured page "${saved.title}" to ${saved.relativePath}`;
|
|
23577
23913
|
}
|
|
23578
23914
|
);
|
|
23579
23915
|
}
|
|
@@ -23822,7 +24158,7 @@ function domainMatches(pattern, hostname) {
|
|
|
23822
24158
|
return isWildcard ? h.endsWith("." + p) : p === h;
|
|
23823
24159
|
}
|
|
23824
24160
|
function generateTotpCode(secret) {
|
|
23825
|
-
const epoch =
|
|
24161
|
+
const epoch = unixNow();
|
|
23826
24162
|
const counter = Math.floor(epoch / 30);
|
|
23827
24163
|
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
23828
24164
|
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
@@ -23867,7 +24203,8 @@ function createAuditLog(filename, maxEntries) {
|
|
|
23867
24203
|
});
|
|
23868
24204
|
fs$1.chmodSync(auditPath, 384);
|
|
23869
24205
|
}
|
|
23870
|
-
} catch {
|
|
24206
|
+
} catch (err) {
|
|
24207
|
+
logger$e.warn("Failed to trim audit log:", err);
|
|
23871
24208
|
}
|
|
23872
24209
|
} catch (err) {
|
|
23873
24210
|
logger$e.error("Failed to write audit log:", err);
|
|
@@ -25436,7 +25773,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25436
25773
|
)
|
|
25437
25774
|
}
|
|
25438
25775
|
},
|
|
25439
|
-
async ({ index, selector, text, label, durationMs, persist
|
|
25776
|
+
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
25440
25777
|
const tab = tabManager.getActiveTab();
|
|
25441
25778
|
if (!tab) return asNoActiveTabResponse();
|
|
25442
25779
|
const normalizedText = normalizeLooseString(text);
|
|
@@ -25450,7 +25787,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25450
25787
|
text: normalizedText,
|
|
25451
25788
|
label,
|
|
25452
25789
|
durationMs,
|
|
25453
|
-
persist
|
|
25790
|
+
persist,
|
|
25454
25791
|
color
|
|
25455
25792
|
},
|
|
25456
25793
|
async () => {
|
|
@@ -25464,7 +25801,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
25464
25801
|
durationMs,
|
|
25465
25802
|
color
|
|
25466
25803
|
);
|
|
25467
|
-
if (
|
|
25804
|
+
if (persist && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
|
|
25468
25805
|
const url = normalizeUrl$1(wc.getURL());
|
|
25469
25806
|
addHighlight(
|
|
25470
25807
|
url,
|
|
@@ -25637,155 +25974,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
25637
25974
|
);
|
|
25638
25975
|
registerBookmarkTools(server, tabManager, runtime2);
|
|
25639
25976
|
registerSessionTools(server, tabManager, runtime2);
|
|
25640
|
-
server
|
|
25641
|
-
"memory_note_create",
|
|
25642
|
-
{
|
|
25643
|
-
title: "Create Memory Note",
|
|
25644
|
-
description: "Write a markdown note into the configured Obsidian vault for research notes, breadcrumbs, or synthesis.",
|
|
25645
|
-
inputSchema: {
|
|
25646
|
-
title: zod.z.string().describe("Title of the note"),
|
|
25647
|
-
body: zod.z.string().describe("Markdown body for the note"),
|
|
25648
|
-
folder: zod.z.string().optional().describe(
|
|
25649
|
-
"Relative folder inside the vault (default: Vessel/Research)"
|
|
25650
|
-
),
|
|
25651
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
25652
|
-
}
|
|
25653
|
-
},
|
|
25654
|
-
async ({ title, body, folder, tags }) => {
|
|
25655
|
-
return withAction(
|
|
25656
|
-
runtime2,
|
|
25657
|
-
tabManager,
|
|
25658
|
-
"memory_note_create",
|
|
25659
|
-
{ title, folder, tags },
|
|
25660
|
-
async () => {
|
|
25661
|
-
const saved = writeMemoryNote({ title, body, folder, tags });
|
|
25662
|
-
return `Saved memory note "${saved.title}" to ${saved.relativePath}`;
|
|
25663
|
-
}
|
|
25664
|
-
);
|
|
25665
|
-
}
|
|
25666
|
-
);
|
|
25667
|
-
server.registerTool(
|
|
25668
|
-
"memory_append",
|
|
25669
|
-
{
|
|
25670
|
-
title: "Append Memory Note",
|
|
25671
|
-
description: "Append markdown content to an existing note in the configured Obsidian vault.",
|
|
25672
|
-
inputSchema: {
|
|
25673
|
-
note_path: zod.z.string().describe("Relative path to an existing note inside the vault"),
|
|
25674
|
-
content: zod.z.string().describe("Markdown content to append"),
|
|
25675
|
-
heading: zod.z.string().optional().describe("Optional section heading to add before the content")
|
|
25676
|
-
}
|
|
25677
|
-
},
|
|
25678
|
-
async ({ note_path, content, heading }) => {
|
|
25679
|
-
return withAction(
|
|
25680
|
-
runtime2,
|
|
25681
|
-
tabManager,
|
|
25682
|
-
"memory_note_append",
|
|
25683
|
-
{ note_path, heading },
|
|
25684
|
-
async () => {
|
|
25685
|
-
const saved = appendToMemoryNote({
|
|
25686
|
-
notePath: note_path,
|
|
25687
|
-
content,
|
|
25688
|
-
heading
|
|
25689
|
-
});
|
|
25690
|
-
return `Appended memory note at ${saved.relativePath}`;
|
|
25691
|
-
}
|
|
25692
|
-
);
|
|
25693
|
-
}
|
|
25694
|
-
);
|
|
25695
|
-
server.registerTool(
|
|
25696
|
-
"memory_list",
|
|
25697
|
-
{
|
|
25698
|
-
title: "List Memory Notes",
|
|
25699
|
-
description: "List recent markdown notes in the configured Obsidian vault.",
|
|
25700
|
-
inputSchema: {
|
|
25701
|
-
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
25702
|
-
limit: zod.z.number().int().positive().max(200).optional().describe("Maximum number of notes to return")
|
|
25703
|
-
}
|
|
25704
|
-
},
|
|
25705
|
-
async ({ folder, limit }) => {
|
|
25706
|
-
return withAction(
|
|
25707
|
-
runtime2,
|
|
25708
|
-
tabManager,
|
|
25709
|
-
"memory_note_list",
|
|
25710
|
-
{ folder, limit },
|
|
25711
|
-
async () => {
|
|
25712
|
-
const notes = listMemoryNotes({ folder, limit });
|
|
25713
|
-
if (notes.length === 0) {
|
|
25714
|
-
return "No memory notes found.";
|
|
25715
|
-
}
|
|
25716
|
-
return notes.map(
|
|
25717
|
-
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
25718
|
-
).join("\n");
|
|
25719
|
-
}
|
|
25720
|
-
);
|
|
25721
|
-
}
|
|
25722
|
-
);
|
|
25723
|
-
server.registerTool(
|
|
25724
|
-
"memory_search",
|
|
25725
|
-
{
|
|
25726
|
-
title: "Search Memory Notes",
|
|
25727
|
-
description: "Search markdown notes in the configured Obsidian vault by title, path, body, and optional tags.",
|
|
25728
|
-
inputSchema: {
|
|
25729
|
-
query: zod.z.string().describe("Search query"),
|
|
25730
|
-
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
25731
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags that matching notes must contain"),
|
|
25732
|
-
limit: zod.z.number().int().positive().max(100).optional().describe("Maximum number of matching notes to return")
|
|
25733
|
-
}
|
|
25734
|
-
},
|
|
25735
|
-
async ({ query, folder, tags, limit }) => {
|
|
25736
|
-
return withAction(
|
|
25737
|
-
runtime2,
|
|
25738
|
-
tabManager,
|
|
25739
|
-
"memory_note_search",
|
|
25740
|
-
{ query, folder, tags, limit },
|
|
25741
|
-
async () => {
|
|
25742
|
-
const notes = searchMemoryNotes({ query, folder, tags, limit });
|
|
25743
|
-
if (notes.length === 0) {
|
|
25744
|
-
return `No memory notes matched "${query}".`;
|
|
25745
|
-
}
|
|
25746
|
-
return notes.map(
|
|
25747
|
-
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
25748
|
-
).join("\n");
|
|
25749
|
-
}
|
|
25750
|
-
);
|
|
25751
|
-
}
|
|
25752
|
-
);
|
|
25753
|
-
server.registerTool(
|
|
25754
|
-
"memory_page_capture",
|
|
25755
|
-
{
|
|
25756
|
-
title: "Capture Page To Memory",
|
|
25757
|
-
description: "Capture the current page into the configured Obsidian vault as a markdown note with URL, excerpt, and content snapshot.",
|
|
25758
|
-
inputSchema: {
|
|
25759
|
-
title: zod.z.string().optional().describe("Optional note title override"),
|
|
25760
|
-
folder: zod.z.string().optional().describe("Relative folder inside the vault (default: Vessel/Pages)"),
|
|
25761
|
-
summary: zod.z.string().optional().describe("Optional summary written into the note"),
|
|
25762
|
-
note: zod.z.string().optional().describe("Optional research note or breadcrumb"),
|
|
25763
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
25764
|
-
}
|
|
25765
|
-
},
|
|
25766
|
-
async ({ title, folder, summary, note, tags }) => {
|
|
25767
|
-
const tab = tabManager.getActiveTab();
|
|
25768
|
-
if (!tab) return asNoActiveTabResponse();
|
|
25769
|
-
return withAction(
|
|
25770
|
-
runtime2,
|
|
25771
|
-
tabManager,
|
|
25772
|
-
"memory_page_capture",
|
|
25773
|
-
{ title, folder, tags },
|
|
25774
|
-
async () => {
|
|
25775
|
-
const page = await extractContent$1(tab.view.webContents);
|
|
25776
|
-
const saved = capturePageToVault({
|
|
25777
|
-
page,
|
|
25778
|
-
title,
|
|
25779
|
-
folder,
|
|
25780
|
-
summary,
|
|
25781
|
-
note,
|
|
25782
|
-
tags
|
|
25783
|
-
});
|
|
25784
|
-
return `Captured page "${saved.title}" to ${saved.relativePath}`;
|
|
25785
|
-
}
|
|
25786
|
-
);
|
|
25787
|
-
}
|
|
25788
|
-
);
|
|
25977
|
+
registerMemoryTools(server, tabManager, runtime2);
|
|
25789
25978
|
server.registerTool(
|
|
25790
25979
|
"flow_start",
|
|
25791
25980
|
{
|
|
@@ -27464,15 +27653,21 @@ function isSafeAutomationKitId(id) {
|
|
|
27464
27653
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
27465
27654
|
}
|
|
27466
27655
|
const logger$9 = createLogger("KitRegistry");
|
|
27656
|
+
const { access, mkdir, readFile, readdir, unlink, writeFile } = fs$1.promises;
|
|
27467
27657
|
function getUserKitsDir() {
|
|
27468
27658
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
27469
27659
|
}
|
|
27470
|
-
function
|
|
27471
|
-
|
|
27472
|
-
|
|
27473
|
-
|
|
27660
|
+
async function pathExists(filePath2) {
|
|
27661
|
+
try {
|
|
27662
|
+
await access(filePath2);
|
|
27663
|
+
return true;
|
|
27664
|
+
} catch {
|
|
27665
|
+
return false;
|
|
27474
27666
|
}
|
|
27475
27667
|
}
|
|
27668
|
+
async function ensureKitsDir() {
|
|
27669
|
+
await mkdir(getUserKitsDir(), { recursive: true });
|
|
27670
|
+
}
|
|
27476
27671
|
function getKitFilePath(id) {
|
|
27477
27672
|
if (!isSafeAutomationKitId(id)) return null;
|
|
27478
27673
|
const kitsDir = path$1.resolve(getUserKitsDir());
|
|
@@ -27484,12 +27679,12 @@ function isValidKit(value) {
|
|
|
27484
27679
|
const k = value;
|
|
27485
27680
|
return typeof k.id === "string" && isSafeAutomationKitId(k.id) && typeof k.name === "string" && k.name.length > 0 && typeof k.description === "string" && typeof k.category === "string" && VALID_KIT_CATEGORIES.has(k.category) && typeof k.icon === "string" && typeof k.promptTemplate === "string" && k.promptTemplate.length > 0 && Array.isArray(k.inputs);
|
|
27486
27681
|
}
|
|
27487
|
-
function getInstalledKits() {
|
|
27488
|
-
ensureKitsDir();
|
|
27682
|
+
async function getInstalledKits() {
|
|
27683
|
+
await ensureKitsDir();
|
|
27489
27684
|
const dir = getUserKitsDir();
|
|
27490
27685
|
let files;
|
|
27491
27686
|
try {
|
|
27492
|
-
files =
|
|
27687
|
+
files = (await readdir(dir)).filter((f) => f.endsWith(".kit.json"));
|
|
27493
27688
|
} catch (err) {
|
|
27494
27689
|
logger$9.warn("Failed to read kit directory:", err);
|
|
27495
27690
|
return [];
|
|
@@ -27497,7 +27692,7 @@ function getInstalledKits() {
|
|
|
27497
27692
|
const kits = [];
|
|
27498
27693
|
for (const file of files) {
|
|
27499
27694
|
try {
|
|
27500
|
-
const raw =
|
|
27695
|
+
const raw = await readFile(path$1.join(dir, file), "utf-8");
|
|
27501
27696
|
const parsed = JSON.parse(raw);
|
|
27502
27697
|
if (isValidKit(parsed)) {
|
|
27503
27698
|
kits.push(parsed);
|
|
@@ -27521,7 +27716,7 @@ async function installKitFromFile() {
|
|
|
27521
27716
|
}
|
|
27522
27717
|
let raw;
|
|
27523
27718
|
try {
|
|
27524
|
-
raw =
|
|
27719
|
+
raw = await readFile(filePaths[0], "utf-8");
|
|
27525
27720
|
} catch (err) {
|
|
27526
27721
|
logger$9.warn("Failed to read selected kit file:", err);
|
|
27527
27722
|
return errorResult("Could not read the selected file.");
|
|
@@ -27543,20 +27738,20 @@ async function installKitFromFile() {
|
|
|
27543
27738
|
`Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
|
|
27544
27739
|
);
|
|
27545
27740
|
}
|
|
27546
|
-
ensureKitsDir();
|
|
27741
|
+
await ensureKitsDir();
|
|
27547
27742
|
const dest = getKitFilePath(parsed.id);
|
|
27548
27743
|
if (!dest) {
|
|
27549
27744
|
return errorResult("Kit id contains unsupported characters.");
|
|
27550
27745
|
}
|
|
27551
27746
|
try {
|
|
27552
|
-
|
|
27747
|
+
await writeFile(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
27553
27748
|
} catch (err) {
|
|
27554
27749
|
logger$9.warn("Failed to save kit file:", err);
|
|
27555
27750
|
return errorResult("Failed to save the kit file.");
|
|
27556
27751
|
}
|
|
27557
27752
|
return okResult({ kit: parsed });
|
|
27558
27753
|
}
|
|
27559
|
-
function uninstallKit(id, scheduledKitIds) {
|
|
27754
|
+
async function uninstallKit(id, scheduledKitIds) {
|
|
27560
27755
|
if (BUNDLED_KIT_IDS.has(id)) {
|
|
27561
27756
|
return errorResult("Built-in kits cannot be removed.");
|
|
27562
27757
|
}
|
|
@@ -27565,16 +27760,16 @@ function uninstallKit(id, scheduledKitIds) {
|
|
|
27565
27760
|
"This kit has active scheduled jobs. Delete or reassign them first."
|
|
27566
27761
|
);
|
|
27567
27762
|
}
|
|
27568
|
-
ensureKitsDir();
|
|
27763
|
+
await ensureKitsDir();
|
|
27569
27764
|
const target = getKitFilePath(id);
|
|
27570
27765
|
if (!target) {
|
|
27571
27766
|
return errorResult("Kit id contains unsupported characters.");
|
|
27572
27767
|
}
|
|
27573
|
-
if (!
|
|
27768
|
+
if (!await pathExists(target)) {
|
|
27574
27769
|
return errorResult("Kit not found.");
|
|
27575
27770
|
}
|
|
27576
27771
|
try {
|
|
27577
|
-
|
|
27772
|
+
await unlink(target);
|
|
27578
27773
|
return okResult();
|
|
27579
27774
|
} catch (err) {
|
|
27580
27775
|
logger$9.warn("Failed to remove kit file:", err);
|
|
@@ -27910,17 +28105,20 @@ function registerSystemHandlers(windowState2, sendToRendererViews) {
|
|
|
27910
28105
|
layoutViews(windowState2);
|
|
27911
28106
|
return clamped;
|
|
27912
28107
|
});
|
|
27913
|
-
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, (event) => {
|
|
28108
|
+
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, async (event) => {
|
|
27914
28109
|
assertTrustedIpcSender(event);
|
|
27915
|
-
return getInstalledKits();
|
|
28110
|
+
return await getInstalledKits();
|
|
27916
28111
|
});
|
|
27917
28112
|
electron.ipcMain.handle(Channels.AUTOMATION_INSTALL_FROM_FILE, async (event) => {
|
|
27918
28113
|
assertTrustedIpcSender(event);
|
|
27919
28114
|
return await installKitFromFile();
|
|
27920
28115
|
});
|
|
27921
|
-
electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, (event, id) => {
|
|
28116
|
+
electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, async (event, id) => {
|
|
27922
28117
|
assertTrustedIpcSender(event);
|
|
27923
|
-
return uninstallKit(
|
|
28118
|
+
return await uninstallKit(
|
|
28119
|
+
parseIpc(KitIdSchema, id, "id"),
|
|
28120
|
+
getScheduledKitIds()
|
|
28121
|
+
);
|
|
27924
28122
|
});
|
|
27925
28123
|
electron.ipcMain.handle(Channels.CLEAR_BROWSING_DATA, async (event, options) => {
|
|
27926
28124
|
assertTrustedIpcSender(event);
|
|
@@ -27984,6 +28182,25 @@ function registerSystemHandlers(windowState2, sendToRendererViews) {
|
|
|
27984
28182
|
return togglePictureInPicture(tabManager);
|
|
27985
28183
|
});
|
|
27986
28184
|
}
|
|
28185
|
+
const FolderNameSchema = zod.z.string().min(1);
|
|
28186
|
+
const BookmarkUrlSchema = zod.z.string().min(1);
|
|
28187
|
+
const BookmarkIdSchema = zod.z.string().min(1);
|
|
28188
|
+
const OptionalStringSchema = zod.z.string().optional();
|
|
28189
|
+
const OptionalStringArraySchema = zod.z.array(zod.z.string()).optional();
|
|
28190
|
+
const OptionalRecordSchema = zod.z.record(zod.z.string(), zod.z.string()).optional();
|
|
28191
|
+
const OptionalBooleanSchema = zod.z.boolean().optional();
|
|
28192
|
+
const BookmarkUpdateSchema = zod.z.object({
|
|
28193
|
+
title: zod.z.string().optional(),
|
|
28194
|
+
note: zod.z.string().optional(),
|
|
28195
|
+
folderId: zod.z.string().optional(),
|
|
28196
|
+
intent: zod.z.string().optional(),
|
|
28197
|
+
expectedContent: zod.z.string().optional(),
|
|
28198
|
+
keyFields: zod.z.array(zod.z.string()).optional(),
|
|
28199
|
+
agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional()
|
|
28200
|
+
});
|
|
28201
|
+
const ExportOptionsSchema = zod.z.object({
|
|
28202
|
+
includeNotes: zod.z.boolean().optional()
|
|
28203
|
+
}).optional();
|
|
27987
28204
|
function getSafeBookmarkExportName(name) {
|
|
27988
28205
|
const safeName = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
27989
28206
|
return safeName || "folder";
|
|
@@ -27997,23 +28214,33 @@ function registerBookmarkHandlers() {
|
|
|
27997
28214
|
Channels.FOLDER_CREATE,
|
|
27998
28215
|
(event, name, summary) => {
|
|
27999
28216
|
assertTrustedIpcSender(event);
|
|
28217
|
+
const validatedName = parseIpc(FolderNameSchema, name, "name");
|
|
28218
|
+
const validatedSummary = parseIpc(OptionalStringSchema, summary, "summary");
|
|
28000
28219
|
trackBookmarkAction("folder_create");
|
|
28001
|
-
return createFolderWithSummary(
|
|
28220
|
+
return createFolderWithSummary(validatedName, validatedSummary);
|
|
28002
28221
|
}
|
|
28003
28222
|
);
|
|
28004
28223
|
electron.ipcMain.handle(
|
|
28005
28224
|
Channels.BOOKMARK_SAVE,
|
|
28006
28225
|
(event, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
|
|
28007
28226
|
assertTrustedIpcSender(event);
|
|
28227
|
+
const validatedUrl = parseIpc(BookmarkUrlSchema, url, "url");
|
|
28228
|
+
const validatedTitle = parseIpc(zod.z.string(), title, "title");
|
|
28229
|
+
const validatedFolderId = parseIpc(OptionalStringSchema, folderId, "folderId");
|
|
28230
|
+
const validatedNote = parseIpc(OptionalStringSchema, note, "note");
|
|
28231
|
+
const validatedIntent = parseIpc(OptionalStringSchema, intent, "intent");
|
|
28232
|
+
const validatedExpectedContent = parseIpc(OptionalStringSchema, expectedContent, "expectedContent");
|
|
28233
|
+
const validatedKeyFields = parseIpc(OptionalStringArraySchema, keyFields, "keyFields");
|
|
28234
|
+
const validatedAgentHints = parseIpc(OptionalRecordSchema, agentHints, "agentHints");
|
|
28008
28235
|
trackBookmarkAction("save");
|
|
28009
|
-
const result = saveBookmarkWithPolicy(
|
|
28236
|
+
const result = saveBookmarkWithPolicy(validatedUrl, validatedTitle, validatedFolderId, validatedNote, {
|
|
28010
28237
|
onDuplicate: "update",
|
|
28011
28238
|
extra: {
|
|
28012
28239
|
...normalizeBookmarkMetadata({
|
|
28013
|
-
intent,
|
|
28014
|
-
expectedContent,
|
|
28015
|
-
keyFields,
|
|
28016
|
-
agentHints
|
|
28240
|
+
intent: validatedIntent,
|
|
28241
|
+
expectedContent: validatedExpectedContent,
|
|
28242
|
+
keyFields: validatedKeyFields,
|
|
28243
|
+
agentHints: validatedAgentHints
|
|
28017
28244
|
})
|
|
28018
28245
|
}
|
|
28019
28246
|
});
|
|
@@ -28027,19 +28254,23 @@ function registerBookmarkHandlers() {
|
|
|
28027
28254
|
Channels.BOOKMARK_UPDATE,
|
|
28028
28255
|
(event, id, updates) => {
|
|
28029
28256
|
assertTrustedIpcSender(event);
|
|
28257
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28258
|
+
const validatedUpdates = parseIpc(BookmarkUpdateSchema, updates, "updates");
|
|
28030
28259
|
trackBookmarkAction("save");
|
|
28031
|
-
return updateBookmark(
|
|
28260
|
+
return updateBookmark(validatedId, validatedUpdates);
|
|
28032
28261
|
}
|
|
28033
28262
|
);
|
|
28034
28263
|
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (event, id) => {
|
|
28035
28264
|
assertTrustedIpcSender(event);
|
|
28265
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28036
28266
|
trackBookmarkAction("remove");
|
|
28037
|
-
return removeBookmark(
|
|
28267
|
+
return removeBookmark(validatedId);
|
|
28038
28268
|
});
|
|
28039
28269
|
electron.ipcMain.handle(
|
|
28040
28270
|
Channels.BOOKMARKS_EXPORT_HTML,
|
|
28041
28271
|
async (event, options) => {
|
|
28042
28272
|
assertTrustedIpcSender(event);
|
|
28273
|
+
const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
|
|
28043
28274
|
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
28044
28275
|
title: "Export Bookmarks",
|
|
28045
28276
|
defaultPath: "vessel-bookmarks.html",
|
|
@@ -28047,7 +28278,7 @@ function registerBookmarkHandlers() {
|
|
|
28047
28278
|
});
|
|
28048
28279
|
if (canceled || !filePath2) return null;
|
|
28049
28280
|
const content = exportBookmarksHtml({
|
|
28050
|
-
includeNotes:
|
|
28281
|
+
includeNotes: validatedOptions?.includeNotes ?? false
|
|
28051
28282
|
});
|
|
28052
28283
|
await fs.promises.writeFile(filePath2, content, "utf-8");
|
|
28053
28284
|
trackBookmarkAction("export");
|
|
@@ -28077,7 +28308,9 @@ function registerBookmarkHandlers() {
|
|
|
28077
28308
|
Channels.FOLDER_EXPORT_HTML,
|
|
28078
28309
|
async (event, folderId, options) => {
|
|
28079
28310
|
assertTrustedIpcSender(event);
|
|
28080
|
-
const
|
|
28311
|
+
const validatedFolderId = parseIpc(BookmarkIdSchema, folderId, "folderId");
|
|
28312
|
+
const validatedOptions = parseIpc(ExportOptionsSchema, options, "options");
|
|
28313
|
+
const folder = getFolder(validatedFolderId);
|
|
28081
28314
|
if (!folder) return null;
|
|
28082
28315
|
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
28083
28316
|
title: `Export ${folder.name}`,
|
|
@@ -28085,8 +28318,8 @@ function registerBookmarkHandlers() {
|
|
|
28085
28318
|
filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
|
|
28086
28319
|
});
|
|
28087
28320
|
if (canceled || !filePath2) return null;
|
|
28088
|
-
const result = exportBookmarkFolderHtml(
|
|
28089
|
-
includeNotes:
|
|
28321
|
+
const result = exportBookmarkFolderHtml(validatedFolderId, {
|
|
28322
|
+
includeNotes: validatedOptions?.includeNotes ?? true
|
|
28090
28323
|
});
|
|
28091
28324
|
if (!result) return null;
|
|
28092
28325
|
await fs.promises.writeFile(filePath2, result.content, "utf-8");
|
|
@@ -28127,14 +28360,23 @@ function registerBookmarkHandlers() {
|
|
|
28127
28360
|
});
|
|
28128
28361
|
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (event, id, deleteContents) => {
|
|
28129
28362
|
assertTrustedIpcSender(event);
|
|
28363
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28364
|
+
const validatedDeleteContents = parseIpc(
|
|
28365
|
+
OptionalBooleanSchema,
|
|
28366
|
+
deleteContents,
|
|
28367
|
+
"deleteContents"
|
|
28368
|
+
);
|
|
28130
28369
|
trackBookmarkAction("folder_remove");
|
|
28131
|
-
return removeFolder(
|
|
28370
|
+
return removeFolder(validatedId, validatedDeleteContents ?? false);
|
|
28132
28371
|
});
|
|
28133
28372
|
electron.ipcMain.handle(
|
|
28134
28373
|
Channels.FOLDER_RENAME,
|
|
28135
28374
|
(event, id, newName, summary) => {
|
|
28136
28375
|
assertTrustedIpcSender(event);
|
|
28137
|
-
|
|
28376
|
+
const validatedId = parseIpc(BookmarkIdSchema, id, "id");
|
|
28377
|
+
const validatedName = parseIpc(FolderNameSchema, newName, "newName");
|
|
28378
|
+
const validatedSummary = parseIpc(OptionalStringSchema, summary, "summary");
|
|
28379
|
+
return renameFolder(validatedId, validatedName, validatedSummary);
|
|
28138
28380
|
}
|
|
28139
28381
|
);
|
|
28140
28382
|
}
|
|
@@ -28399,6 +28641,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
28399
28641
|
return result;
|
|
28400
28642
|
});
|
|
28401
28643
|
}
|
|
28644
|
+
const SessionNameSchema = zod.z.string().min(1);
|
|
28402
28645
|
function registerSessionHandlers(tabManager) {
|
|
28403
28646
|
electron.ipcMain.handle(Channels.SESSION_LIST, (event) => {
|
|
28404
28647
|
assertTrustedIpcSender(event);
|
|
@@ -28406,18 +28649,18 @@ function registerSessionHandlers(tabManager) {
|
|
|
28406
28649
|
});
|
|
28407
28650
|
electron.ipcMain.handle(Channels.SESSION_SAVE, async (event, name) => {
|
|
28408
28651
|
assertTrustedIpcSender(event);
|
|
28409
|
-
|
|
28410
|
-
return await saveNamedSession(tabManager,
|
|
28652
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28653
|
+
return await saveNamedSession(tabManager, validatedName);
|
|
28411
28654
|
});
|
|
28412
28655
|
electron.ipcMain.handle(Channels.SESSION_LOAD, async (event, name) => {
|
|
28413
28656
|
assertTrustedIpcSender(event);
|
|
28414
|
-
|
|
28415
|
-
return await loadNamedSession(tabManager,
|
|
28657
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28658
|
+
return await loadNamedSession(tabManager, validatedName);
|
|
28416
28659
|
});
|
|
28417
28660
|
electron.ipcMain.handle(Channels.SESSION_DELETE, (event, name) => {
|
|
28418
28661
|
assertTrustedIpcSender(event);
|
|
28419
|
-
|
|
28420
|
-
return deleteNamedSession(
|
|
28662
|
+
const validatedName = parseIpc(SessionNameSchema, name, "name");
|
|
28663
|
+
return deleteNamedSession(validatedName);
|
|
28421
28664
|
});
|
|
28422
28665
|
}
|
|
28423
28666
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -30507,7 +30750,8 @@ ${progress}
|
|
|
30507
30750
|
const raw = fs$1.readFileSync(getRuntimeStatePath(), "utf-8");
|
|
30508
30751
|
const parsed = JSON.parse(raw);
|
|
30509
30752
|
return sanitizePersistence(parsed);
|
|
30510
|
-
} catch {
|
|
30753
|
+
} catch (err) {
|
|
30754
|
+
logger$3.warn("Failed to load persisted runtime state, starting fresh:", err);
|
|
30511
30755
|
return sanitizePersistence(null);
|
|
30512
30756
|
}
|
|
30513
30757
|
}
|
|
@@ -30766,7 +31010,7 @@ function findIconBase64() {
|
|
|
30766
31010
|
return "";
|
|
30767
31011
|
}
|
|
30768
31012
|
function buildSplashHTML(iconSrc) {
|
|
30769
|
-
const imgTag = iconSrc ? `<img class="logo" src="${iconSrc}" alt="" />` : `<div class="logo-fallback">V</div>`;
|
|
31013
|
+
const imgTag = iconSrc ? `<img class="logo" src="${escapeHtml(iconSrc)}" alt="" />` : `<div class="logo-fallback">V</div>`;
|
|
30770
31014
|
return `<!DOCTYPE html>
|
|
30771
31015
|
<html>
|
|
30772
31016
|
<head>
|
|
@@ -30906,7 +31150,8 @@ function createSplashWindow() {
|
|
|
30906
31150
|
splash.once("closed", () => {
|
|
30907
31151
|
try {
|
|
30908
31152
|
fs$1.rmSync(tmpDir, { recursive: true, force: true });
|
|
30909
|
-
} catch {
|
|
31153
|
+
} catch (err) {
|
|
31154
|
+
logger$1.debug("Failed to clean up splash temp dir:", err);
|
|
30910
31155
|
}
|
|
30911
31156
|
});
|
|
30912
31157
|
fs$1.writeFileSync(tmpPath, html, "utf-8");
|