@quanta-intellect/vessel-browser 0.1.86 → 0.1.90
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 = [];
|
|
@@ -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 filePath = getCodexTokensPath();
|
|
153
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
154
|
+
const payload = JSON.stringify(tokens);
|
|
155
|
+
if (canUseSafeStorage$1()) {
|
|
156
|
+
const encrypted = electron.safeStorage.encryptString(payload);
|
|
157
|
+
fs.writeFileSync(filePath, encrypted, { mode: 384 });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
fs.writeFileSync(filePath, 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;
|
|
@@ -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);
|
|
@@ -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
|
});
|
|
@@ -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();
|
|
@@ -1067,7 +1103,7 @@ function createDebouncedJsonPersistence({
|
|
|
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;
|
|
@@ -1429,8 +1465,7 @@ const VESSEL_HIGHLIGHT_CSS = `
|
|
|
1429
1465
|
opacity: 1;
|
|
1430
1466
|
}
|
|
1431
1467
|
`;
|
|
1432
|
-
async function
|
|
1433
|
-
const c = resolveColor(color);
|
|
1468
|
+
async function ensureHighlightStyles(wc) {
|
|
1434
1469
|
await wc.executeJavaScript(`
|
|
1435
1470
|
(function() {
|
|
1436
1471
|
if (!document.getElementById('__vessel-highlight-styles')) {
|
|
@@ -1439,6 +1474,14 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
|
|
|
1439
1474
|
s.textContent = ${JSON.stringify(VESSEL_HIGHLIGHT_CSS)};
|
|
1440
1475
|
document.head.appendChild(s);
|
|
1441
1476
|
}
|
|
1477
|
+
})()
|
|
1478
|
+
`);
|
|
1479
|
+
}
|
|
1480
|
+
async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, color) {
|
|
1481
|
+
const c = resolveColor(color);
|
|
1482
|
+
await ensureHighlightStyles(wc);
|
|
1483
|
+
await wc.executeJavaScript(`
|
|
1484
|
+
(function() {
|
|
1442
1485
|
if (!window.__vesselHighlightLabelManager) {
|
|
1443
1486
|
var overlap = function(a, b) {
|
|
1444
1487
|
return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
|
|
@@ -1643,14 +1686,9 @@ async function highlightBatchOnPage(wc, entries) {
|
|
|
1643
1686
|
color: resolveColor(e.color)
|
|
1644
1687
|
}));
|
|
1645
1688
|
if (serialized.length === 0) return;
|
|
1689
|
+
await ensureHighlightStyles(wc);
|
|
1646
1690
|
await wc.executeJavaScript(`
|
|
1647
1691
|
(function() {
|
|
1648
|
-
if (!document.getElementById('__vessel-highlight-styles')) {
|
|
1649
|
-
var s = document.createElement('style');
|
|
1650
|
-
s.id = '__vessel-highlight-styles';
|
|
1651
|
-
s.textContent = ${JSON.stringify(VESSEL_HIGHLIGHT_CSS)};
|
|
1652
|
-
document.head.appendChild(s);
|
|
1653
|
-
}
|
|
1654
1692
|
var entries = ${JSON.stringify(serialized)};
|
|
1655
1693
|
${SKIP_TAGS_JS}
|
|
1656
1694
|
${CONTENT_ROOTS_JS}
|
|
@@ -1754,6 +1792,8 @@ async function clearAllHighlightElements(wc) {
|
|
|
1754
1792
|
el.style.removeProperty('outline');
|
|
1755
1793
|
el.style.removeProperty('outline-offset');
|
|
1756
1794
|
});
|
|
1795
|
+
var style = document.getElementById('__vessel-highlight-styles');
|
|
1796
|
+
if (style) style.remove();
|
|
1757
1797
|
return true;
|
|
1758
1798
|
})()
|
|
1759
1799
|
`);
|
|
@@ -2742,7 +2782,7 @@ function destroySession(tabId) {
|
|
|
2742
2782
|
sessions.delete(tabId);
|
|
2743
2783
|
}
|
|
2744
2784
|
}
|
|
2745
|
-
const logger$
|
|
2785
|
+
const logger$k = createLogger("TabManager");
|
|
2746
2786
|
function sanitizePdfFilename(title) {
|
|
2747
2787
|
const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2748
2788
|
const base = (clean || "Vessel Page").replace(/\.pdf$/i, "");
|
|
@@ -3141,7 +3181,7 @@ class TabManager {
|
|
|
3141
3181
|
}));
|
|
3142
3182
|
if (entries.length > 0) {
|
|
3143
3183
|
void highlightBatchOnPage(wc, entries).catch(
|
|
3144
|
-
(err) => logger$
|
|
3184
|
+
(err) => logger$k.warn("Failed to batch highlight:", err)
|
|
3145
3185
|
);
|
|
3146
3186
|
}
|
|
3147
3187
|
}
|
|
@@ -3163,12 +3203,12 @@ class TabManager {
|
|
|
3163
3203
|
const result = await captureSelectionHighlight(wc);
|
|
3164
3204
|
if (result.success && result.text) {
|
|
3165
3205
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
3166
|
-
(err) => logger$
|
|
3206
|
+
(err) => logger$k.warn("Failed to capture highlight:", err)
|
|
3167
3207
|
);
|
|
3168
3208
|
}
|
|
3169
3209
|
this.highlightCaptureCallback?.(result);
|
|
3170
3210
|
} catch (err) {
|
|
3171
|
-
logger$
|
|
3211
|
+
logger$k.warn("Failed to capture highlight from page:", err);
|
|
3172
3212
|
this.highlightCaptureCallback?.({
|
|
3173
3213
|
success: false,
|
|
3174
3214
|
message: "Could not capture selection"
|
|
@@ -3193,7 +3233,7 @@ class TabManager {
|
|
|
3193
3233
|
void this.removeHighlightMarksForText(wc, text);
|
|
3194
3234
|
}
|
|
3195
3235
|
} catch (err) {
|
|
3196
|
-
logger$
|
|
3236
|
+
logger$k.warn("Failed to remove highlight from matching tab:", err);
|
|
3197
3237
|
}
|
|
3198
3238
|
}
|
|
3199
3239
|
this.highlightCaptureCallback?.({
|
|
@@ -3224,12 +3264,12 @@ class TabManager {
|
|
|
3224
3264
|
void 0,
|
|
3225
3265
|
color
|
|
3226
3266
|
).catch(
|
|
3227
|
-
(err) => logger$
|
|
3267
|
+
(err) => logger$k.warn("Failed to update highlight color:", err)
|
|
3228
3268
|
);
|
|
3229
3269
|
});
|
|
3230
3270
|
}
|
|
3231
3271
|
} catch (err) {
|
|
3232
|
-
logger$
|
|
3272
|
+
logger$k.warn("Failed to iterate highlights for color change:", err);
|
|
3233
3273
|
}
|
|
3234
3274
|
}
|
|
3235
3275
|
this.highlightCaptureCallback?.({
|
|
@@ -3270,7 +3310,7 @@ class TabManager {
|
|
|
3270
3310
|
});
|
|
3271
3311
|
})()`
|
|
3272
3312
|
).catch(
|
|
3273
|
-
(err) => logger$
|
|
3313
|
+
(err) => logger$k.warn("Failed to remove highlight marks:", err)
|
|
3274
3314
|
);
|
|
3275
3315
|
}
|
|
3276
3316
|
broadcastState() {
|
|
@@ -3473,7 +3513,12 @@ const Channels = {
|
|
|
3473
3513
|
CLEAR_BROWSING_DATA: "browsing-data:clear",
|
|
3474
3514
|
CLEAR_BROWSING_DATA_OPEN: "browsing-data:open",
|
|
3475
3515
|
// Picture-in-Picture
|
|
3476
|
-
TAB_TOGGLE_PIP: "tab:toggle-pip"
|
|
3516
|
+
TAB_TOGGLE_PIP: "tab:toggle-pip",
|
|
3517
|
+
// Codex OAuth
|
|
3518
|
+
CODEX_START_AUTH: "codex:start-auth",
|
|
3519
|
+
CODEX_CANCEL_AUTH: "codex:cancel-auth",
|
|
3520
|
+
CODEX_AUTH_STATUS: "codex:auth-status",
|
|
3521
|
+
CODEX_DISCONNECT: "codex:disconnect"
|
|
3477
3522
|
};
|
|
3478
3523
|
const MAX_DETAIL_ITEMS = 3;
|
|
3479
3524
|
const MIN_BLOCK_SIMILARITY = 0.82;
|
|
@@ -4431,7 +4476,7 @@ function errorResult(error, value) {
|
|
|
4431
4476
|
function getErrorMessage(error, fallback = "Unknown error") {
|
|
4432
4477
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
4433
4478
|
}
|
|
4434
|
-
const logger$
|
|
4479
|
+
const logger$j = createLogger("Premium");
|
|
4435
4480
|
const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
4436
4481
|
const FREE_TOOL_ITERATION_LIMIT = 50;
|
|
4437
4482
|
const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4547,7 +4592,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4547
4592
|
});
|
|
4548
4593
|
if (!res.ok) {
|
|
4549
4594
|
const detail = await readApiErrorDetail(res);
|
|
4550
|
-
logger$
|
|
4595
|
+
logger$j.warn(
|
|
4551
4596
|
"Verification API returned a non-OK status:",
|
|
4552
4597
|
res.status,
|
|
4553
4598
|
detail
|
|
@@ -4566,7 +4611,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4566
4611
|
setSetting("premium", updated);
|
|
4567
4612
|
return updated;
|
|
4568
4613
|
} catch (err) {
|
|
4569
|
-
logger$
|
|
4614
|
+
logger$j.warn("Verification failed:", err);
|
|
4570
4615
|
return current;
|
|
4571
4616
|
}
|
|
4572
4617
|
}
|
|
@@ -5139,7 +5184,7 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
|
5139
5184
|
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
5140
5185
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
5141
5186
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
5142
|
-
const logger$
|
|
5187
|
+
const logger$i = createLogger("Extractor");
|
|
5143
5188
|
const EMPTY_PAGE_CONTENT = {
|
|
5144
5189
|
title: "",
|
|
5145
5190
|
content: "",
|
|
@@ -5889,7 +5934,7 @@ async function executeScript(webContents, script) {
|
|
|
5889
5934
|
})
|
|
5890
5935
|
]);
|
|
5891
5936
|
} catch (err) {
|
|
5892
|
-
logger$
|
|
5937
|
+
logger$i.warn("Failed to execute page script:", err);
|
|
5893
5938
|
return null;
|
|
5894
5939
|
} finally {
|
|
5895
5940
|
if (timer) {
|
|
@@ -5996,7 +6041,7 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
5996
6041
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
5997
6042
|
}
|
|
5998
6043
|
} catch (err) {
|
|
5999
|
-
logger$
|
|
6044
|
+
logger$i.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
6000
6045
|
}
|
|
6001
6046
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
6002
6047
|
}
|
|
@@ -6605,8 +6650,8 @@ function resizeSidebarViews(state2) {
|
|
|
6605
6650
|
}
|
|
6606
6651
|
}
|
|
6607
6652
|
function generateReaderHTML(page) {
|
|
6608
|
-
const escapedTitle = escapeHtml(page.title);
|
|
6609
|
-
const escapedByline = escapeHtml(page.byline);
|
|
6653
|
+
const escapedTitle = escapeHtml$1(page.title);
|
|
6654
|
+
const escapedByline = escapeHtml$1(page.byline);
|
|
6610
6655
|
const renderedContent = renderReaderContent(page);
|
|
6611
6656
|
return `<!DOCTYPE html>
|
|
6612
6657
|
<html lang="en">
|
|
@@ -6694,9 +6739,9 @@ function renderReaderContent(page) {
|
|
|
6694
6739
|
if (!source) {
|
|
6695
6740
|
return "<p>No readable content was available for this page.</p>";
|
|
6696
6741
|
}
|
|
6697
|
-
return source.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean).map((block) => `<p>${escapeHtml(block).replace(/\n/g, "<br>")}</p>`).join("\n");
|
|
6742
|
+
return source.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean).map((block) => `<p>${escapeHtml$1(block).replace(/\n/g, "<br>")}</p>`).join("\n");
|
|
6698
6743
|
}
|
|
6699
|
-
function escapeHtml(str) {
|
|
6744
|
+
function escapeHtml$1(str) {
|
|
6700
6745
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6701
6746
|
}
|
|
6702
6747
|
const mcpStatusChangeListeners = /* @__PURE__ */ new Set();
|
|
@@ -7173,6 +7218,17 @@ const PROVIDERS = {
|
|
|
7173
7218
|
apiKeyPlaceholder: "sk-...",
|
|
7174
7219
|
apiKeyHint: "Get your key from platform.openai.com"
|
|
7175
7220
|
},
|
|
7221
|
+
openai_codex: {
|
|
7222
|
+
id: "openai_codex",
|
|
7223
|
+
name: "OpenAI Codex",
|
|
7224
|
+
type: "codex_oauth",
|
|
7225
|
+
defaultModel: "gpt-5",
|
|
7226
|
+
models: ["gpt-5", "gpt-5-mini", "gpt-5-nano", "o4", "o4-mini"],
|
|
7227
|
+
requiresApiKey: false,
|
|
7228
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
7229
|
+
apiKeyPlaceholder: "",
|
|
7230
|
+
apiKeyHint: "Sign in with your ChatGPT Plus or Pro subscription"
|
|
7231
|
+
},
|
|
7176
7232
|
openrouter: {
|
|
7177
7233
|
id: "openrouter",
|
|
7178
7234
|
name: "OpenRouter",
|
|
@@ -7344,7 +7400,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
7344
7400
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7345
7401
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7346
7402
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7347
|
-
const logger$
|
|
7403
|
+
const logger$h = createLogger("OpenAIProvider");
|
|
7348
7404
|
function shouldDebugAgentLoop() {
|
|
7349
7405
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
7350
7406
|
return value === "1" || value === "true";
|
|
@@ -7869,9 +7925,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
7869
7925
|
function logAgentLoopDebug(payload) {
|
|
7870
7926
|
if (!shouldDebugAgentLoop()) return;
|
|
7871
7927
|
try {
|
|
7872
|
-
logger$
|
|
7928
|
+
logger$h.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
7873
7929
|
} catch (err) {
|
|
7874
|
-
logger$
|
|
7930
|
+
logger$h.warn("Failed to serialize debug payload:", err);
|
|
7875
7931
|
}
|
|
7876
7932
|
}
|
|
7877
7933
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -8405,6 +8461,525 @@ class OpenAICompatProvider {
|
|
|
8405
8461
|
this.abortController?.abort();
|
|
8406
8462
|
}
|
|
8407
8463
|
}
|
|
8464
|
+
const logger$g = createLogger("CodexOAuth");
|
|
8465
|
+
const ISSUER = "https://auth.openai.com";
|
|
8466
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
8467
|
+
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
8468
|
+
const AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
8469
|
+
const PREFERRED_PORT = 1455;
|
|
8470
|
+
const FALLBACK_PORT = 1457;
|
|
8471
|
+
let activeFlow = null;
|
|
8472
|
+
function base64url(buffer) {
|
|
8473
|
+
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
8474
|
+
}
|
|
8475
|
+
function generatePkce() {
|
|
8476
|
+
const codeVerifier = base64url(crypto$1.randomBytes(64));
|
|
8477
|
+
const hash = crypto$1.createHash("sha256").update(codeVerifier).digest();
|
|
8478
|
+
const codeChallenge = base64url(hash);
|
|
8479
|
+
return { codeVerifier, codeChallenge };
|
|
8480
|
+
}
|
|
8481
|
+
function generateState() {
|
|
8482
|
+
return base64url(crypto$1.randomBytes(32));
|
|
8483
|
+
}
|
|
8484
|
+
function buildAuthorizeUrl(port, pkce, state2) {
|
|
8485
|
+
const redirectUri = `http://localhost:${port}/auth/callback`;
|
|
8486
|
+
const params = new URLSearchParams({
|
|
8487
|
+
response_type: "code",
|
|
8488
|
+
client_id: CLIENT_ID,
|
|
8489
|
+
redirect_uri: redirectUri,
|
|
8490
|
+
scope: SCOPE,
|
|
8491
|
+
code_challenge: pkce.codeChallenge,
|
|
8492
|
+
code_challenge_method: "S256",
|
|
8493
|
+
state: state2,
|
|
8494
|
+
id_token_add_organizations: "true",
|
|
8495
|
+
codex_cli_simplified_flow: "true",
|
|
8496
|
+
originator: "codex_cli_rs"
|
|
8497
|
+
});
|
|
8498
|
+
return `${ISSUER}/oauth/authorize?${params.toString()}`;
|
|
8499
|
+
}
|
|
8500
|
+
function parseJwtClaims(idToken) {
|
|
8501
|
+
try {
|
|
8502
|
+
const parts = idToken.split(".");
|
|
8503
|
+
if (parts.length !== 3) return null;
|
|
8504
|
+
const payload = JSON.parse(
|
|
8505
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
8506
|
+
);
|
|
8507
|
+
const authClaims = payload["https://api.openai.com/auth"] || {};
|
|
8508
|
+
const accountId = authClaims.chatgpt_account_id || payload.chatgpt_account_id || payload.sub || "";
|
|
8509
|
+
const email = authClaims.email || payload.email || void 0;
|
|
8510
|
+
return { accountId, email };
|
|
8511
|
+
} catch {
|
|
8512
|
+
return null;
|
|
8513
|
+
}
|
|
8514
|
+
}
|
|
8515
|
+
function parseTokenExpiry(accessToken) {
|
|
8516
|
+
try {
|
|
8517
|
+
const parts = accessToken.split(".");
|
|
8518
|
+
if (parts.length !== 3) return Date.now() + 36e5;
|
|
8519
|
+
const payload = JSON.parse(
|
|
8520
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
8521
|
+
);
|
|
8522
|
+
if (payload.exp && typeof payload.exp === "number") {
|
|
8523
|
+
return payload.exp * 1e3;
|
|
8524
|
+
}
|
|
8525
|
+
} catch {
|
|
8526
|
+
}
|
|
8527
|
+
return Date.now() + 36e5;
|
|
8528
|
+
}
|
|
8529
|
+
async function exchangeIdTokenForApiKey(idToken) {
|
|
8530
|
+
const body = new URLSearchParams({
|
|
8531
|
+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
8532
|
+
client_id: CLIENT_ID,
|
|
8533
|
+
requested_token: "openai-api-key",
|
|
8534
|
+
subject_token: idToken,
|
|
8535
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:id_token"
|
|
8536
|
+
});
|
|
8537
|
+
const response = await fetch(`${ISSUER}/oauth/token`, {
|
|
8538
|
+
method: "POST",
|
|
8539
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8540
|
+
body: body.toString()
|
|
8541
|
+
});
|
|
8542
|
+
if (!response.ok) {
|
|
8543
|
+
let errorMsg = `OpenAI API token exchange failed: ${response.status}`;
|
|
8544
|
+
try {
|
|
8545
|
+
const err = await response.json();
|
|
8546
|
+
if (typeof err.error_description === "string") {
|
|
8547
|
+
errorMsg = err.error_description;
|
|
8548
|
+
} else if (typeof err.error === "string") {
|
|
8549
|
+
errorMsg = err.error;
|
|
8550
|
+
}
|
|
8551
|
+
} catch {
|
|
8552
|
+
}
|
|
8553
|
+
throw new Error(errorMsg);
|
|
8554
|
+
}
|
|
8555
|
+
const data = await response.json();
|
|
8556
|
+
if (!data.access_token) {
|
|
8557
|
+
throw new Error("OpenAI API token exchange did not return an access token");
|
|
8558
|
+
}
|
|
8559
|
+
return data.access_token;
|
|
8560
|
+
}
|
|
8561
|
+
async function ensureCodexApiKey(tokens) {
|
|
8562
|
+
if (tokens.apiKey) return tokens;
|
|
8563
|
+
if (!tokens.idToken) return tokens;
|
|
8564
|
+
try {
|
|
8565
|
+
return {
|
|
8566
|
+
...tokens,
|
|
8567
|
+
apiKey: await exchangeIdTokenForApiKey(tokens.idToken)
|
|
8568
|
+
};
|
|
8569
|
+
} catch (err) {
|
|
8570
|
+
logger$g.warn(
|
|
8571
|
+
"Codex API-key token exchange failed; continuing with ChatGPT OAuth tokens:",
|
|
8572
|
+
err
|
|
8573
|
+
);
|
|
8574
|
+
return tokens;
|
|
8575
|
+
}
|
|
8576
|
+
}
|
|
8577
|
+
async function exchangeCodeForTokens(code, redirectUri, codeVerifier) {
|
|
8578
|
+
const body = new URLSearchParams({
|
|
8579
|
+
grant_type: "authorization_code",
|
|
8580
|
+
code,
|
|
8581
|
+
redirect_uri: redirectUri,
|
|
8582
|
+
client_id: CLIENT_ID,
|
|
8583
|
+
code_verifier: codeVerifier
|
|
8584
|
+
});
|
|
8585
|
+
const response = await fetch(`${ISSUER}/oauth/token`, {
|
|
8586
|
+
method: "POST",
|
|
8587
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8588
|
+
body: body.toString()
|
|
8589
|
+
});
|
|
8590
|
+
if (!response.ok) {
|
|
8591
|
+
let errorMsg = `Token exchange failed: ${response.status}`;
|
|
8592
|
+
try {
|
|
8593
|
+
const err = await response.json();
|
|
8594
|
+
if (typeof err.error_description === "string") {
|
|
8595
|
+
errorMsg = err.error_description;
|
|
8596
|
+
} else if (typeof err.error === "string") {
|
|
8597
|
+
errorMsg = err.error;
|
|
8598
|
+
}
|
|
8599
|
+
} catch {
|
|
8600
|
+
}
|
|
8601
|
+
throw new Error(errorMsg);
|
|
8602
|
+
}
|
|
8603
|
+
const data = await response.json();
|
|
8604
|
+
const claims = parseJwtClaims(data.id_token);
|
|
8605
|
+
const expiresAt = parseTokenExpiry(data.access_token);
|
|
8606
|
+
const tokens = {
|
|
8607
|
+
accessToken: data.access_token,
|
|
8608
|
+
refreshToken: data.refresh_token,
|
|
8609
|
+
idToken: data.id_token,
|
|
8610
|
+
expiresAt,
|
|
8611
|
+
accountId: claims?.accountId || "",
|
|
8612
|
+
accountEmail: claims?.email
|
|
8613
|
+
};
|
|
8614
|
+
return ensureCodexApiKey(tokens);
|
|
8615
|
+
}
|
|
8616
|
+
async function refreshAccessToken(tokens) {
|
|
8617
|
+
const body = new URLSearchParams({
|
|
8618
|
+
grant_type: "refresh_token",
|
|
8619
|
+
refresh_token: tokens.refreshToken,
|
|
8620
|
+
client_id: CLIENT_ID
|
|
8621
|
+
});
|
|
8622
|
+
const response = await fetch(`${ISSUER}/oauth/token`, {
|
|
8623
|
+
method: "POST",
|
|
8624
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8625
|
+
body: body.toString()
|
|
8626
|
+
});
|
|
8627
|
+
if (!response.ok) {
|
|
8628
|
+
let errorMsg = `Token refresh failed: ${response.status}`;
|
|
8629
|
+
try {
|
|
8630
|
+
const err = await response.json();
|
|
8631
|
+
if (typeof err.error_description === "string") errorMsg = err.error_description;
|
|
8632
|
+
else if (typeof err.error === "string") errorMsg = err.error;
|
|
8633
|
+
} catch {
|
|
8634
|
+
}
|
|
8635
|
+
throw new Error(errorMsg);
|
|
8636
|
+
}
|
|
8637
|
+
const data = await response.json();
|
|
8638
|
+
const idToken = data.id_token || tokens.idToken || "";
|
|
8639
|
+
const claims = idToken ? parseJwtClaims(idToken) : null;
|
|
8640
|
+
const expiresAt = parseTokenExpiry(data.access_token);
|
|
8641
|
+
const refreshedTokens = {
|
|
8642
|
+
accessToken: data.access_token,
|
|
8643
|
+
refreshToken: data.refresh_token || tokens.refreshToken,
|
|
8644
|
+
idToken,
|
|
8645
|
+
apiKey: tokens.apiKey,
|
|
8646
|
+
expiresAt,
|
|
8647
|
+
accountId: claims?.accountId || tokens.accountId || "",
|
|
8648
|
+
accountEmail: claims?.email || tokens.accountEmail
|
|
8649
|
+
};
|
|
8650
|
+
return ensureCodexApiKey(refreshedTokens);
|
|
8651
|
+
}
|
|
8652
|
+
function startServer(port, pkce, expectedState, resolve, reject) {
|
|
8653
|
+
const server = http.createServer(async (req, res) => {
|
|
8654
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
8655
|
+
if (url.pathname === "/auth/callback") {
|
|
8656
|
+
const state2 = url.searchParams.get("state");
|
|
8657
|
+
const code = url.searchParams.get("code");
|
|
8658
|
+
const error = url.searchParams.get("error");
|
|
8659
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
8660
|
+
if (error) {
|
|
8661
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8662
|
+
const msg = errorDescription || error;
|
|
8663
|
+
res.end(`Authorization failed: ${msg}`);
|
|
8664
|
+
reject(new Error(msg));
|
|
8665
|
+
return;
|
|
8666
|
+
}
|
|
8667
|
+
if (state2 !== expectedState) {
|
|
8668
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8669
|
+
res.end("State mismatch. Please try again.");
|
|
8670
|
+
reject(new Error("State mismatch"));
|
|
8671
|
+
return;
|
|
8672
|
+
}
|
|
8673
|
+
if (!code) {
|
|
8674
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8675
|
+
res.end("Missing authorization code.");
|
|
8676
|
+
reject(new Error("Missing authorization code"));
|
|
8677
|
+
return;
|
|
8678
|
+
}
|
|
8679
|
+
try {
|
|
8680
|
+
activeFlow?.onStatus("exchanging");
|
|
8681
|
+
const redirectUri = `http://localhost:${activeFlow?.port ?? port}/auth/callback`;
|
|
8682
|
+
const tokens = await exchangeCodeForTokens(code, redirectUri, pkce.codeVerifier);
|
|
8683
|
+
res.writeHead(302, {
|
|
8684
|
+
Location: `/success?email=${encodeURIComponent(tokens.accountEmail || tokens.accountId)}`,
|
|
8685
|
+
Connection: "close"
|
|
8686
|
+
});
|
|
8687
|
+
res.end();
|
|
8688
|
+
resolve(tokens);
|
|
8689
|
+
} catch (err) {
|
|
8690
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8691
|
+
res.end(`Token exchange failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
8692
|
+
reject(err instanceof Error ? err : new Error("Token exchange failed"));
|
|
8693
|
+
}
|
|
8694
|
+
return;
|
|
8695
|
+
}
|
|
8696
|
+
if (url.pathname === "/success") {
|
|
8697
|
+
const email = url.searchParams.get("email") || "";
|
|
8698
|
+
res.writeHead(200, { "Content-Type": "text/html", "Connection": "close" });
|
|
8699
|
+
res.end(`<!DOCTYPE html>
|
|
8700
|
+
<html><head><meta charset="utf-8"><title>Vessel — Signed In</title>
|
|
8701
|
+
<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>
|
|
8702
|
+
<body><div style="text-align:center"><h1>✓ Signed In</h1>
|
|
8703
|
+
<p>Connected as ${escapeHtml(email)}</p><p>You can close this tab.</p></div></body></html>`);
|
|
8704
|
+
return;
|
|
8705
|
+
}
|
|
8706
|
+
if (url.pathname === "/cancel") {
|
|
8707
|
+
res.writeHead(200, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8708
|
+
res.end("Login cancelled");
|
|
8709
|
+
reject(new Error("Login cancelled by user"));
|
|
8710
|
+
return;
|
|
8711
|
+
}
|
|
8712
|
+
res.writeHead(404, { "Connection": "close" });
|
|
8713
|
+
res.end("Not found");
|
|
8714
|
+
});
|
|
8715
|
+
return server;
|
|
8716
|
+
}
|
|
8717
|
+
function escapeHtml(text) {
|
|
8718
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
8719
|
+
}
|
|
8720
|
+
async function bindServer(server) {
|
|
8721
|
+
const allowedPorts = [PREFERRED_PORT, FALLBACK_PORT];
|
|
8722
|
+
for (const port of allowedPorts) {
|
|
8723
|
+
try {
|
|
8724
|
+
await new Promise((resolve, reject) => {
|
|
8725
|
+
const onError = (err) => {
|
|
8726
|
+
server.off("listening", onListening);
|
|
8727
|
+
reject(err);
|
|
8728
|
+
};
|
|
8729
|
+
const onListening = () => {
|
|
8730
|
+
server.off("error", onError);
|
|
8731
|
+
resolve();
|
|
8732
|
+
};
|
|
8733
|
+
server.once("error", onError);
|
|
8734
|
+
server.once("listening", onListening);
|
|
8735
|
+
server.listen(port, "127.0.0.1");
|
|
8736
|
+
});
|
|
8737
|
+
return port;
|
|
8738
|
+
} catch (err) {
|
|
8739
|
+
if (err.code === "EADDRINUSE") {
|
|
8740
|
+
continue;
|
|
8741
|
+
}
|
|
8742
|
+
throw err;
|
|
8743
|
+
}
|
|
8744
|
+
}
|
|
8745
|
+
throw new Error(
|
|
8746
|
+
`Could not bind Codex OAuth callback server to registered ports ${allowedPorts.join(", ")}`
|
|
8747
|
+
);
|
|
8748
|
+
}
|
|
8749
|
+
async function startCodexOAuth(onStatus) {
|
|
8750
|
+
if (activeFlow) {
|
|
8751
|
+
throw new Error("Auth flow already in progress");
|
|
8752
|
+
}
|
|
8753
|
+
const pkce = generatePkce();
|
|
8754
|
+
const state2 = generateState();
|
|
8755
|
+
return new Promise((resolve, reject) => {
|
|
8756
|
+
let settled = false;
|
|
8757
|
+
const safeOnStatus = (status, error) => {
|
|
8758
|
+
try {
|
|
8759
|
+
onStatus(status, error);
|
|
8760
|
+
} catch {
|
|
8761
|
+
logger$g.warn("Codex OAuth status callback failed — window may be closed");
|
|
8762
|
+
}
|
|
8763
|
+
};
|
|
8764
|
+
const wrappedResolve = (tokens) => {
|
|
8765
|
+
if (settled) return;
|
|
8766
|
+
settled = true;
|
|
8767
|
+
cleanup();
|
|
8768
|
+
safeOnStatus("connected");
|
|
8769
|
+
resolve(tokens);
|
|
8770
|
+
};
|
|
8771
|
+
const wrappedReject = (err) => {
|
|
8772
|
+
if (settled) return;
|
|
8773
|
+
settled = true;
|
|
8774
|
+
cleanup();
|
|
8775
|
+
safeOnStatus("error", err.message);
|
|
8776
|
+
reject(err);
|
|
8777
|
+
};
|
|
8778
|
+
const server = startServer(0, pkce, state2, wrappedResolve, wrappedReject);
|
|
8779
|
+
const timeout = setTimeout(() => {
|
|
8780
|
+
wrappedReject(new Error("Auth flow timed out after 5 minutes"));
|
|
8781
|
+
}, AUTH_TIMEOUT_MS);
|
|
8782
|
+
activeFlow = {
|
|
8783
|
+
state: state2,
|
|
8784
|
+
codeVerifier: pkce.codeVerifier,
|
|
8785
|
+
port: 0,
|
|
8786
|
+
server,
|
|
8787
|
+
timeout,
|
|
8788
|
+
onStatus
|
|
8789
|
+
};
|
|
8790
|
+
const cleanup = () => {
|
|
8791
|
+
if (activeFlow?.timeout) clearTimeout(activeFlow.timeout);
|
|
8792
|
+
activeFlow?.server.close();
|
|
8793
|
+
activeFlow = null;
|
|
8794
|
+
};
|
|
8795
|
+
bindServer(server).then((port) => {
|
|
8796
|
+
if (settled) return;
|
|
8797
|
+
activeFlow.port = port;
|
|
8798
|
+
const authUrl = buildAuthorizeUrl(port, pkce, state2);
|
|
8799
|
+
safeOnStatus("waiting");
|
|
8800
|
+
electron.shell.openExternal(authUrl).catch((err) => {
|
|
8801
|
+
logger$g.warn("Failed to open browser, user will need the URL:", err);
|
|
8802
|
+
});
|
|
8803
|
+
}).catch(wrappedReject);
|
|
8804
|
+
});
|
|
8805
|
+
}
|
|
8806
|
+
function cancelCodexOAuth() {
|
|
8807
|
+
if (!activeFlow) return;
|
|
8808
|
+
activeFlow.server.close();
|
|
8809
|
+
if (activeFlow.timeout) clearTimeout(activeFlow.timeout);
|
|
8810
|
+
try {
|
|
8811
|
+
activeFlow.onStatus("idle");
|
|
8812
|
+
} catch {
|
|
8813
|
+
logger$g.warn("Codex OAuth cancel status callback failed — window may be closed");
|
|
8814
|
+
}
|
|
8815
|
+
activeFlow = null;
|
|
8816
|
+
}
|
|
8817
|
+
const logger$f = createLogger("CodexProvider");
|
|
8818
|
+
const REFRESH_WINDOW_MS = 5 * 60 * 1e3;
|
|
8819
|
+
const CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
8820
|
+
const CODEX_CLIENT_VERSION = "0.129.0";
|
|
8821
|
+
class CodexProvider {
|
|
8822
|
+
agentToolProfile;
|
|
8823
|
+
tokens;
|
|
8824
|
+
model;
|
|
8825
|
+
abortController = null;
|
|
8826
|
+
constructor(tokens, model, _baseUrl) {
|
|
8827
|
+
this.tokens = tokens;
|
|
8828
|
+
this.model = model;
|
|
8829
|
+
this.agentToolProfile = "default";
|
|
8830
|
+
}
|
|
8831
|
+
async ensureFreshTokens() {
|
|
8832
|
+
if (Date.now() < this.tokens.expiresAt - REFRESH_WINDOW_MS) return;
|
|
8833
|
+
try {
|
|
8834
|
+
logger$f.info("Refreshing Codex access token");
|
|
8835
|
+
const fresh = await refreshAccessToken(this.tokens);
|
|
8836
|
+
this.tokens = fresh;
|
|
8837
|
+
writeStoredCodexTokens(fresh);
|
|
8838
|
+
} catch (err) {
|
|
8839
|
+
clearStoredCodexTokens();
|
|
8840
|
+
throw new Error(
|
|
8841
|
+
`Codex token refresh failed — please re-authenticate. ${err instanceof Error ? err.message : ""}`
|
|
8842
|
+
);
|
|
8843
|
+
}
|
|
8844
|
+
}
|
|
8845
|
+
backendHeaders() {
|
|
8846
|
+
const headers = {
|
|
8847
|
+
Authorization: `Bearer ${this.tokens.accessToken}`,
|
|
8848
|
+
"Content-Type": "application/json",
|
|
8849
|
+
Accept: "text/event-stream",
|
|
8850
|
+
originator: "codex_cli_rs",
|
|
8851
|
+
"User-Agent": `codex_cli_rs/${CODEX_CLIENT_VERSION} Vessel`
|
|
8852
|
+
};
|
|
8853
|
+
if (this.tokens.accountId) {
|
|
8854
|
+
headers["ChatGPT-Account-ID"] = this.tokens.accountId;
|
|
8855
|
+
}
|
|
8856
|
+
return headers;
|
|
8857
|
+
}
|
|
8858
|
+
buildInput(userMessage, history) {
|
|
8859
|
+
const input = [];
|
|
8860
|
+
for (const msg of history ?? []) {
|
|
8861
|
+
input.push({
|
|
8862
|
+
type: "message",
|
|
8863
|
+
role: msg.role,
|
|
8864
|
+
content: [{ type: "input_text", text: msg.content }]
|
|
8865
|
+
});
|
|
8866
|
+
}
|
|
8867
|
+
input.push({
|
|
8868
|
+
type: "message",
|
|
8869
|
+
role: "user",
|
|
8870
|
+
content: [{ type: "input_text", text: userMessage }]
|
|
8871
|
+
});
|
|
8872
|
+
return input;
|
|
8873
|
+
}
|
|
8874
|
+
handleStreamEvent(raw, onChunk, emittedTextFromDelta) {
|
|
8875
|
+
if (!raw.trim() || raw.trim() === "[DONE]") return;
|
|
8876
|
+
let event;
|
|
8877
|
+
try {
|
|
8878
|
+
event = JSON.parse(raw);
|
|
8879
|
+
} catch {
|
|
8880
|
+
return;
|
|
8881
|
+
}
|
|
8882
|
+
if (event.type === "response.output_text.delta" && event.delta) {
|
|
8883
|
+
emittedTextFromDelta.value = true;
|
|
8884
|
+
onChunk(event.delta);
|
|
8885
|
+
return;
|
|
8886
|
+
}
|
|
8887
|
+
if (event.type === "response.output_item.done" && !emittedTextFromDelta.value) {
|
|
8888
|
+
const text = event.item?.content?.filter((item) => item.type === "output_text" && item.text).map((item) => item.text).join("");
|
|
8889
|
+
if (text) onChunk(text);
|
|
8890
|
+
return;
|
|
8891
|
+
}
|
|
8892
|
+
if (event.type === "response.failed") {
|
|
8893
|
+
const error = event.response?.error;
|
|
8894
|
+
const message = error?.message || error?.code || "Codex response failed";
|
|
8895
|
+
throw new Error(message);
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8898
|
+
async streamCodexResponse(systemPrompt, userMessage, onChunk, history) {
|
|
8899
|
+
const response = await fetch(`${CODEX_BACKEND_BASE_URL}/responses`, {
|
|
8900
|
+
method: "POST",
|
|
8901
|
+
headers: this.backendHeaders(),
|
|
8902
|
+
signal: this.abortController?.signal,
|
|
8903
|
+
body: JSON.stringify({
|
|
8904
|
+
model: this.model,
|
|
8905
|
+
instructions: systemPrompt,
|
|
8906
|
+
input: this.buildInput(userMessage, history),
|
|
8907
|
+
stream: true,
|
|
8908
|
+
store: false
|
|
8909
|
+
})
|
|
8910
|
+
});
|
|
8911
|
+
if (!response.ok) {
|
|
8912
|
+
const text = await response.text().catch(() => "");
|
|
8913
|
+
throw new Error(
|
|
8914
|
+
`Codex backend request failed: ${response.status}${text ? ` ${text}` : ""}`
|
|
8915
|
+
);
|
|
8916
|
+
}
|
|
8917
|
+
if (!response.body) {
|
|
8918
|
+
throw new Error("Codex backend returned an empty response stream");
|
|
8919
|
+
}
|
|
8920
|
+
const reader = response.body.getReader();
|
|
8921
|
+
const decoder = new TextDecoder();
|
|
8922
|
+
let buffer = "";
|
|
8923
|
+
const emittedTextFromDelta = { value: false };
|
|
8924
|
+
while (true) {
|
|
8925
|
+
const { value, done } = await reader.read();
|
|
8926
|
+
if (done) break;
|
|
8927
|
+
buffer += decoder.decode(value, { stream: true });
|
|
8928
|
+
let separatorIndex;
|
|
8929
|
+
while ((separatorIndex = buffer.indexOf("\n\n")) !== -1) {
|
|
8930
|
+
const block = buffer.slice(0, separatorIndex);
|
|
8931
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
8932
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
8933
|
+
this.handleStreamEvent(data, onChunk, emittedTextFromDelta);
|
|
8934
|
+
}
|
|
8935
|
+
}
|
|
8936
|
+
const trailing = buffer.trim();
|
|
8937
|
+
if (trailing) {
|
|
8938
|
+
const data = trailing.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
8939
|
+
this.handleStreamEvent(data, onChunk, emittedTextFromDelta);
|
|
8940
|
+
}
|
|
8941
|
+
}
|
|
8942
|
+
async streamQuery(systemPrompt, userMessage, onChunk, onEnd, history) {
|
|
8943
|
+
await this.ensureFreshTokens();
|
|
8944
|
+
this.abortController = new AbortController();
|
|
8945
|
+
try {
|
|
8946
|
+
await this.streamCodexResponse(systemPrompt, userMessage, onChunk, history);
|
|
8947
|
+
} catch (err) {
|
|
8948
|
+
if (err.name !== "AbortError") {
|
|
8949
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8950
|
+
logger$f.error("Codex streamQuery error:", err);
|
|
8951
|
+
onChunk(`
|
|
8952
|
+
|
|
8953
|
+
[Error: ${msg}]`);
|
|
8954
|
+
}
|
|
8955
|
+
} finally {
|
|
8956
|
+
this.abortController = null;
|
|
8957
|
+
onEnd();
|
|
8958
|
+
}
|
|
8959
|
+
}
|
|
8960
|
+
async streamAgentQuery(systemPrompt, userMessage, _tools, onChunk, _onToolCall, onEnd, history) {
|
|
8961
|
+
await this.ensureFreshTokens();
|
|
8962
|
+
this.abortController = new AbortController();
|
|
8963
|
+
try {
|
|
8964
|
+
await this.streamCodexResponse(systemPrompt, userMessage, onChunk, history);
|
|
8965
|
+
} catch (err) {
|
|
8966
|
+
if (err.name !== "AbortError") {
|
|
8967
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8968
|
+
logger$f.error("Codex streamAgentQuery error:", err);
|
|
8969
|
+
onChunk(`
|
|
8970
|
+
|
|
8971
|
+
[Error: ${msg}]`);
|
|
8972
|
+
}
|
|
8973
|
+
} finally {
|
|
8974
|
+
this.abortController = null;
|
|
8975
|
+
onEnd();
|
|
8976
|
+
}
|
|
8977
|
+
}
|
|
8978
|
+
cancel() {
|
|
8979
|
+
this.abortController?.abort();
|
|
8980
|
+
this.abortController = null;
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8408
8983
|
function sanitizeProviderConfig(config) {
|
|
8409
8984
|
return {
|
|
8410
8985
|
...config,
|
|
@@ -8426,7 +9001,7 @@ function validateProviderConnection(config, options = { requireModel: true }) {
|
|
|
8426
9001
|
if (!meta) {
|
|
8427
9002
|
return "Selected AI provider is not supported.";
|
|
8428
9003
|
}
|
|
8429
|
-
if (meta.requiresApiKey && !normalized.apiKey) {
|
|
9004
|
+
if (meta.type !== "codex_oauth" && meta.requiresApiKey && !normalized.apiKey) {
|
|
8430
9005
|
return `${meta.name} requires an API key. Open settings (Ctrl+,) to add one.`;
|
|
8431
9006
|
}
|
|
8432
9007
|
if (options.requireModel && !normalized.model) {
|
|
@@ -8468,6 +9043,34 @@ function buildLlamaCppCtxWarning(ctxSize) {
|
|
|
8468
9043
|
}
|
|
8469
9044
|
return void 0;
|
|
8470
9045
|
}
|
|
9046
|
+
async function fetchCodexBackendModels(tokens) {
|
|
9047
|
+
const url = new URL("https://chatgpt.com/backend-api/codex/models");
|
|
9048
|
+
url.searchParams.set("client_version", "0.129.0");
|
|
9049
|
+
const headers = {
|
|
9050
|
+
Authorization: `Bearer ${tokens.accessToken}`,
|
|
9051
|
+
originator: "codex_cli_rs",
|
|
9052
|
+
"User-Agent": "codex_cli_rs/0.129.0 Vessel"
|
|
9053
|
+
};
|
|
9054
|
+
if (tokens.accountId) {
|
|
9055
|
+
headers["ChatGPT-Account-ID"] = tokens.accountId;
|
|
9056
|
+
}
|
|
9057
|
+
const response = await fetch(url.toString(), { headers });
|
|
9058
|
+
if (!response.ok) {
|
|
9059
|
+
throw new Error(`Codex backend model discovery failed: ${response.status}`);
|
|
9060
|
+
}
|
|
9061
|
+
const payload = await response.json();
|
|
9062
|
+
if (!Array.isArray(payload.models)) {
|
|
9063
|
+
throw new Error("Codex backend model discovery returned an invalid response");
|
|
9064
|
+
}
|
|
9065
|
+
return payload.models.map((model) => {
|
|
9066
|
+
if (!model || typeof model !== "object") return null;
|
|
9067
|
+
const record = model;
|
|
9068
|
+
const id = record.slug || record.id || record.model;
|
|
9069
|
+
const visibility = record.visibility;
|
|
9070
|
+
if (visibility === "hidden") return null;
|
|
9071
|
+
return typeof id === "string" && id.trim() ? id.trim() : null;
|
|
9072
|
+
}).filter((id) => id !== null);
|
|
9073
|
+
}
|
|
8471
9074
|
async function probeLlamaCppCtxWarning(baseURL) {
|
|
8472
9075
|
try {
|
|
8473
9076
|
const root = new URL(baseURL);
|
|
@@ -8495,6 +9098,24 @@ async function fetchProviderModels(config) {
|
|
|
8495
9098
|
const page2 = await client2.models.list();
|
|
8496
9099
|
return okResult({ models: page2.data.map((model) => model.id) });
|
|
8497
9100
|
}
|
|
9101
|
+
if (normalized.id === "openai_codex") {
|
|
9102
|
+
const tokens = readStoredCodexTokens();
|
|
9103
|
+
if (!tokens) {
|
|
9104
|
+
throw new Error("Codex provider requires authentication. Connect your ChatGPT account in settings.");
|
|
9105
|
+
}
|
|
9106
|
+
try {
|
|
9107
|
+
const models2 = await fetchCodexBackendModels(tokens);
|
|
9108
|
+
if (models2.length > 0) {
|
|
9109
|
+
return okResult({ models: models2 });
|
|
9110
|
+
}
|
|
9111
|
+
throw new Error("Codex backend model discovery returned no models");
|
|
9112
|
+
} catch (err) {
|
|
9113
|
+
return okResult({
|
|
9114
|
+
models: PROVIDERS.openai_codex.models,
|
|
9115
|
+
warning: `Using built-in Codex model list because live discovery failed: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
}
|
|
8498
9119
|
const meta = PROVIDERS[normalized.id];
|
|
8499
9120
|
const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
8500
9121
|
const client = new OpenAI({
|
|
@@ -8524,10 +9145,19 @@ function createProvider(config) {
|
|
|
8524
9145
|
normalized.reasoningEffort
|
|
8525
9146
|
);
|
|
8526
9147
|
}
|
|
9148
|
+
if (normalized.id === "openai_codex") {
|
|
9149
|
+
const tokens = readStoredCodexTokens();
|
|
9150
|
+
if (!tokens) {
|
|
9151
|
+
throw new Error(
|
|
9152
|
+
"OpenAI Codex requires authentication. Open settings to connect your ChatGPT account."
|
|
9153
|
+
);
|
|
9154
|
+
}
|
|
9155
|
+
return new CodexProvider(tokens, normalized.model, normalized.baseUrl);
|
|
9156
|
+
}
|
|
8527
9157
|
return new OpenAICompatProvider(normalized);
|
|
8528
9158
|
}
|
|
8529
9159
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
8530
|
-
const logger$
|
|
9160
|
+
const logger$e = createLogger("DevTrace");
|
|
8531
9161
|
let cachedFactory;
|
|
8532
9162
|
function createNoopTraceSession() {
|
|
8533
9163
|
return {
|
|
@@ -8560,7 +9190,7 @@ function loadLocalFactory() {
|
|
|
8560
9190
|
return cachedFactory;
|
|
8561
9191
|
}
|
|
8562
9192
|
} catch (err) {
|
|
8563
|
-
logger$
|
|
9193
|
+
logger$e.warn("Failed to load local trace logger:", err);
|
|
8564
9194
|
}
|
|
8565
9195
|
}
|
|
8566
9196
|
return cachedFactory;
|
|
@@ -12679,7 +13309,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
12679
13309
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
12680
13310
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
12681
13311
|
}
|
|
12682
|
-
const logger$
|
|
13312
|
+
const logger$d = createLogger("Screenshot");
|
|
12683
13313
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
12684
13314
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
12685
13315
|
async function captureScreenshot(wc) {
|
|
@@ -12701,7 +13331,7 @@ async function captureScreenshot(wc) {
|
|
|
12701
13331
|
}
|
|
12702
13332
|
}
|
|
12703
13333
|
} catch (err) {
|
|
12704
|
-
logger$
|
|
13334
|
+
logger$d.debug(
|
|
12705
13335
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
12706
13336
|
getErrorMessage(err)
|
|
12707
13337
|
);
|
|
@@ -13575,7 +14205,7 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
13575
14205
|
appliedFilters
|
|
13576
14206
|
};
|
|
13577
14207
|
}
|
|
13578
|
-
const logger$
|
|
14208
|
+
const logger$c = createLogger("PageActions");
|
|
13579
14209
|
function getBookmarkMetadataFromArgs(args) {
|
|
13580
14210
|
return normalizeBookmarkMetadata({
|
|
13581
14211
|
intent: args.intent ?? args.intent,
|
|
@@ -13761,7 +14391,7 @@ async function executePageScript(wc, script, options) {
|
|
|
13761
14391
|
return result;
|
|
13762
14392
|
} catch (err) {
|
|
13763
14393
|
const label = options?.label ? ` (${options.label})` : "";
|
|
13764
|
-
logger$
|
|
14394
|
+
logger$c.warn(`Failed to execute page script${label}:`, err);
|
|
13765
14395
|
return null;
|
|
13766
14396
|
} finally {
|
|
13767
14397
|
if (timer) {
|
|
@@ -13862,7 +14492,7 @@ Search results snapshot:
|
|
|
13862
14492
|
${truncated}`;
|
|
13863
14493
|
}
|
|
13864
14494
|
} catch (err) {
|
|
13865
|
-
logger$
|
|
14495
|
+
logger$c.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
13866
14496
|
}
|
|
13867
14497
|
const fallback = await getPostNavSummary(wc);
|
|
13868
14498
|
return fallback ? `${fallback}
|
|
@@ -13885,7 +14515,7 @@ Page snapshot after navigation:
|
|
|
13885
14515
|
${truncated}`;
|
|
13886
14516
|
}
|
|
13887
14517
|
} catch (err) {
|
|
13888
|
-
logger$
|
|
14518
|
+
logger$c.warn("Failed to build post-click navigation summary:", err);
|
|
13889
14519
|
}
|
|
13890
14520
|
return "";
|
|
13891
14521
|
}
|
|
@@ -14379,7 +15009,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14379
15009
|
}
|
|
14380
15010
|
}
|
|
14381
15011
|
} catch (err) {
|
|
14382
|
-
logger$
|
|
15012
|
+
logger$c.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
14383
15013
|
}
|
|
14384
15014
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
14385
15015
|
try {
|
|
@@ -14388,7 +15018,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14388
15018
|
await waitForLoad(wc, 3e3);
|
|
14389
15019
|
return;
|
|
14390
15020
|
} catch (err) {
|
|
14391
|
-
logger$
|
|
15021
|
+
logger$c.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
14392
15022
|
}
|
|
14393
15023
|
}
|
|
14394
15024
|
if (snapshot.url) {
|
|
@@ -14396,7 +15026,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14396
15026
|
await wc.reload();
|
|
14397
15027
|
await waitForLoad(wc, 3e3);
|
|
14398
15028
|
} catch (err) {
|
|
14399
|
-
logger$
|
|
15029
|
+
logger$c.warn("Failed to restore locale via page reload:", err);
|
|
14400
15030
|
}
|
|
14401
15031
|
}
|
|
14402
15032
|
}
|
|
@@ -14748,7 +15378,7 @@ ${postActivationOverlayHint}`;
|
|
|
14748
15378
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
14749
15379
|
}
|
|
14750
15380
|
} catch (err) {
|
|
14751
|
-
logger$
|
|
15381
|
+
logger$c.warn("Failed href fallback after click, returning generic click result:", err);
|
|
14752
15382
|
}
|
|
14753
15383
|
}
|
|
14754
15384
|
}
|
|
@@ -14793,7 +15423,7 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
14793
15423
|
return result;
|
|
14794
15424
|
}
|
|
14795
15425
|
} catch (err) {
|
|
14796
|
-
logger$
|
|
15426
|
+
logger$c.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
14797
15427
|
}
|
|
14798
15428
|
return null;
|
|
14799
15429
|
}
|
|
@@ -17063,7 +17693,7 @@ async function executeAction(name, args, ctx) {
|
|
|
17063
17693
|
)
|
|
17064
17694
|
]);
|
|
17065
17695
|
} catch (err) {
|
|
17066
|
-
logger$
|
|
17696
|
+
logger$c.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
17067
17697
|
content = null;
|
|
17068
17698
|
}
|
|
17069
17699
|
if (!content || content.content.length === 0) {
|
|
@@ -17080,12 +17710,12 @@ async function executeAction(name, args, ctx) {
|
|
|
17080
17710
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
17081
17711
|
]);
|
|
17082
17712
|
} catch (err) {
|
|
17083
|
-
logger$
|
|
17713
|
+
logger$c.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
17084
17714
|
content = null;
|
|
17085
17715
|
}
|
|
17086
17716
|
}
|
|
17087
17717
|
} catch (err) {
|
|
17088
|
-
logger$
|
|
17718
|
+
logger$c.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
17089
17719
|
}
|
|
17090
17720
|
}
|
|
17091
17721
|
if (content && content.content.length > 0) {
|
|
@@ -17498,7 +18128,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
17498
18128
|
try {
|
|
17499
18129
|
page = await extractContent(wc);
|
|
17500
18130
|
} catch (err) {
|
|
17501
|
-
logger$
|
|
18131
|
+
logger$c.warn("Failed to extract content for suggest:", err);
|
|
17502
18132
|
return "Could not read page. Try navigate to a working URL.";
|
|
17503
18133
|
}
|
|
17504
18134
|
const suggestions = [];
|
|
@@ -18823,7 +19453,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
18823
19453
|
}
|
|
18824
19454
|
);
|
|
18825
19455
|
}
|
|
18826
|
-
const logger$
|
|
19456
|
+
const logger$b = createLogger("VaultShared");
|
|
18827
19457
|
const ALGORITHM = "aes-256-gcm";
|
|
18828
19458
|
const IV_LENGTH = 12;
|
|
18829
19459
|
const AUTH_TAG_LENGTH = 16;
|
|
@@ -18900,7 +19530,7 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
|
18900
19530
|
cachedEntries = JSON.parse(json);
|
|
18901
19531
|
return cachedEntries;
|
|
18902
19532
|
} catch (err) {
|
|
18903
|
-
logger$
|
|
19533
|
+
logger$b.error("Failed to load vault:", err);
|
|
18904
19534
|
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
18905
19535
|
}
|
|
18906
19536
|
}
|
|
@@ -18969,7 +19599,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
18969
19599
|
} catch {
|
|
18970
19600
|
}
|
|
18971
19601
|
} catch (err) {
|
|
18972
|
-
logger$
|
|
19602
|
+
logger$b.error("Failed to write audit log:", err);
|
|
18973
19603
|
}
|
|
18974
19604
|
}
|
|
18975
19605
|
function readAuditLog2(limit = 100) {
|
|
@@ -18979,7 +19609,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
18979
19609
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
18980
19610
|
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
18981
19611
|
} catch (err) {
|
|
18982
|
-
logger$
|
|
19612
|
+
logger$b.error("Failed to read audit log:", err);
|
|
18983
19613
|
return [];
|
|
18984
19614
|
}
|
|
18985
19615
|
}
|
|
@@ -19087,7 +19717,7 @@ async function requestConsent(request) {
|
|
|
19087
19717
|
}
|
|
19088
19718
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
19089
19719
|
const MAX_ENTRIES = 1e3;
|
|
19090
|
-
const logger$
|
|
19720
|
+
const logger$a = createLogger("VaultAudit");
|
|
19091
19721
|
function getAuditPath() {
|
|
19092
19722
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
19093
19723
|
}
|
|
@@ -19097,7 +19727,7 @@ function appendAuditEntry(entry) {
|
|
|
19097
19727
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
19098
19728
|
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
19099
19729
|
} catch (err) {
|
|
19100
|
-
logger$
|
|
19730
|
+
logger$a.error("Failed to write audit log:", err);
|
|
19101
19731
|
}
|
|
19102
19732
|
}
|
|
19103
19733
|
function readAuditLog$1(limit = 100) {
|
|
@@ -19107,7 +19737,7 @@ function readAuditLog$1(limit = 100) {
|
|
|
19107
19737
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19108
19738
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
19109
19739
|
} catch (err) {
|
|
19110
|
-
logger$
|
|
19740
|
+
logger$a.error("Failed to read audit log:", err);
|
|
19111
19741
|
return [];
|
|
19112
19742
|
}
|
|
19113
19743
|
}
|
|
@@ -19280,7 +19910,7 @@ async function requestHumanVaultConsent(request) {
|
|
|
19280
19910
|
}
|
|
19281
19911
|
let httpServer = null;
|
|
19282
19912
|
let mcpAuthToken = null;
|
|
19283
|
-
const logger$
|
|
19913
|
+
const logger$9 = createLogger("MCP");
|
|
19284
19914
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
19285
19915
|
function getMcpAuthFilePath() {
|
|
19286
19916
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -19316,7 +19946,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
19316
19946
|
{ mode: 384 }
|
|
19317
19947
|
);
|
|
19318
19948
|
} catch (err) {
|
|
19319
|
-
logger$
|
|
19949
|
+
logger$9.warn("Failed to write auth file:", err);
|
|
19320
19950
|
}
|
|
19321
19951
|
}
|
|
19322
19952
|
function clearMcpAuthFile() {
|
|
@@ -19341,7 +19971,7 @@ function clearMcpAuthFile() {
|
|
|
19341
19971
|
{ mode: 384 }
|
|
19342
19972
|
);
|
|
19343
19973
|
} catch (err) {
|
|
19344
|
-
logger$
|
|
19974
|
+
logger$9.warn("Failed to clear auth file:", err);
|
|
19345
19975
|
}
|
|
19346
19976
|
}
|
|
19347
19977
|
function asTextResponse(text) {
|
|
@@ -19431,7 +20061,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
19431
20061
|
}
|
|
19432
20062
|
}
|
|
19433
20063
|
} catch (err) {
|
|
19434
|
-
logger$
|
|
20064
|
+
logger$9.warn("Failed to compute post-action state warning:", err);
|
|
19435
20065
|
}
|
|
19436
20066
|
return `${warning}
|
|
19437
20067
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -19533,7 +20163,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
19533
20163
|
}
|
|
19534
20164
|
})()
|
|
19535
20165
|
`).catch((err) => {
|
|
19536
|
-
logger$
|
|
20166
|
+
logger$9.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
19537
20167
|
return null;
|
|
19538
20168
|
});
|
|
19539
20169
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -19620,7 +20250,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
19620
20250
|
const page = await extractContent(wc);
|
|
19621
20251
|
pageType = detectPageType(page);
|
|
19622
20252
|
} catch (err) {
|
|
19623
|
-
logger$
|
|
20253
|
+
logger$9.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
19624
20254
|
}
|
|
19625
20255
|
}
|
|
19626
20256
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -21018,7 +21648,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
21018
21648
|
void 0,
|
|
21019
21649
|
h.color
|
|
21020
21650
|
).catch(
|
|
21021
|
-
(err) => logger$
|
|
21651
|
+
(err) => logger$9.warn("Failed to restore highlight after removal:", err)
|
|
21022
21652
|
);
|
|
21023
21653
|
}
|
|
21024
21654
|
}
|
|
@@ -21866,7 +22496,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21866
22496
|
try {
|
|
21867
22497
|
page = await extractContent(wc);
|
|
21868
22498
|
} catch (err) {
|
|
21869
|
-
logger$
|
|
22499
|
+
logger$9.warn("Failed to extract page while generating suggestions:", err);
|
|
21870
22500
|
return asTextResponse(
|
|
21871
22501
|
"Could not read page. Try navigate to a working URL."
|
|
21872
22502
|
);
|
|
@@ -22475,7 +23105,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22475
23105
|
try {
|
|
22476
23106
|
targetDomain = new URL(tab.state.url).hostname;
|
|
22477
23107
|
} catch (err) {
|
|
22478
|
-
logger$
|
|
23108
|
+
logger$9.warn("Failed to parse active tab URL for vault_status:", err);
|
|
22479
23109
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22480
23110
|
}
|
|
22481
23111
|
}
|
|
@@ -22541,7 +23171,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22541
23171
|
try {
|
|
22542
23172
|
hostname = new URL(tab.state.url).hostname;
|
|
22543
23173
|
} catch (err) {
|
|
22544
|
-
logger$
|
|
23174
|
+
logger$9.warn("Failed to parse active tab URL for vault_login:", err);
|
|
22545
23175
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22546
23176
|
}
|
|
22547
23177
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -22635,7 +23265,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22635
23265
|
try {
|
|
22636
23266
|
hostname = new URL(tab.state.url).hostname;
|
|
22637
23267
|
} catch (err) {
|
|
22638
|
-
logger$
|
|
23268
|
+
logger$9.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
22639
23269
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22640
23270
|
}
|
|
22641
23271
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -22933,7 +23563,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22933
23563
|
});
|
|
22934
23564
|
mcpAuthToken = getPersistentMcpAuthToken();
|
|
22935
23565
|
return new Promise((resolve) => {
|
|
22936
|
-
const server = http.createServer(async (req, res) => {
|
|
23566
|
+
const server = http$1.createServer(async (req, res) => {
|
|
22937
23567
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
22938
23568
|
if (url.pathname !== "/mcp") {
|
|
22939
23569
|
res.writeHead(404);
|
|
@@ -22975,7 +23605,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22975
23605
|
await mcpServer.connect(transport);
|
|
22976
23606
|
await transport.handleRequest(req, res);
|
|
22977
23607
|
} catch (error) {
|
|
22978
|
-
logger$
|
|
23608
|
+
logger$9.error("Error handling request:", error);
|
|
22979
23609
|
if (!res.headersSent) {
|
|
22980
23610
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22981
23611
|
res.end(
|
|
@@ -22994,7 +23624,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22994
23624
|
};
|
|
22995
23625
|
server.once("error", (error) => {
|
|
22996
23626
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
22997
|
-
logger$
|
|
23627
|
+
logger$9.error("Server error:", error);
|
|
22998
23628
|
clearMcpAuthFile();
|
|
22999
23629
|
setMcpHealth({
|
|
23000
23630
|
configuredPort: port,
|
|
@@ -23026,7 +23656,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
23026
23656
|
message: `MCP server listening on ${endpoint}.`
|
|
23027
23657
|
});
|
|
23028
23658
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23029
|
-
logger$
|
|
23659
|
+
logger$9.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
23030
23660
|
}
|
|
23031
23661
|
if (mcpAuthToken) {
|
|
23032
23662
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -23065,7 +23695,7 @@ function stopMcpServer() {
|
|
|
23065
23695
|
message: "MCP server is stopped."
|
|
23066
23696
|
});
|
|
23067
23697
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23068
|
-
logger$
|
|
23698
|
+
logger$9.info("Server stopped");
|
|
23069
23699
|
}
|
|
23070
23700
|
resolve();
|
|
23071
23701
|
});
|
|
@@ -23086,7 +23716,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
23086
23716
|
function isSafeAutomationKitId(id) {
|
|
23087
23717
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
23088
23718
|
}
|
|
23089
|
-
const logger$
|
|
23719
|
+
const logger$8 = createLogger("KitRegistry");
|
|
23090
23720
|
function getUserKitsDir() {
|
|
23091
23721
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
23092
23722
|
}
|
|
@@ -23124,10 +23754,10 @@ function getInstalledKits() {
|
|
|
23124
23754
|
if (isValidKit(parsed)) {
|
|
23125
23755
|
kits.push(parsed);
|
|
23126
23756
|
} else {
|
|
23127
|
-
logger$
|
|
23757
|
+
logger$8.warn(`Skipping invalid kit file: ${file}`);
|
|
23128
23758
|
}
|
|
23129
23759
|
} catch (err) {
|
|
23130
|
-
logger$
|
|
23760
|
+
logger$8.warn(`Failed to read kit file: ${file}`, err);
|
|
23131
23761
|
}
|
|
23132
23762
|
}
|
|
23133
23763
|
return kits;
|
|
@@ -23199,7 +23829,7 @@ function uninstallKit(id, scheduledKitIds) {
|
|
|
23199
23829
|
return errorResult("Failed to remove the kit file.");
|
|
23200
23830
|
}
|
|
23201
23831
|
}
|
|
23202
|
-
const logger$
|
|
23832
|
+
const logger$7 = createLogger("Scheduler");
|
|
23203
23833
|
let jobs = [];
|
|
23204
23834
|
let removeIdleListener = null;
|
|
23205
23835
|
let broadcastFn = null;
|
|
@@ -23224,7 +23854,7 @@ function saveJobs() {
|
|
|
23224
23854
|
try {
|
|
23225
23855
|
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
23226
23856
|
} catch (err) {
|
|
23227
|
-
logger$
|
|
23857
|
+
logger$7.warn("Failed to save jobs:", err);
|
|
23228
23858
|
}
|
|
23229
23859
|
}
|
|
23230
23860
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -23346,7 +23976,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
23346
23976
|
};
|
|
23347
23977
|
startActivity();
|
|
23348
23978
|
if (!settings2.chatProvider) {
|
|
23349
|
-
logger$
|
|
23979
|
+
logger$7.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
23350
23980
|
appendActivity(
|
|
23351
23981
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
23352
23982
|
);
|
|
@@ -23354,7 +23984,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
23354
23984
|
return;
|
|
23355
23985
|
}
|
|
23356
23986
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
23357
|
-
logger$
|
|
23987
|
+
logger$7.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
23358
23988
|
}
|
|
23359
23989
|
try {
|
|
23360
23990
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -23407,7 +24037,7 @@ function tick(windowState, runtime2) {
|
|
|
23407
24037
|
saveJobs();
|
|
23408
24038
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
23409
24039
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
23410
|
-
logger$
|
|
24040
|
+
logger$7.warn("Unexpected error firing job:", err);
|
|
23411
24041
|
}).finally(fireNext);
|
|
23412
24042
|
};
|
|
23413
24043
|
fireNext();
|
|
@@ -24517,7 +25147,7 @@ function createFindInPageBridge(tabManager, chromeView) {
|
|
|
24517
25147
|
}
|
|
24518
25148
|
};
|
|
24519
25149
|
}
|
|
24520
|
-
const logger$
|
|
25150
|
+
const logger$6 = createLogger("PrivateWindow");
|
|
24521
25151
|
const privateWindows = /* @__PURE__ */ new Set();
|
|
24522
25152
|
function layoutPrivateViews(state2) {
|
|
24523
25153
|
const { window: win, chromeView, tabManager } = state2;
|
|
@@ -24740,7 +25370,7 @@ function createPrivateWindow() {
|
|
|
24740
25370
|
privateSession.clearStorageData(),
|
|
24741
25371
|
privateSession.clearCache()
|
|
24742
25372
|
]).catch((error) => {
|
|
24743
|
-
logger$
|
|
25373
|
+
logger$6.warn("Failed to clear private browsing session:", error);
|
|
24744
25374
|
});
|
|
24745
25375
|
});
|
|
24746
25376
|
privateWindows.add(state2);
|
|
@@ -24750,7 +25380,7 @@ function createPrivateWindow() {
|
|
|
24750
25380
|
});
|
|
24751
25381
|
loadPrivateRenderer(chromeView);
|
|
24752
25382
|
win.show();
|
|
24753
|
-
logger$
|
|
25383
|
+
logger$6.info("Private browsing window opened");
|
|
24754
25384
|
return state2;
|
|
24755
25385
|
}
|
|
24756
25386
|
const secondaryWindows = /* @__PURE__ */ new Set();
|
|
@@ -25364,6 +25994,48 @@ function registerSecurityHandlers(tabManager) {
|
|
|
25364
25994
|
tabManager.goBackToSafety(tabId);
|
|
25365
25995
|
});
|
|
25366
25996
|
}
|
|
25997
|
+
const logger$5 = createLogger("CodexIPC");
|
|
25998
|
+
function registerCodexHandlers() {
|
|
25999
|
+
electron.ipcMain.handle(Channels.CODEX_START_AUTH, async (event) => {
|
|
26000
|
+
const wc = event.sender;
|
|
26001
|
+
if (!wc || wc.isDestroyed()) {
|
|
26002
|
+
return {
|
|
26003
|
+
ok: false,
|
|
26004
|
+
error: "No active window found for sender"
|
|
26005
|
+
};
|
|
26006
|
+
}
|
|
26007
|
+
const sendStatus = (status, error) => {
|
|
26008
|
+
try {
|
|
26009
|
+
wc.send(Channels.CODEX_AUTH_STATUS, { status, error: error || null });
|
|
26010
|
+
} catch {
|
|
26011
|
+
logger$5.warn("Codex auth status send failed — window may be closed");
|
|
26012
|
+
}
|
|
26013
|
+
};
|
|
26014
|
+
try {
|
|
26015
|
+
const tokens = await startCodexOAuth(sendStatus);
|
|
26016
|
+
writeStoredCodexTokens(tokens);
|
|
26017
|
+
return {
|
|
26018
|
+
ok: true,
|
|
26019
|
+
accountEmail: tokens.accountEmail || tokens.accountId,
|
|
26020
|
+
accountId: tokens.accountId
|
|
26021
|
+
};
|
|
26022
|
+
} catch (err) {
|
|
26023
|
+
logger$5.error("Codex auth failed:", err);
|
|
26024
|
+
return {
|
|
26025
|
+
ok: false,
|
|
26026
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
26027
|
+
};
|
|
26028
|
+
}
|
|
26029
|
+
});
|
|
26030
|
+
electron.ipcMain.handle(Channels.CODEX_CANCEL_AUTH, () => {
|
|
26031
|
+
cancelCodexOAuth();
|
|
26032
|
+
return { ok: true };
|
|
26033
|
+
});
|
|
26034
|
+
electron.ipcMain.handle(Channels.CODEX_DISCONNECT, () => {
|
|
26035
|
+
clearStoredCodexTokens();
|
|
26036
|
+
return { ok: true };
|
|
26037
|
+
});
|
|
26038
|
+
}
|
|
25367
26039
|
let activeChatProvider = null;
|
|
25368
26040
|
const logger$4 = createLogger("IPC");
|
|
25369
26041
|
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
@@ -25937,6 +26609,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25937
26609
|
registerVaultHandlers();
|
|
25938
26610
|
registerHumanVaultHandlers();
|
|
25939
26611
|
registerWindowControlHandlers(mainWindow);
|
|
26612
|
+
registerCodexHandlers();
|
|
25940
26613
|
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, () => {
|
|
25941
26614
|
return getInstalledKits();
|
|
25942
26615
|
});
|