@quanta-intellect/vessel-browser 0.1.88 → 0.1.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/main/index.js
CHANGED
|
@@ -6,11 +6,12 @@ const fs = require("fs");
|
|
|
6
6
|
const crypto$1 = require("crypto");
|
|
7
7
|
const Anthropic = require("@anthropic-ai/sdk");
|
|
8
8
|
const OpenAI = require("openai");
|
|
9
|
+
const http = require("http");
|
|
9
10
|
const path$1 = require("node:path");
|
|
10
11
|
const node_module = require("node:module");
|
|
11
12
|
const zod = require("zod");
|
|
12
13
|
const crypto$2 = require("node:crypto");
|
|
13
|
-
const http = require("node:http");
|
|
14
|
+
const http$1 = require("node:http");
|
|
14
15
|
const os = require("node:os");
|
|
15
16
|
const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
16
17
|
const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
@@ -77,7 +78,8 @@ const defaults = {
|
|
|
77
78
|
};
|
|
78
79
|
const SAVE_DEBOUNCE_MS$6 = 150;
|
|
79
80
|
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
80
|
-
const
|
|
81
|
+
const CODEX_TOKENS_FILENAME = "vessel-codex-tokens";
|
|
82
|
+
const logger$n = createLogger("Settings");
|
|
81
83
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
82
84
|
let settings = null;
|
|
83
85
|
let settingsIssues = [];
|
|
@@ -115,15 +117,15 @@ function readStoredProviderSecret() {
|
|
|
115
117
|
return null;
|
|
116
118
|
}
|
|
117
119
|
function writeStoredProviderSecret(secret) {
|
|
118
|
-
const
|
|
119
|
-
fs.mkdirSync(path.dirname(
|
|
120
|
+
const filePath2 = getChatProviderSecretPath();
|
|
121
|
+
fs.mkdirSync(path.dirname(filePath2), { recursive: true });
|
|
120
122
|
const payload = JSON.stringify(secret);
|
|
121
123
|
if (canUseSafeStorage$1()) {
|
|
122
124
|
const encrypted = electron.safeStorage.encryptString(payload);
|
|
123
|
-
fs.writeFileSync(
|
|
125
|
+
fs.writeFileSync(filePath2, encrypted, { mode: 384 });
|
|
124
126
|
return;
|
|
125
127
|
}
|
|
126
|
-
fs.writeFileSync(
|
|
128
|
+
fs.writeFileSync(filePath2, payload, { mode: 384 });
|
|
127
129
|
}
|
|
128
130
|
function clearStoredProviderSecret() {
|
|
129
131
|
try {
|
|
@@ -131,6 +133,38 @@ function clearStoredProviderSecret() {
|
|
|
131
133
|
} catch {
|
|
132
134
|
}
|
|
133
135
|
}
|
|
136
|
+
function getCodexTokensPath() {
|
|
137
|
+
return path.join(getUserDataPath(), CODEX_TOKENS_FILENAME);
|
|
138
|
+
}
|
|
139
|
+
function readStoredCodexTokens() {
|
|
140
|
+
try {
|
|
141
|
+
const raw = fs.readFileSync(getCodexTokensPath());
|
|
142
|
+
const decoded = canUseSafeStorage$1() && electron.safeStorage.decryptString ? electron.safeStorage.decryptString(raw) : raw.toString("utf-8");
|
|
143
|
+
const parsed = JSON.parse(decoded);
|
|
144
|
+
if (parsed && typeof parsed === "object" && typeof parsed.accessToken === "string" && typeof parsed.refreshToken === "string") {
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function writeStoredCodexTokens(tokens) {
|
|
152
|
+
const filePath2 = getCodexTokensPath();
|
|
153
|
+
fs.mkdirSync(path.dirname(filePath2), { recursive: true });
|
|
154
|
+
const payload = JSON.stringify(tokens);
|
|
155
|
+
if (canUseSafeStorage$1()) {
|
|
156
|
+
const encrypted = electron.safeStorage.encryptString(payload);
|
|
157
|
+
fs.writeFileSync(filePath2, encrypted, { mode: 384 });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
fs.writeFileSync(filePath2, payload, { mode: 384 });
|
|
161
|
+
}
|
|
162
|
+
function clearStoredCodexTokens() {
|
|
163
|
+
try {
|
|
164
|
+
fs.unlinkSync(getCodexTokensPath());
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
134
168
|
function mergeChatProviderSecret(provider) {
|
|
135
169
|
if (!provider) return null;
|
|
136
170
|
const stored = readStoredProviderSecret();
|
|
@@ -157,12 +191,14 @@ function buildPersistedSettings(source) {
|
|
|
157
191
|
}
|
|
158
192
|
function getRendererSettings() {
|
|
159
193
|
const current = loadSettings();
|
|
194
|
+
const provider = current.chatProvider;
|
|
195
|
+
const hasCodexTokens = provider?.id === "openai_codex" && readStoredCodexTokens() !== null;
|
|
160
196
|
return {
|
|
161
197
|
...current,
|
|
162
|
-
chatProvider:
|
|
163
|
-
...
|
|
198
|
+
chatProvider: provider ? {
|
|
199
|
+
...provider,
|
|
164
200
|
apiKey: "",
|
|
165
|
-
hasApiKey: Boolean(
|
|
201
|
+
hasApiKey: Boolean(provider.apiKey) || hasCodexTokens
|
|
166
202
|
} : null
|
|
167
203
|
};
|
|
168
204
|
}
|
|
@@ -234,7 +270,7 @@ function persistNow() {
|
|
|
234
270
|
getSettingsPath(),
|
|
235
271
|
JSON.stringify(buildPersistedSettings(settings), null, 2)
|
|
236
272
|
)
|
|
237
|
-
).catch((err) => logger$
|
|
273
|
+
).catch((err) => logger$n.error("Failed to save settings:", err));
|
|
238
274
|
}
|
|
239
275
|
function saveSettings() {
|
|
240
276
|
saveDirty = true;
|
|
@@ -246,11 +282,11 @@ function saveSettings() {
|
|
|
246
282
|
}
|
|
247
283
|
}, SAVE_DEBOUNCE_MS$6);
|
|
248
284
|
}
|
|
249
|
-
function setSetting(
|
|
285
|
+
function setSetting(key2, value) {
|
|
250
286
|
loadSettings();
|
|
251
|
-
if (
|
|
287
|
+
if (key2 === "mcpPort") {
|
|
252
288
|
settings.mcpPort = sanitizePort(value);
|
|
253
|
-
} else if (
|
|
289
|
+
} else if (key2 === "chatProvider") {
|
|
254
290
|
const nextProvider = value;
|
|
255
291
|
if (!nextProvider) {
|
|
256
292
|
clearStoredProviderSecret();
|
|
@@ -278,7 +314,7 @@ function setSetting(key, value) {
|
|
|
278
314
|
};
|
|
279
315
|
}
|
|
280
316
|
} else {
|
|
281
|
-
settings[
|
|
317
|
+
settings[key2] = value;
|
|
282
318
|
}
|
|
283
319
|
saveSettings();
|
|
284
320
|
return { ...settings };
|
|
@@ -341,7 +377,7 @@ function assertPermittedNavigationURL(url) {
|
|
|
341
377
|
}
|
|
342
378
|
const MAX_CUSTOM_HISTORY = 50;
|
|
343
379
|
const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
|
|
344
|
-
const logger$
|
|
380
|
+
const logger$m = createLogger("Tab");
|
|
345
381
|
const sessionCertExceptions = /* @__PURE__ */ new WeakMap();
|
|
346
382
|
const sessionsWithVerifyProc = /* @__PURE__ */ new WeakSet();
|
|
347
383
|
const CERT_VERIFY_TRUST = 0;
|
|
@@ -407,7 +443,7 @@ class Tab {
|
|
|
407
443
|
guardedLoadURL(url, options) {
|
|
408
444
|
const blockReason = this.getNavigationBlockReason(url);
|
|
409
445
|
if (blockReason) {
|
|
410
|
-
logger$
|
|
446
|
+
logger$m.warn(blockReason);
|
|
411
447
|
return blockReason;
|
|
412
448
|
}
|
|
413
449
|
void this.view.webContents.loadURL(url, options);
|
|
@@ -454,27 +490,27 @@ class Tab {
|
|
|
454
490
|
this.view.webContents.on("before-input-event", (event, input) => {
|
|
455
491
|
if (!input.control && !input.meta) return;
|
|
456
492
|
if (input.type !== "keyDown") return;
|
|
457
|
-
const
|
|
493
|
+
const key2 = input.key.toLowerCase();
|
|
458
494
|
const wc = this.view.webContents;
|
|
459
|
-
if (
|
|
495
|
+
if (key2 === "+" || key2 === "=") {
|
|
460
496
|
this.zoomIn();
|
|
461
497
|
event.preventDefault();
|
|
462
498
|
return;
|
|
463
499
|
}
|
|
464
|
-
if (
|
|
500
|
+
if (key2 === "-") {
|
|
465
501
|
this.zoomOut();
|
|
466
502
|
event.preventDefault();
|
|
467
503
|
return;
|
|
468
504
|
}
|
|
469
|
-
if (
|
|
505
|
+
if (key2 === "0") {
|
|
470
506
|
this.zoomReset();
|
|
471
507
|
event.preventDefault();
|
|
472
508
|
return;
|
|
473
509
|
}
|
|
474
|
-
if (
|
|
475
|
-
else if (
|
|
476
|
-
else if (
|
|
477
|
-
else if (
|
|
510
|
+
if (key2 === "c") wc.copy();
|
|
511
|
+
else if (key2 === "v") wc.paste();
|
|
512
|
+
else if (key2 === "x") wc.cut();
|
|
513
|
+
else if (key2 === "a") wc.selectAll();
|
|
478
514
|
});
|
|
479
515
|
this.setupListeners();
|
|
480
516
|
if (url) {
|
|
@@ -491,7 +527,7 @@ class Tab {
|
|
|
491
527
|
wc.setWindowOpenHandler(({ url, disposition }) => {
|
|
492
528
|
const error = this.getNavigationBlockReason(url);
|
|
493
529
|
if (error) {
|
|
494
|
-
logger$
|
|
530
|
+
logger$m.warn(error);
|
|
495
531
|
return { action: "deny" };
|
|
496
532
|
}
|
|
497
533
|
this.onOpenUrl?.({
|
|
@@ -505,7 +541,7 @@ class Tab {
|
|
|
505
541
|
const error = this.getNavigationBlockReason(url);
|
|
506
542
|
if (!error) return;
|
|
507
543
|
event.preventDefault();
|
|
508
|
-
logger$
|
|
544
|
+
logger$m.warn(`${context}: ${error}`);
|
|
509
545
|
};
|
|
510
546
|
wc.on("will-navigate", (event, url) => {
|
|
511
547
|
blockNavigation(event, url, "Blocked top-level navigation");
|
|
@@ -589,7 +625,7 @@ class Tab {
|
|
|
589
625
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
|
|
590
626
|
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
|
591
627
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
592
|
-
`).catch((err) => logger$
|
|
628
|
+
`).catch((err) => logger$m.warn("Failed to inject scrollbar CSS:", err));
|
|
593
629
|
});
|
|
594
630
|
wc.on("page-favicon-updated", (_, favicons) => {
|
|
595
631
|
this._state.favicon = favicons[0] || "";
|
|
@@ -625,7 +661,7 @@ class Tab {
|
|
|
625
661
|
).then((highlightedText) => {
|
|
626
662
|
this.buildContextMenu(wc, params, highlightedText.trim());
|
|
627
663
|
}).catch((err) => {
|
|
628
|
-
logger$
|
|
664
|
+
logger$m.warn("Failed to inspect highlighted text for context menu:", err);
|
|
629
665
|
this.buildContextMenu(wc, params, "");
|
|
630
666
|
});
|
|
631
667
|
});
|
|
@@ -748,8 +784,8 @@ class Tab {
|
|
|
748
784
|
}
|
|
749
785
|
if (postBody) {
|
|
750
786
|
const params = new URLSearchParams();
|
|
751
|
-
for (const [
|
|
752
|
-
params.set(
|
|
787
|
+
for (const [key2, value] of Object.entries(postBody)) {
|
|
788
|
+
params.set(key2, value);
|
|
753
789
|
}
|
|
754
790
|
return this.guardedLoadURL(url, {
|
|
755
791
|
method: "POST",
|
|
@@ -826,7 +862,7 @@ class Tab {
|
|
|
826
862
|
"document.documentElement.outerHTML"
|
|
827
863
|
);
|
|
828
864
|
} catch (err) {
|
|
829
|
-
logger$
|
|
865
|
+
logger$m.warn("Failed to retrieve page source:", err);
|
|
830
866
|
return;
|
|
831
867
|
}
|
|
832
868
|
const escaped = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -954,7 +990,7 @@ class Tab {
|
|
|
954
990
|
document.addEventListener('mouseup', window.__vesselHighlightHandler);
|
|
955
991
|
}
|
|
956
992
|
})()
|
|
957
|
-
`).catch((err) => logger$
|
|
993
|
+
`).catch((err) => logger$m.warn("Failed to inject highlight listener:", err));
|
|
958
994
|
} else {
|
|
959
995
|
void wc.executeJavaScript(`
|
|
960
996
|
(function() {
|
|
@@ -965,7 +1001,7 @@ class Tab {
|
|
|
965
1001
|
delete window.__vesselHighlightHandler;
|
|
966
1002
|
}
|
|
967
1003
|
})()
|
|
968
|
-
`).catch((err) => logger$
|
|
1004
|
+
`).catch((err) => logger$m.warn("Failed to remove highlight listener:", err));
|
|
969
1005
|
}
|
|
970
1006
|
}
|
|
971
1007
|
get webContentsId() {
|
|
@@ -1002,7 +1038,7 @@ const SEARCH_ENGINE_PRESETS = {
|
|
|
1002
1038
|
ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
|
|
1003
1039
|
kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
|
|
1004
1040
|
};
|
|
1005
|
-
const logger$
|
|
1041
|
+
const logger$l = createLogger("JsonPersistence");
|
|
1006
1042
|
function canUseSafeStorage() {
|
|
1007
1043
|
try {
|
|
1008
1044
|
return electron.safeStorage.isEncryptionAvailable();
|
|
@@ -1023,22 +1059,22 @@ function encodeStoredData(payload, secure) {
|
|
|
1023
1059
|
return payload;
|
|
1024
1060
|
}
|
|
1025
1061
|
function loadJsonFile({
|
|
1026
|
-
filePath,
|
|
1062
|
+
filePath: filePath2,
|
|
1027
1063
|
fallback,
|
|
1028
|
-
parse,
|
|
1064
|
+
parse: parse2,
|
|
1029
1065
|
secure = false
|
|
1030
1066
|
}) {
|
|
1031
1067
|
try {
|
|
1032
|
-
const raw = fs.readFileSync(
|
|
1068
|
+
const raw = fs.readFileSync(filePath2);
|
|
1033
1069
|
const decoded = decodeStoredData(raw, secure);
|
|
1034
|
-
return
|
|
1070
|
+
return parse2(JSON.parse(decoded));
|
|
1035
1071
|
} catch {
|
|
1036
1072
|
return fallback;
|
|
1037
1073
|
}
|
|
1038
1074
|
}
|
|
1039
1075
|
function createDebouncedJsonPersistence({
|
|
1040
1076
|
debounceMs,
|
|
1041
|
-
filePath,
|
|
1077
|
+
filePath: filePath2,
|
|
1042
1078
|
getValue,
|
|
1043
1079
|
logLabel,
|
|
1044
1080
|
resetOnSchedule = false,
|
|
@@ -1061,13 +1097,13 @@ function createDebouncedJsonPersistence({
|
|
|
1061
1097
|
2
|
|
1062
1098
|
);
|
|
1063
1099
|
const data = encodeStoredData(payload, secure);
|
|
1064
|
-
await fs.promises.mkdir(path.dirname(
|
|
1100
|
+
await fs.promises.mkdir(path.dirname(filePath2), { recursive: true }).then(
|
|
1065
1101
|
() => fs.promises.writeFile(
|
|
1066
|
-
|
|
1102
|
+
filePath2,
|
|
1067
1103
|
data,
|
|
1068
1104
|
typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
|
|
1069
1105
|
)
|
|
1070
|
-
).catch((err) => logger$
|
|
1106
|
+
).catch((err) => logger$l.error(`Failed to save ${logLabel}:`, err));
|
|
1071
1107
|
};
|
|
1072
1108
|
const schedule = () => {
|
|
1073
1109
|
saveDirty2 = true;
|
|
@@ -1089,7 +1125,7 @@ function createDebouncedJsonPersistence({
|
|
|
1089
1125
|
flush: flush2
|
|
1090
1126
|
};
|
|
1091
1127
|
}
|
|
1092
|
-
let state$
|
|
1128
|
+
let state$5 = null;
|
|
1093
1129
|
const listeners$2 = /* @__PURE__ */ new Set();
|
|
1094
1130
|
const SAVE_DEBOUNCE_MS$5 = 250;
|
|
1095
1131
|
function getHighlightsPath() {
|
|
@@ -1099,19 +1135,19 @@ function createPersistence$1() {
|
|
|
1099
1135
|
return createDebouncedJsonPersistence({
|
|
1100
1136
|
debounceMs: SAVE_DEBOUNCE_MS$5,
|
|
1101
1137
|
filePath: getHighlightsPath(),
|
|
1102
|
-
getValue: () => state$
|
|
1138
|
+
getValue: () => state$5,
|
|
1103
1139
|
logLabel: "highlights",
|
|
1104
1140
|
resetOnSchedule: true
|
|
1105
1141
|
});
|
|
1106
1142
|
}
|
|
1107
|
-
let persistence$
|
|
1143
|
+
let persistence$7 = null;
|
|
1108
1144
|
function getPersistence$1() {
|
|
1109
|
-
persistence$
|
|
1110
|
-
return persistence$
|
|
1145
|
+
persistence$7 ??= createPersistence$1();
|
|
1146
|
+
return persistence$7;
|
|
1111
1147
|
}
|
|
1112
1148
|
function load$4() {
|
|
1113
|
-
if (state$
|
|
1114
|
-
state$
|
|
1149
|
+
if (state$5) return state$5;
|
|
1150
|
+
state$5 = loadJsonFile({
|
|
1115
1151
|
filePath: getHighlightsPath(),
|
|
1116
1152
|
fallback: { highlights: [] },
|
|
1117
1153
|
parse: (raw) => {
|
|
@@ -1121,14 +1157,14 @@ function load$4() {
|
|
|
1121
1157
|
};
|
|
1122
1158
|
}
|
|
1123
1159
|
});
|
|
1124
|
-
return state$
|
|
1160
|
+
return state$5;
|
|
1125
1161
|
}
|
|
1126
|
-
function save$
|
|
1162
|
+
function save$3() {
|
|
1127
1163
|
getPersistence$1().schedule();
|
|
1128
1164
|
}
|
|
1129
|
-
function emit$
|
|
1130
|
-
if (!state$
|
|
1131
|
-
const snapshot = { highlights: [...state$
|
|
1165
|
+
function emit$4() {
|
|
1166
|
+
if (!state$5) return;
|
|
1167
|
+
const snapshot = { highlights: [...state$5.highlights] };
|
|
1132
1168
|
for (const listener of listeners$2) {
|
|
1133
1169
|
listener(snapshot);
|
|
1134
1170
|
}
|
|
@@ -1144,12 +1180,12 @@ function normalizeUrl$1(rawUrl) {
|
|
|
1144
1180
|
}
|
|
1145
1181
|
function getState$2() {
|
|
1146
1182
|
load$4();
|
|
1147
|
-
return { highlights: [...state$
|
|
1183
|
+
return { highlights: [...state$5.highlights] };
|
|
1148
1184
|
}
|
|
1149
1185
|
function getHighlightsForUrl(url) {
|
|
1150
1186
|
load$4();
|
|
1151
1187
|
const normalized = normalizeUrl$1(url);
|
|
1152
|
-
return state$
|
|
1188
|
+
return state$5.highlights.filter((h) => h.url === normalized);
|
|
1153
1189
|
}
|
|
1154
1190
|
function addHighlight(url, selector, text, label, color, source) {
|
|
1155
1191
|
load$4();
|
|
@@ -1163,45 +1199,45 @@ function addHighlight(url, selector, text, label, color, source) {
|
|
|
1163
1199
|
source: source || void 0,
|
|
1164
1200
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1165
1201
|
};
|
|
1166
|
-
state$
|
|
1167
|
-
save$
|
|
1168
|
-
emit$
|
|
1202
|
+
state$5.highlights.push(highlight);
|
|
1203
|
+
save$3();
|
|
1204
|
+
emit$4();
|
|
1169
1205
|
return highlight;
|
|
1170
1206
|
}
|
|
1171
1207
|
function removeHighlight(id) {
|
|
1172
1208
|
load$4();
|
|
1173
|
-
const index = state$
|
|
1209
|
+
const index = state$5.highlights.findIndex((h) => h.id === id);
|
|
1174
1210
|
if (index === -1) return null;
|
|
1175
|
-
const [removed] = state$
|
|
1176
|
-
save$
|
|
1177
|
-
emit$
|
|
1211
|
+
const [removed] = state$5.highlights.splice(index, 1);
|
|
1212
|
+
save$3();
|
|
1213
|
+
emit$4();
|
|
1178
1214
|
return removed;
|
|
1179
1215
|
}
|
|
1180
1216
|
function findHighlightByText(url, text) {
|
|
1181
1217
|
load$4();
|
|
1182
1218
|
const normalized = normalizeUrl$1(url);
|
|
1183
|
-
return state$
|
|
1219
|
+
return state$5.highlights.find(
|
|
1184
1220
|
(h) => h.url === normalized && h.text && h.text === text
|
|
1185
1221
|
) ?? null;
|
|
1186
1222
|
}
|
|
1187
1223
|
function updateHighlightColor(id, color) {
|
|
1188
1224
|
load$4();
|
|
1189
|
-
const highlight = state$
|
|
1225
|
+
const highlight = state$5.highlights.find((h) => h.id === id);
|
|
1190
1226
|
if (!highlight) return null;
|
|
1191
1227
|
highlight.color = color;
|
|
1192
|
-
save$
|
|
1193
|
-
emit$
|
|
1228
|
+
save$3();
|
|
1229
|
+
emit$4();
|
|
1194
1230
|
return highlight;
|
|
1195
1231
|
}
|
|
1196
1232
|
function clearHighlightsForUrl(url) {
|
|
1197
1233
|
load$4();
|
|
1198
1234
|
const normalized = normalizeUrl$1(url);
|
|
1199
|
-
const before = state$
|
|
1200
|
-
state$
|
|
1201
|
-
const removed = before - state$
|
|
1235
|
+
const before = state$5.highlights.length;
|
|
1236
|
+
state$5.highlights = state$5.highlights.filter((h) => h.url !== normalized);
|
|
1237
|
+
const removed = before - state$5.highlights.length;
|
|
1202
1238
|
if (removed > 0) {
|
|
1203
|
-
save$
|
|
1204
|
-
emit$
|
|
1239
|
+
save$3();
|
|
1240
|
+
emit$4();
|
|
1205
1241
|
}
|
|
1206
1242
|
return removed;
|
|
1207
1243
|
}
|
|
@@ -1831,14 +1867,14 @@ function persistHighlight(url, text) {
|
|
|
1831
1867
|
}
|
|
1832
1868
|
const MAX_HISTORY_ENTRIES = 5e3;
|
|
1833
1869
|
const SAVE_DEBOUNCE_MS$4 = 250;
|
|
1834
|
-
let state$
|
|
1870
|
+
let state$4 = null;
|
|
1835
1871
|
const listeners$1 = /* @__PURE__ */ new Set();
|
|
1836
1872
|
function getHistoryPath() {
|
|
1837
1873
|
return path.join(electron.app.getPath("userData"), "vessel-history.json");
|
|
1838
1874
|
}
|
|
1839
1875
|
function load$3() {
|
|
1840
|
-
if (state$
|
|
1841
|
-
state$
|
|
1876
|
+
if (state$4) return state$4;
|
|
1877
|
+
state$4 = loadJsonFile({
|
|
1842
1878
|
filePath: getHistoryPath(),
|
|
1843
1879
|
fallback: { entries: [] },
|
|
1844
1880
|
parse: (raw) => {
|
|
@@ -1848,27 +1884,27 @@ function load$3() {
|
|
|
1848
1884
|
};
|
|
1849
1885
|
}
|
|
1850
1886
|
});
|
|
1851
|
-
return state$
|
|
1887
|
+
return state$4;
|
|
1852
1888
|
}
|
|
1853
|
-
const persistence$
|
|
1889
|
+
const persistence$6 = createDebouncedJsonPersistence({
|
|
1854
1890
|
debounceMs: SAVE_DEBOUNCE_MS$4,
|
|
1855
1891
|
filePath: getHistoryPath(),
|
|
1856
|
-
getValue: () => state$
|
|
1892
|
+
getValue: () => state$4,
|
|
1857
1893
|
logLabel: "history"
|
|
1858
1894
|
});
|
|
1859
|
-
function save$
|
|
1860
|
-
persistence$
|
|
1895
|
+
function save$2() {
|
|
1896
|
+
persistence$6.schedule();
|
|
1861
1897
|
}
|
|
1862
|
-
function emit$
|
|
1863
|
-
if (!state$
|
|
1864
|
-
const snapshot = { entries: [...state$
|
|
1898
|
+
function emit$3() {
|
|
1899
|
+
if (!state$4) return;
|
|
1900
|
+
const snapshot = { entries: [...state$4.entries] };
|
|
1865
1901
|
for (const listener of listeners$1) {
|
|
1866
1902
|
listener(snapshot);
|
|
1867
1903
|
}
|
|
1868
1904
|
}
|
|
1869
1905
|
function getState$1() {
|
|
1870
1906
|
load$3();
|
|
1871
|
-
return { entries: [...state$
|
|
1907
|
+
return { entries: [...state$4.entries] };
|
|
1872
1908
|
}
|
|
1873
1909
|
function subscribe$1(listener) {
|
|
1874
1910
|
listeners$1.add(listener);
|
|
@@ -1879,12 +1915,12 @@ function subscribe$1(listener) {
|
|
|
1879
1915
|
function addEntry$1(url, title) {
|
|
1880
1916
|
if (!url || url === "about:blank") return;
|
|
1881
1917
|
load$3();
|
|
1882
|
-
const last = state$
|
|
1918
|
+
const last = state$4.entries[0];
|
|
1883
1919
|
if (last && last.url === url) {
|
|
1884
1920
|
if (title && title !== last.title) {
|
|
1885
1921
|
last.title = title;
|
|
1886
|
-
save$
|
|
1887
|
-
emit$
|
|
1922
|
+
save$2();
|
|
1923
|
+
emit$3();
|
|
1888
1924
|
}
|
|
1889
1925
|
return;
|
|
1890
1926
|
}
|
|
@@ -1893,25 +1929,25 @@ function addEntry$1(url, title) {
|
|
|
1893
1929
|
title: title || url,
|
|
1894
1930
|
visitedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1895
1931
|
};
|
|
1896
|
-
state$
|
|
1897
|
-
if (state$
|
|
1898
|
-
state$
|
|
1932
|
+
state$4.entries.unshift(entry);
|
|
1933
|
+
if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
|
|
1934
|
+
state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
|
|
1899
1935
|
}
|
|
1900
|
-
save$
|
|
1901
|
-
emit$
|
|
1936
|
+
save$2();
|
|
1937
|
+
emit$3();
|
|
1902
1938
|
}
|
|
1903
1939
|
function search(query, limit = 50) {
|
|
1904
1940
|
load$3();
|
|
1905
|
-
if (!query.trim()) return state$
|
|
1941
|
+
if (!query.trim()) return state$4.entries.slice(0, limit);
|
|
1906
1942
|
const normalized = query.toLowerCase();
|
|
1907
|
-
return state$
|
|
1943
|
+
return state$4.entries.filter(
|
|
1908
1944
|
(e) => e.url.toLowerCase().includes(normalized) || e.title.toLowerCase().includes(normalized)
|
|
1909
1945
|
).slice(0, limit);
|
|
1910
1946
|
}
|
|
1911
1947
|
function clearAll$1() {
|
|
1912
|
-
state$
|
|
1913
|
-
save$
|
|
1914
|
-
emit$
|
|
1948
|
+
state$4 = { entries: [] };
|
|
1949
|
+
save$2();
|
|
1950
|
+
emit$3();
|
|
1915
1951
|
}
|
|
1916
1952
|
function clearByTimeRange(timeRange) {
|
|
1917
1953
|
load$3();
|
|
@@ -1921,12 +1957,12 @@ function clearByTimeRange(timeRange) {
|
|
|
1921
1957
|
}
|
|
1922
1958
|
const now = Date.now();
|
|
1923
1959
|
const cutoff = new Date(now - timeRangeToMs(timeRange));
|
|
1924
|
-
state$
|
|
1960
|
+
state$4.entries = state$4.entries.filter((entry) => {
|
|
1925
1961
|
const visitedAt = new Date(entry.visitedAt).getTime();
|
|
1926
1962
|
return Number.isNaN(visitedAt) || visitedAt < cutoff.getTime();
|
|
1927
1963
|
});
|
|
1928
|
-
save$
|
|
1929
|
-
emit$
|
|
1964
|
+
save$2();
|
|
1965
|
+
emit$3();
|
|
1930
1966
|
}
|
|
1931
1967
|
function timeRangeToMs(range) {
|
|
1932
1968
|
switch (range) {
|
|
@@ -1980,7 +2016,7 @@ function importHistoryFromJson(content) {
|
|
|
1980
2016
|
const parsed = JSON.parse(content);
|
|
1981
2017
|
const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
|
|
1982
2018
|
load$3();
|
|
1983
|
-
const existingUrls = new Set(state$
|
|
2019
|
+
const existingUrls = new Set(state$4.entries.map((e) => e.url));
|
|
1984
2020
|
for (const entry of entries) {
|
|
1985
2021
|
if (!entry?.url || typeof entry.url !== "string") {
|
|
1986
2022
|
errors++;
|
|
@@ -1990,7 +2026,7 @@ function importHistoryFromJson(content) {
|
|
|
1990
2026
|
skipped++;
|
|
1991
2027
|
continue;
|
|
1992
2028
|
}
|
|
1993
|
-
state$
|
|
2029
|
+
state$4.entries.push({
|
|
1994
2030
|
url: entry.url,
|
|
1995
2031
|
title: typeof entry.title === "string" ? entry.title : entry.url,
|
|
1996
2032
|
visitedAt: typeof entry.visitedAt === "string" ? entry.visitedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1998,14 +2034,14 @@ function importHistoryFromJson(content) {
|
|
|
1998
2034
|
existingUrls.add(entry.url);
|
|
1999
2035
|
imported++;
|
|
2000
2036
|
}
|
|
2001
|
-
state$
|
|
2037
|
+
state$4.entries.sort(
|
|
2002
2038
|
(a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
|
|
2003
2039
|
);
|
|
2004
|
-
if (state$
|
|
2005
|
-
state$
|
|
2040
|
+
if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
|
|
2041
|
+
state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
|
|
2006
2042
|
}
|
|
2007
|
-
save$
|
|
2008
|
-
emit$
|
|
2043
|
+
save$2();
|
|
2044
|
+
emit$3();
|
|
2009
2045
|
} catch {
|
|
2010
2046
|
errors++;
|
|
2011
2047
|
}
|
|
@@ -2016,7 +2052,7 @@ function importHistoryFromHtml(content) {
|
|
|
2016
2052
|
let skipped = 0;
|
|
2017
2053
|
let errors = 0;
|
|
2018
2054
|
load$3();
|
|
2019
|
-
const existingUrls = new Set(state$
|
|
2055
|
+
const existingUrls = new Set(state$4.entries.map((e) => e.url));
|
|
2020
2056
|
const hrefRegex = /<A\s+[^>]*HREF="([^"]+)"[^>]*>([^<]*)<\/A>/gi;
|
|
2021
2057
|
let match;
|
|
2022
2058
|
while ((match = hrefRegex.exec(content)) !== null) {
|
|
@@ -2027,7 +2063,7 @@ function importHistoryFromHtml(content) {
|
|
|
2027
2063
|
else errors++;
|
|
2028
2064
|
continue;
|
|
2029
2065
|
}
|
|
2030
|
-
state$
|
|
2066
|
+
state$4.entries.push({
|
|
2031
2067
|
url,
|
|
2032
2068
|
title,
|
|
2033
2069
|
visitedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2035,18 +2071,18 @@ function importHistoryFromHtml(content) {
|
|
|
2035
2071
|
existingUrls.add(url);
|
|
2036
2072
|
imported++;
|
|
2037
2073
|
}
|
|
2038
|
-
state$
|
|
2074
|
+
state$4.entries.sort(
|
|
2039
2075
|
(a, b) => new Date(b.visitedAt).getTime() - new Date(a.visitedAt).getTime()
|
|
2040
2076
|
);
|
|
2041
|
-
if (state$
|
|
2042
|
-
state$
|
|
2077
|
+
if (state$4.entries.length > MAX_HISTORY_ENTRIES) {
|
|
2078
|
+
state$4.entries = state$4.entries.slice(0, MAX_HISTORY_ENTRIES);
|
|
2043
2079
|
}
|
|
2044
|
-
save$
|
|
2045
|
-
emit$
|
|
2080
|
+
save$2();
|
|
2081
|
+
emit$3();
|
|
2046
2082
|
return { imported, skipped, errors };
|
|
2047
2083
|
}
|
|
2048
2084
|
function flushPersist$3() {
|
|
2049
|
-
return persistence$
|
|
2085
|
+
return persistence$6.flush();
|
|
2050
2086
|
}
|
|
2051
2087
|
const MAX_CONSOLE_ENTRIES = 500;
|
|
2052
2088
|
const MAX_NETWORK_ENTRIES = 200;
|
|
@@ -2403,18 +2439,18 @@ class DevToolsSession {
|
|
|
2403
2439
|
if (result?.__error) throw new Error(result.__error);
|
|
2404
2440
|
return { type, origin, entries: result ?? {} };
|
|
2405
2441
|
}
|
|
2406
|
-
async setStorage(type,
|
|
2442
|
+
async setStorage(type, key2, value) {
|
|
2407
2443
|
const storageType = type === "localStorage" ? "localStorage" : "sessionStorage";
|
|
2408
2444
|
if (value === null) {
|
|
2409
2445
|
await this.wc.executeJavaScript(
|
|
2410
|
-
`window.${storageType}.removeItem(${JSON.stringify(
|
|
2446
|
+
`window.${storageType}.removeItem(${JSON.stringify(key2)})`
|
|
2411
2447
|
);
|
|
2412
|
-
return `Removed "${
|
|
2448
|
+
return `Removed "${key2}" from ${type}`;
|
|
2413
2449
|
}
|
|
2414
2450
|
await this.wc.executeJavaScript(
|
|
2415
|
-
`window.${storageType}.setItem(${JSON.stringify(
|
|
2451
|
+
`window.${storageType}.setItem(${JSON.stringify(key2)}, ${JSON.stringify(value)})`
|
|
2416
2452
|
);
|
|
2417
|
-
return `Set ${type}["${
|
|
2453
|
+
return `Set ${type}["${key2}"] = ${value.length > 80 ? value.slice(0, 77) + "..." : value}`;
|
|
2418
2454
|
}
|
|
2419
2455
|
// ---------------------------------------------------------------------------
|
|
2420
2456
|
// Performance
|
|
@@ -2746,7 +2782,7 @@ function destroySession(tabId) {
|
|
|
2746
2782
|
sessions.delete(tabId);
|
|
2747
2783
|
}
|
|
2748
2784
|
}
|
|
2749
|
-
const logger$
|
|
2785
|
+
const logger$k = createLogger("TabManager");
|
|
2750
2786
|
function sanitizePdfFilename(title) {
|
|
2751
2787
|
const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2752
2788
|
const base = (clean || "Vessel Page").replace(/\.pdf$/i, "");
|
|
@@ -2979,23 +3015,23 @@ class TabManager {
|
|
|
2979
3015
|
async saveTabAsPdf(id) {
|
|
2980
3016
|
const tab = this.tabs.get(id);
|
|
2981
3017
|
if (!tab) return null;
|
|
2982
|
-
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
3018
|
+
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
2983
3019
|
title: "Save Page as PDF",
|
|
2984
3020
|
defaultPath: sanitizePdfFilename(tab.state.title || "Vessel Page"),
|
|
2985
3021
|
filters: [{ name: "PDF", extensions: ["pdf"] }]
|
|
2986
3022
|
});
|
|
2987
|
-
if (canceled || !
|
|
3023
|
+
if (canceled || !filePath2) return null;
|
|
2988
3024
|
const data = await tab.view.webContents.printToPDF({
|
|
2989
3025
|
printBackground: true
|
|
2990
3026
|
});
|
|
2991
|
-
await fs.promises.writeFile(
|
|
2992
|
-
return
|
|
3027
|
+
await fs.promises.writeFile(filePath2, data);
|
|
3028
|
+
return filePath2;
|
|
2993
3029
|
}
|
|
2994
3030
|
async savePage(id, format = "MHTML") {
|
|
2995
3031
|
const tab = this.tabs.get(id);
|
|
2996
3032
|
if (!tab) return null;
|
|
2997
3033
|
const ext = format === "MHTML" ? "mhtml" : "html";
|
|
2998
|
-
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
3034
|
+
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
2999
3035
|
title: "Save Page As",
|
|
3000
3036
|
defaultPath: sanitizePageFilename(
|
|
3001
3037
|
tab.state.title || "Vessel Page",
|
|
@@ -3005,9 +3041,9 @@ class TabManager {
|
|
|
3005
3041
|
{ name: format === "MHTML" ? "MHTML" : "HTML", extensions: [ext] }
|
|
3006
3042
|
]
|
|
3007
3043
|
});
|
|
3008
|
-
if (canceled || !
|
|
3009
|
-
await tab.view.webContents.savePage(
|
|
3010
|
-
return
|
|
3044
|
+
if (canceled || !filePath2) return null;
|
|
3045
|
+
await tab.view.webContents.savePage(filePath2, format);
|
|
3046
|
+
return filePath2;
|
|
3011
3047
|
}
|
|
3012
3048
|
getActiveTab() {
|
|
3013
3049
|
return this.activeTabId ? this.tabs.get(this.activeTabId) : void 0;
|
|
@@ -3091,11 +3127,11 @@ class TabManager {
|
|
|
3091
3127
|
this.pinTab(ids[index]);
|
|
3092
3128
|
}
|
|
3093
3129
|
if (tab.groupName && ids[index]) {
|
|
3094
|
-
const
|
|
3095
|
-
let groupId = restoredGroups.get(
|
|
3130
|
+
const key2 = `${tab.groupName}|${tab.groupColor ?? "blue"}`;
|
|
3131
|
+
let groupId = restoredGroups.get(key2);
|
|
3096
3132
|
if (!groupId) {
|
|
3097
3133
|
groupId = crypto$1.randomUUID();
|
|
3098
|
-
restoredGroups.set(
|
|
3134
|
+
restoredGroups.set(key2, groupId);
|
|
3099
3135
|
this.tabGroups.set(groupId, {
|
|
3100
3136
|
id: groupId,
|
|
3101
3137
|
name: tab.groupName,
|
|
@@ -3145,7 +3181,7 @@ class TabManager {
|
|
|
3145
3181
|
}));
|
|
3146
3182
|
if (entries.length > 0) {
|
|
3147
3183
|
void highlightBatchOnPage(wc, entries).catch(
|
|
3148
|
-
(err) => logger$
|
|
3184
|
+
(err) => logger$k.warn("Failed to batch highlight:", err)
|
|
3149
3185
|
);
|
|
3150
3186
|
}
|
|
3151
3187
|
}
|
|
@@ -3167,12 +3203,12 @@ class TabManager {
|
|
|
3167
3203
|
const result = await captureSelectionHighlight(wc);
|
|
3168
3204
|
if (result.success && result.text) {
|
|
3169
3205
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
3170
|
-
(err) => logger$
|
|
3206
|
+
(err) => logger$k.warn("Failed to capture highlight:", err)
|
|
3171
3207
|
);
|
|
3172
3208
|
}
|
|
3173
3209
|
this.highlightCaptureCallback?.(result);
|
|
3174
3210
|
} catch (err) {
|
|
3175
|
-
logger$
|
|
3211
|
+
logger$k.warn("Failed to capture highlight from page:", err);
|
|
3176
3212
|
this.highlightCaptureCallback?.({
|
|
3177
3213
|
success: false,
|
|
3178
3214
|
message: "Could not capture selection"
|
|
@@ -3197,7 +3233,7 @@ class TabManager {
|
|
|
3197
3233
|
void this.removeHighlightMarksForText(wc, text);
|
|
3198
3234
|
}
|
|
3199
3235
|
} catch (err) {
|
|
3200
|
-
logger$
|
|
3236
|
+
logger$k.warn("Failed to remove highlight from matching tab:", err);
|
|
3201
3237
|
}
|
|
3202
3238
|
}
|
|
3203
3239
|
this.highlightCaptureCallback?.({
|
|
@@ -3228,12 +3264,12 @@ class TabManager {
|
|
|
3228
3264
|
void 0,
|
|
3229
3265
|
color
|
|
3230
3266
|
).catch(
|
|
3231
|
-
(err) => logger$
|
|
3267
|
+
(err) => logger$k.warn("Failed to update highlight color:", err)
|
|
3232
3268
|
);
|
|
3233
3269
|
});
|
|
3234
3270
|
}
|
|
3235
3271
|
} catch (err) {
|
|
3236
|
-
logger$
|
|
3272
|
+
logger$k.warn("Failed to iterate highlights for color change:", err);
|
|
3237
3273
|
}
|
|
3238
3274
|
}
|
|
3239
3275
|
this.highlightCaptureCallback?.({
|
|
@@ -3274,7 +3310,7 @@ class TabManager {
|
|
|
3274
3310
|
});
|
|
3275
3311
|
})()`
|
|
3276
3312
|
).catch(
|
|
3277
|
-
(err) => logger$
|
|
3313
|
+
(err) => logger$k.warn("Failed to remove highlight marks:", err)
|
|
3278
3314
|
);
|
|
3279
3315
|
}
|
|
3280
3316
|
broadcastState() {
|
|
@@ -3420,6 +3456,11 @@ const Channels = {
|
|
|
3420
3456
|
DOWNLOAD_STARTED: "download:started",
|
|
3421
3457
|
DOWNLOAD_PROGRESS: "download:progress",
|
|
3422
3458
|
DOWNLOAD_DONE: "download:done",
|
|
3459
|
+
DOWNLOADS_GET: "downloads:get",
|
|
3460
|
+
DOWNLOADS_CLEAR: "downloads:clear",
|
|
3461
|
+
DOWNLOADS_OPEN: "downloads:open",
|
|
3462
|
+
DOWNLOADS_SHOW_IN_FOLDER: "downloads:show-in-folder",
|
|
3463
|
+
DOWNLOADS_UPDATE: "downloads:update",
|
|
3423
3464
|
// Premium
|
|
3424
3465
|
PREMIUM_GET_STATE: "premium:get-state",
|
|
3425
3466
|
PREMIUM_ACTIVATION_START: "premium:activation-start",
|
|
@@ -3477,7 +3518,19 @@ const Channels = {
|
|
|
3477
3518
|
CLEAR_BROWSING_DATA: "browsing-data:clear",
|
|
3478
3519
|
CLEAR_BROWSING_DATA_OPEN: "browsing-data:open",
|
|
3479
3520
|
// Picture-in-Picture
|
|
3480
|
-
TAB_TOGGLE_PIP: "tab:toggle-pip"
|
|
3521
|
+
TAB_TOGGLE_PIP: "tab:toggle-pip",
|
|
3522
|
+
// Codex OAuth
|
|
3523
|
+
CODEX_START_AUTH: "codex:start-auth",
|
|
3524
|
+
CODEX_CANCEL_AUTH: "codex:cancel-auth",
|
|
3525
|
+
CODEX_AUTH_STATUS: "codex:auth-status",
|
|
3526
|
+
CODEX_DISCONNECT: "codex:disconnect",
|
|
3527
|
+
// Updates
|
|
3528
|
+
UPDATES_CHECK: "updates:check",
|
|
3529
|
+
UPDATES_OPEN_DOWNLOAD: "updates:open-download",
|
|
3530
|
+
// Permissions
|
|
3531
|
+
PERMISSIONS_GET: "permissions:get",
|
|
3532
|
+
PERMISSIONS_CLEAR: "permissions:clear",
|
|
3533
|
+
PERMISSIONS_CLEAR_ORIGIN: "permissions:clear-origin"
|
|
3481
3534
|
};
|
|
3482
3535
|
const MAX_DETAIL_ITEMS = 3;
|
|
3483
3536
|
const MIN_BLOCK_SIMILARITY = 0.82;
|
|
@@ -3719,25 +3772,25 @@ function normalizeQueryValue(value) {
|
|
|
3719
3772
|
}
|
|
3720
3773
|
function serializeSnapshotParams(params) {
|
|
3721
3774
|
return params.map(
|
|
3722
|
-
([
|
|
3775
|
+
([key2, value]) => `${encodeURIComponent(key2)}=${encodeURIComponent(value)}`
|
|
3723
3776
|
).join("&");
|
|
3724
3777
|
}
|
|
3725
3778
|
function normalizeSnapshotParams(entries, pathname) {
|
|
3726
3779
|
return Array.from(entries).filter(
|
|
3727
|
-
([
|
|
3728
|
-
).map(([
|
|
3729
|
-
|
|
3780
|
+
([key2, value]) => shouldKeepSnapshotQueryParam(pathname, key2, value)
|
|
3781
|
+
).map(([key2, value]) => [
|
|
3782
|
+
key2.trim().toLowerCase(),
|
|
3730
3783
|
normalizeQueryValue(value)
|
|
3731
3784
|
]).sort(
|
|
3732
3785
|
([keyA, valueA], [keyB, valueB]) => keyA === keyB ? valueA.localeCompare(valueB) : keyA.localeCompare(keyB)
|
|
3733
3786
|
);
|
|
3734
3787
|
}
|
|
3735
3788
|
function shouldKeepSnapshotQueryParam(pathname, rawKey, value) {
|
|
3736
|
-
const
|
|
3737
|
-
if (!
|
|
3738
|
-
if (
|
|
3739
|
-
if (TRACKING_QUERY_KEYS.has(
|
|
3740
|
-
if (SNAPSHOT_QUERY_KEYS.has(
|
|
3789
|
+
const key2 = rawKey.trim().toLowerCase();
|
|
3790
|
+
if (!key2 || !value.trim()) return false;
|
|
3791
|
+
if (key2.startsWith("utm_")) return false;
|
|
3792
|
+
if (TRACKING_QUERY_KEYS.has(key2)) return false;
|
|
3793
|
+
if (SNAPSHOT_QUERY_KEYS.has(key2)) return true;
|
|
3741
3794
|
return /\/(search|results|browse|discover|find|category|tag|topics?|collections?|list)(\/|$)/i.test(
|
|
3742
3795
|
pathname
|
|
3743
3796
|
);
|
|
@@ -3830,7 +3883,7 @@ function load$2() {
|
|
|
3830
3883
|
});
|
|
3831
3884
|
return snapshots;
|
|
3832
3885
|
}
|
|
3833
|
-
const persistence$
|
|
3886
|
+
const persistence$5 = createDebouncedJsonPersistence({
|
|
3834
3887
|
debounceMs: SAVE_DEBOUNCE_MS$3,
|
|
3835
3888
|
filePath: getFilePath$1(),
|
|
3836
3889
|
getValue: () => snapshots,
|
|
@@ -3849,21 +3902,21 @@ function getSnapshot(normalizedUrl) {
|
|
|
3849
3902
|
}
|
|
3850
3903
|
function saveSnapshot(rawUrl, title, textContent, headings) {
|
|
3851
3904
|
const s = load$2();
|
|
3852
|
-
const
|
|
3905
|
+
const key2 = normalizeUrl(rawUrl);
|
|
3853
3906
|
const snapshot = {
|
|
3854
|
-
url:
|
|
3907
|
+
url: key2,
|
|
3855
3908
|
title,
|
|
3856
3909
|
textContent: textContent.slice(0, MAX_TEXT_LENGTH),
|
|
3857
3910
|
headings: headings.map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n"),
|
|
3858
3911
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3859
3912
|
};
|
|
3860
|
-
s.delete(
|
|
3861
|
-
s.set(
|
|
3862
|
-
persistence$
|
|
3913
|
+
s.delete(key2);
|
|
3914
|
+
s.set(key2, snapshot);
|
|
3915
|
+
persistence$5.schedule();
|
|
3863
3916
|
return snapshot;
|
|
3864
3917
|
}
|
|
3865
3918
|
function flushPersist$2() {
|
|
3866
|
-
return persistence$
|
|
3919
|
+
return persistence$5.flush();
|
|
3867
3920
|
}
|
|
3868
3921
|
const SEARCH_ENGINE_HOSTS = [
|
|
3869
3922
|
"google.",
|
|
@@ -4178,11 +4231,11 @@ function sanitizeValue(value, depth = 0) {
|
|
|
4178
4231
|
const record = asRecord(value);
|
|
4179
4232
|
if (!record || depth >= MAX_DEPTH) return void 0;
|
|
4180
4233
|
const objectValue = {};
|
|
4181
|
-
for (const [
|
|
4182
|
-
if (SKIP_FIELDS.has(
|
|
4234
|
+
for (const [key2, entry] of Object.entries(record)) {
|
|
4235
|
+
if (SKIP_FIELDS.has(key2)) continue;
|
|
4183
4236
|
const normalized = sanitizeValue(entry, depth + 1);
|
|
4184
4237
|
if (normalized !== void 0) {
|
|
4185
|
-
objectValue[
|
|
4238
|
+
objectValue[key2] = normalized;
|
|
4186
4239
|
}
|
|
4187
4240
|
}
|
|
4188
4241
|
return Object.keys(objectValue).length > 0 ? objectValue : void 0;
|
|
@@ -4190,10 +4243,10 @@ function sanitizeValue(value, depth = 0) {
|
|
|
4190
4243
|
function buildNormalizedAttributes(record, types) {
|
|
4191
4244
|
const attributes = {};
|
|
4192
4245
|
const consumed = /* @__PURE__ */ new Set();
|
|
4193
|
-
const consume = (
|
|
4246
|
+
const consume = (key2, value) => {
|
|
4194
4247
|
if (value === void 0) return;
|
|
4195
|
-
consumed.add(
|
|
4196
|
-
attributes[
|
|
4248
|
+
consumed.add(key2);
|
|
4249
|
+
attributes[key2] = value;
|
|
4197
4250
|
};
|
|
4198
4251
|
if (types.includes("Recipe")) {
|
|
4199
4252
|
consume("yield", sanitizeValue(record.recipeYield));
|
|
@@ -4237,14 +4290,14 @@ function buildNormalizedAttributes(record, types) {
|
|
|
4237
4290
|
attributes.questions = questions;
|
|
4238
4291
|
}
|
|
4239
4292
|
}
|
|
4240
|
-
for (const [
|
|
4241
|
-
if (SKIP_FIELDS.has(
|
|
4242
|
-
if (
|
|
4293
|
+
for (const [key2, value] of Object.entries(record)) {
|
|
4294
|
+
if (SKIP_FIELDS.has(key2) || consumed.has(key2)) continue;
|
|
4295
|
+
if (key2 === "name" || key2 === "headline" || key2 === "url" || key2 === "description") {
|
|
4243
4296
|
continue;
|
|
4244
4297
|
}
|
|
4245
4298
|
const normalized = sanitizeValue(value);
|
|
4246
4299
|
if (normalized !== void 0) {
|
|
4247
|
-
attributes[
|
|
4300
|
+
attributes[key2] = normalized;
|
|
4248
4301
|
}
|
|
4249
4302
|
}
|
|
4250
4303
|
return attributes;
|
|
@@ -4264,7 +4317,7 @@ function collectCandidateEntities(value, results = [], seen = /* @__PURE__ */ ne
|
|
|
4264
4317
|
if (types.length > 0 || hasIdentity) {
|
|
4265
4318
|
results.push(record);
|
|
4266
4319
|
}
|
|
4267
|
-
for (const
|
|
4320
|
+
for (const key2 of [
|
|
4268
4321
|
"@graph",
|
|
4269
4322
|
"mainEntity",
|
|
4270
4323
|
"mainEntityOfPage",
|
|
@@ -4277,7 +4330,7 @@ function collectCandidateEntities(value, results = [], seen = /* @__PURE__ */ ne
|
|
|
4277
4330
|
"acceptedAnswer",
|
|
4278
4331
|
"suggestedAnswer"
|
|
4279
4332
|
]) {
|
|
4280
|
-
collectCandidateEntities(record[
|
|
4333
|
+
collectCandidateEntities(record[key2], results, seen);
|
|
4281
4334
|
}
|
|
4282
4335
|
return results;
|
|
4283
4336
|
}
|
|
@@ -4290,11 +4343,11 @@ function dedupeKey(entity) {
|
|
|
4290
4343
|
JSON.stringify(entity.attributes)
|
|
4291
4344
|
].join("::");
|
|
4292
4345
|
}
|
|
4293
|
-
function extractEntitiesFromRecords(
|
|
4294
|
-
if (!
|
|
4346
|
+
function extractEntitiesFromRecords(records2, source) {
|
|
4347
|
+
if (!records2 || records2.length === 0) return [];
|
|
4295
4348
|
const entities = [];
|
|
4296
4349
|
const seen = /* @__PURE__ */ new Set();
|
|
4297
|
-
for (const candidate of collectCandidateEntities(
|
|
4350
|
+
for (const candidate of collectCandidateEntities(records2)) {
|
|
4298
4351
|
const types = getTypes(candidate);
|
|
4299
4352
|
const name = firstString(candidate.name, candidate.headline);
|
|
4300
4353
|
const url = firstString(candidate.url, candidate["@id"]);
|
|
@@ -4314,9 +4367,9 @@ function extractEntitiesFromRecords(records, source) {
|
|
|
4314
4367
|
addIfPresent(entity, "name", name);
|
|
4315
4368
|
addIfPresent(entity, "url", url);
|
|
4316
4369
|
addIfPresent(entity, "description", description);
|
|
4317
|
-
const
|
|
4318
|
-
if (seen.has(
|
|
4319
|
-
seen.add(
|
|
4370
|
+
const key2 = dedupeKey(entity);
|
|
4371
|
+
if (seen.has(key2)) continue;
|
|
4372
|
+
seen.add(key2);
|
|
4320
4373
|
entities.push(entity);
|
|
4321
4374
|
}
|
|
4322
4375
|
return entities.slice(0, 25);
|
|
@@ -4339,13 +4392,13 @@ function extractEntityFromMetaTags(metaTags, pageTitle, pageUrl) {
|
|
|
4339
4392
|
const url = metaTags["og:url"] || metaTags.canonical || pageUrl;
|
|
4340
4393
|
const types = getMetaType(metaTags);
|
|
4341
4394
|
const attributes = {};
|
|
4342
|
-
for (const [
|
|
4343
|
-
if (
|
|
4395
|
+
for (const [key2, value] of Object.entries(metaTags)) {
|
|
4396
|
+
if (key2 === "og:title" || key2 === "twitter:title" || key2 === "title" || key2 === "og:description" || key2 === "description" || key2 === "twitter:description" || key2 === "og:url" || key2 === "canonical") {
|
|
4344
4397
|
continue;
|
|
4345
4398
|
}
|
|
4346
4399
|
const normalized = sanitizeValue(value);
|
|
4347
4400
|
if (normalized !== void 0) {
|
|
4348
|
-
attributes[
|
|
4401
|
+
attributes[key2] = normalized;
|
|
4349
4402
|
}
|
|
4350
4403
|
}
|
|
4351
4404
|
const entity = {
|
|
@@ -4398,9 +4451,9 @@ function extractStructuredDataFromJsonLd(jsonLd, microdata, rdfa, metaTags, page
|
|
|
4398
4451
|
const deduped = [];
|
|
4399
4452
|
const seen = /* @__PURE__ */ new Set();
|
|
4400
4453
|
for (const entity of candidates) {
|
|
4401
|
-
const
|
|
4402
|
-
if (seen.has(
|
|
4403
|
-
seen.add(
|
|
4454
|
+
const key2 = dedupeKey(entity);
|
|
4455
|
+
if (seen.has(key2)) continue;
|
|
4456
|
+
seen.add(key2);
|
|
4404
4457
|
deduped.push(entity);
|
|
4405
4458
|
}
|
|
4406
4459
|
if (deduped.length > 0) {
|
|
@@ -4414,9 +4467,9 @@ function extractStructuredDataFromJsonLd(jsonLd, microdata, rdfa, metaTags, page
|
|
|
4414
4467
|
pageHeadings
|
|
4415
4468
|
);
|
|
4416
4469
|
}
|
|
4417
|
-
function addIfPresent(target,
|
|
4470
|
+
function addIfPresent(target, key2, value) {
|
|
4418
4471
|
if (value !== void 0) {
|
|
4419
|
-
target[
|
|
4472
|
+
target[key2] = value;
|
|
4420
4473
|
}
|
|
4421
4474
|
}
|
|
4422
4475
|
function okResult(value) {
|
|
@@ -4435,7 +4488,7 @@ function errorResult(error, value) {
|
|
|
4435
4488
|
function getErrorMessage(error, fallback = "Unknown error") {
|
|
4436
4489
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
4437
4490
|
}
|
|
4438
|
-
const logger$
|
|
4491
|
+
const logger$j = createLogger("Premium");
|
|
4439
4492
|
const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
4440
4493
|
const FREE_TOOL_ITERATION_LIMIT = 50;
|
|
4441
4494
|
const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4551,7 +4604,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4551
4604
|
});
|
|
4552
4605
|
if (!res.ok) {
|
|
4553
4606
|
const detail = await readApiErrorDetail(res);
|
|
4554
|
-
logger$
|
|
4607
|
+
logger$j.warn(
|
|
4555
4608
|
"Verification API returned a non-OK status:",
|
|
4556
4609
|
res.status,
|
|
4557
4610
|
detail
|
|
@@ -4570,7 +4623,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4570
4623
|
setSetting("premium", updated);
|
|
4571
4624
|
return updated;
|
|
4572
4625
|
} catch (err) {
|
|
4573
|
-
logger$
|
|
4626
|
+
logger$j.warn("Verification failed:", err);
|
|
4574
4627
|
return current;
|
|
4575
4628
|
}
|
|
4576
4629
|
}
|
|
@@ -4729,8 +4782,8 @@ function trackProviderConfigured(providerId) {
|
|
|
4729
4782
|
provider_id: providerId
|
|
4730
4783
|
});
|
|
4731
4784
|
}
|
|
4732
|
-
function trackSettingChanged(
|
|
4733
|
-
trackEvent("setting_changed", { setting_key:
|
|
4785
|
+
function trackSettingChanged(key2) {
|
|
4786
|
+
trackEvent("setting_changed", { setting_key: key2 });
|
|
4734
4787
|
}
|
|
4735
4788
|
function trackApprovalModeChanged(mode) {
|
|
4736
4789
|
trackEvent("approval_mode_changed", { mode });
|
|
@@ -4912,10 +4965,10 @@ function mapFormFields(forms, interactiveElements) {
|
|
|
4912
4965
|
}
|
|
4913
4966
|
}
|
|
4914
4967
|
for (const el of interactiveElements) {
|
|
4915
|
-
const
|
|
4916
|
-
if (formFieldSelectors.has(
|
|
4968
|
+
const key2 = el.selector || el.name || el.label || String(el.index);
|
|
4969
|
+
if (formFieldSelectors.has(key2)) {
|
|
4917
4970
|
fields.push({
|
|
4918
|
-
name: el.name || el.label ||
|
|
4971
|
+
name: el.name || el.label || key2,
|
|
4919
4972
|
type: mapInputType(el),
|
|
4920
4973
|
label: el.label,
|
|
4921
4974
|
required: el.required,
|
|
@@ -5143,7 +5196,7 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
|
5143
5196
|
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
5144
5197
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
5145
5198
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
5146
|
-
const logger$
|
|
5199
|
+
const logger$i = createLogger("Extractor");
|
|
5147
5200
|
const EMPTY_PAGE_CONTENT = {
|
|
5148
5201
|
title: "",
|
|
5149
5202
|
content: "",
|
|
@@ -5893,7 +5946,7 @@ async function executeScript(webContents, script) {
|
|
|
5893
5946
|
})
|
|
5894
5947
|
]);
|
|
5895
5948
|
} catch (err) {
|
|
5896
|
-
logger$
|
|
5949
|
+
logger$i.warn("Failed to execute page script:", err);
|
|
5897
5950
|
return null;
|
|
5898
5951
|
} finally {
|
|
5899
5952
|
if (timer) {
|
|
@@ -6000,7 +6053,7 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
6000
6053
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
6001
6054
|
}
|
|
6002
6055
|
} catch (err) {
|
|
6003
|
-
logger$
|
|
6056
|
+
logger$i.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
6004
6057
|
}
|
|
6005
6058
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
6006
6059
|
}
|
|
@@ -6154,12 +6207,12 @@ function loadHistory() {
|
|
|
6154
6207
|
return next;
|
|
6155
6208
|
}
|
|
6156
6209
|
});
|
|
6157
|
-
for (const [
|
|
6158
|
-
recentPageDiffBursts.set(
|
|
6210
|
+
for (const [key2, bursts] of loaded.entries()) {
|
|
6211
|
+
recentPageDiffBursts.set(key2, bursts);
|
|
6159
6212
|
}
|
|
6160
6213
|
return recentPageDiffBursts;
|
|
6161
6214
|
}
|
|
6162
|
-
const persistence$
|
|
6215
|
+
const persistence$4 = createDebouncedJsonPersistence({
|
|
6163
6216
|
debounceMs: SAVE_DEBOUNCE_MS$2,
|
|
6164
6217
|
filePath: getHistoryFilePath(),
|
|
6165
6218
|
getValue: () => recentPageDiffBursts,
|
|
@@ -6176,20 +6229,20 @@ function getLatestPageDiff(rawUrl) {
|
|
|
6176
6229
|
}
|
|
6177
6230
|
function getPageDiffBursts(rawUrl) {
|
|
6178
6231
|
if (!shouldTrackSnapshotUrl(rawUrl)) return [];
|
|
6179
|
-
const
|
|
6232
|
+
const key2 = normalizeUrl(rawUrl);
|
|
6180
6233
|
const history = loadHistory();
|
|
6181
|
-
const bursts = prunePageDiffHistory(history.get(
|
|
6234
|
+
const bursts = prunePageDiffHistory(history.get(key2) ?? [], {
|
|
6182
6235
|
maxAgeDays: MAX_HISTORY_DAYS,
|
|
6183
6236
|
maxItems: MAX_PERSISTED_DIFF_BURSTS
|
|
6184
6237
|
});
|
|
6185
|
-
const current = history.get(
|
|
6238
|
+
const current = history.get(key2) ?? [];
|
|
6186
6239
|
if (current.length !== bursts.length) {
|
|
6187
6240
|
if (bursts.length > 0) {
|
|
6188
|
-
history.set(
|
|
6241
|
+
history.set(key2, bursts);
|
|
6189
6242
|
} else {
|
|
6190
|
-
history.delete(
|
|
6243
|
+
history.delete(key2);
|
|
6191
6244
|
}
|
|
6192
|
-
persistence$
|
|
6245
|
+
persistence$4.schedule();
|
|
6193
6246
|
}
|
|
6194
6247
|
return bursts.slice().reverse();
|
|
6195
6248
|
}
|
|
@@ -6197,20 +6250,20 @@ function summarizeDiffBurst(diff) {
|
|
|
6197
6250
|
const items = diff.changes.slice(0, 2).map((change) => `${change.section}: ${change.summary}`);
|
|
6198
6251
|
return items.join(" | ");
|
|
6199
6252
|
}
|
|
6200
|
-
function enrichWithBurstHistory(
|
|
6253
|
+
function enrichWithBurstHistory(key2, diff) {
|
|
6201
6254
|
const detectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6202
6255
|
const nextBurst = {
|
|
6203
6256
|
detectedAt,
|
|
6204
6257
|
summary: summarizeDiffBurst(diff)
|
|
6205
6258
|
};
|
|
6206
6259
|
const history = loadHistory();
|
|
6207
|
-
const bursts = appendPageDiffHistoryItem(history.get(
|
|
6260
|
+
const bursts = appendPageDiffHistoryItem(history.get(key2) ?? [], nextBurst, {
|
|
6208
6261
|
maxAgeDays: MAX_HISTORY_DAYS,
|
|
6209
6262
|
maxItems: MAX_PERSISTED_DIFF_BURSTS,
|
|
6210
6263
|
now: Date.parse(detectedAt)
|
|
6211
6264
|
});
|
|
6212
|
-
history.set(
|
|
6213
|
-
persistence$
|
|
6265
|
+
history.set(key2, bursts);
|
|
6266
|
+
persistence$4.schedule();
|
|
6214
6267
|
const recentBursts = bursts.slice(-5);
|
|
6215
6268
|
return {
|
|
6216
6269
|
...diff,
|
|
@@ -6223,8 +6276,8 @@ function enrichWithBurstHistory(key, diff) {
|
|
|
6223
6276
|
async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
6224
6277
|
try {
|
|
6225
6278
|
if (!shouldTrackSnapshotUrl(url)) return;
|
|
6226
|
-
const
|
|
6227
|
-
const oldSnap = getSnapshot(
|
|
6279
|
+
const key2 = normalizeUrl(url);
|
|
6280
|
+
const oldSnap = getSnapshot(key2);
|
|
6228
6281
|
const content = await extractContent(wc);
|
|
6229
6282
|
const textContent = content.content || "";
|
|
6230
6283
|
const title = content.title || "";
|
|
@@ -6233,14 +6286,14 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
|
6233
6286
|
if (oldSnap) {
|
|
6234
6287
|
const diff = diffSnapshots(oldSnap, textContent, title, currentHeadings);
|
|
6235
6288
|
if (diff.hasChanges) {
|
|
6236
|
-
const enrichedDiff = enrichWithBurstHistory(
|
|
6237
|
-
latestPageDiffs.set(
|
|
6289
|
+
const enrichedDiff = enrichWithBurstHistory(key2, diff);
|
|
6290
|
+
latestPageDiffs.set(key2, enrichedDiff);
|
|
6238
6291
|
sendToRendererViews(Channels.PAGE_CHANGED, enrichedDiff);
|
|
6239
6292
|
} else {
|
|
6240
|
-
latestPageDiffs.delete(
|
|
6293
|
+
latestPageDiffs.delete(key2);
|
|
6241
6294
|
}
|
|
6242
6295
|
} else {
|
|
6243
|
-
latestPageDiffs.delete(
|
|
6296
|
+
latestPageDiffs.delete(key2);
|
|
6244
6297
|
}
|
|
6245
6298
|
saveSnapshot(url, title, textContent, headings);
|
|
6246
6299
|
} catch {
|
|
@@ -6292,19 +6345,19 @@ function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
|
|
|
6292
6345
|
function enableClipboardShortcuts(view) {
|
|
6293
6346
|
view.webContents.on("before-input-event", (event, input) => {
|
|
6294
6347
|
if (!input.control && !input.meta) return;
|
|
6295
|
-
const
|
|
6348
|
+
const key2 = input.key.toLowerCase();
|
|
6296
6349
|
const wc = view.webContents;
|
|
6297
6350
|
if (input.type === "keyDown") {
|
|
6298
|
-
if (
|
|
6351
|
+
if (key2 === "c") {
|
|
6299
6352
|
wc.copy();
|
|
6300
6353
|
event.preventDefault();
|
|
6301
|
-
} else if (
|
|
6354
|
+
} else if (key2 === "v") {
|
|
6302
6355
|
wc.paste();
|
|
6303
6356
|
event.preventDefault();
|
|
6304
|
-
} else if (
|
|
6357
|
+
} else if (key2 === "x") {
|
|
6305
6358
|
wc.cut();
|
|
6306
6359
|
event.preventDefault();
|
|
6307
|
-
} else if (
|
|
6360
|
+
} else if (key2 === "a") {
|
|
6308
6361
|
wc.selectAll();
|
|
6309
6362
|
event.preventDefault();
|
|
6310
6363
|
}
|
|
@@ -6609,8 +6662,8 @@ function resizeSidebarViews(state2) {
|
|
|
6609
6662
|
}
|
|
6610
6663
|
}
|
|
6611
6664
|
function generateReaderHTML(page) {
|
|
6612
|
-
const escapedTitle = escapeHtml(page.title);
|
|
6613
|
-
const escapedByline = escapeHtml(page.byline);
|
|
6665
|
+
const escapedTitle = escapeHtml$1(page.title);
|
|
6666
|
+
const escapedByline = escapeHtml$1(page.byline);
|
|
6614
6667
|
const renderedContent = renderReaderContent(page);
|
|
6615
6668
|
return `<!DOCTYPE html>
|
|
6616
6669
|
<html lang="en">
|
|
@@ -6698,9 +6751,9 @@ function renderReaderContent(page) {
|
|
|
6698
6751
|
if (!source) {
|
|
6699
6752
|
return "<p>No readable content was available for this page.</p>";
|
|
6700
6753
|
}
|
|
6701
|
-
return source.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean).map((block) => `<p>${escapeHtml(block).replace(/\n/g, "<br>")}</p>`).join("\n");
|
|
6754
|
+
return source.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean).map((block) => `<p>${escapeHtml$1(block).replace(/\n/g, "<br>")}</p>`).join("\n");
|
|
6702
6755
|
}
|
|
6703
|
-
function escapeHtml(str) {
|
|
6756
|
+
function escapeHtml$1(str) {
|
|
6704
6757
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6705
6758
|
}
|
|
6706
6759
|
const mcpStatusChangeListeners = /* @__PURE__ */ new Set();
|
|
@@ -6718,7 +6771,7 @@ function onRuntimeHealthChange(listener) {
|
|
|
6718
6771
|
};
|
|
6719
6772
|
}
|
|
6720
6773
|
function getMcpStatus() {
|
|
6721
|
-
return state$
|
|
6774
|
+
return state$3.mcp.status;
|
|
6722
6775
|
}
|
|
6723
6776
|
function emitRuntimeHealthChange() {
|
|
6724
6777
|
const snapshot = getRuntimeHealth();
|
|
@@ -6726,7 +6779,7 @@ function emitRuntimeHealthChange() {
|
|
|
6726
6779
|
listener(snapshot);
|
|
6727
6780
|
}
|
|
6728
6781
|
}
|
|
6729
|
-
const state$
|
|
6782
|
+
const state$3 = {
|
|
6730
6783
|
userDataPath: "",
|
|
6731
6784
|
settingsPath: "",
|
|
6732
6785
|
startupIssues: [],
|
|
@@ -6739,43 +6792,43 @@ const state$2 = {
|
|
|
6739
6792
|
}
|
|
6740
6793
|
};
|
|
6741
6794
|
function initializeRuntimeHealth(paths) {
|
|
6742
|
-
state$
|
|
6743
|
-
state$
|
|
6744
|
-
state$
|
|
6745
|
-
state$
|
|
6746
|
-
state$
|
|
6747
|
-
state$
|
|
6748
|
-
state$
|
|
6795
|
+
state$3.userDataPath = paths.userDataPath;
|
|
6796
|
+
state$3.settingsPath = paths.settingsPath;
|
|
6797
|
+
state$3.mcp.configuredPort = paths.configuredPort;
|
|
6798
|
+
state$3.mcp.activePort = null;
|
|
6799
|
+
state$3.mcp.endpoint = null;
|
|
6800
|
+
state$3.mcp.status = "stopped";
|
|
6801
|
+
state$3.mcp.message = "MCP server has not started yet.";
|
|
6749
6802
|
emitRuntimeHealthChange();
|
|
6750
6803
|
}
|
|
6751
6804
|
function setStartupIssues(issues) {
|
|
6752
|
-
state$
|
|
6805
|
+
state$3.startupIssues = issues.map((issue) => ({ ...issue }));
|
|
6753
6806
|
emitRuntimeHealthChange();
|
|
6754
6807
|
}
|
|
6755
6808
|
function getRuntimeHealth() {
|
|
6756
6809
|
return {
|
|
6757
|
-
userDataPath: state$
|
|
6758
|
-
settingsPath: state$
|
|
6759
|
-
startupIssues: state$
|
|
6760
|
-
mcp: { ...state$
|
|
6810
|
+
userDataPath: state$3.userDataPath,
|
|
6811
|
+
settingsPath: state$3.settingsPath,
|
|
6812
|
+
startupIssues: state$3.startupIssues.map((issue) => ({ ...issue })),
|
|
6813
|
+
mcp: { ...state$3.mcp }
|
|
6761
6814
|
};
|
|
6762
6815
|
}
|
|
6763
6816
|
function setMcpHealth(update) {
|
|
6764
6817
|
if (typeof update.configuredPort === "number") {
|
|
6765
|
-
state$
|
|
6818
|
+
state$3.mcp.configuredPort = update.configuredPort;
|
|
6766
6819
|
}
|
|
6767
6820
|
if ("activePort" in update) {
|
|
6768
|
-
state$
|
|
6821
|
+
state$3.mcp.activePort = update.activePort ?? null;
|
|
6769
6822
|
}
|
|
6770
6823
|
if ("endpoint" in update) {
|
|
6771
|
-
state$
|
|
6824
|
+
state$3.mcp.endpoint = update.endpoint ?? null;
|
|
6772
6825
|
}
|
|
6773
|
-
const prevStatus = state$
|
|
6774
|
-
state$
|
|
6775
|
-
state$
|
|
6776
|
-
if (prevStatus !== state$
|
|
6826
|
+
const prevStatus = state$3.mcp.status;
|
|
6827
|
+
state$3.mcp.status = update.status;
|
|
6828
|
+
state$3.mcp.message = update.message;
|
|
6829
|
+
if (prevStatus !== state$3.mcp.status) {
|
|
6777
6830
|
for (const listener of mcpStatusChangeListeners) {
|
|
6778
|
-
listener(state$
|
|
6831
|
+
listener(state$3.mcp.status);
|
|
6779
6832
|
}
|
|
6780
6833
|
}
|
|
6781
6834
|
emitRuntimeHealthChange();
|
|
@@ -7177,6 +7230,17 @@ const PROVIDERS = {
|
|
|
7177
7230
|
apiKeyPlaceholder: "sk-...",
|
|
7178
7231
|
apiKeyHint: "Get your key from platform.openai.com"
|
|
7179
7232
|
},
|
|
7233
|
+
openai_codex: {
|
|
7234
|
+
id: "openai_codex",
|
|
7235
|
+
name: "OpenAI Codex",
|
|
7236
|
+
type: "codex_oauth",
|
|
7237
|
+
defaultModel: "gpt-5",
|
|
7238
|
+
models: ["gpt-5", "gpt-5-mini", "gpt-5-nano", "o4", "o4-mini"],
|
|
7239
|
+
requiresApiKey: false,
|
|
7240
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
7241
|
+
apiKeyPlaceholder: "",
|
|
7242
|
+
apiKeyHint: "Sign in with your ChatGPT Plus or Pro subscription"
|
|
7243
|
+
},
|
|
7180
7244
|
openrouter: {
|
|
7181
7245
|
id: "openrouter",
|
|
7182
7246
|
name: "OpenRouter",
|
|
@@ -7348,7 +7412,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
7348
7412
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7349
7413
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7350
7414
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7351
|
-
const logger$
|
|
7415
|
+
const logger$h = createLogger("OpenAIProvider");
|
|
7352
7416
|
function shouldDebugAgentLoop() {
|
|
7353
7417
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
7354
7418
|
return value === "1" || value === "true";
|
|
@@ -7676,8 +7740,8 @@ function scalarArgsForTool(name, scalar) {
|
|
|
7676
7740
|
return null;
|
|
7677
7741
|
}
|
|
7678
7742
|
function firstStringArg(args, keys) {
|
|
7679
|
-
for (const
|
|
7680
|
-
const value = args[
|
|
7743
|
+
for (const key2 of keys) {
|
|
7744
|
+
const value = args[key2];
|
|
7681
7745
|
if (typeof value === "string" && value.trim()) {
|
|
7682
7746
|
return value.trim();
|
|
7683
7747
|
}
|
|
@@ -7873,9 +7937,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
7873
7937
|
function logAgentLoopDebug(payload) {
|
|
7874
7938
|
if (!shouldDebugAgentLoop()) return;
|
|
7875
7939
|
try {
|
|
7876
|
-
logger$
|
|
7940
|
+
logger$h.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
7877
7941
|
} catch (err) {
|
|
7878
|
-
logger$
|
|
7942
|
+
logger$h.warn("Failed to serialize debug payload:", err);
|
|
7879
7943
|
}
|
|
7880
7944
|
}
|
|
7881
7945
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -8409,6 +8473,525 @@ class OpenAICompatProvider {
|
|
|
8409
8473
|
this.abortController?.abort();
|
|
8410
8474
|
}
|
|
8411
8475
|
}
|
|
8476
|
+
const logger$g = createLogger("CodexOAuth");
|
|
8477
|
+
const ISSUER = "https://auth.openai.com";
|
|
8478
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
8479
|
+
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
8480
|
+
const AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
8481
|
+
const PREFERRED_PORT = 1455;
|
|
8482
|
+
const FALLBACK_PORT = 1457;
|
|
8483
|
+
let activeFlow = null;
|
|
8484
|
+
function base64url(buffer) {
|
|
8485
|
+
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
8486
|
+
}
|
|
8487
|
+
function generatePkce() {
|
|
8488
|
+
const codeVerifier = base64url(crypto$1.randomBytes(64));
|
|
8489
|
+
const hash = crypto$1.createHash("sha256").update(codeVerifier).digest();
|
|
8490
|
+
const codeChallenge = base64url(hash);
|
|
8491
|
+
return { codeVerifier, codeChallenge };
|
|
8492
|
+
}
|
|
8493
|
+
function generateState() {
|
|
8494
|
+
return base64url(crypto$1.randomBytes(32));
|
|
8495
|
+
}
|
|
8496
|
+
function buildAuthorizeUrl(port, pkce, state2) {
|
|
8497
|
+
const redirectUri = `http://localhost:${port}/auth/callback`;
|
|
8498
|
+
const params = new URLSearchParams({
|
|
8499
|
+
response_type: "code",
|
|
8500
|
+
client_id: CLIENT_ID,
|
|
8501
|
+
redirect_uri: redirectUri,
|
|
8502
|
+
scope: SCOPE,
|
|
8503
|
+
code_challenge: pkce.codeChallenge,
|
|
8504
|
+
code_challenge_method: "S256",
|
|
8505
|
+
state: state2,
|
|
8506
|
+
id_token_add_organizations: "true",
|
|
8507
|
+
codex_cli_simplified_flow: "true",
|
|
8508
|
+
originator: "codex_cli_rs"
|
|
8509
|
+
});
|
|
8510
|
+
return `${ISSUER}/oauth/authorize?${params.toString()}`;
|
|
8511
|
+
}
|
|
8512
|
+
function parseJwtClaims(idToken) {
|
|
8513
|
+
try {
|
|
8514
|
+
const parts = idToken.split(".");
|
|
8515
|
+
if (parts.length !== 3) return null;
|
|
8516
|
+
const payload = JSON.parse(
|
|
8517
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
8518
|
+
);
|
|
8519
|
+
const authClaims = payload["https://api.openai.com/auth"] || {};
|
|
8520
|
+
const accountId = authClaims.chatgpt_account_id || payload.chatgpt_account_id || payload.sub || "";
|
|
8521
|
+
const email = authClaims.email || payload.email || void 0;
|
|
8522
|
+
return { accountId, email };
|
|
8523
|
+
} catch {
|
|
8524
|
+
return null;
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
8527
|
+
function parseTokenExpiry(accessToken) {
|
|
8528
|
+
try {
|
|
8529
|
+
const parts = accessToken.split(".");
|
|
8530
|
+
if (parts.length !== 3) return Date.now() + 36e5;
|
|
8531
|
+
const payload = JSON.parse(
|
|
8532
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
8533
|
+
);
|
|
8534
|
+
if (payload.exp && typeof payload.exp === "number") {
|
|
8535
|
+
return payload.exp * 1e3;
|
|
8536
|
+
}
|
|
8537
|
+
} catch {
|
|
8538
|
+
}
|
|
8539
|
+
return Date.now() + 36e5;
|
|
8540
|
+
}
|
|
8541
|
+
async function exchangeIdTokenForApiKey(idToken) {
|
|
8542
|
+
const body = new URLSearchParams({
|
|
8543
|
+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
8544
|
+
client_id: CLIENT_ID,
|
|
8545
|
+
requested_token: "openai-api-key",
|
|
8546
|
+
subject_token: idToken,
|
|
8547
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:id_token"
|
|
8548
|
+
});
|
|
8549
|
+
const response = await fetch(`${ISSUER}/oauth/token`, {
|
|
8550
|
+
method: "POST",
|
|
8551
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8552
|
+
body: body.toString()
|
|
8553
|
+
});
|
|
8554
|
+
if (!response.ok) {
|
|
8555
|
+
let errorMsg = `OpenAI API token exchange failed: ${response.status}`;
|
|
8556
|
+
try {
|
|
8557
|
+
const err = await response.json();
|
|
8558
|
+
if (typeof err.error_description === "string") {
|
|
8559
|
+
errorMsg = err.error_description;
|
|
8560
|
+
} else if (typeof err.error === "string") {
|
|
8561
|
+
errorMsg = err.error;
|
|
8562
|
+
}
|
|
8563
|
+
} catch {
|
|
8564
|
+
}
|
|
8565
|
+
throw new Error(errorMsg);
|
|
8566
|
+
}
|
|
8567
|
+
const data = await response.json();
|
|
8568
|
+
if (!data.access_token) {
|
|
8569
|
+
throw new Error("OpenAI API token exchange did not return an access token");
|
|
8570
|
+
}
|
|
8571
|
+
return data.access_token;
|
|
8572
|
+
}
|
|
8573
|
+
async function ensureCodexApiKey(tokens) {
|
|
8574
|
+
if (tokens.apiKey) return tokens;
|
|
8575
|
+
if (!tokens.idToken) return tokens;
|
|
8576
|
+
try {
|
|
8577
|
+
return {
|
|
8578
|
+
...tokens,
|
|
8579
|
+
apiKey: await exchangeIdTokenForApiKey(tokens.idToken)
|
|
8580
|
+
};
|
|
8581
|
+
} catch (err) {
|
|
8582
|
+
logger$g.warn(
|
|
8583
|
+
"Codex API-key token exchange failed; continuing with ChatGPT OAuth tokens:",
|
|
8584
|
+
err
|
|
8585
|
+
);
|
|
8586
|
+
return tokens;
|
|
8587
|
+
}
|
|
8588
|
+
}
|
|
8589
|
+
async function exchangeCodeForTokens(code, redirectUri, codeVerifier) {
|
|
8590
|
+
const body = new URLSearchParams({
|
|
8591
|
+
grant_type: "authorization_code",
|
|
8592
|
+
code,
|
|
8593
|
+
redirect_uri: redirectUri,
|
|
8594
|
+
client_id: CLIENT_ID,
|
|
8595
|
+
code_verifier: codeVerifier
|
|
8596
|
+
});
|
|
8597
|
+
const response = await fetch(`${ISSUER}/oauth/token`, {
|
|
8598
|
+
method: "POST",
|
|
8599
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8600
|
+
body: body.toString()
|
|
8601
|
+
});
|
|
8602
|
+
if (!response.ok) {
|
|
8603
|
+
let errorMsg = `Token exchange failed: ${response.status}`;
|
|
8604
|
+
try {
|
|
8605
|
+
const err = await response.json();
|
|
8606
|
+
if (typeof err.error_description === "string") {
|
|
8607
|
+
errorMsg = err.error_description;
|
|
8608
|
+
} else if (typeof err.error === "string") {
|
|
8609
|
+
errorMsg = err.error;
|
|
8610
|
+
}
|
|
8611
|
+
} catch {
|
|
8612
|
+
}
|
|
8613
|
+
throw new Error(errorMsg);
|
|
8614
|
+
}
|
|
8615
|
+
const data = await response.json();
|
|
8616
|
+
const claims = parseJwtClaims(data.id_token);
|
|
8617
|
+
const expiresAt = parseTokenExpiry(data.access_token);
|
|
8618
|
+
const tokens = {
|
|
8619
|
+
accessToken: data.access_token,
|
|
8620
|
+
refreshToken: data.refresh_token,
|
|
8621
|
+
idToken: data.id_token,
|
|
8622
|
+
expiresAt,
|
|
8623
|
+
accountId: claims?.accountId || "",
|
|
8624
|
+
accountEmail: claims?.email
|
|
8625
|
+
};
|
|
8626
|
+
return ensureCodexApiKey(tokens);
|
|
8627
|
+
}
|
|
8628
|
+
async function refreshAccessToken(tokens) {
|
|
8629
|
+
const body = new URLSearchParams({
|
|
8630
|
+
grant_type: "refresh_token",
|
|
8631
|
+
refresh_token: tokens.refreshToken,
|
|
8632
|
+
client_id: CLIENT_ID
|
|
8633
|
+
});
|
|
8634
|
+
const response = await fetch(`${ISSUER}/oauth/token`, {
|
|
8635
|
+
method: "POST",
|
|
8636
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8637
|
+
body: body.toString()
|
|
8638
|
+
});
|
|
8639
|
+
if (!response.ok) {
|
|
8640
|
+
let errorMsg = `Token refresh failed: ${response.status}`;
|
|
8641
|
+
try {
|
|
8642
|
+
const err = await response.json();
|
|
8643
|
+
if (typeof err.error_description === "string") errorMsg = err.error_description;
|
|
8644
|
+
else if (typeof err.error === "string") errorMsg = err.error;
|
|
8645
|
+
} catch {
|
|
8646
|
+
}
|
|
8647
|
+
throw new Error(errorMsg);
|
|
8648
|
+
}
|
|
8649
|
+
const data = await response.json();
|
|
8650
|
+
const idToken = data.id_token || tokens.idToken || "";
|
|
8651
|
+
const claims = idToken ? parseJwtClaims(idToken) : null;
|
|
8652
|
+
const expiresAt = parseTokenExpiry(data.access_token);
|
|
8653
|
+
const refreshedTokens = {
|
|
8654
|
+
accessToken: data.access_token,
|
|
8655
|
+
refreshToken: data.refresh_token || tokens.refreshToken,
|
|
8656
|
+
idToken,
|
|
8657
|
+
apiKey: tokens.apiKey,
|
|
8658
|
+
expiresAt,
|
|
8659
|
+
accountId: claims?.accountId || tokens.accountId || "",
|
|
8660
|
+
accountEmail: claims?.email || tokens.accountEmail
|
|
8661
|
+
};
|
|
8662
|
+
return ensureCodexApiKey(refreshedTokens);
|
|
8663
|
+
}
|
|
8664
|
+
function startServer(port, pkce, expectedState, resolve, reject) {
|
|
8665
|
+
const server = http.createServer(async (req, res) => {
|
|
8666
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
8667
|
+
if (url.pathname === "/auth/callback") {
|
|
8668
|
+
const state2 = url.searchParams.get("state");
|
|
8669
|
+
const code = url.searchParams.get("code");
|
|
8670
|
+
const error = url.searchParams.get("error");
|
|
8671
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
8672
|
+
if (error) {
|
|
8673
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8674
|
+
const msg = errorDescription || error;
|
|
8675
|
+
res.end(`Authorization failed: ${msg}`);
|
|
8676
|
+
reject(new Error(msg));
|
|
8677
|
+
return;
|
|
8678
|
+
}
|
|
8679
|
+
if (state2 !== expectedState) {
|
|
8680
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8681
|
+
res.end("State mismatch. Please try again.");
|
|
8682
|
+
reject(new Error("State mismatch"));
|
|
8683
|
+
return;
|
|
8684
|
+
}
|
|
8685
|
+
if (!code) {
|
|
8686
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8687
|
+
res.end("Missing authorization code.");
|
|
8688
|
+
reject(new Error("Missing authorization code"));
|
|
8689
|
+
return;
|
|
8690
|
+
}
|
|
8691
|
+
try {
|
|
8692
|
+
activeFlow?.onStatus("exchanging");
|
|
8693
|
+
const redirectUri = `http://localhost:${activeFlow?.port ?? port}/auth/callback`;
|
|
8694
|
+
const tokens = await exchangeCodeForTokens(code, redirectUri, pkce.codeVerifier);
|
|
8695
|
+
res.writeHead(302, {
|
|
8696
|
+
Location: `/success?email=${encodeURIComponent(tokens.accountEmail || tokens.accountId)}`,
|
|
8697
|
+
Connection: "close"
|
|
8698
|
+
});
|
|
8699
|
+
res.end();
|
|
8700
|
+
resolve(tokens);
|
|
8701
|
+
} catch (err) {
|
|
8702
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8703
|
+
res.end(`Token exchange failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
8704
|
+
reject(err instanceof Error ? err : new Error("Token exchange failed"));
|
|
8705
|
+
}
|
|
8706
|
+
return;
|
|
8707
|
+
}
|
|
8708
|
+
if (url.pathname === "/success") {
|
|
8709
|
+
const email = url.searchParams.get("email") || "";
|
|
8710
|
+
res.writeHead(200, { "Content-Type": "text/html", "Connection": "close" });
|
|
8711
|
+
res.end(`<!DOCTYPE html>
|
|
8712
|
+
<html><head><meta charset="utf-8"><title>Vessel — Signed In</title>
|
|
8713
|
+
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#111;color:#eee}</style></head>
|
|
8714
|
+
<body><div style="text-align:center"><h1>✓ Signed In</h1>
|
|
8715
|
+
<p>Connected as ${escapeHtml(email)}</p><p>You can close this tab.</p></div></body></html>`);
|
|
8716
|
+
return;
|
|
8717
|
+
}
|
|
8718
|
+
if (url.pathname === "/cancel") {
|
|
8719
|
+
res.writeHead(200, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8720
|
+
res.end("Login cancelled");
|
|
8721
|
+
reject(new Error("Login cancelled by user"));
|
|
8722
|
+
return;
|
|
8723
|
+
}
|
|
8724
|
+
res.writeHead(404, { "Connection": "close" });
|
|
8725
|
+
res.end("Not found");
|
|
8726
|
+
});
|
|
8727
|
+
return server;
|
|
8728
|
+
}
|
|
8729
|
+
function escapeHtml(text) {
|
|
8730
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
8731
|
+
}
|
|
8732
|
+
async function bindServer(server) {
|
|
8733
|
+
const allowedPorts = [PREFERRED_PORT, FALLBACK_PORT];
|
|
8734
|
+
for (const port of allowedPorts) {
|
|
8735
|
+
try {
|
|
8736
|
+
await new Promise((resolve, reject) => {
|
|
8737
|
+
const onError = (err) => {
|
|
8738
|
+
server.off("listening", onListening);
|
|
8739
|
+
reject(err);
|
|
8740
|
+
};
|
|
8741
|
+
const onListening = () => {
|
|
8742
|
+
server.off("error", onError);
|
|
8743
|
+
resolve();
|
|
8744
|
+
};
|
|
8745
|
+
server.once("error", onError);
|
|
8746
|
+
server.once("listening", onListening);
|
|
8747
|
+
server.listen(port, "127.0.0.1");
|
|
8748
|
+
});
|
|
8749
|
+
return port;
|
|
8750
|
+
} catch (err) {
|
|
8751
|
+
if (err.code === "EADDRINUSE") {
|
|
8752
|
+
continue;
|
|
8753
|
+
}
|
|
8754
|
+
throw err;
|
|
8755
|
+
}
|
|
8756
|
+
}
|
|
8757
|
+
throw new Error(
|
|
8758
|
+
`Could not bind Codex OAuth callback server to registered ports ${allowedPorts.join(", ")}`
|
|
8759
|
+
);
|
|
8760
|
+
}
|
|
8761
|
+
async function startCodexOAuth(onStatus) {
|
|
8762
|
+
if (activeFlow) {
|
|
8763
|
+
throw new Error("Auth flow already in progress");
|
|
8764
|
+
}
|
|
8765
|
+
const pkce = generatePkce();
|
|
8766
|
+
const state2 = generateState();
|
|
8767
|
+
return new Promise((resolve, reject) => {
|
|
8768
|
+
let settled = false;
|
|
8769
|
+
const safeOnStatus = (status, error) => {
|
|
8770
|
+
try {
|
|
8771
|
+
onStatus(status, error);
|
|
8772
|
+
} catch {
|
|
8773
|
+
logger$g.warn("Codex OAuth status callback failed — window may be closed");
|
|
8774
|
+
}
|
|
8775
|
+
};
|
|
8776
|
+
const wrappedResolve = (tokens) => {
|
|
8777
|
+
if (settled) return;
|
|
8778
|
+
settled = true;
|
|
8779
|
+
cleanup();
|
|
8780
|
+
safeOnStatus("connected");
|
|
8781
|
+
resolve(tokens);
|
|
8782
|
+
};
|
|
8783
|
+
const wrappedReject = (err) => {
|
|
8784
|
+
if (settled) return;
|
|
8785
|
+
settled = true;
|
|
8786
|
+
cleanup();
|
|
8787
|
+
safeOnStatus("error", err.message);
|
|
8788
|
+
reject(err);
|
|
8789
|
+
};
|
|
8790
|
+
const server = startServer(0, pkce, state2, wrappedResolve, wrappedReject);
|
|
8791
|
+
const timeout = setTimeout(() => {
|
|
8792
|
+
wrappedReject(new Error("Auth flow timed out after 5 minutes"));
|
|
8793
|
+
}, AUTH_TIMEOUT_MS);
|
|
8794
|
+
activeFlow = {
|
|
8795
|
+
state: state2,
|
|
8796
|
+
codeVerifier: pkce.codeVerifier,
|
|
8797
|
+
port: 0,
|
|
8798
|
+
server,
|
|
8799
|
+
timeout,
|
|
8800
|
+
onStatus
|
|
8801
|
+
};
|
|
8802
|
+
const cleanup = () => {
|
|
8803
|
+
if (activeFlow?.timeout) clearTimeout(activeFlow.timeout);
|
|
8804
|
+
activeFlow?.server.close();
|
|
8805
|
+
activeFlow = null;
|
|
8806
|
+
};
|
|
8807
|
+
bindServer(server).then((port) => {
|
|
8808
|
+
if (settled) return;
|
|
8809
|
+
activeFlow.port = port;
|
|
8810
|
+
const authUrl = buildAuthorizeUrl(port, pkce, state2);
|
|
8811
|
+
safeOnStatus("waiting");
|
|
8812
|
+
electron.shell.openExternal(authUrl).catch((err) => {
|
|
8813
|
+
logger$g.warn("Failed to open browser, user will need the URL:", err);
|
|
8814
|
+
});
|
|
8815
|
+
}).catch(wrappedReject);
|
|
8816
|
+
});
|
|
8817
|
+
}
|
|
8818
|
+
function cancelCodexOAuth() {
|
|
8819
|
+
if (!activeFlow) return;
|
|
8820
|
+
activeFlow.server.close();
|
|
8821
|
+
if (activeFlow.timeout) clearTimeout(activeFlow.timeout);
|
|
8822
|
+
try {
|
|
8823
|
+
activeFlow.onStatus("idle");
|
|
8824
|
+
} catch {
|
|
8825
|
+
logger$g.warn("Codex OAuth cancel status callback failed — window may be closed");
|
|
8826
|
+
}
|
|
8827
|
+
activeFlow = null;
|
|
8828
|
+
}
|
|
8829
|
+
const logger$f = createLogger("CodexProvider");
|
|
8830
|
+
const REFRESH_WINDOW_MS = 5 * 60 * 1e3;
|
|
8831
|
+
const CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
8832
|
+
const CODEX_CLIENT_VERSION = "0.129.0";
|
|
8833
|
+
class CodexProvider {
|
|
8834
|
+
agentToolProfile;
|
|
8835
|
+
tokens;
|
|
8836
|
+
model;
|
|
8837
|
+
abortController = null;
|
|
8838
|
+
constructor(tokens, model, _baseUrl) {
|
|
8839
|
+
this.tokens = tokens;
|
|
8840
|
+
this.model = model;
|
|
8841
|
+
this.agentToolProfile = "default";
|
|
8842
|
+
}
|
|
8843
|
+
async ensureFreshTokens() {
|
|
8844
|
+
if (Date.now() < this.tokens.expiresAt - REFRESH_WINDOW_MS) return;
|
|
8845
|
+
try {
|
|
8846
|
+
logger$f.info("Refreshing Codex access token");
|
|
8847
|
+
const fresh = await refreshAccessToken(this.tokens);
|
|
8848
|
+
this.tokens = fresh;
|
|
8849
|
+
writeStoredCodexTokens(fresh);
|
|
8850
|
+
} catch (err) {
|
|
8851
|
+
clearStoredCodexTokens();
|
|
8852
|
+
throw new Error(
|
|
8853
|
+
`Codex token refresh failed — please re-authenticate. ${err instanceof Error ? err.message : ""}`
|
|
8854
|
+
);
|
|
8855
|
+
}
|
|
8856
|
+
}
|
|
8857
|
+
backendHeaders() {
|
|
8858
|
+
const headers = {
|
|
8859
|
+
Authorization: `Bearer ${this.tokens.accessToken}`,
|
|
8860
|
+
"Content-Type": "application/json",
|
|
8861
|
+
Accept: "text/event-stream",
|
|
8862
|
+
originator: "codex_cli_rs",
|
|
8863
|
+
"User-Agent": `codex_cli_rs/${CODEX_CLIENT_VERSION} Vessel`
|
|
8864
|
+
};
|
|
8865
|
+
if (this.tokens.accountId) {
|
|
8866
|
+
headers["ChatGPT-Account-ID"] = this.tokens.accountId;
|
|
8867
|
+
}
|
|
8868
|
+
return headers;
|
|
8869
|
+
}
|
|
8870
|
+
buildInput(userMessage, history) {
|
|
8871
|
+
const input = [];
|
|
8872
|
+
for (const msg of history ?? []) {
|
|
8873
|
+
input.push({
|
|
8874
|
+
type: "message",
|
|
8875
|
+
role: msg.role,
|
|
8876
|
+
content: [{ type: "input_text", text: msg.content }]
|
|
8877
|
+
});
|
|
8878
|
+
}
|
|
8879
|
+
input.push({
|
|
8880
|
+
type: "message",
|
|
8881
|
+
role: "user",
|
|
8882
|
+
content: [{ type: "input_text", text: userMessage }]
|
|
8883
|
+
});
|
|
8884
|
+
return input;
|
|
8885
|
+
}
|
|
8886
|
+
handleStreamEvent(raw, onChunk, emittedTextFromDelta) {
|
|
8887
|
+
if (!raw.trim() || raw.trim() === "[DONE]") return;
|
|
8888
|
+
let event;
|
|
8889
|
+
try {
|
|
8890
|
+
event = JSON.parse(raw);
|
|
8891
|
+
} catch {
|
|
8892
|
+
return;
|
|
8893
|
+
}
|
|
8894
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
8895
|
+
emittedTextFromDelta.value = true;
|
|
8896
|
+
onChunk(event.delta);
|
|
8897
|
+
return;
|
|
8898
|
+
}
|
|
8899
|
+
if (event.type === "response.output_item.done" && !emittedTextFromDelta.value) {
|
|
8900
|
+
const text = event.item?.content?.filter((item) => item.type === "output_text" && item.text).map((item) => item.text).join("");
|
|
8901
|
+
if (text) onChunk(text);
|
|
8902
|
+
return;
|
|
8903
|
+
}
|
|
8904
|
+
if (event.type === "response.failed") {
|
|
8905
|
+
const error = event.response?.error;
|
|
8906
|
+
const message = error?.message || error?.code || "Codex response failed";
|
|
8907
|
+
throw new Error(message);
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8910
|
+
async streamCodexResponse(systemPrompt, userMessage, onChunk, history) {
|
|
8911
|
+
const response = await fetch(`${CODEX_BACKEND_BASE_URL}/responses`, {
|
|
8912
|
+
method: "POST",
|
|
8913
|
+
headers: this.backendHeaders(),
|
|
8914
|
+
signal: this.abortController?.signal,
|
|
8915
|
+
body: JSON.stringify({
|
|
8916
|
+
model: this.model,
|
|
8917
|
+
instructions: systemPrompt,
|
|
8918
|
+
input: this.buildInput(userMessage, history),
|
|
8919
|
+
stream: true,
|
|
8920
|
+
store: false
|
|
8921
|
+
})
|
|
8922
|
+
});
|
|
8923
|
+
if (!response.ok) {
|
|
8924
|
+
const text = await response.text().catch(() => "");
|
|
8925
|
+
throw new Error(
|
|
8926
|
+
`Codex backend request failed: ${response.status}${text ? ` ${text}` : ""}`
|
|
8927
|
+
);
|
|
8928
|
+
}
|
|
8929
|
+
if (!response.body) {
|
|
8930
|
+
throw new Error("Codex backend returned an empty response stream");
|
|
8931
|
+
}
|
|
8932
|
+
const reader = response.body.getReader();
|
|
8933
|
+
const decoder = new TextDecoder();
|
|
8934
|
+
let buffer = "";
|
|
8935
|
+
const emittedTextFromDelta = { value: false };
|
|
8936
|
+
while (true) {
|
|
8937
|
+
const { value, done } = await reader.read();
|
|
8938
|
+
if (done) break;
|
|
8939
|
+
buffer += decoder.decode(value, { stream: true });
|
|
8940
|
+
let separatorIndex;
|
|
8941
|
+
while ((separatorIndex = buffer.indexOf("\n\n")) !== -1) {
|
|
8942
|
+
const block = buffer.slice(0, separatorIndex);
|
|
8943
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
8944
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
8945
|
+
this.handleStreamEvent(data, onChunk, emittedTextFromDelta);
|
|
8946
|
+
}
|
|
8947
|
+
}
|
|
8948
|
+
const trailing = buffer.trim();
|
|
8949
|
+
if (trailing) {
|
|
8950
|
+
const data = trailing.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
8951
|
+
this.handleStreamEvent(data, onChunk, emittedTextFromDelta);
|
|
8952
|
+
}
|
|
8953
|
+
}
|
|
8954
|
+
async streamQuery(systemPrompt, userMessage, onChunk, onEnd, history) {
|
|
8955
|
+
await this.ensureFreshTokens();
|
|
8956
|
+
this.abortController = new AbortController();
|
|
8957
|
+
try {
|
|
8958
|
+
await this.streamCodexResponse(systemPrompt, userMessage, onChunk, history);
|
|
8959
|
+
} catch (err) {
|
|
8960
|
+
if (err.name !== "AbortError") {
|
|
8961
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8962
|
+
logger$f.error("Codex streamQuery error:", err);
|
|
8963
|
+
onChunk(`
|
|
8964
|
+
|
|
8965
|
+
[Error: ${msg}]`);
|
|
8966
|
+
}
|
|
8967
|
+
} finally {
|
|
8968
|
+
this.abortController = null;
|
|
8969
|
+
onEnd();
|
|
8970
|
+
}
|
|
8971
|
+
}
|
|
8972
|
+
async streamAgentQuery(systemPrompt, userMessage, _tools, onChunk, _onToolCall, onEnd, history) {
|
|
8973
|
+
await this.ensureFreshTokens();
|
|
8974
|
+
this.abortController = new AbortController();
|
|
8975
|
+
try {
|
|
8976
|
+
await this.streamCodexResponse(systemPrompt, userMessage, onChunk, history);
|
|
8977
|
+
} catch (err) {
|
|
8978
|
+
if (err.name !== "AbortError") {
|
|
8979
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8980
|
+
logger$f.error("Codex streamAgentQuery error:", err);
|
|
8981
|
+
onChunk(`
|
|
8982
|
+
|
|
8983
|
+
[Error: ${msg}]`);
|
|
8984
|
+
}
|
|
8985
|
+
} finally {
|
|
8986
|
+
this.abortController = null;
|
|
8987
|
+
onEnd();
|
|
8988
|
+
}
|
|
8989
|
+
}
|
|
8990
|
+
cancel() {
|
|
8991
|
+
this.abortController?.abort();
|
|
8992
|
+
this.abortController = null;
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8412
8995
|
function sanitizeProviderConfig(config) {
|
|
8413
8996
|
return {
|
|
8414
8997
|
...config,
|
|
@@ -8430,7 +9013,7 @@ function validateProviderConnection(config, options = { requireModel: true }) {
|
|
|
8430
9013
|
if (!meta) {
|
|
8431
9014
|
return "Selected AI provider is not supported.";
|
|
8432
9015
|
}
|
|
8433
|
-
if (meta.requiresApiKey && !normalized.apiKey) {
|
|
9016
|
+
if (meta.type !== "codex_oauth" && meta.requiresApiKey && !normalized.apiKey) {
|
|
8434
9017
|
return `${meta.name} requires an API key. Open settings (Ctrl+,) to add one.`;
|
|
8435
9018
|
}
|
|
8436
9019
|
if (options.requireModel && !normalized.model) {
|
|
@@ -8449,8 +9032,8 @@ function extractLlamaCppCtxSize(payload) {
|
|
|
8449
9032
|
const current = queue.shift();
|
|
8450
9033
|
if (!current || typeof current !== "object" || visited.has(current)) continue;
|
|
8451
9034
|
visited.add(current);
|
|
8452
|
-
for (const [
|
|
8453
|
-
if (typeof value === "number" && Number.isFinite(value) && /^(n_ctx|ctx_size|context_size)$/i.test(
|
|
9035
|
+
for (const [key2, value] of Object.entries(current)) {
|
|
9036
|
+
if (typeof value === "number" && Number.isFinite(value) && /^(n_ctx|ctx_size|context_size)$/i.test(key2)) {
|
|
8454
9037
|
return value;
|
|
8455
9038
|
}
|
|
8456
9039
|
if (value && typeof value === "object") {
|
|
@@ -8472,6 +9055,34 @@ function buildLlamaCppCtxWarning(ctxSize) {
|
|
|
8472
9055
|
}
|
|
8473
9056
|
return void 0;
|
|
8474
9057
|
}
|
|
9058
|
+
async function fetchCodexBackendModels(tokens) {
|
|
9059
|
+
const url = new URL("https://chatgpt.com/backend-api/codex/models");
|
|
9060
|
+
url.searchParams.set("client_version", "0.129.0");
|
|
9061
|
+
const headers = {
|
|
9062
|
+
Authorization: `Bearer ${tokens.accessToken}`,
|
|
9063
|
+
originator: "codex_cli_rs",
|
|
9064
|
+
"User-Agent": "codex_cli_rs/0.129.0 Vessel"
|
|
9065
|
+
};
|
|
9066
|
+
if (tokens.accountId) {
|
|
9067
|
+
headers["ChatGPT-Account-ID"] = tokens.accountId;
|
|
9068
|
+
}
|
|
9069
|
+
const response = await fetch(url.toString(), { headers });
|
|
9070
|
+
if (!response.ok) {
|
|
9071
|
+
throw new Error(`Codex backend model discovery failed: ${response.status}`);
|
|
9072
|
+
}
|
|
9073
|
+
const payload = await response.json();
|
|
9074
|
+
if (!Array.isArray(payload.models)) {
|
|
9075
|
+
throw new Error("Codex backend model discovery returned an invalid response");
|
|
9076
|
+
}
|
|
9077
|
+
return payload.models.map((model) => {
|
|
9078
|
+
if (!model || typeof model !== "object") return null;
|
|
9079
|
+
const record = model;
|
|
9080
|
+
const id = record.slug || record.id || record.model;
|
|
9081
|
+
const visibility = record.visibility;
|
|
9082
|
+
if (visibility === "hidden") return null;
|
|
9083
|
+
return typeof id === "string" && id.trim() ? id.trim() : null;
|
|
9084
|
+
}).filter((id) => id !== null);
|
|
9085
|
+
}
|
|
8475
9086
|
async function probeLlamaCppCtxWarning(baseURL) {
|
|
8476
9087
|
try {
|
|
8477
9088
|
const root = new URL(baseURL);
|
|
@@ -8499,6 +9110,24 @@ async function fetchProviderModels(config) {
|
|
|
8499
9110
|
const page2 = await client2.models.list();
|
|
8500
9111
|
return okResult({ models: page2.data.map((model) => model.id) });
|
|
8501
9112
|
}
|
|
9113
|
+
if (normalized.id === "openai_codex") {
|
|
9114
|
+
const tokens = readStoredCodexTokens();
|
|
9115
|
+
if (!tokens) {
|
|
9116
|
+
throw new Error("Codex provider requires authentication. Connect your ChatGPT account in settings.");
|
|
9117
|
+
}
|
|
9118
|
+
try {
|
|
9119
|
+
const models2 = await fetchCodexBackendModels(tokens);
|
|
9120
|
+
if (models2.length > 0) {
|
|
9121
|
+
return okResult({ models: models2 });
|
|
9122
|
+
}
|
|
9123
|
+
throw new Error("Codex backend model discovery returned no models");
|
|
9124
|
+
} catch (err) {
|
|
9125
|
+
return okResult({
|
|
9126
|
+
models: PROVIDERS.openai_codex.models,
|
|
9127
|
+
warning: `Using built-in Codex model list because live discovery failed: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
9128
|
+
});
|
|
9129
|
+
}
|
|
9130
|
+
}
|
|
8502
9131
|
const meta = PROVIDERS[normalized.id];
|
|
8503
9132
|
const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
8504
9133
|
const client = new OpenAI({
|
|
@@ -8528,10 +9157,19 @@ function createProvider(config) {
|
|
|
8528
9157
|
normalized.reasoningEffort
|
|
8529
9158
|
);
|
|
8530
9159
|
}
|
|
9160
|
+
if (normalized.id === "openai_codex") {
|
|
9161
|
+
const tokens = readStoredCodexTokens();
|
|
9162
|
+
if (!tokens) {
|
|
9163
|
+
throw new Error(
|
|
9164
|
+
"OpenAI Codex requires authentication. Open settings to connect your ChatGPT account."
|
|
9165
|
+
);
|
|
9166
|
+
}
|
|
9167
|
+
return new CodexProvider(tokens, normalized.model, normalized.baseUrl);
|
|
9168
|
+
}
|
|
8531
9169
|
return new OpenAICompatProvider(normalized);
|
|
8532
9170
|
}
|
|
8533
9171
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
8534
|
-
const logger$
|
|
9172
|
+
const logger$e = createLogger("DevTrace");
|
|
8535
9173
|
let cachedFactory;
|
|
8536
9174
|
function createNoopTraceSession() {
|
|
8537
9175
|
return {
|
|
@@ -8564,7 +9202,7 @@ function loadLocalFactory() {
|
|
|
8564
9202
|
return cachedFactory;
|
|
8565
9203
|
}
|
|
8566
9204
|
} catch (err) {
|
|
8567
|
-
logger$
|
|
9205
|
+
logger$e.warn("Failed to load local trace logger:", err);
|
|
8568
9206
|
}
|
|
8569
9207
|
}
|
|
8570
9208
|
return cachedFactory;
|
|
@@ -8617,14 +9255,14 @@ function normalizeStoredRadioOption(option) {
|
|
|
8617
9255
|
function dedupeCandidates(actions) {
|
|
8618
9256
|
const seen = /* @__PURE__ */ new Set();
|
|
8619
9257
|
return actions.filter((action) => {
|
|
8620
|
-
const
|
|
9258
|
+
const key2 = [
|
|
8621
9259
|
action.selector || "",
|
|
8622
9260
|
action.label || "",
|
|
8623
9261
|
action.role || "",
|
|
8624
9262
|
action.labelSource || ""
|
|
8625
9263
|
].join("::");
|
|
8626
|
-
if (seen.has(
|
|
8627
|
-
seen.add(
|
|
9264
|
+
if (seen.has(key2)) return false;
|
|
9265
|
+
seen.add(key2);
|
|
8628
9266
|
return true;
|
|
8629
9267
|
});
|
|
8630
9268
|
}
|
|
@@ -8869,11 +9507,11 @@ function getQuantityElements(page) {
|
|
|
8869
9507
|
];
|
|
8870
9508
|
return elements.filter((el) => {
|
|
8871
9509
|
if (!isQuantityLike(el)) return false;
|
|
8872
|
-
const
|
|
9510
|
+
const key2 = String(
|
|
8873
9511
|
el.index ?? el.selector ?? `${el.type}|${el.name || ""}|${el.label || ""}|${el.value || ""}`
|
|
8874
9512
|
);
|
|
8875
|
-
if (seen.has(
|
|
8876
|
-
seen.add(
|
|
9513
|
+
if (seen.has(key2)) return false;
|
|
9514
|
+
seen.add(key2);
|
|
8877
9515
|
return true;
|
|
8878
9516
|
});
|
|
8879
9517
|
}
|
|
@@ -8915,9 +9553,9 @@ function getCartItemLinks(page) {
|
|
|
8915
9553
|
return false;
|
|
8916
9554
|
}
|
|
8917
9555
|
if (blockedText.test(text) || blockedHref.test(href)) return false;
|
|
8918
|
-
const
|
|
8919
|
-
if (seen.has(
|
|
8920
|
-
seen.add(
|
|
9556
|
+
const key2 = `${normalizeComparable(text)}|${normalizeUrlForMatch(href) || href}`;
|
|
9557
|
+
if (seen.has(key2)) return false;
|
|
9558
|
+
seen.add(key2);
|
|
8921
9559
|
return true;
|
|
8922
9560
|
}).slice(0, 12);
|
|
8923
9561
|
}
|
|
@@ -9018,11 +9656,11 @@ function getPurchaseActionElements(page, options) {
|
|
|
9018
9656
|
if (!isPurchaseActionElement(el)) return false;
|
|
9019
9657
|
if (visibleOnly && !isVisibleToUser(el)) return false;
|
|
9020
9658
|
if (el.blockedByOverlay) return false;
|
|
9021
|
-
const
|
|
9659
|
+
const key2 = String(
|
|
9022
9660
|
el.index ?? el.selector ?? `${el.type}|${el.text || ""}|${el.label || ""}|${el.href || ""}`
|
|
9023
9661
|
);
|
|
9024
|
-
if (seen.has(
|
|
9025
|
-
seen.add(
|
|
9662
|
+
if (seen.has(key2)) return false;
|
|
9663
|
+
seen.add(key2);
|
|
9026
9664
|
return true;
|
|
9027
9665
|
}).sort((a, b) => {
|
|
9028
9666
|
const delta = purchaseActionPriority(a) - purchaseActionPriority(b);
|
|
@@ -9039,10 +9677,10 @@ function getOffscreenPurchaseActionElements(page) {
|
|
|
9039
9677
|
)
|
|
9040
9678
|
);
|
|
9041
9679
|
return getPurchaseActionElements(page, { visibleOnly: false }).filter((el) => {
|
|
9042
|
-
const
|
|
9680
|
+
const key2 = String(
|
|
9043
9681
|
el.index ?? el.selector ?? `${el.type}|${el.text || ""}|${el.label || ""}|${el.href || ""}`
|
|
9044
9682
|
);
|
|
9045
|
-
return !visibleKeys.has(
|
|
9683
|
+
return !visibleKeys.has(key2) && el.visible !== false;
|
|
9046
9684
|
});
|
|
9047
9685
|
}
|
|
9048
9686
|
function getDialogFocusedElements(page) {
|
|
@@ -9308,7 +9946,7 @@ function formatStructuredValue(value, depth = 0) {
|
|
|
9308
9946
|
const rendered = value.map((item) => formatStructuredValue(item, depth + 1)).filter(Boolean).slice(0, depth === 0 ? 8 : 5);
|
|
9309
9947
|
return rendered.join(depth === 0 ? ", " : " | ");
|
|
9310
9948
|
}
|
|
9311
|
-
const entries = Object.entries(value).slice(0, 6).map(([
|
|
9949
|
+
const entries = Object.entries(value).slice(0, 6).map(([key2, entry]) => `${key2}: ${formatStructuredValue(entry, depth + 1)}`).filter((entry) => !entry.endsWith(": "));
|
|
9312
9950
|
return entries.join(", ");
|
|
9313
9951
|
}
|
|
9314
9952
|
function formatStructuredEntities(entities) {
|
|
@@ -9323,13 +9961,13 @@ function formatStructuredEntities(entities) {
|
|
|
9323
9961
|
if (entity.url && entity.url !== entity.name) {
|
|
9324
9962
|
lines.push(` url: ${entity.url}`);
|
|
9325
9963
|
}
|
|
9326
|
-
for (const [
|
|
9964
|
+
for (const [key2, value] of Object.entries(entity.attributes).slice(
|
|
9327
9965
|
0,
|
|
9328
9966
|
8
|
|
9329
9967
|
)) {
|
|
9330
9968
|
const rendered = formatStructuredValue(value);
|
|
9331
9969
|
if (rendered) {
|
|
9332
|
-
lines.push(` ${
|
|
9970
|
+
lines.push(` ${key2}: ${rendered}`);
|
|
9333
9971
|
}
|
|
9334
9972
|
}
|
|
9335
9973
|
return lines.join("\n");
|
|
@@ -9427,17 +10065,17 @@ function formatJsonLd(items) {
|
|
|
9427
10065
|
}
|
|
9428
10066
|
return String(val);
|
|
9429
10067
|
};
|
|
9430
|
-
for (const
|
|
9431
|
-
if (
|
|
9432
|
-
seen.add(
|
|
9433
|
-
const rendered = renderValue(item[
|
|
9434
|
-
if (rendered) lines.push(` ${
|
|
10068
|
+
for (const key2 of priorityFields) {
|
|
10069
|
+
if (key2 in item) {
|
|
10070
|
+
seen.add(key2);
|
|
10071
|
+
const rendered = renderValue(item[key2]);
|
|
10072
|
+
if (rendered) lines.push(` ${key2}: ${rendered}`);
|
|
9435
10073
|
}
|
|
9436
10074
|
}
|
|
9437
|
-
for (const [
|
|
9438
|
-
if (seen.has(
|
|
10075
|
+
for (const [key2, val] of Object.entries(item)) {
|
|
10076
|
+
if (seen.has(key2) || SKIP.has(key2) || key2 === "@type") continue;
|
|
9439
10077
|
const rendered = renderValue(val);
|
|
9440
|
-
if (rendered) lines.push(` ${
|
|
10078
|
+
if (rendered) lines.push(` ${key2}: ${rendered}`);
|
|
9441
10079
|
}
|
|
9442
10080
|
lines.push("");
|
|
9443
10081
|
}
|
|
@@ -9572,9 +10210,9 @@ function getResultCandidates(page) {
|
|
|
9572
10210
|
);
|
|
9573
10211
|
const seen = /* @__PURE__ */ new Set();
|
|
9574
10212
|
return scored.map(({ element }) => element).filter((element) => {
|
|
9575
|
-
const
|
|
9576
|
-
if (seen.has(
|
|
9577
|
-
seen.add(
|
|
10213
|
+
const key2 = `${normalizeComparable(element.text || "")}|${normalizeUrlForMatch(element.href) || ""}`;
|
|
10214
|
+
if (seen.has(key2)) return false;
|
|
10215
|
+
seen.add(key2);
|
|
9578
10216
|
return true;
|
|
9579
10217
|
});
|
|
9580
10218
|
}
|
|
@@ -10371,9 +11009,9 @@ function getCompactPrimaryResultLinks(page, options) {
|
|
|
10371
11009
|
})).filter(({ score }) => score >= (listingLike ? 5 : 7)).sort(
|
|
10372
11010
|
(a, b) => b.score - a.score || (a.element.index ?? Number.MAX_SAFE_INTEGER) - (b.element.index ?? Number.MAX_SAFE_INTEGER)
|
|
10373
11011
|
).map(({ element }) => element).filter((element) => {
|
|
10374
|
-
const
|
|
10375
|
-
if (seen.has(
|
|
10376
|
-
seen.add(
|
|
11012
|
+
const key2 = `${normalizeComparable(element.text)}|${normalizeUrlForMatch(element.href) || ""}`;
|
|
11013
|
+
if (seen.has(key2)) return false;
|
|
11014
|
+
seen.add(key2);
|
|
10377
11015
|
return true;
|
|
10378
11016
|
}).slice(0, max);
|
|
10379
11017
|
}
|
|
@@ -10419,9 +11057,9 @@ function formatElement(element) {
|
|
|
10419
11057
|
function uniqueElements(elements) {
|
|
10420
11058
|
const seen = /* @__PURE__ */ new Set();
|
|
10421
11059
|
return elements.filter((element) => {
|
|
10422
|
-
const
|
|
10423
|
-
if (seen.has(
|
|
10424
|
-
seen.add(
|
|
11060
|
+
const key2 = `${element.index ?? ""}|${element.type}|${elementLabel(element)}|${element.href ?? ""}`;
|
|
11061
|
+
if (seen.has(key2)) return false;
|
|
11062
|
+
seen.add(key2);
|
|
10425
11063
|
return true;
|
|
10426
11064
|
});
|
|
10427
11065
|
}
|
|
@@ -11744,12 +12382,12 @@ function normalizeAgentHints(value) {
|
|
|
11744
12382
|
return void 0;
|
|
11745
12383
|
}
|
|
11746
12384
|
const normalized = Object.fromEntries(
|
|
11747
|
-
Object.entries(value).map(([
|
|
12385
|
+
Object.entries(value).map(([key2, hint]) => [key2.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
|
|
11748
12386
|
);
|
|
11749
12387
|
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
11750
12388
|
}
|
|
11751
|
-
function hasOwn(value,
|
|
11752
|
-
return Object.prototype.hasOwnProperty.call(value,
|
|
12389
|
+
function hasOwn(value, key2) {
|
|
12390
|
+
return Object.prototype.hasOwnProperty.call(value, key2);
|
|
11753
12391
|
}
|
|
11754
12392
|
function normalizeBookmarkMetadata(input) {
|
|
11755
12393
|
const normalized = {};
|
|
@@ -11787,7 +12425,7 @@ const UNSORTED_ID = "unsorted";
|
|
|
11787
12425
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
11788
12426
|
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
11789
12427
|
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
11790
|
-
let state$
|
|
12428
|
+
let state$2 = null;
|
|
11791
12429
|
const listeners = /* @__PURE__ */ new Set();
|
|
11792
12430
|
function cloneState(current) {
|
|
11793
12431
|
return {
|
|
@@ -11802,18 +12440,18 @@ function createPersistence() {
|
|
|
11802
12440
|
return createDebouncedJsonPersistence({
|
|
11803
12441
|
debounceMs: SAVE_DEBOUNCE_MS$1,
|
|
11804
12442
|
filePath: getBookmarksPath(),
|
|
11805
|
-
getValue: () => state$
|
|
12443
|
+
getValue: () => state$2,
|
|
11806
12444
|
logLabel: "bookmarks"
|
|
11807
12445
|
});
|
|
11808
12446
|
}
|
|
11809
|
-
let persistence$
|
|
12447
|
+
let persistence$3 = null;
|
|
11810
12448
|
function getPersistence() {
|
|
11811
|
-
persistence$
|
|
11812
|
-
return persistence$
|
|
12449
|
+
persistence$3 ??= createPersistence();
|
|
12450
|
+
return persistence$3;
|
|
11813
12451
|
}
|
|
11814
12452
|
function load$1() {
|
|
11815
|
-
if (state$
|
|
11816
|
-
state$
|
|
12453
|
+
if (state$2) return state$2;
|
|
12454
|
+
state$2 = loadJsonFile({
|
|
11817
12455
|
filePath: getBookmarksPath(),
|
|
11818
12456
|
fallback: { folders: [], bookmarks: [] },
|
|
11819
12457
|
parse: (raw) => {
|
|
@@ -11824,21 +12462,21 @@ function load$1() {
|
|
|
11824
12462
|
};
|
|
11825
12463
|
}
|
|
11826
12464
|
});
|
|
11827
|
-
return state$
|
|
12465
|
+
return state$2;
|
|
11828
12466
|
}
|
|
11829
|
-
function save() {
|
|
12467
|
+
function save$1() {
|
|
11830
12468
|
getPersistence().schedule();
|
|
11831
12469
|
}
|
|
11832
12470
|
function assignDefinedBookmarkFields(bookmark, fields) {
|
|
11833
12471
|
if (!fields) return;
|
|
11834
|
-
for (const [
|
|
12472
|
+
for (const [key2, value] of Object.entries(fields)) {
|
|
11835
12473
|
if (value === void 0) continue;
|
|
11836
|
-
Object.assign(bookmark, { [
|
|
12474
|
+
Object.assign(bookmark, { [key2]: value });
|
|
11837
12475
|
}
|
|
11838
12476
|
}
|
|
11839
|
-
function emit() {
|
|
11840
|
-
if (!state$
|
|
11841
|
-
const snapshot = cloneState(state$
|
|
12477
|
+
function emit$2() {
|
|
12478
|
+
if (!state$2) return;
|
|
12479
|
+
const snapshot = cloneState(state$2);
|
|
11842
12480
|
for (const listener of listeners) {
|
|
11843
12481
|
listener(snapshot);
|
|
11844
12482
|
}
|
|
@@ -11857,7 +12495,7 @@ function getBookmarkDescription(bookmark) {
|
|
|
11857
12495
|
bookmark.intent ? `Intent: ${bookmark.intent}` : "",
|
|
11858
12496
|
bookmark.expectedContent ? `Expected content: ${bookmark.expectedContent}` : "",
|
|
11859
12497
|
bookmark.keyFields?.length ? `Key fields: ${bookmark.keyFields.join(", ")}` : "",
|
|
11860
|
-
bookmark.agentHints && Object.keys(bookmark.agentHints).length > 0 ? `Agent hints: ${Object.entries(bookmark.agentHints).map(([
|
|
12498
|
+
bookmark.agentHints && Object.keys(bookmark.agentHints).length > 0 ? `Agent hints: ${Object.entries(bookmark.agentHints).map(([key2, value]) => `${key2}: ${value}`).join("; ")}` : ""
|
|
11861
12499
|
].filter(Boolean);
|
|
11862
12500
|
return lines.join("\n");
|
|
11863
12501
|
}
|
|
@@ -11960,28 +12598,28 @@ function subscribe(listener) {
|
|
|
11960
12598
|
};
|
|
11961
12599
|
}
|
|
11962
12600
|
function clearAll() {
|
|
11963
|
-
state$
|
|
11964
|
-
save();
|
|
11965
|
-
emit();
|
|
12601
|
+
state$2 = { folders: [], bookmarks: [] };
|
|
12602
|
+
save$1();
|
|
12603
|
+
emit$2();
|
|
11966
12604
|
}
|
|
11967
12605
|
function getBookmark(id) {
|
|
11968
12606
|
load$1();
|
|
11969
|
-
const bookmark = state$
|
|
12607
|
+
const bookmark = state$2.bookmarks.find((item) => item.id === id);
|
|
11970
12608
|
return bookmark ? { ...bookmark } : null;
|
|
11971
12609
|
}
|
|
11972
12610
|
function getBookmarkByUrl(url) {
|
|
11973
12611
|
load$1();
|
|
11974
12612
|
const normalized = url.trim();
|
|
11975
12613
|
if (!normalized) return null;
|
|
11976
|
-
const bookmark = [...state$
|
|
12614
|
+
const bookmark = [...state$2.bookmarks].reverse().find((item) => item.url === normalized);
|
|
11977
12615
|
return bookmark ? { ...bookmark } : null;
|
|
11978
12616
|
}
|
|
11979
12617
|
function getBookmarkByUrlInFolder(url, folderId) {
|
|
11980
12618
|
load$1();
|
|
11981
12619
|
const normalizedUrl = url.trim();
|
|
11982
12620
|
if (!normalizedUrl) return null;
|
|
11983
|
-
const targetFolderId = folderId && folderId !== UNSORTED_ID ? state$
|
|
11984
|
-
const bookmark = [...state$
|
|
12621
|
+
const targetFolderId = folderId && folderId !== UNSORTED_ID ? state$2.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
12622
|
+
const bookmark = [...state$2.bookmarks].reverse().find(
|
|
11985
12623
|
(item) => item.url === normalizedUrl && item.folderId === targetFolderId
|
|
11986
12624
|
);
|
|
11987
12625
|
return bookmark ? { ...bookmark } : null;
|
|
@@ -11989,14 +12627,14 @@ function getBookmarkByUrlInFolder(url, folderId) {
|
|
|
11989
12627
|
function getFolder(id) {
|
|
11990
12628
|
load$1();
|
|
11991
12629
|
if (!id || id === UNSORTED_ID) return null;
|
|
11992
|
-
const folder = state$
|
|
12630
|
+
const folder = state$2.folders.find((item) => item.id === id);
|
|
11993
12631
|
return folder ? { ...folder } : null;
|
|
11994
12632
|
}
|
|
11995
12633
|
function findFolderByName(name) {
|
|
11996
12634
|
load$1();
|
|
11997
12635
|
const normalized = name.trim().toLowerCase();
|
|
11998
12636
|
if (!normalized || normalized === "unsorted") return null;
|
|
11999
|
-
const folder = state$
|
|
12637
|
+
const folder = state$2.folders.find(
|
|
12000
12638
|
(item) => item.name.trim().toLowerCase() === normalized
|
|
12001
12639
|
);
|
|
12002
12640
|
return folder ? { ...folder } : null;
|
|
@@ -12004,7 +12642,7 @@ function findFolderByName(name) {
|
|
|
12004
12642
|
function listFolderOverviews() {
|
|
12005
12643
|
load$1();
|
|
12006
12644
|
const counts = /* @__PURE__ */ new Map();
|
|
12007
|
-
for (const bookmark of state$
|
|
12645
|
+
for (const bookmark of state$2.bookmarks) {
|
|
12008
12646
|
counts.set(bookmark.folderId, (counts.get(bookmark.folderId) ?? 0) + 1);
|
|
12009
12647
|
}
|
|
12010
12648
|
return [
|
|
@@ -12013,7 +12651,7 @@ function listFolderOverviews() {
|
|
|
12013
12651
|
name: "Unsorted",
|
|
12014
12652
|
count: counts.get(UNSORTED_ID) ?? 0
|
|
12015
12653
|
},
|
|
12016
|
-
...state$
|
|
12654
|
+
...state$2.folders.map((folder) => ({
|
|
12017
12655
|
id: folder.id,
|
|
12018
12656
|
name: folder.name,
|
|
12019
12657
|
summary: folder.summary,
|
|
@@ -12024,8 +12662,8 @@ function listFolderOverviews() {
|
|
|
12024
12662
|
function searchBookmarks(query) {
|
|
12025
12663
|
load$1();
|
|
12026
12664
|
if (!query.trim()) return [];
|
|
12027
|
-
return state$
|
|
12028
|
-
const folder = state$
|
|
12665
|
+
return state$2.bookmarks.map((bookmark) => {
|
|
12666
|
+
const folder = state$2.folders.find(
|
|
12029
12667
|
(item) => item.id === bookmark.folderId
|
|
12030
12668
|
);
|
|
12031
12669
|
const { matchedFields, score } = getBookmarkSearchMatch({
|
|
@@ -12063,9 +12701,9 @@ function createFolderWithSummary(name, summary) {
|
|
|
12063
12701
|
summary: summary?.trim() || void 0,
|
|
12064
12702
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12065
12703
|
};
|
|
12066
|
-
state$
|
|
12067
|
-
save();
|
|
12068
|
-
emit();
|
|
12704
|
+
state$2.folders.push(folder);
|
|
12705
|
+
save$1();
|
|
12706
|
+
emit$2();
|
|
12069
12707
|
return folder;
|
|
12070
12708
|
}
|
|
12071
12709
|
function ensureFolder(name, summary) {
|
|
@@ -12094,7 +12732,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
12094
12732
|
throw new Error("Bookmark URL cannot be empty");
|
|
12095
12733
|
}
|
|
12096
12734
|
const normalizedTitle = title.trim() || normalizedUrl;
|
|
12097
|
-
const targetId = folderId && folderId !== UNSORTED_ID ? state$
|
|
12735
|
+
const targetId = folderId && folderId !== UNSORTED_ID ? state$2.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
12098
12736
|
const duplicatePolicy = options?.onDuplicate ?? "ask";
|
|
12099
12737
|
const existing = getBookmarkByUrlInFolder(normalizedUrl, targetId);
|
|
12100
12738
|
if (existing) {
|
|
@@ -12105,7 +12743,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
12105
12743
|
};
|
|
12106
12744
|
}
|
|
12107
12745
|
if (duplicatePolicy === "update") {
|
|
12108
|
-
const bookmark2 = state$
|
|
12746
|
+
const bookmark2 = state$2.bookmarks.find((item) => item.id === existing.id);
|
|
12109
12747
|
if (!bookmark2) {
|
|
12110
12748
|
return {
|
|
12111
12749
|
status: "conflict",
|
|
@@ -12118,8 +12756,8 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
12118
12756
|
}
|
|
12119
12757
|
assignDefinedBookmarkFields(bookmark2, options?.extra);
|
|
12120
12758
|
bookmark2.savedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12121
|
-
save();
|
|
12122
|
-
emit();
|
|
12759
|
+
save$1();
|
|
12760
|
+
emit$2();
|
|
12123
12761
|
return {
|
|
12124
12762
|
status: "updated",
|
|
12125
12763
|
bookmark: { ...bookmark2 }
|
|
@@ -12135,9 +12773,9 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
12135
12773
|
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12136
12774
|
...options?.extra
|
|
12137
12775
|
};
|
|
12138
|
-
state$
|
|
12139
|
-
save();
|
|
12140
|
-
emit();
|
|
12776
|
+
state$2.bookmarks.push(bookmark);
|
|
12777
|
+
save$1();
|
|
12778
|
+
emit$2();
|
|
12141
12779
|
return {
|
|
12142
12780
|
status: "created",
|
|
12143
12781
|
bookmark
|
|
@@ -12145,18 +12783,18 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
12145
12783
|
}
|
|
12146
12784
|
function removeBookmark(id) {
|
|
12147
12785
|
load$1();
|
|
12148
|
-
const before = state$
|
|
12149
|
-
state$
|
|
12150
|
-
if (state$
|
|
12151
|
-
save();
|
|
12152
|
-
emit();
|
|
12786
|
+
const before = state$2.bookmarks.length;
|
|
12787
|
+
state$2.bookmarks = state$2.bookmarks.filter((b) => b.id !== id);
|
|
12788
|
+
if (state$2.bookmarks.length !== before) {
|
|
12789
|
+
save$1();
|
|
12790
|
+
emit$2();
|
|
12153
12791
|
return true;
|
|
12154
12792
|
}
|
|
12155
12793
|
return false;
|
|
12156
12794
|
}
|
|
12157
12795
|
function updateBookmark(id, updates) {
|
|
12158
12796
|
load$1();
|
|
12159
|
-
const bookmark = state$
|
|
12797
|
+
const bookmark = state$2.bookmarks.find((item) => item.id === id);
|
|
12160
12798
|
if (!bookmark) return null;
|
|
12161
12799
|
const metadataUpdates = normalizeBookmarkMetadataUpdate({
|
|
12162
12800
|
intent: updates.intent,
|
|
@@ -12173,7 +12811,7 @@ function updateBookmark(id, updates) {
|
|
|
12173
12811
|
bookmark.note = trimmed || void 0;
|
|
12174
12812
|
}
|
|
12175
12813
|
if (typeof updates.folderId === "string") {
|
|
12176
|
-
bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$
|
|
12814
|
+
bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$2.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
12177
12815
|
}
|
|
12178
12816
|
if ("intent" in metadataUpdates) {
|
|
12179
12817
|
bookmark.intent = metadataUpdates.intent;
|
|
@@ -12190,36 +12828,36 @@ function updateBookmark(id, updates) {
|
|
|
12190
12828
|
if ("agentHints" in metadataUpdates) {
|
|
12191
12829
|
bookmark.agentHints = metadataUpdates.agentHints;
|
|
12192
12830
|
}
|
|
12193
|
-
save();
|
|
12194
|
-
emit();
|
|
12831
|
+
save$1();
|
|
12832
|
+
emit$2();
|
|
12195
12833
|
return { ...bookmark };
|
|
12196
12834
|
}
|
|
12197
12835
|
function removeFolder(id, deleteContents = false) {
|
|
12198
12836
|
load$1();
|
|
12199
|
-
const exists = state$
|
|
12837
|
+
const exists = state$2.folders.some((f) => f.id === id);
|
|
12200
12838
|
if (!exists) return false;
|
|
12201
12839
|
if (deleteContents) {
|
|
12202
|
-
state$
|
|
12840
|
+
state$2.bookmarks = state$2.bookmarks.filter((b) => b.folderId !== id);
|
|
12203
12841
|
} else {
|
|
12204
|
-
state$
|
|
12842
|
+
state$2.bookmarks = state$2.bookmarks.map(
|
|
12205
12843
|
(b) => b.folderId === id ? { ...b, folderId: UNSORTED_ID } : b
|
|
12206
12844
|
);
|
|
12207
12845
|
}
|
|
12208
|
-
state$
|
|
12209
|
-
save();
|
|
12210
|
-
emit();
|
|
12846
|
+
state$2.folders = state$2.folders.filter((f) => f.id !== id);
|
|
12847
|
+
save$1();
|
|
12848
|
+
emit$2();
|
|
12211
12849
|
return true;
|
|
12212
12850
|
}
|
|
12213
12851
|
function renameFolder(id, newName, summary) {
|
|
12214
12852
|
load$1();
|
|
12215
|
-
const folder = state$
|
|
12853
|
+
const folder = state$2.folders.find((f) => f.id === id);
|
|
12216
12854
|
if (!folder) return null;
|
|
12217
12855
|
const trimmed = newName.trim();
|
|
12218
12856
|
if (!trimmed) return null;
|
|
12219
12857
|
folder.name = trimmed;
|
|
12220
12858
|
folder.summary = summary?.trim() || void 0;
|
|
12221
|
-
save();
|
|
12222
|
-
emit();
|
|
12859
|
+
save$1();
|
|
12860
|
+
emit$2();
|
|
12223
12861
|
return { ...folder };
|
|
12224
12862
|
}
|
|
12225
12863
|
function flushPersist$1() {
|
|
@@ -12230,7 +12868,7 @@ function importBookmarksFromHtml(content) {
|
|
|
12230
12868
|
let skipped = 0;
|
|
12231
12869
|
let errors = 0;
|
|
12232
12870
|
load$1();
|
|
12233
|
-
const existingUrls = new Set(state$
|
|
12871
|
+
const existingUrls = new Set(state$2.bookmarks.map((b) => b.url));
|
|
12234
12872
|
let currentFolderId = UNSORTED_ID;
|
|
12235
12873
|
let currentFolderName = "Imported";
|
|
12236
12874
|
const lines = content.split("\n");
|
|
@@ -12239,7 +12877,7 @@ function importBookmarksFromHtml(content) {
|
|
|
12239
12877
|
const folderMatch = line.match(/<DT><H3[^>]*>([^<]+)<\/H3>/i);
|
|
12240
12878
|
if (folderMatch) {
|
|
12241
12879
|
currentFolderName = folderMatch[1].trim();
|
|
12242
|
-
const existing = state$
|
|
12880
|
+
const existing = state$2.folders.find(
|
|
12243
12881
|
(f) => f.name.toLowerCase() === currentFolderName.toLowerCase()
|
|
12244
12882
|
);
|
|
12245
12883
|
if (existing) {
|
|
@@ -12250,7 +12888,7 @@ function importBookmarksFromHtml(content) {
|
|
|
12250
12888
|
name: currentFolderName,
|
|
12251
12889
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12252
12890
|
};
|
|
12253
|
-
state$
|
|
12891
|
+
state$2.folders.push(folder);
|
|
12254
12892
|
currentFolderId = folder.id;
|
|
12255
12893
|
}
|
|
12256
12894
|
continue;
|
|
@@ -12271,7 +12909,7 @@ function importBookmarksFromHtml(content) {
|
|
|
12271
12909
|
else errors++;
|
|
12272
12910
|
continue;
|
|
12273
12911
|
}
|
|
12274
|
-
state$
|
|
12912
|
+
state$2.bookmarks.push({
|
|
12275
12913
|
id: crypto$1.randomUUID(),
|
|
12276
12914
|
url,
|
|
12277
12915
|
title,
|
|
@@ -12283,8 +12921,8 @@ function importBookmarksFromHtml(content) {
|
|
|
12283
12921
|
}
|
|
12284
12922
|
}
|
|
12285
12923
|
if (imported > 0) {
|
|
12286
|
-
save();
|
|
12287
|
-
emit();
|
|
12924
|
+
save$1();
|
|
12925
|
+
emit$2();
|
|
12288
12926
|
}
|
|
12289
12927
|
return { imported, skipped, errors };
|
|
12290
12928
|
}
|
|
@@ -12297,14 +12935,14 @@ function importBookmarksFromJson(content) {
|
|
|
12297
12935
|
const incomingFolders = Array.isArray(parsed?.folders) ? parsed.folders : [];
|
|
12298
12936
|
const incomingBookmarks = Array.isArray(parsed?.bookmarks) ? parsed.bookmarks : [];
|
|
12299
12937
|
load$1();
|
|
12300
|
-
const existingUrls = new Set(state$
|
|
12938
|
+
const existingUrls = new Set(state$2.bookmarks.map((b) => b.url));
|
|
12301
12939
|
const folderIdMap = /* @__PURE__ */ new Map();
|
|
12302
12940
|
for (const folder of incomingFolders) {
|
|
12303
12941
|
if (!folder?.id || !folder?.name) {
|
|
12304
12942
|
errors++;
|
|
12305
12943
|
continue;
|
|
12306
12944
|
}
|
|
12307
|
-
const existing = state$
|
|
12945
|
+
const existing = state$2.folders.find(
|
|
12308
12946
|
(f) => f.name.toLowerCase() === folder.name.toLowerCase()
|
|
12309
12947
|
);
|
|
12310
12948
|
if (existing) {
|
|
@@ -12316,7 +12954,7 @@ function importBookmarksFromJson(content) {
|
|
|
12316
12954
|
summary: folder.summary?.trim() || void 0,
|
|
12317
12955
|
createdAt: folder.createdAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
12318
12956
|
};
|
|
12319
|
-
state$
|
|
12957
|
+
state$2.folders.push(newFolder);
|
|
12320
12958
|
folderIdMap.set(folder.id, newFolder.id);
|
|
12321
12959
|
}
|
|
12322
12960
|
}
|
|
@@ -12330,7 +12968,7 @@ function importBookmarksFromJson(content) {
|
|
|
12330
12968
|
continue;
|
|
12331
12969
|
}
|
|
12332
12970
|
const mappedFolderId = bookmark.folderId ? folderIdMap.get(bookmark.folderId) ?? UNSORTED_ID : UNSORTED_ID;
|
|
12333
|
-
state$
|
|
12971
|
+
state$2.bookmarks.push({
|
|
12334
12972
|
id: crypto$1.randomUUID(),
|
|
12335
12973
|
url: bookmark.url.trim(),
|
|
12336
12974
|
title: typeof bookmark.title === "string" ? bookmark.title.trim() : bookmark.url,
|
|
@@ -12342,8 +12980,8 @@ function importBookmarksFromJson(content) {
|
|
|
12342
12980
|
imported++;
|
|
12343
12981
|
}
|
|
12344
12982
|
if (imported > 0 || errors > 0) {
|
|
12345
|
-
save();
|
|
12346
|
-
emit();
|
|
12983
|
+
save$1();
|
|
12984
|
+
emit$2();
|
|
12347
12985
|
}
|
|
12348
12986
|
} catch {
|
|
12349
12987
|
errors++;
|
|
@@ -12387,9 +13025,9 @@ async function captureLiveHighlightSnapshot(wc, savedHighlights = []) {
|
|
|
12387
13025
|
text: normalizeText(highlight.text),
|
|
12388
13026
|
color: highlight.color?.trim() || void 0
|
|
12389
13027
|
})).filter((highlight) => highlight.text.length > 0).filter((highlight) => {
|
|
12390
|
-
const
|
|
12391
|
-
if (seen.has(
|
|
12392
|
-
seen.add(
|
|
13028
|
+
const key2 = `${highlight.text}\0${highlight.color ?? ""}`;
|
|
13029
|
+
if (seen.has(key2)) return false;
|
|
13030
|
+
seen.add(key2);
|
|
12393
13031
|
return true;
|
|
12394
13032
|
}).map((highlight) => ({
|
|
12395
13033
|
...highlight,
|
|
@@ -12683,7 +13321,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
12683
13321
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
12684
13322
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
12685
13323
|
}
|
|
12686
|
-
const logger$
|
|
13324
|
+
const logger$d = createLogger("Screenshot");
|
|
12687
13325
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
12688
13326
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
12689
13327
|
async function captureScreenshot(wc) {
|
|
@@ -12705,7 +13343,7 @@ async function captureScreenshot(wc) {
|
|
|
12705
13343
|
}
|
|
12706
13344
|
}
|
|
12707
13345
|
} catch (err) {
|
|
12708
|
-
logger$
|
|
13346
|
+
logger$d.debug(
|
|
12709
13347
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
12710
13348
|
getErrorMessage(err)
|
|
12711
13349
|
);
|
|
@@ -12738,17 +13376,17 @@ function sessionFileName(name) {
|
|
|
12738
13376
|
function getSessionPath(name) {
|
|
12739
13377
|
return path$1.join(ensureSessionsDir(), sessionFileName(name));
|
|
12740
13378
|
}
|
|
12741
|
-
function writeSessionFile(
|
|
13379
|
+
function writeSessionFile(filePath2, data) {
|
|
12742
13380
|
fs$1.writeFileSync(
|
|
12743
|
-
|
|
13381
|
+
filePath2,
|
|
12744
13382
|
JSON.stringify({ version: SESSION_VERSION, ...data }, null, 2),
|
|
12745
13383
|
{ encoding: "utf-8", mode: 384 }
|
|
12746
13384
|
);
|
|
12747
|
-
fs$1.chmodSync(
|
|
13385
|
+
fs$1.chmodSync(filePath2, 384);
|
|
12748
13386
|
}
|
|
12749
|
-
function readSessionFile(
|
|
13387
|
+
function readSessionFile(filePath2) {
|
|
12750
13388
|
try {
|
|
12751
|
-
const raw = fs$1.readFileSync(
|
|
13389
|
+
const raw = fs$1.readFileSync(filePath2, "utf-8");
|
|
12752
13390
|
const parsed = JSON.parse(raw);
|
|
12753
13391
|
if (!parsed || typeof parsed.name !== "string") {
|
|
12754
13392
|
return null;
|
|
@@ -12969,9 +13607,9 @@ async function loadNamedSession(tabManager, name) {
|
|
|
12969
13607
|
};
|
|
12970
13608
|
}
|
|
12971
13609
|
function deleteNamedSession(name) {
|
|
12972
|
-
const
|
|
12973
|
-
if (!fs$1.existsSync(
|
|
12974
|
-
fs$1.unlinkSync(
|
|
13610
|
+
const filePath2 = getSessionPath(name);
|
|
13611
|
+
if (!fs$1.existsSync(filePath2)) return false;
|
|
13612
|
+
fs$1.unlinkSync(filePath2);
|
|
12975
13613
|
return true;
|
|
12976
13614
|
}
|
|
12977
13615
|
function isInvalidTextTargetQuery(rawQuery) {
|
|
@@ -13579,7 +14217,7 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
13579
14217
|
appliedFilters
|
|
13580
14218
|
};
|
|
13581
14219
|
}
|
|
13582
|
-
const logger$
|
|
14220
|
+
const logger$c = createLogger("PageActions");
|
|
13583
14221
|
function getBookmarkMetadataFromArgs(args) {
|
|
13584
14222
|
return normalizeBookmarkMetadata({
|
|
13585
14223
|
intent: args.intent ?? args.intent,
|
|
@@ -13765,7 +14403,7 @@ async function executePageScript(wc, script, options) {
|
|
|
13765
14403
|
return result;
|
|
13766
14404
|
} catch (err) {
|
|
13767
14405
|
const label = options?.label ? ` (${options.label})` : "";
|
|
13768
|
-
logger$
|
|
14406
|
+
logger$c.warn(`Failed to execute page script${label}:`, err);
|
|
13769
14407
|
return null;
|
|
13770
14408
|
} finally {
|
|
13771
14409
|
if (timer) {
|
|
@@ -13866,7 +14504,7 @@ Search results snapshot:
|
|
|
13866
14504
|
${truncated}`;
|
|
13867
14505
|
}
|
|
13868
14506
|
} catch (err) {
|
|
13869
|
-
logger$
|
|
14507
|
+
logger$c.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
13870
14508
|
}
|
|
13871
14509
|
const fallback = await getPostNavSummary(wc);
|
|
13872
14510
|
return fallback ? `${fallback}
|
|
@@ -13889,7 +14527,7 @@ Page snapshot after navigation:
|
|
|
13889
14527
|
${truncated}`;
|
|
13890
14528
|
}
|
|
13891
14529
|
} catch (err) {
|
|
13892
|
-
logger$
|
|
14530
|
+
logger$c.warn("Failed to build post-click navigation summary:", err);
|
|
13893
14531
|
}
|
|
13894
14532
|
return "";
|
|
13895
14533
|
}
|
|
@@ -14383,7 +15021,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14383
15021
|
}
|
|
14384
15022
|
}
|
|
14385
15023
|
} catch (err) {
|
|
14386
|
-
logger$
|
|
15024
|
+
logger$c.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
14387
15025
|
}
|
|
14388
15026
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
14389
15027
|
try {
|
|
@@ -14392,7 +15030,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14392
15030
|
await waitForLoad(wc, 3e3);
|
|
14393
15031
|
return;
|
|
14394
15032
|
} catch (err) {
|
|
14395
|
-
logger$
|
|
15033
|
+
logger$c.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
14396
15034
|
}
|
|
14397
15035
|
}
|
|
14398
15036
|
if (snapshot.url) {
|
|
@@ -14400,7 +15038,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14400
15038
|
await wc.reload();
|
|
14401
15039
|
await waitForLoad(wc, 3e3);
|
|
14402
15040
|
} catch (err) {
|
|
14403
|
-
logger$
|
|
15041
|
+
logger$c.warn("Failed to restore locale via page reload:", err);
|
|
14404
15042
|
}
|
|
14405
15043
|
}
|
|
14406
15044
|
}
|
|
@@ -14428,9 +15066,9 @@ function isAddToCartText(text) {
|
|
|
14428
15066
|
}
|
|
14429
15067
|
function recordCartClick(url, text) {
|
|
14430
15068
|
recentCartClicks.set(url, { text, ts: Date.now() });
|
|
14431
|
-
for (const [
|
|
15069
|
+
for (const [key2, entry] of recentCartClicks) {
|
|
14432
15070
|
if (Date.now() - entry.ts > CART_CLICK_COOLDOWN_MS) {
|
|
14433
|
-
recentCartClicks.delete(
|
|
15071
|
+
recentCartClicks.delete(key2);
|
|
14434
15072
|
}
|
|
14435
15073
|
}
|
|
14436
15074
|
}
|
|
@@ -14477,9 +15115,9 @@ function normalizeCartProductKey(url) {
|
|
|
14477
15115
|
}
|
|
14478
15116
|
}
|
|
14479
15117
|
function pruneCartAddedProducts(now = Date.now()) {
|
|
14480
|
-
for (const [
|
|
15118
|
+
for (const [key2, entry] of cartAddedProducts) {
|
|
14481
15119
|
if (now - entry.ts > CART_ADDED_TTL_MS) {
|
|
14482
|
-
cartAddedProducts.delete(
|
|
15120
|
+
cartAddedProducts.delete(key2);
|
|
14483
15121
|
}
|
|
14484
15122
|
}
|
|
14485
15123
|
}
|
|
@@ -14505,7 +15143,7 @@ function isProductAlreadyInCart(url) {
|
|
|
14505
15143
|
function getCartAddedSummary(url) {
|
|
14506
15144
|
pruneCartAddedProducts();
|
|
14507
15145
|
const origin = cartOrigin(url);
|
|
14508
|
-
const items = Array.from(cartAddedProducts.entries()).filter(([
|
|
15146
|
+
const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
|
|
14509
15147
|
if (!items) return "";
|
|
14510
15148
|
const count = items.split("\n").length;
|
|
14511
15149
|
return `
|
|
@@ -14752,7 +15390,7 @@ ${postActivationOverlayHint}`;
|
|
|
14752
15390
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
14753
15391
|
}
|
|
14754
15392
|
} catch (err) {
|
|
14755
|
-
logger$
|
|
15393
|
+
logger$c.warn("Failed href fallback after click, returning generic click result:", err);
|
|
14756
15394
|
}
|
|
14757
15395
|
}
|
|
14758
15396
|
}
|
|
@@ -14797,7 +15435,7 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
14797
15435
|
return result;
|
|
14798
15436
|
}
|
|
14799
15437
|
} catch (err) {
|
|
14800
|
-
logger$
|
|
15438
|
+
logger$c.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
14801
15439
|
}
|
|
14802
15440
|
return null;
|
|
14803
15441
|
}
|
|
@@ -16161,8 +16799,8 @@ async function submitForm(wc, args) {
|
|
|
16161
16799
|
}
|
|
16162
16800
|
return "Submitted form";
|
|
16163
16801
|
}
|
|
16164
|
-
async function pressKeyDirect(wc,
|
|
16165
|
-
return pressKey(wc, { key, index, selector });
|
|
16802
|
+
async function pressKeyDirect(wc, key2, index, selector) {
|
|
16803
|
+
return pressKey(wc, { key: key2, index, selector });
|
|
16166
16804
|
}
|
|
16167
16805
|
async function submitFormDirect(wc, index, selector) {
|
|
16168
16806
|
return submitForm(wc, { index, selector });
|
|
@@ -16528,8 +17166,8 @@ async function searchPage(wc, args) {
|
|
|
16528
17166
|
return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
|
|
16529
17167
|
}
|
|
16530
17168
|
async function pressKey(wc, args) {
|
|
16531
|
-
const
|
|
16532
|
-
if (!
|
|
17169
|
+
const key2 = typeof args.key === "string" ? args.key.trim() : "";
|
|
17170
|
+
if (!key2) return "Error: No key provided";
|
|
16533
17171
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
16534
17172
|
const focusResult = await executePageScript(
|
|
16535
17173
|
wc,
|
|
@@ -16567,16 +17205,16 @@ async function pressKey(wc, args) {
|
|
|
16567
17205
|
return focusResult.error;
|
|
16568
17206
|
}
|
|
16569
17207
|
wc.focus();
|
|
16570
|
-
const normalizedKey =
|
|
17208
|
+
const normalizedKey = key2.length === 1 ? key2 : key2[0].toUpperCase() + key2.slice(1);
|
|
16571
17209
|
const electronKeyCode = normalizedKey === "Enter" ? "Return" : normalizedKey === "ArrowUp" ? "Up" : normalizedKey === "ArrowDown" ? "Down" : normalizedKey === "ArrowLeft" ? "Left" : normalizedKey === "ArrowRight" ? "Right" : normalizedKey;
|
|
16572
17210
|
wc.sendInputEvent({ type: "keyDown", keyCode: electronKeyCode });
|
|
16573
|
-
if (
|
|
16574
|
-
wc.sendInputEvent({ type: "char", keyCode:
|
|
17211
|
+
if (key2.length === 1) {
|
|
17212
|
+
wc.sendInputEvent({ type: "char", keyCode: key2 });
|
|
16575
17213
|
}
|
|
16576
17214
|
await sleep(16);
|
|
16577
17215
|
wc.sendInputEvent({ type: "keyUp", keyCode: electronKeyCode });
|
|
16578
17216
|
const label = "label" in focusResult && typeof focusResult.label === "string" ? focusResult.label : null;
|
|
16579
|
-
return label ? `Pressed key: ${
|
|
17217
|
+
return label ? `Pressed key: ${key2} on ${label}` : `Pressed key: ${key2}`;
|
|
16580
17218
|
}
|
|
16581
17219
|
async function getPostActionState$1(ctx, name) {
|
|
16582
17220
|
const tab = ctx.tabManager.getActiveTab();
|
|
@@ -16986,8 +17624,8 @@ async function executeAction(name, args, ctx) {
|
|
|
16986
17624
|
if (!wc) return "Error: No active tab";
|
|
16987
17625
|
const beforeUrl = wc.getURL();
|
|
16988
17626
|
const result2 = await pressKey(wc, args);
|
|
16989
|
-
const
|
|
16990
|
-
if (
|
|
17627
|
+
const key2 = typeof args.key === "string" ? args.key.trim() : "";
|
|
17628
|
+
if (key2 === "Enter") {
|
|
16991
17629
|
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
16992
17630
|
const afterUrl = wc.getURL();
|
|
16993
17631
|
if (afterUrl !== beforeUrl) {
|
|
@@ -17067,7 +17705,7 @@ async function executeAction(name, args, ctx) {
|
|
|
17067
17705
|
)
|
|
17068
17706
|
]);
|
|
17069
17707
|
} catch (err) {
|
|
17070
|
-
logger$
|
|
17708
|
+
logger$c.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
17071
17709
|
content = null;
|
|
17072
17710
|
}
|
|
17073
17711
|
if (!content || content.content.length === 0) {
|
|
@@ -17084,12 +17722,12 @@ async function executeAction(name, args, ctx) {
|
|
|
17084
17722
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
17085
17723
|
]);
|
|
17086
17724
|
} catch (err) {
|
|
17087
|
-
logger$
|
|
17725
|
+
logger$c.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
17088
17726
|
content = null;
|
|
17089
17727
|
}
|
|
17090
17728
|
}
|
|
17091
17729
|
} catch (err) {
|
|
17092
|
-
logger$
|
|
17730
|
+
logger$c.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
17093
17731
|
}
|
|
17094
17732
|
}
|
|
17095
17733
|
if (content && content.content.length > 0) {
|
|
@@ -17502,7 +18140,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
17502
18140
|
try {
|
|
17503
18141
|
page = await extractContent(wc);
|
|
17504
18142
|
} catch (err) {
|
|
17505
|
-
logger$
|
|
18143
|
+
logger$c.warn("Failed to extract content for suggest:", err);
|
|
17506
18144
|
return "Could not read page. Try navigate to a working URL.";
|
|
17507
18145
|
}
|
|
17508
18146
|
const suggestions = [];
|
|
@@ -18092,17 +18730,17 @@ function escapeYaml(value) {
|
|
|
18092
18730
|
}
|
|
18093
18731
|
function renderFrontmatter(data) {
|
|
18094
18732
|
const lines = ["---"];
|
|
18095
|
-
for (const [
|
|
18733
|
+
for (const [key2, value] of Object.entries(data)) {
|
|
18096
18734
|
if (value == null) continue;
|
|
18097
18735
|
if (Array.isArray(value)) {
|
|
18098
18736
|
if (value.length === 0) continue;
|
|
18099
|
-
lines.push(`${
|
|
18737
|
+
lines.push(`${key2}:`);
|
|
18100
18738
|
for (const item of value) {
|
|
18101
18739
|
lines.push(` - ${escapeYaml(item)}`);
|
|
18102
18740
|
}
|
|
18103
18741
|
continue;
|
|
18104
18742
|
}
|
|
18105
|
-
lines.push(`${
|
|
18743
|
+
lines.push(`${key2}: ${escapeYaml(value)}`);
|
|
18106
18744
|
}
|
|
18107
18745
|
lines.push("---", "");
|
|
18108
18746
|
return lines.join("\n");
|
|
@@ -18155,11 +18793,11 @@ function parseFrontmatter(content) {
|
|
|
18155
18793
|
activeArrayKey = "";
|
|
18156
18794
|
const separatorIndex = trimmed.indexOf(":");
|
|
18157
18795
|
if (separatorIndex === -1) continue;
|
|
18158
|
-
const
|
|
18796
|
+
const key2 = trimmed.slice(0, separatorIndex).trim();
|
|
18159
18797
|
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
18160
|
-
if (
|
|
18798
|
+
if (key2 === "title" && value) {
|
|
18161
18799
|
result.title = value.replace(/^["']|["']$/g, "");
|
|
18162
|
-
} else if (
|
|
18800
|
+
} else if (key2 === "tags") {
|
|
18163
18801
|
activeArrayKey = "tags";
|
|
18164
18802
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
18165
18803
|
const inline = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
@@ -18745,15 +19383,15 @@ Exception: ${result.exceptionDetails}`);
|
|
|
18745
19383
|
value: zod.z.string().nullable().describe("Value to set, or null to remove the key")
|
|
18746
19384
|
}
|
|
18747
19385
|
},
|
|
18748
|
-
async ({ type, key, value }) => {
|
|
19386
|
+
async ({ type, key: key2, value }) => {
|
|
18749
19387
|
return withDevToolsAction(
|
|
18750
19388
|
runtime2,
|
|
18751
19389
|
tabManager,
|
|
18752
19390
|
"devtools_set_storage",
|
|
18753
|
-
{ type, key, value: value ? value.slice(0, 100) : null },
|
|
19391
|
+
{ type, key: key2, value: value ? value.slice(0, 100) : null },
|
|
18754
19392
|
async () => {
|
|
18755
19393
|
const session = getOrCreateSession(tabManager);
|
|
18756
|
-
return session.setStorage(type,
|
|
19394
|
+
return session.setStorage(type, key2, value);
|
|
18757
19395
|
}
|
|
18758
19396
|
);
|
|
18759
19397
|
}
|
|
@@ -18827,7 +19465,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
18827
19465
|
}
|
|
18828
19466
|
);
|
|
18829
19467
|
}
|
|
18830
|
-
const logger$
|
|
19468
|
+
const logger$b = createLogger("VaultShared");
|
|
18831
19469
|
const ALGORITHM = "aes-256-gcm";
|
|
18832
19470
|
const IV_LENGTH = 12;
|
|
18833
19471
|
const AUTH_TAG_LENGTH = 16;
|
|
@@ -18845,11 +19483,11 @@ function getOrCreateEncryptionKey(keyFilename) {
|
|
|
18845
19483
|
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
18846
19484
|
return Buffer.from(electron.safeStorage.decryptString(encryptedKey), "utf-8");
|
|
18847
19485
|
}
|
|
18848
|
-
const
|
|
19486
|
+
const key2 = crypto$2.randomBytes(32);
|
|
18849
19487
|
fs$1.mkdirSync(path$1.dirname(keyPath), { recursive: true });
|
|
18850
|
-
const encrypted = electron.safeStorage.encryptString(
|
|
19488
|
+
const encrypted = electron.safeStorage.encryptString(key2.toString("utf-8"));
|
|
18851
19489
|
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
18852
|
-
return
|
|
19490
|
+
return key2;
|
|
18853
19491
|
}
|
|
18854
19492
|
function createEncryptDecrypt(keyFilename) {
|
|
18855
19493
|
let cachedKey = null;
|
|
@@ -18858,9 +19496,9 @@ function createEncryptDecrypt(keyFilename) {
|
|
|
18858
19496
|
return cachedKey;
|
|
18859
19497
|
}
|
|
18860
19498
|
function encrypt2(plaintext) {
|
|
18861
|
-
const
|
|
19499
|
+
const key2 = getKey();
|
|
18862
19500
|
const iv = crypto$2.randomBytes(IV_LENGTH);
|
|
18863
|
-
const cipher = crypto$2.createCipheriv(ALGORITHM,
|
|
19501
|
+
const cipher = crypto$2.createCipheriv(ALGORITHM, key2, iv, {
|
|
18864
19502
|
authTagLength: AUTH_TAG_LENGTH
|
|
18865
19503
|
});
|
|
18866
19504
|
const encrypted = Buffer.concat([
|
|
@@ -18871,11 +19509,11 @@ function createEncryptDecrypt(keyFilename) {
|
|
|
18871
19509
|
return Buffer.concat([iv, authTag, encrypted]);
|
|
18872
19510
|
}
|
|
18873
19511
|
function decrypt2(data) {
|
|
18874
|
-
const
|
|
19512
|
+
const key2 = getKey();
|
|
18875
19513
|
const iv = data.subarray(0, IV_LENGTH);
|
|
18876
19514
|
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
18877
19515
|
const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
18878
|
-
const decipher = crypto$2.createDecipheriv(ALGORITHM,
|
|
19516
|
+
const decipher = crypto$2.createDecipheriv(ALGORITHM, key2, iv, {
|
|
18879
19517
|
authTagLength: AUTH_TAG_LENGTH
|
|
18880
19518
|
});
|
|
18881
19519
|
decipher.setAuthTag(authTag);
|
|
@@ -18904,7 +19542,7 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
|
18904
19542
|
cachedEntries = JSON.parse(json);
|
|
18905
19543
|
return cachedEntries;
|
|
18906
19544
|
} catch (err) {
|
|
18907
|
-
logger$
|
|
19545
|
+
logger$b.error("Failed to load vault:", err);
|
|
18908
19546
|
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
18909
19547
|
}
|
|
18910
19548
|
}
|
|
@@ -18973,7 +19611,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
18973
19611
|
} catch {
|
|
18974
19612
|
}
|
|
18975
19613
|
} catch (err) {
|
|
18976
|
-
logger$
|
|
19614
|
+
logger$b.error("Failed to write audit log:", err);
|
|
18977
19615
|
}
|
|
18978
19616
|
}
|
|
18979
19617
|
function readAuditLog2(limit = 100) {
|
|
@@ -18983,7 +19621,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
18983
19621
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
18984
19622
|
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
18985
19623
|
} catch (err) {
|
|
18986
|
-
logger$
|
|
19624
|
+
logger$b.error("Failed to read audit log:", err);
|
|
18987
19625
|
return [];
|
|
18988
19626
|
}
|
|
18989
19627
|
}
|
|
@@ -19091,7 +19729,7 @@ async function requestConsent(request) {
|
|
|
19091
19729
|
}
|
|
19092
19730
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
19093
19731
|
const MAX_ENTRIES = 1e3;
|
|
19094
|
-
const logger$
|
|
19732
|
+
const logger$a = createLogger("VaultAudit");
|
|
19095
19733
|
function getAuditPath() {
|
|
19096
19734
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
19097
19735
|
}
|
|
@@ -19101,7 +19739,7 @@ function appendAuditEntry(entry) {
|
|
|
19101
19739
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
19102
19740
|
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
19103
19741
|
} catch (err) {
|
|
19104
|
-
logger$
|
|
19742
|
+
logger$a.error("Failed to write audit log:", err);
|
|
19105
19743
|
}
|
|
19106
19744
|
}
|
|
19107
19745
|
function readAuditLog$1(limit = 100) {
|
|
@@ -19111,7 +19749,7 @@ function readAuditLog$1(limit = 100) {
|
|
|
19111
19749
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19112
19750
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
19113
19751
|
} catch (err) {
|
|
19114
|
-
logger$
|
|
19752
|
+
logger$a.error("Failed to read audit log:", err);
|
|
19115
19753
|
return [];
|
|
19116
19754
|
}
|
|
19117
19755
|
}
|
|
@@ -19284,7 +19922,7 @@ async function requestHumanVaultConsent(request) {
|
|
|
19284
19922
|
}
|
|
19285
19923
|
let httpServer = null;
|
|
19286
19924
|
let mcpAuthToken = null;
|
|
19287
|
-
const logger$
|
|
19925
|
+
const logger$9 = createLogger("MCP");
|
|
19288
19926
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
19289
19927
|
function getMcpAuthFilePath() {
|
|
19290
19928
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -19312,15 +19950,15 @@ function getPersistentMcpAuthToken() {
|
|
|
19312
19950
|
}
|
|
19313
19951
|
function writeMcpAuthFile(endpoint, token) {
|
|
19314
19952
|
try {
|
|
19315
|
-
const
|
|
19316
|
-
fs$1.mkdirSync(path$1.dirname(
|
|
19953
|
+
const filePath2 = getMcpAuthFilePath();
|
|
19954
|
+
fs$1.mkdirSync(path$1.dirname(filePath2), { recursive: true });
|
|
19317
19955
|
fs$1.writeFileSync(
|
|
19318
|
-
|
|
19956
|
+
filePath2,
|
|
19319
19957
|
JSON.stringify({ endpoint, token, pid: process.pid }, null, 2) + "\n",
|
|
19320
19958
|
{ mode: 384 }
|
|
19321
19959
|
);
|
|
19322
19960
|
} catch (err) {
|
|
19323
|
-
logger$
|
|
19961
|
+
logger$9.warn("Failed to write auth file:", err);
|
|
19324
19962
|
}
|
|
19325
19963
|
}
|
|
19326
19964
|
function clearMcpAuthFile() {
|
|
@@ -19333,10 +19971,10 @@ function clearMcpAuthFile() {
|
|
|
19333
19971
|
return;
|
|
19334
19972
|
}
|
|
19335
19973
|
try {
|
|
19336
|
-
const
|
|
19337
|
-
fs$1.mkdirSync(path$1.dirname(
|
|
19974
|
+
const filePath2 = getMcpAuthFilePath();
|
|
19975
|
+
fs$1.mkdirSync(path$1.dirname(filePath2), { recursive: true });
|
|
19338
19976
|
fs$1.writeFileSync(
|
|
19339
|
-
|
|
19977
|
+
filePath2,
|
|
19340
19978
|
JSON.stringify(
|
|
19341
19979
|
{ endpoint: "", token: existingToken, pid: null },
|
|
19342
19980
|
null,
|
|
@@ -19345,7 +19983,7 @@ function clearMcpAuthFile() {
|
|
|
19345
19983
|
{ mode: 384 }
|
|
19346
19984
|
);
|
|
19347
19985
|
} catch (err) {
|
|
19348
|
-
logger$
|
|
19986
|
+
logger$9.warn("Failed to clear auth file:", err);
|
|
19349
19987
|
}
|
|
19350
19988
|
}
|
|
19351
19989
|
function asTextResponse(text) {
|
|
@@ -19435,7 +20073,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
19435
20073
|
}
|
|
19436
20074
|
}
|
|
19437
20075
|
} catch (err) {
|
|
19438
|
-
logger$
|
|
20076
|
+
logger$9.warn("Failed to compute post-action state warning:", err);
|
|
19439
20077
|
}
|
|
19440
20078
|
return `${warning}
|
|
19441
20079
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -19537,7 +20175,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
19537
20175
|
}
|
|
19538
20176
|
})()
|
|
19539
20177
|
`).catch((err) => {
|
|
19540
|
-
logger$
|
|
20178
|
+
logger$9.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
19541
20179
|
return null;
|
|
19542
20180
|
});
|
|
19543
20181
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -19624,7 +20262,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
19624
20262
|
const page = await extractContent(wc);
|
|
19625
20263
|
pageType = detectPageType(page);
|
|
19626
20264
|
} catch (err) {
|
|
19627
|
-
logger$
|
|
20265
|
+
logger$9.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
19628
20266
|
}
|
|
19629
20267
|
}
|
|
19630
20268
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -20352,19 +20990,19 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
20352
20990
|
selector: zod.z.string().optional().describe("CSS selector to focus first")
|
|
20353
20991
|
}
|
|
20354
20992
|
},
|
|
20355
|
-
async ({ key, index, selector }) => {
|
|
20993
|
+
async ({ key: key2, index, selector }) => {
|
|
20356
20994
|
const tab = tabManager.getActiveTab();
|
|
20357
20995
|
if (!tab) return asNoActiveTabResponse();
|
|
20358
20996
|
return withAction(
|
|
20359
20997
|
runtime2,
|
|
20360
20998
|
tabManager,
|
|
20361
20999
|
"press_key",
|
|
20362
|
-
{ key, index, selector },
|
|
21000
|
+
{ key: key2, index, selector },
|
|
20363
21001
|
async () => {
|
|
20364
21002
|
const wc = tab.view.webContents;
|
|
20365
21003
|
const beforeUrl = wc.getURL();
|
|
20366
|
-
const result = await pressKeyDirect(wc,
|
|
20367
|
-
if (
|
|
21004
|
+
const result = await pressKeyDirect(wc, key2, index, selector);
|
|
21005
|
+
if (key2 === "Enter") {
|
|
20368
21006
|
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
20369
21007
|
const afterUrl = wc.getURL();
|
|
20370
21008
|
if (afterUrl !== beforeUrl) {
|
|
@@ -20830,7 +21468,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20830
21468
|
)
|
|
20831
21469
|
}
|
|
20832
21470
|
},
|
|
20833
|
-
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
21471
|
+
async ({ index, selector, text, label, durationMs, persist: persist2, color }) => {
|
|
20834
21472
|
const tab = tabManager.getActiveTab();
|
|
20835
21473
|
if (!tab) return asNoActiveTabResponse();
|
|
20836
21474
|
const normalizedText = normalizeLooseString(text);
|
|
@@ -20844,7 +21482,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20844
21482
|
text: normalizedText,
|
|
20845
21483
|
label,
|
|
20846
21484
|
durationMs,
|
|
20847
|
-
persist,
|
|
21485
|
+
persist: persist2,
|
|
20848
21486
|
color
|
|
20849
21487
|
},
|
|
20850
21488
|
async () => {
|
|
@@ -20858,7 +21496,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20858
21496
|
durationMs,
|
|
20859
21497
|
color
|
|
20860
21498
|
);
|
|
20861
|
-
if (
|
|
21499
|
+
if (persist2 && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
|
|
20862
21500
|
const url = normalizeUrl$1(wc.getURL());
|
|
20863
21501
|
addHighlight(
|
|
20864
21502
|
url,
|
|
@@ -21022,7 +21660,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
21022
21660
|
void 0,
|
|
21023
21661
|
h.color
|
|
21024
21662
|
).catch(
|
|
21025
|
-
(err) => logger$
|
|
21663
|
+
(err) => logger$9.warn("Failed to restore highlight after removal:", err)
|
|
21026
21664
|
);
|
|
21027
21665
|
}
|
|
21028
21666
|
}
|
|
@@ -21870,7 +22508,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21870
22508
|
try {
|
|
21871
22509
|
page = await extractContent(wc);
|
|
21872
22510
|
} catch (err) {
|
|
21873
|
-
logger$
|
|
22511
|
+
logger$9.warn("Failed to extract page while generating suggestions:", err);
|
|
21874
22512
|
return asTextResponse(
|
|
21875
22513
|
"Could not read page. Try navigate to a working URL."
|
|
21876
22514
|
);
|
|
@@ -22479,7 +23117,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22479
23117
|
try {
|
|
22480
23118
|
targetDomain = new URL(tab.state.url).hostname;
|
|
22481
23119
|
} catch (err) {
|
|
22482
|
-
logger$
|
|
23120
|
+
logger$9.warn("Failed to parse active tab URL for vault_status:", err);
|
|
22483
23121
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22484
23122
|
}
|
|
22485
23123
|
}
|
|
@@ -22545,7 +23183,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22545
23183
|
try {
|
|
22546
23184
|
hostname = new URL(tab.state.url).hostname;
|
|
22547
23185
|
} catch (err) {
|
|
22548
|
-
logger$
|
|
23186
|
+
logger$9.warn("Failed to parse active tab URL for vault_login:", err);
|
|
22549
23187
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22550
23188
|
}
|
|
22551
23189
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -22639,7 +23277,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22639
23277
|
try {
|
|
22640
23278
|
hostname = new URL(tab.state.url).hostname;
|
|
22641
23279
|
} catch (err) {
|
|
22642
|
-
logger$
|
|
23280
|
+
logger$9.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
22643
23281
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22644
23282
|
}
|
|
22645
23283
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -22937,7 +23575,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22937
23575
|
});
|
|
22938
23576
|
mcpAuthToken = getPersistentMcpAuthToken();
|
|
22939
23577
|
return new Promise((resolve) => {
|
|
22940
|
-
const server = http.createServer(async (req, res) => {
|
|
23578
|
+
const server = http$1.createServer(async (req, res) => {
|
|
22941
23579
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
22942
23580
|
if (url.pathname !== "/mcp") {
|
|
22943
23581
|
res.writeHead(404);
|
|
@@ -22979,7 +23617,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22979
23617
|
await mcpServer.connect(transport);
|
|
22980
23618
|
await transport.handleRequest(req, res);
|
|
22981
23619
|
} catch (error) {
|
|
22982
|
-
logger$
|
|
23620
|
+
logger$9.error("Error handling request:", error);
|
|
22983
23621
|
if (!res.headersSent) {
|
|
22984
23622
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22985
23623
|
res.end(
|
|
@@ -22998,7 +23636,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22998
23636
|
};
|
|
22999
23637
|
server.once("error", (error) => {
|
|
23000
23638
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
23001
|
-
logger$
|
|
23639
|
+
logger$9.error("Server error:", error);
|
|
23002
23640
|
clearMcpAuthFile();
|
|
23003
23641
|
setMcpHealth({
|
|
23004
23642
|
configuredPort: port,
|
|
@@ -23030,7 +23668,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
23030
23668
|
message: `MCP server listening on ${endpoint}.`
|
|
23031
23669
|
});
|
|
23032
23670
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23033
|
-
logger$
|
|
23671
|
+
logger$9.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
23034
23672
|
}
|
|
23035
23673
|
if (mcpAuthToken) {
|
|
23036
23674
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -23069,7 +23707,7 @@ function stopMcpServer() {
|
|
|
23069
23707
|
message: "MCP server is stopped."
|
|
23070
23708
|
});
|
|
23071
23709
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23072
|
-
logger$
|
|
23710
|
+
logger$9.info("Server stopped");
|
|
23073
23711
|
}
|
|
23074
23712
|
resolve();
|
|
23075
23713
|
});
|
|
@@ -23090,7 +23728,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
23090
23728
|
function isSafeAutomationKitId(id) {
|
|
23091
23729
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
23092
23730
|
}
|
|
23093
|
-
const logger$
|
|
23731
|
+
const logger$8 = createLogger("KitRegistry");
|
|
23094
23732
|
function getUserKitsDir() {
|
|
23095
23733
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
23096
23734
|
}
|
|
@@ -23128,10 +23766,10 @@ function getInstalledKits() {
|
|
|
23128
23766
|
if (isValidKit(parsed)) {
|
|
23129
23767
|
kits.push(parsed);
|
|
23130
23768
|
} else {
|
|
23131
|
-
logger$
|
|
23769
|
+
logger$8.warn(`Skipping invalid kit file: ${file}`);
|
|
23132
23770
|
}
|
|
23133
23771
|
} catch (err) {
|
|
23134
|
-
logger$
|
|
23772
|
+
logger$8.warn(`Failed to read kit file: ${file}`, err);
|
|
23135
23773
|
}
|
|
23136
23774
|
}
|
|
23137
23775
|
return kits;
|
|
@@ -23203,7 +23841,7 @@ function uninstallKit(id, scheduledKitIds) {
|
|
|
23203
23841
|
return errorResult("Failed to remove the kit file.");
|
|
23204
23842
|
}
|
|
23205
23843
|
}
|
|
23206
|
-
const logger$
|
|
23844
|
+
const logger$7 = createLogger("Scheduler");
|
|
23207
23845
|
let jobs = [];
|
|
23208
23846
|
let removeIdleListener = null;
|
|
23209
23847
|
let broadcastFn = null;
|
|
@@ -23228,7 +23866,7 @@ function saveJobs() {
|
|
|
23228
23866
|
try {
|
|
23229
23867
|
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
23230
23868
|
} catch (err) {
|
|
23231
|
-
logger$
|
|
23869
|
+
logger$7.warn("Failed to save jobs:", err);
|
|
23232
23870
|
}
|
|
23233
23871
|
}
|
|
23234
23872
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -23350,7 +23988,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
23350
23988
|
};
|
|
23351
23989
|
startActivity();
|
|
23352
23990
|
if (!settings2.chatProvider) {
|
|
23353
|
-
logger$
|
|
23991
|
+
logger$7.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
23354
23992
|
appendActivity(
|
|
23355
23993
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
23356
23994
|
);
|
|
@@ -23358,7 +23996,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
23358
23996
|
return;
|
|
23359
23997
|
}
|
|
23360
23998
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
23361
|
-
logger$
|
|
23999
|
+
logger$7.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
23362
24000
|
}
|
|
23363
24001
|
try {
|
|
23364
24002
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -23411,7 +24049,7 @@ function tick(windowState, runtime2) {
|
|
|
23411
24049
|
saveJobs();
|
|
23412
24050
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
23413
24051
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
23414
|
-
logger$
|
|
24052
|
+
logger$7.warn("Unexpected error firing job:", err);
|
|
23415
24053
|
}).finally(fireNext);
|
|
23416
24054
|
};
|
|
23417
24055
|
fireNext();
|
|
@@ -23523,7 +24161,7 @@ const PROFILE_FIELDS = [
|
|
|
23523
24161
|
"postalCode",
|
|
23524
24162
|
"country"
|
|
23525
24163
|
];
|
|
23526
|
-
let state = null;
|
|
24164
|
+
let state$1 = null;
|
|
23527
24165
|
function getFilePath() {
|
|
23528
24166
|
return path.join(electron.app.getPath("userData"), "vessel-autofill.json");
|
|
23529
24167
|
}
|
|
@@ -23548,8 +24186,8 @@ function normalizeStoredProfile(value) {
|
|
|
23548
24186
|
return profile;
|
|
23549
24187
|
}
|
|
23550
24188
|
function load() {
|
|
23551
|
-
if (state) return state;
|
|
23552
|
-
state = loadJsonFile({
|
|
24189
|
+
if (state$1) return state$1;
|
|
24190
|
+
state$1 = loadJsonFile({
|
|
23553
24191
|
filePath: getFilePath(),
|
|
23554
24192
|
fallback: getDefaultState(),
|
|
23555
24193
|
secure: true,
|
|
@@ -23560,12 +24198,12 @@ function load() {
|
|
|
23560
24198
|
};
|
|
23561
24199
|
}
|
|
23562
24200
|
});
|
|
23563
|
-
return state;
|
|
24201
|
+
return state$1;
|
|
23564
24202
|
}
|
|
23565
|
-
const persistence = createDebouncedJsonPersistence({
|
|
24203
|
+
const persistence$2 = createDebouncedJsonPersistence({
|
|
23566
24204
|
debounceMs: SAVE_DEBOUNCE_MS,
|
|
23567
24205
|
filePath: getFilePath(),
|
|
23568
|
-
getValue: () => state,
|
|
24206
|
+
getValue: () => state$1,
|
|
23569
24207
|
logLabel: "autofill",
|
|
23570
24208
|
secure: true
|
|
23571
24209
|
});
|
|
@@ -23585,7 +24223,7 @@ function addProfile(input) {
|
|
|
23585
24223
|
updatedAt: now
|
|
23586
24224
|
};
|
|
23587
24225
|
s.profiles.push(profile);
|
|
23588
|
-
persistence.schedule();
|
|
24226
|
+
persistence$2.schedule();
|
|
23589
24227
|
return profile;
|
|
23590
24228
|
}
|
|
23591
24229
|
function updateProfile(id, updates) {
|
|
@@ -23597,7 +24235,7 @@ function updateProfile(id, updates) {
|
|
|
23597
24235
|
...updates,
|
|
23598
24236
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23599
24237
|
};
|
|
23600
|
-
persistence.schedule();
|
|
24238
|
+
persistence$2.schedule();
|
|
23601
24239
|
return s.profiles[idx];
|
|
23602
24240
|
}
|
|
23603
24241
|
function deleteProfile(id) {
|
|
@@ -23605,11 +24243,11 @@ function deleteProfile(id) {
|
|
|
23605
24243
|
const len = s.profiles.length;
|
|
23606
24244
|
s.profiles = s.profiles.filter((p) => p.id !== id);
|
|
23607
24245
|
if (s.profiles.length === len) return false;
|
|
23608
|
-
persistence.schedule();
|
|
24246
|
+
persistence$2.schedule();
|
|
23609
24247
|
return true;
|
|
23610
24248
|
}
|
|
23611
24249
|
function flushPersist() {
|
|
23612
|
-
return persistence.flush();
|
|
24250
|
+
return persistence$2.flush();
|
|
23613
24251
|
}
|
|
23614
24252
|
const AUTOCOMPLETE_MAP = {
|
|
23615
24253
|
"given-name": "firstName",
|
|
@@ -23738,12 +24376,12 @@ function matchField(el, profile) {
|
|
|
23738
24376
|
if (inputType === "hidden" || inputType === "submit" || inputType === "button" || inputType === "file" || inputType === "image") return null;
|
|
23739
24377
|
if (inputType === "password" || inputType === "checkbox" || inputType === "radio") return null;
|
|
23740
24378
|
if (el.autocomplete) {
|
|
23741
|
-
const
|
|
23742
|
-
if (
|
|
24379
|
+
const key2 = el.autocomplete.replace(/section-\w+\s+/, "").replace(/^shipping\s+|^billing\s+/, "");
|
|
24380
|
+
if (key2 === "name" || key2 === "additional-name") {
|
|
23743
24381
|
const fullName = getFullName(profile);
|
|
23744
24382
|
if (fullName) return mk(fullName, 100, "autocomplete", "fullName");
|
|
23745
24383
|
}
|
|
23746
|
-
const pk = AUTOCOMPLETE_MAP[
|
|
24384
|
+
const pk = AUTOCOMPLETE_MAP[key2];
|
|
23747
24385
|
if (pk && profile[pk]) return mk(profile[pk], 100, "autocomplete", pk);
|
|
23748
24386
|
}
|
|
23749
24387
|
if (INPUT_TYPE_MAP[inputType]) {
|
|
@@ -24231,6 +24869,64 @@ function installAdBlockingForSession(ses, tabManager) {
|
|
|
24231
24869
|
callback(getAdBlockDecision(details));
|
|
24232
24870
|
});
|
|
24233
24871
|
}
|
|
24872
|
+
const filePath$1 = () => path$1.join(electron.app.getPath("userData"), "vessel-downloads.json");
|
|
24873
|
+
function parse(raw) {
|
|
24874
|
+
if (!raw || typeof raw !== "object") return { items: [] };
|
|
24875
|
+
const items = Array.isArray(raw.items) ? raw.items : [];
|
|
24876
|
+
return { items };
|
|
24877
|
+
}
|
|
24878
|
+
let state = loadJsonFile({ filePath: filePath$1(), fallback: { items: [] }, parse });
|
|
24879
|
+
const persistence$1 = createDebouncedJsonPersistence({
|
|
24880
|
+
debounceMs: 250,
|
|
24881
|
+
filePath: filePath$1(),
|
|
24882
|
+
getValue: () => state,
|
|
24883
|
+
logLabel: "downloads"
|
|
24884
|
+
});
|
|
24885
|
+
let broadcaster$1 = null;
|
|
24886
|
+
function persist() {
|
|
24887
|
+
state.items = state.items.slice(0, 200);
|
|
24888
|
+
persistence$1.schedule();
|
|
24889
|
+
}
|
|
24890
|
+
function emit$1() {
|
|
24891
|
+
broadcaster$1?.(Channels.DOWNLOADS_UPDATE, state.items);
|
|
24892
|
+
}
|
|
24893
|
+
function setDownloadBroadcaster(fn) {
|
|
24894
|
+
broadcaster$1 = fn;
|
|
24895
|
+
}
|
|
24896
|
+
function listDownloads() {
|
|
24897
|
+
return state.items;
|
|
24898
|
+
}
|
|
24899
|
+
function upsertDownload(input) {
|
|
24900
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24901
|
+
const existing = state.items.find((item) => item.savePath === input.savePath);
|
|
24902
|
+
if (existing) {
|
|
24903
|
+
Object.assign(existing, input, { updatedAt: now });
|
|
24904
|
+
persist();
|
|
24905
|
+
emit$1();
|
|
24906
|
+
return existing;
|
|
24907
|
+
}
|
|
24908
|
+
const record = { id: crypto$2.randomUUID(), ...input, startedAt: now, updatedAt: now };
|
|
24909
|
+
state.items = [record, ...state.items];
|
|
24910
|
+
persist();
|
|
24911
|
+
emit$1();
|
|
24912
|
+
return record;
|
|
24913
|
+
}
|
|
24914
|
+
function clearDownloads() {
|
|
24915
|
+
state.items = [];
|
|
24916
|
+
persist();
|
|
24917
|
+
emit$1();
|
|
24918
|
+
}
|
|
24919
|
+
async function openDownload(id) {
|
|
24920
|
+
const item = state.items.find((d) => d.id === id);
|
|
24921
|
+
if (!item || !fs$1.existsSync(item.savePath)) return false;
|
|
24922
|
+
return await electron.shell.openPath(item.savePath) === "";
|
|
24923
|
+
}
|
|
24924
|
+
async function showDownloadInFolder(id) {
|
|
24925
|
+
const item = state.items.find((d) => d.id === id);
|
|
24926
|
+
if (!item || !fs$1.existsSync(item.savePath)) return false;
|
|
24927
|
+
electron.shell.showItemInFolder(item.savePath);
|
|
24928
|
+
return true;
|
|
24929
|
+
}
|
|
24234
24930
|
const defaultDownloadViews = /* @__PURE__ */ new Set();
|
|
24235
24931
|
let defaultDownloadHandlerInstalled = false;
|
|
24236
24932
|
function resolveDownloadPath(downloadDir, filename) {
|
|
@@ -24277,17 +24973,20 @@ function installDownloadHandlerForSession(targetSession, chromeView) {
|
|
|
24277
24973
|
receivedBytes: 0,
|
|
24278
24974
|
state: "progressing"
|
|
24279
24975
|
};
|
|
24280
|
-
|
|
24976
|
+
const record = upsertDownload(info);
|
|
24977
|
+
send(Channels.DOWNLOAD_STARTED, { ...info, id: record.id, startedAt: record.startedAt, updatedAt: record.updatedAt });
|
|
24281
24978
|
item.on("updated", (_event2, state2) => {
|
|
24282
24979
|
info.receivedBytes = item.getReceivedBytes();
|
|
24283
24980
|
info.totalBytes = item.getTotalBytes();
|
|
24284
24981
|
info.state = state2 === "progressing" ? "progressing" : "interrupted";
|
|
24285
|
-
|
|
24982
|
+
const record2 = upsertDownload(info);
|
|
24983
|
+
send(Channels.DOWNLOAD_PROGRESS, { ...info, id: record2.id, startedAt: record2.startedAt, updatedAt: record2.updatedAt });
|
|
24286
24984
|
});
|
|
24287
24985
|
item.once("done", (_event2, state2) => {
|
|
24288
24986
|
info.receivedBytes = item.getReceivedBytes();
|
|
24289
24987
|
info.state = state2 === "completed" ? "completed" : "cancelled";
|
|
24290
|
-
|
|
24988
|
+
const record2 = upsertDownload(info);
|
|
24989
|
+
send(Channels.DOWNLOAD_DONE, { ...info, id: record2.id, startedAt: record2.startedAt, updatedAt: record2.updatedAt });
|
|
24291
24990
|
});
|
|
24292
24991
|
});
|
|
24293
24992
|
}
|
|
@@ -24521,7 +25220,7 @@ function createFindInPageBridge(tabManager, chromeView) {
|
|
|
24521
25220
|
}
|
|
24522
25221
|
};
|
|
24523
25222
|
}
|
|
24524
|
-
const logger$
|
|
25223
|
+
const logger$6 = createLogger("PrivateWindow");
|
|
24525
25224
|
const privateWindows = /* @__PURE__ */ new Set();
|
|
24526
25225
|
function layoutPrivateViews(state2) {
|
|
24527
25226
|
const { window: win, chromeView, tabManager } = state2;
|
|
@@ -24744,7 +25443,7 @@ function createPrivateWindow() {
|
|
|
24744
25443
|
privateSession.clearStorageData(),
|
|
24745
25444
|
privateSession.clearCache()
|
|
24746
25445
|
]).catch((error) => {
|
|
24747
|
-
logger$
|
|
25446
|
+
logger$6.warn("Failed to clear private browsing session:", error);
|
|
24748
25447
|
});
|
|
24749
25448
|
});
|
|
24750
25449
|
privateWindows.add(state2);
|
|
@@ -24754,7 +25453,7 @@ function createPrivateWindow() {
|
|
|
24754
25453
|
});
|
|
24755
25454
|
loadPrivateRenderer(chromeView);
|
|
24756
25455
|
win.show();
|
|
24757
|
-
logger$
|
|
25456
|
+
logger$6.info("Private browsing window opened");
|
|
24758
25457
|
return state2;
|
|
24759
25458
|
}
|
|
24760
25459
|
const secondaryWindows = /* @__PURE__ */ new Set();
|
|
@@ -25001,35 +25700,35 @@ function registerBookmarkHandlers() {
|
|
|
25001
25700
|
electron.ipcMain.handle(
|
|
25002
25701
|
Channels.BOOKMARKS_EXPORT_HTML,
|
|
25003
25702
|
async (_, options) => {
|
|
25004
|
-
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
25703
|
+
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
25005
25704
|
title: "Export Bookmarks",
|
|
25006
25705
|
defaultPath: "vessel-bookmarks.html",
|
|
25007
25706
|
filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
|
|
25008
25707
|
});
|
|
25009
|
-
if (canceled || !
|
|
25708
|
+
if (canceled || !filePath2) return null;
|
|
25010
25709
|
const content = exportBookmarksHtml({
|
|
25011
25710
|
includeNotes: options?.includeNotes ?? false
|
|
25012
25711
|
});
|
|
25013
|
-
await fs.promises.writeFile(
|
|
25712
|
+
await fs.promises.writeFile(filePath2, content, "utf-8");
|
|
25014
25713
|
trackBookmarkAction("export");
|
|
25015
25714
|
return {
|
|
25016
|
-
filePath,
|
|
25715
|
+
filePath: filePath2,
|
|
25017
25716
|
count: getState().bookmarks.length
|
|
25018
25717
|
};
|
|
25019
25718
|
}
|
|
25020
25719
|
);
|
|
25021
25720
|
electron.ipcMain.handle(Channels.BOOKMARKS_EXPORT_JSON, async () => {
|
|
25022
|
-
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
25721
|
+
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
25023
25722
|
title: "Export Vessel Bookmark Archive",
|
|
25024
25723
|
defaultPath: "vessel-bookmarks.json",
|
|
25025
25724
|
filters: [{ name: "Vessel Bookmark Archive", extensions: ["json"] }]
|
|
25026
25725
|
});
|
|
25027
|
-
if (canceled || !
|
|
25726
|
+
if (canceled || !filePath2) return null;
|
|
25028
25727
|
const content = exportBookmarksJson();
|
|
25029
|
-
await fs.promises.writeFile(
|
|
25728
|
+
await fs.promises.writeFile(filePath2, content, "utf-8");
|
|
25030
25729
|
trackBookmarkAction("export");
|
|
25031
25730
|
return {
|
|
25032
|
-
filePath,
|
|
25731
|
+
filePath: filePath2,
|
|
25033
25732
|
count: getState().bookmarks.length
|
|
25034
25733
|
};
|
|
25035
25734
|
});
|
|
@@ -25038,20 +25737,20 @@ function registerBookmarkHandlers() {
|
|
|
25038
25737
|
async (_, folderId, options) => {
|
|
25039
25738
|
const folder = getFolder(folderId);
|
|
25040
25739
|
if (!folder) return null;
|
|
25041
|
-
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
25740
|
+
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
25042
25741
|
title: `Export ${folder.name}`,
|
|
25043
25742
|
defaultPath: `vessel-bookmarks-${getSafeBookmarkExportName(folder.name)}.html`,
|
|
25044
25743
|
filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
|
|
25045
25744
|
});
|
|
25046
|
-
if (canceled || !
|
|
25745
|
+
if (canceled || !filePath2) return null;
|
|
25047
25746
|
const result = exportBookmarkFolderHtml(folderId, {
|
|
25048
25747
|
includeNotes: options?.includeNotes ?? true
|
|
25049
25748
|
});
|
|
25050
25749
|
if (!result) return null;
|
|
25051
|
-
await fs.promises.writeFile(
|
|
25750
|
+
await fs.promises.writeFile(filePath2, result.content, "utf-8");
|
|
25052
25751
|
trackBookmarkAction("export");
|
|
25053
25752
|
return {
|
|
25054
|
-
filePath,
|
|
25753
|
+
filePath: filePath2,
|
|
25055
25754
|
count: result.count
|
|
25056
25755
|
};
|
|
25057
25756
|
}
|
|
@@ -25104,26 +25803,26 @@ function registerHistoryHandlers() {
|
|
|
25104
25803
|
clearAll$1();
|
|
25105
25804
|
});
|
|
25106
25805
|
electron.ipcMain.handle(Channels.HISTORY_EXPORT_HTML, async () => {
|
|
25107
|
-
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
25806
|
+
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
25108
25807
|
title: "Export History",
|
|
25109
25808
|
defaultPath: "vessel-history.html",
|
|
25110
25809
|
filters: [{ name: "HTML", extensions: ["html"] }]
|
|
25111
25810
|
});
|
|
25112
|
-
if (canceled || !
|
|
25811
|
+
if (canceled || !filePath2) return null;
|
|
25113
25812
|
const content = exportHistoryHtml();
|
|
25114
|
-
await fs.promises.writeFile(
|
|
25115
|
-
return { filePath, count: getState$1().entries.length };
|
|
25813
|
+
await fs.promises.writeFile(filePath2, content, "utf-8");
|
|
25814
|
+
return { filePath: filePath2, count: getState$1().entries.length };
|
|
25116
25815
|
});
|
|
25117
25816
|
electron.ipcMain.handle(Channels.HISTORY_EXPORT_JSON, async () => {
|
|
25118
|
-
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
25817
|
+
const { canceled, filePath: filePath2 } = await electron.dialog.showSaveDialog({
|
|
25119
25818
|
title: "Export History",
|
|
25120
25819
|
defaultPath: "vessel-history.json",
|
|
25121
25820
|
filters: [{ name: "JSON", extensions: ["json"] }]
|
|
25122
25821
|
});
|
|
25123
|
-
if (canceled || !
|
|
25822
|
+
if (canceled || !filePath2) return null;
|
|
25124
25823
|
const content = exportHistoryJson();
|
|
25125
|
-
await fs.promises.writeFile(
|
|
25126
|
-
return { filePath, count: getState$1().entries.length };
|
|
25824
|
+
await fs.promises.writeFile(filePath2, content, "utf-8");
|
|
25825
|
+
return { filePath: filePath2, count: getState$1().entries.length };
|
|
25127
25826
|
});
|
|
25128
25827
|
electron.ipcMain.handle(Channels.HISTORY_IMPORT, async () => {
|
|
25129
25828
|
const { canceled, filePaths } = await electron.dialog.showOpenDialog({
|
|
@@ -25134,9 +25833,9 @@ function registerHistoryHandlers() {
|
|
|
25134
25833
|
properties: ["openFile"]
|
|
25135
25834
|
});
|
|
25136
25835
|
if (canceled || filePaths.length === 0) return null;
|
|
25137
|
-
const
|
|
25138
|
-
const content = await fs.promises.readFile(
|
|
25139
|
-
const result =
|
|
25836
|
+
const filePath2 = filePaths[0];
|
|
25837
|
+
const content = await fs.promises.readFile(filePath2, "utf-8");
|
|
25838
|
+
const result = filePath2.endsWith(".json") ? importHistoryFromJson(content) : importHistoryFromHtml(content);
|
|
25140
25839
|
return result;
|
|
25141
25840
|
});
|
|
25142
25841
|
}
|
|
@@ -25368,6 +26067,161 @@ function registerSecurityHandlers(tabManager) {
|
|
|
25368
26067
|
tabManager.goBackToSafety(tabId);
|
|
25369
26068
|
});
|
|
25370
26069
|
}
|
|
26070
|
+
const logger$5 = createLogger("CodexIPC");
|
|
26071
|
+
function registerCodexHandlers() {
|
|
26072
|
+
electron.ipcMain.handle(Channels.CODEX_START_AUTH, async (event) => {
|
|
26073
|
+
const wc = event.sender;
|
|
26074
|
+
if (!wc || wc.isDestroyed()) {
|
|
26075
|
+
return {
|
|
26076
|
+
ok: false,
|
|
26077
|
+
error: "No active window found for sender"
|
|
26078
|
+
};
|
|
26079
|
+
}
|
|
26080
|
+
const sendStatus = (status, error) => {
|
|
26081
|
+
try {
|
|
26082
|
+
wc.send(Channels.CODEX_AUTH_STATUS, { status, error: error || null });
|
|
26083
|
+
} catch {
|
|
26084
|
+
logger$5.warn("Codex auth status send failed — window may be closed");
|
|
26085
|
+
}
|
|
26086
|
+
};
|
|
26087
|
+
try {
|
|
26088
|
+
const tokens = await startCodexOAuth(sendStatus);
|
|
26089
|
+
writeStoredCodexTokens(tokens);
|
|
26090
|
+
return {
|
|
26091
|
+
ok: true,
|
|
26092
|
+
accountEmail: tokens.accountEmail || tokens.accountId,
|
|
26093
|
+
accountId: tokens.accountId
|
|
26094
|
+
};
|
|
26095
|
+
} catch (err) {
|
|
26096
|
+
logger$5.error("Codex auth failed:", err);
|
|
26097
|
+
return {
|
|
26098
|
+
ok: false,
|
|
26099
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
26100
|
+
};
|
|
26101
|
+
}
|
|
26102
|
+
});
|
|
26103
|
+
electron.ipcMain.handle(Channels.CODEX_CANCEL_AUTH, () => {
|
|
26104
|
+
cancelCodexOAuth();
|
|
26105
|
+
return { ok: true };
|
|
26106
|
+
});
|
|
26107
|
+
electron.ipcMain.handle(Channels.CODEX_DISCONNECT, () => {
|
|
26108
|
+
clearStoredCodexTokens();
|
|
26109
|
+
return { ok: true };
|
|
26110
|
+
});
|
|
26111
|
+
}
|
|
26112
|
+
const filePath = () => path$1.join(electron.app.getPath("userData"), "vessel-permissions.json");
|
|
26113
|
+
let records = loadJsonFile({
|
|
26114
|
+
filePath: filePath(),
|
|
26115
|
+
fallback: [],
|
|
26116
|
+
parse: (raw) => Array.isArray(raw) ? raw : []
|
|
26117
|
+
});
|
|
26118
|
+
const persistence = createDebouncedJsonPersistence({ debounceMs: 250, filePath: filePath(), getValue: () => records, logLabel: "permissions" });
|
|
26119
|
+
let broadcaster = null;
|
|
26120
|
+
function key(origin, permission) {
|
|
26121
|
+
return `${origin}
|
|
26122
|
+
${permission}`;
|
|
26123
|
+
}
|
|
26124
|
+
function emit() {
|
|
26125
|
+
broadcaster?.(Channels.PERMISSIONS_GET, records);
|
|
26126
|
+
}
|
|
26127
|
+
function save(origin, permission, decision) {
|
|
26128
|
+
const k = key(origin, permission);
|
|
26129
|
+
const existing = records.find((r) => key(r.origin, r.permission) === k);
|
|
26130
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26131
|
+
if (existing) Object.assign(existing, { decision, updatedAt });
|
|
26132
|
+
else records.unshift({ origin, permission, decision, updatedAt });
|
|
26133
|
+
persistence.schedule();
|
|
26134
|
+
emit();
|
|
26135
|
+
}
|
|
26136
|
+
function listPermissions() {
|
|
26137
|
+
return records;
|
|
26138
|
+
}
|
|
26139
|
+
function clearPermissions() {
|
|
26140
|
+
records = [];
|
|
26141
|
+
persistence.schedule();
|
|
26142
|
+
emit();
|
|
26143
|
+
}
|
|
26144
|
+
function clearPermissionsForOrigin(origin) {
|
|
26145
|
+
records = records.filter((record) => record.origin !== origin);
|
|
26146
|
+
persistence.schedule();
|
|
26147
|
+
emit();
|
|
26148
|
+
}
|
|
26149
|
+
function setPermissionBroadcaster(fn) {
|
|
26150
|
+
broadcaster = fn;
|
|
26151
|
+
}
|
|
26152
|
+
function installPermissionHandler() {
|
|
26153
|
+
electron.session.defaultSession.setPermissionRequestHandler((webContents, permission, callback, details) => {
|
|
26154
|
+
const origin = new URL(details.requestingUrl || webContents.getURL()).origin;
|
|
26155
|
+
const existing = records.find((r) => r.origin === origin && r.permission === permission);
|
|
26156
|
+
if (existing) {
|
|
26157
|
+
callback(existing.decision === "allow");
|
|
26158
|
+
return;
|
|
26159
|
+
}
|
|
26160
|
+
const result = electron.dialog.showMessageBoxSync({
|
|
26161
|
+
type: "question",
|
|
26162
|
+
buttons: ["Deny", "Allow"],
|
|
26163
|
+
defaultId: 0,
|
|
26164
|
+
cancelId: 0,
|
|
26165
|
+
title: "Site permission request",
|
|
26166
|
+
message: `${origin} wants to use ${permission}`,
|
|
26167
|
+
detail: "Vessel will remember your choice. You can clear saved permissions in Settings > Privacy."
|
|
26168
|
+
});
|
|
26169
|
+
const decision = result === 1 ? "allow" : "deny";
|
|
26170
|
+
save(origin, permission, decision);
|
|
26171
|
+
callback(decision === "allow");
|
|
26172
|
+
});
|
|
26173
|
+
}
|
|
26174
|
+
const NPM_PACKAGE_URL = "https://registry.npmjs.org/@quanta-intellect%2Fvessel-browser/latest";
|
|
26175
|
+
const RELEASES_URL = "https://github.com/unmodeled-tyler/quanta-vessel-browser/releases/latest";
|
|
26176
|
+
function normalizeVersion(version) {
|
|
26177
|
+
return version.replace(/^v/i, "").split(/[.-]/).slice(0, 3).map((part) => {
|
|
26178
|
+
const n = Number.parseInt(part, 10);
|
|
26179
|
+
return Number.isFinite(n) ? n : 0;
|
|
26180
|
+
});
|
|
26181
|
+
}
|
|
26182
|
+
function compareVersions(a, b) {
|
|
26183
|
+
const av = normalizeVersion(a);
|
|
26184
|
+
const bv = normalizeVersion(b);
|
|
26185
|
+
for (let i = 0; i < 3; i += 1) {
|
|
26186
|
+
if ((av[i] ?? 0) > (bv[i] ?? 0)) return 1;
|
|
26187
|
+
if ((av[i] ?? 0) < (bv[i] ?? 0)) return -1;
|
|
26188
|
+
}
|
|
26189
|
+
return 0;
|
|
26190
|
+
}
|
|
26191
|
+
async function checkForUpdates() {
|
|
26192
|
+
const currentVersion = electron.app.getVersion();
|
|
26193
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26194
|
+
try {
|
|
26195
|
+
const response = await fetch(NPM_PACKAGE_URL, {
|
|
26196
|
+
headers: { accept: "application/json", "user-agent": `Vessel/${currentVersion}` }
|
|
26197
|
+
});
|
|
26198
|
+
if (!response.ok) {
|
|
26199
|
+
throw new Error(`Registry responded with ${response.status}`);
|
|
26200
|
+
}
|
|
26201
|
+
const body = await response.json();
|
|
26202
|
+
const latestVersion = typeof body.version === "string" ? body.version : null;
|
|
26203
|
+
if (!latestVersion) throw new Error("Registry response did not include a version");
|
|
26204
|
+
return {
|
|
26205
|
+
currentVersion,
|
|
26206
|
+
latestVersion,
|
|
26207
|
+
updateAvailable: compareVersions(latestVersion, currentVersion) > 0,
|
|
26208
|
+
checkedAt,
|
|
26209
|
+
releaseUrl: RELEASES_URL
|
|
26210
|
+
};
|
|
26211
|
+
} catch (error) {
|
|
26212
|
+
return {
|
|
26213
|
+
currentVersion,
|
|
26214
|
+
latestVersion: null,
|
|
26215
|
+
updateAvailable: false,
|
|
26216
|
+
checkedAt,
|
|
26217
|
+
releaseUrl: RELEASES_URL,
|
|
26218
|
+
error: error instanceof Error ? error.message : "Update check failed"
|
|
26219
|
+
};
|
|
26220
|
+
}
|
|
26221
|
+
}
|
|
26222
|
+
async function openUpdateDownload() {
|
|
26223
|
+
await electron.shell.openExternal(RELEASES_URL);
|
|
26224
|
+
}
|
|
25371
26225
|
let activeChatProvider = null;
|
|
25372
26226
|
const logger$4 = createLogger("IPC");
|
|
25373
26227
|
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
@@ -25765,18 +26619,18 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25765
26619
|
return getRendererSettings();
|
|
25766
26620
|
});
|
|
25767
26621
|
electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, () => getRuntimeHealth());
|
|
25768
|
-
electron.ipcMain.handle(Channels.SETTINGS_SET, async (_,
|
|
25769
|
-
assertString(
|
|
25770
|
-
if (!SETTABLE_KEYS.has(
|
|
25771
|
-
throw new Error(`Unknown setting key: ${
|
|
26622
|
+
electron.ipcMain.handle(Channels.SETTINGS_SET, async (_, key2, value) => {
|
|
26623
|
+
assertString(key2, "key");
|
|
26624
|
+
if (!SETTABLE_KEYS.has(key2)) {
|
|
26625
|
+
throw new Error(`Unknown setting key: ${key2}`);
|
|
25772
26626
|
}
|
|
25773
|
-
const settingsKey =
|
|
26627
|
+
const settingsKey = key2;
|
|
25774
26628
|
const updatedSettings = setSetting(settingsKey, value);
|
|
25775
|
-
trackSettingChanged(
|
|
25776
|
-
if (
|
|
26629
|
+
trackSettingChanged(key2);
|
|
26630
|
+
if (key2 === "approvalMode") {
|
|
25777
26631
|
runtime2.setApprovalMode(value);
|
|
25778
26632
|
}
|
|
25779
|
-
if (
|
|
26633
|
+
if (key2 === "mcpPort") {
|
|
25780
26634
|
await stopMcpServer();
|
|
25781
26635
|
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
25782
26636
|
}
|
|
@@ -25941,6 +26795,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25941
26795
|
registerVaultHandlers();
|
|
25942
26796
|
registerHumanVaultHandlers();
|
|
25943
26797
|
registerWindowControlHandlers(mainWindow);
|
|
26798
|
+
registerCodexHandlers();
|
|
25944
26799
|
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, () => {
|
|
25945
26800
|
return getInstalledKits();
|
|
25946
26801
|
});
|
|
@@ -25969,6 +26824,26 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25969
26824
|
clearByTimeRange(timeRange);
|
|
25970
26825
|
}
|
|
25971
26826
|
});
|
|
26827
|
+
setDownloadBroadcaster(sendToRendererViews);
|
|
26828
|
+
setPermissionBroadcaster(sendToRendererViews);
|
|
26829
|
+
electron.ipcMain.handle(Channels.DOWNLOADS_GET, () => listDownloads());
|
|
26830
|
+
electron.ipcMain.handle(Channels.DOWNLOADS_CLEAR, () => {
|
|
26831
|
+
clearDownloads();
|
|
26832
|
+
return true;
|
|
26833
|
+
});
|
|
26834
|
+
electron.ipcMain.handle(Channels.DOWNLOADS_OPEN, (_event, id) => openDownload(id));
|
|
26835
|
+
electron.ipcMain.handle(Channels.DOWNLOADS_SHOW_IN_FOLDER, (_event, id) => showDownloadInFolder(id));
|
|
26836
|
+
electron.ipcMain.handle(Channels.PERMISSIONS_GET, () => listPermissions());
|
|
26837
|
+
electron.ipcMain.handle(Channels.PERMISSIONS_CLEAR, () => {
|
|
26838
|
+
clearPermissions();
|
|
26839
|
+
return true;
|
|
26840
|
+
});
|
|
26841
|
+
electron.ipcMain.handle(Channels.PERMISSIONS_CLEAR_ORIGIN, (_event, origin) => {
|
|
26842
|
+
clearPermissionsForOrigin(origin);
|
|
26843
|
+
return true;
|
|
26844
|
+
});
|
|
26845
|
+
electron.ipcMain.handle(Channels.UPDATES_CHECK, () => checkForUpdates());
|
|
26846
|
+
electron.ipcMain.handle(Channels.UPDATES_OPEN_DOWNLOAD, () => openUpdateDownload());
|
|
25972
26847
|
electron.ipcMain.handle(Channels.TAB_TOGGLE_PIP, async () => {
|
|
25973
26848
|
return togglePictureInPicture(tabManager);
|
|
25974
26849
|
});
|
|
@@ -26364,9 +27239,9 @@ function clone(value) {
|
|
|
26364
27239
|
function summarizeArgs(args) {
|
|
26365
27240
|
const entries = Object.entries(args).filter(([, value]) => value != null);
|
|
26366
27241
|
if (entries.length === 0) return "No arguments";
|
|
26367
|
-
return entries.map(([
|
|
27242
|
+
return entries.map(([key2, value]) => {
|
|
26368
27243
|
const rendered = typeof value === "string" ? value : JSON.stringify(value);
|
|
26369
|
-
return `${
|
|
27244
|
+
return `${key2}=${String(rendered).slice(0, 120)}`;
|
|
26370
27245
|
}).join(", ");
|
|
26371
27246
|
}
|
|
26372
27247
|
function summarizeText(value) {
|
|
@@ -27392,6 +28267,7 @@ async function bootstrap() {
|
|
|
27392
28267
|
sidebarView.webContents.send(Channels.HISTORY_UPDATE, state2);
|
|
27393
28268
|
});
|
|
27394
28269
|
installDownloadHandler(chromeView);
|
|
28270
|
+
installPermissionHandler();
|
|
27395
28271
|
startBackgroundRevalidation();
|
|
27396
28272
|
startTelemetry();
|
|
27397
28273
|
const initializeChromeRenderer = () => {
|