@quanta-intellect/vessel-browser 0.1.88 → 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;
|
|
@@ -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, "");
|
|
@@ -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() {
|
|
@@ -3477,7 +3513,12 @@ const Channels = {
|
|
|
3477
3513
|
CLEAR_BROWSING_DATA: "browsing-data:clear",
|
|
3478
3514
|
CLEAR_BROWSING_DATA_OPEN: "browsing-data:open",
|
|
3479
3515
|
// Picture-in-Picture
|
|
3480
|
-
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"
|
|
3481
3522
|
};
|
|
3482
3523
|
const MAX_DETAIL_ITEMS = 3;
|
|
3483
3524
|
const MIN_BLOCK_SIMILARITY = 0.82;
|
|
@@ -4435,7 +4476,7 @@ function errorResult(error, value) {
|
|
|
4435
4476
|
function getErrorMessage(error, fallback = "Unknown error") {
|
|
4436
4477
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
4437
4478
|
}
|
|
4438
|
-
const logger$
|
|
4479
|
+
const logger$j = createLogger("Premium");
|
|
4439
4480
|
const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
4440
4481
|
const FREE_TOOL_ITERATION_LIMIT = 50;
|
|
4441
4482
|
const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4551,7 +4592,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4551
4592
|
});
|
|
4552
4593
|
if (!res.ok) {
|
|
4553
4594
|
const detail = await readApiErrorDetail(res);
|
|
4554
|
-
logger$
|
|
4595
|
+
logger$j.warn(
|
|
4555
4596
|
"Verification API returned a non-OK status:",
|
|
4556
4597
|
res.status,
|
|
4557
4598
|
detail
|
|
@@ -4570,7 +4611,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4570
4611
|
setSetting("premium", updated);
|
|
4571
4612
|
return updated;
|
|
4572
4613
|
} catch (err) {
|
|
4573
|
-
logger$
|
|
4614
|
+
logger$j.warn("Verification failed:", err);
|
|
4574
4615
|
return current;
|
|
4575
4616
|
}
|
|
4576
4617
|
}
|
|
@@ -5143,7 +5184,7 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
|
5143
5184
|
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
5144
5185
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
5145
5186
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
5146
|
-
const logger$
|
|
5187
|
+
const logger$i = createLogger("Extractor");
|
|
5147
5188
|
const EMPTY_PAGE_CONTENT = {
|
|
5148
5189
|
title: "",
|
|
5149
5190
|
content: "",
|
|
@@ -5893,7 +5934,7 @@ async function executeScript(webContents, script) {
|
|
|
5893
5934
|
})
|
|
5894
5935
|
]);
|
|
5895
5936
|
} catch (err) {
|
|
5896
|
-
logger$
|
|
5937
|
+
logger$i.warn("Failed to execute page script:", err);
|
|
5897
5938
|
return null;
|
|
5898
5939
|
} finally {
|
|
5899
5940
|
if (timer) {
|
|
@@ -6000,7 +6041,7 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
6000
6041
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
6001
6042
|
}
|
|
6002
6043
|
} catch (err) {
|
|
6003
|
-
logger$
|
|
6044
|
+
logger$i.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
6004
6045
|
}
|
|
6005
6046
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
6006
6047
|
}
|
|
@@ -6609,8 +6650,8 @@ function resizeSidebarViews(state2) {
|
|
|
6609
6650
|
}
|
|
6610
6651
|
}
|
|
6611
6652
|
function generateReaderHTML(page) {
|
|
6612
|
-
const escapedTitle = escapeHtml(page.title);
|
|
6613
|
-
const escapedByline = escapeHtml(page.byline);
|
|
6653
|
+
const escapedTitle = escapeHtml$1(page.title);
|
|
6654
|
+
const escapedByline = escapeHtml$1(page.byline);
|
|
6614
6655
|
const renderedContent = renderReaderContent(page);
|
|
6615
6656
|
return `<!DOCTYPE html>
|
|
6616
6657
|
<html lang="en">
|
|
@@ -6698,9 +6739,9 @@ function renderReaderContent(page) {
|
|
|
6698
6739
|
if (!source) {
|
|
6699
6740
|
return "<p>No readable content was available for this page.</p>";
|
|
6700
6741
|
}
|
|
6701
|
-
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");
|
|
6702
6743
|
}
|
|
6703
|
-
function escapeHtml(str) {
|
|
6744
|
+
function escapeHtml$1(str) {
|
|
6704
6745
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6705
6746
|
}
|
|
6706
6747
|
const mcpStatusChangeListeners = /* @__PURE__ */ new Set();
|
|
@@ -7177,6 +7218,17 @@ const PROVIDERS = {
|
|
|
7177
7218
|
apiKeyPlaceholder: "sk-...",
|
|
7178
7219
|
apiKeyHint: "Get your key from platform.openai.com"
|
|
7179
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
|
+
},
|
|
7180
7232
|
openrouter: {
|
|
7181
7233
|
id: "openrouter",
|
|
7182
7234
|
name: "OpenRouter",
|
|
@@ -7348,7 +7400,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
7348
7400
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7349
7401
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7350
7402
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7351
|
-
const logger$
|
|
7403
|
+
const logger$h = createLogger("OpenAIProvider");
|
|
7352
7404
|
function shouldDebugAgentLoop() {
|
|
7353
7405
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
7354
7406
|
return value === "1" || value === "true";
|
|
@@ -7873,9 +7925,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
7873
7925
|
function logAgentLoopDebug(payload) {
|
|
7874
7926
|
if (!shouldDebugAgentLoop()) return;
|
|
7875
7927
|
try {
|
|
7876
|
-
logger$
|
|
7928
|
+
logger$h.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
7877
7929
|
} catch (err) {
|
|
7878
|
-
logger$
|
|
7930
|
+
logger$h.warn("Failed to serialize debug payload:", err);
|
|
7879
7931
|
}
|
|
7880
7932
|
}
|
|
7881
7933
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -8409,6 +8461,525 @@ class OpenAICompatProvider {
|
|
|
8409
8461
|
this.abortController?.abort();
|
|
8410
8462
|
}
|
|
8411
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
|
+
}
|
|
8412
8983
|
function sanitizeProviderConfig(config) {
|
|
8413
8984
|
return {
|
|
8414
8985
|
...config,
|
|
@@ -8430,7 +9001,7 @@ function validateProviderConnection(config, options = { requireModel: true }) {
|
|
|
8430
9001
|
if (!meta) {
|
|
8431
9002
|
return "Selected AI provider is not supported.";
|
|
8432
9003
|
}
|
|
8433
|
-
if (meta.requiresApiKey && !normalized.apiKey) {
|
|
9004
|
+
if (meta.type !== "codex_oauth" && meta.requiresApiKey && !normalized.apiKey) {
|
|
8434
9005
|
return `${meta.name} requires an API key. Open settings (Ctrl+,) to add one.`;
|
|
8435
9006
|
}
|
|
8436
9007
|
if (options.requireModel && !normalized.model) {
|
|
@@ -8472,6 +9043,34 @@ function buildLlamaCppCtxWarning(ctxSize) {
|
|
|
8472
9043
|
}
|
|
8473
9044
|
return void 0;
|
|
8474
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
|
+
}
|
|
8475
9074
|
async function probeLlamaCppCtxWarning(baseURL) {
|
|
8476
9075
|
try {
|
|
8477
9076
|
const root = new URL(baseURL);
|
|
@@ -8499,6 +9098,24 @@ async function fetchProviderModels(config) {
|
|
|
8499
9098
|
const page2 = await client2.models.list();
|
|
8500
9099
|
return okResult({ models: page2.data.map((model) => model.id) });
|
|
8501
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
|
+
}
|
|
8502
9119
|
const meta = PROVIDERS[normalized.id];
|
|
8503
9120
|
const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
8504
9121
|
const client = new OpenAI({
|
|
@@ -8528,10 +9145,19 @@ function createProvider(config) {
|
|
|
8528
9145
|
normalized.reasoningEffort
|
|
8529
9146
|
);
|
|
8530
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
|
+
}
|
|
8531
9157
|
return new OpenAICompatProvider(normalized);
|
|
8532
9158
|
}
|
|
8533
9159
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
8534
|
-
const logger$
|
|
9160
|
+
const logger$e = createLogger("DevTrace");
|
|
8535
9161
|
let cachedFactory;
|
|
8536
9162
|
function createNoopTraceSession() {
|
|
8537
9163
|
return {
|
|
@@ -8564,7 +9190,7 @@ function loadLocalFactory() {
|
|
|
8564
9190
|
return cachedFactory;
|
|
8565
9191
|
}
|
|
8566
9192
|
} catch (err) {
|
|
8567
|
-
logger$
|
|
9193
|
+
logger$e.warn("Failed to load local trace logger:", err);
|
|
8568
9194
|
}
|
|
8569
9195
|
}
|
|
8570
9196
|
return cachedFactory;
|
|
@@ -12683,7 +13309,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
12683
13309
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
12684
13310
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
12685
13311
|
}
|
|
12686
|
-
const logger$
|
|
13312
|
+
const logger$d = createLogger("Screenshot");
|
|
12687
13313
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
12688
13314
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
12689
13315
|
async function captureScreenshot(wc) {
|
|
@@ -12705,7 +13331,7 @@ async function captureScreenshot(wc) {
|
|
|
12705
13331
|
}
|
|
12706
13332
|
}
|
|
12707
13333
|
} catch (err) {
|
|
12708
|
-
logger$
|
|
13334
|
+
logger$d.debug(
|
|
12709
13335
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
12710
13336
|
getErrorMessage(err)
|
|
12711
13337
|
);
|
|
@@ -13579,7 +14205,7 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
13579
14205
|
appliedFilters
|
|
13580
14206
|
};
|
|
13581
14207
|
}
|
|
13582
|
-
const logger$
|
|
14208
|
+
const logger$c = createLogger("PageActions");
|
|
13583
14209
|
function getBookmarkMetadataFromArgs(args) {
|
|
13584
14210
|
return normalizeBookmarkMetadata({
|
|
13585
14211
|
intent: args.intent ?? args.intent,
|
|
@@ -13765,7 +14391,7 @@ async function executePageScript(wc, script, options) {
|
|
|
13765
14391
|
return result;
|
|
13766
14392
|
} catch (err) {
|
|
13767
14393
|
const label = options?.label ? ` (${options.label})` : "";
|
|
13768
|
-
logger$
|
|
14394
|
+
logger$c.warn(`Failed to execute page script${label}:`, err);
|
|
13769
14395
|
return null;
|
|
13770
14396
|
} finally {
|
|
13771
14397
|
if (timer) {
|
|
@@ -13866,7 +14492,7 @@ Search results snapshot:
|
|
|
13866
14492
|
${truncated}`;
|
|
13867
14493
|
}
|
|
13868
14494
|
} catch (err) {
|
|
13869
|
-
logger$
|
|
14495
|
+
logger$c.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
13870
14496
|
}
|
|
13871
14497
|
const fallback = await getPostNavSummary(wc);
|
|
13872
14498
|
return fallback ? `${fallback}
|
|
@@ -13889,7 +14515,7 @@ Page snapshot after navigation:
|
|
|
13889
14515
|
${truncated}`;
|
|
13890
14516
|
}
|
|
13891
14517
|
} catch (err) {
|
|
13892
|
-
logger$
|
|
14518
|
+
logger$c.warn("Failed to build post-click navigation summary:", err);
|
|
13893
14519
|
}
|
|
13894
14520
|
return "";
|
|
13895
14521
|
}
|
|
@@ -14383,7 +15009,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14383
15009
|
}
|
|
14384
15010
|
}
|
|
14385
15011
|
} catch (err) {
|
|
14386
|
-
logger$
|
|
15012
|
+
logger$c.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
14387
15013
|
}
|
|
14388
15014
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
14389
15015
|
try {
|
|
@@ -14392,7 +15018,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14392
15018
|
await waitForLoad(wc, 3e3);
|
|
14393
15019
|
return;
|
|
14394
15020
|
} catch (err) {
|
|
14395
|
-
logger$
|
|
15021
|
+
logger$c.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
14396
15022
|
}
|
|
14397
15023
|
}
|
|
14398
15024
|
if (snapshot.url) {
|
|
@@ -14400,7 +15026,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
14400
15026
|
await wc.reload();
|
|
14401
15027
|
await waitForLoad(wc, 3e3);
|
|
14402
15028
|
} catch (err) {
|
|
14403
|
-
logger$
|
|
15029
|
+
logger$c.warn("Failed to restore locale via page reload:", err);
|
|
14404
15030
|
}
|
|
14405
15031
|
}
|
|
14406
15032
|
}
|
|
@@ -14752,7 +15378,7 @@ ${postActivationOverlayHint}`;
|
|
|
14752
15378
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
14753
15379
|
}
|
|
14754
15380
|
} catch (err) {
|
|
14755
|
-
logger$
|
|
15381
|
+
logger$c.warn("Failed href fallback after click, returning generic click result:", err);
|
|
14756
15382
|
}
|
|
14757
15383
|
}
|
|
14758
15384
|
}
|
|
@@ -14797,7 +15423,7 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
14797
15423
|
return result;
|
|
14798
15424
|
}
|
|
14799
15425
|
} catch (err) {
|
|
14800
|
-
logger$
|
|
15426
|
+
logger$c.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
14801
15427
|
}
|
|
14802
15428
|
return null;
|
|
14803
15429
|
}
|
|
@@ -17067,7 +17693,7 @@ async function executeAction(name, args, ctx) {
|
|
|
17067
17693
|
)
|
|
17068
17694
|
]);
|
|
17069
17695
|
} catch (err) {
|
|
17070
|
-
logger$
|
|
17696
|
+
logger$c.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
17071
17697
|
content = null;
|
|
17072
17698
|
}
|
|
17073
17699
|
if (!content || content.content.length === 0) {
|
|
@@ -17084,12 +17710,12 @@ async function executeAction(name, args, ctx) {
|
|
|
17084
17710
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
17085
17711
|
]);
|
|
17086
17712
|
} catch (err) {
|
|
17087
|
-
logger$
|
|
17713
|
+
logger$c.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
17088
17714
|
content = null;
|
|
17089
17715
|
}
|
|
17090
17716
|
}
|
|
17091
17717
|
} catch (err) {
|
|
17092
|
-
logger$
|
|
17718
|
+
logger$c.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
17093
17719
|
}
|
|
17094
17720
|
}
|
|
17095
17721
|
if (content && content.content.length > 0) {
|
|
@@ -17502,7 +18128,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
17502
18128
|
try {
|
|
17503
18129
|
page = await extractContent(wc);
|
|
17504
18130
|
} catch (err) {
|
|
17505
|
-
logger$
|
|
18131
|
+
logger$c.warn("Failed to extract content for suggest:", err);
|
|
17506
18132
|
return "Could not read page. Try navigate to a working URL.";
|
|
17507
18133
|
}
|
|
17508
18134
|
const suggestions = [];
|
|
@@ -18827,7 +19453,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
18827
19453
|
}
|
|
18828
19454
|
);
|
|
18829
19455
|
}
|
|
18830
|
-
const logger$
|
|
19456
|
+
const logger$b = createLogger("VaultShared");
|
|
18831
19457
|
const ALGORITHM = "aes-256-gcm";
|
|
18832
19458
|
const IV_LENGTH = 12;
|
|
18833
19459
|
const AUTH_TAG_LENGTH = 16;
|
|
@@ -18904,7 +19530,7 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
|
18904
19530
|
cachedEntries = JSON.parse(json);
|
|
18905
19531
|
return cachedEntries;
|
|
18906
19532
|
} catch (err) {
|
|
18907
|
-
logger$
|
|
19533
|
+
logger$b.error("Failed to load vault:", err);
|
|
18908
19534
|
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
18909
19535
|
}
|
|
18910
19536
|
}
|
|
@@ -18973,7 +19599,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
18973
19599
|
} catch {
|
|
18974
19600
|
}
|
|
18975
19601
|
} catch (err) {
|
|
18976
|
-
logger$
|
|
19602
|
+
logger$b.error("Failed to write audit log:", err);
|
|
18977
19603
|
}
|
|
18978
19604
|
}
|
|
18979
19605
|
function readAuditLog2(limit = 100) {
|
|
@@ -18983,7 +19609,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
18983
19609
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
18984
19610
|
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
18985
19611
|
} catch (err) {
|
|
18986
|
-
logger$
|
|
19612
|
+
logger$b.error("Failed to read audit log:", err);
|
|
18987
19613
|
return [];
|
|
18988
19614
|
}
|
|
18989
19615
|
}
|
|
@@ -19091,7 +19717,7 @@ async function requestConsent(request) {
|
|
|
19091
19717
|
}
|
|
19092
19718
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
19093
19719
|
const MAX_ENTRIES = 1e3;
|
|
19094
|
-
const logger$
|
|
19720
|
+
const logger$a = createLogger("VaultAudit");
|
|
19095
19721
|
function getAuditPath() {
|
|
19096
19722
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
19097
19723
|
}
|
|
@@ -19101,7 +19727,7 @@ function appendAuditEntry(entry) {
|
|
|
19101
19727
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
19102
19728
|
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
19103
19729
|
} catch (err) {
|
|
19104
|
-
logger$
|
|
19730
|
+
logger$a.error("Failed to write audit log:", err);
|
|
19105
19731
|
}
|
|
19106
19732
|
}
|
|
19107
19733
|
function readAuditLog$1(limit = 100) {
|
|
@@ -19111,7 +19737,7 @@ function readAuditLog$1(limit = 100) {
|
|
|
19111
19737
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19112
19738
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
19113
19739
|
} catch (err) {
|
|
19114
|
-
logger$
|
|
19740
|
+
logger$a.error("Failed to read audit log:", err);
|
|
19115
19741
|
return [];
|
|
19116
19742
|
}
|
|
19117
19743
|
}
|
|
@@ -19284,7 +19910,7 @@ async function requestHumanVaultConsent(request) {
|
|
|
19284
19910
|
}
|
|
19285
19911
|
let httpServer = null;
|
|
19286
19912
|
let mcpAuthToken = null;
|
|
19287
|
-
const logger$
|
|
19913
|
+
const logger$9 = createLogger("MCP");
|
|
19288
19914
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
19289
19915
|
function getMcpAuthFilePath() {
|
|
19290
19916
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -19320,7 +19946,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
19320
19946
|
{ mode: 384 }
|
|
19321
19947
|
);
|
|
19322
19948
|
} catch (err) {
|
|
19323
|
-
logger$
|
|
19949
|
+
logger$9.warn("Failed to write auth file:", err);
|
|
19324
19950
|
}
|
|
19325
19951
|
}
|
|
19326
19952
|
function clearMcpAuthFile() {
|
|
@@ -19345,7 +19971,7 @@ function clearMcpAuthFile() {
|
|
|
19345
19971
|
{ mode: 384 }
|
|
19346
19972
|
);
|
|
19347
19973
|
} catch (err) {
|
|
19348
|
-
logger$
|
|
19974
|
+
logger$9.warn("Failed to clear auth file:", err);
|
|
19349
19975
|
}
|
|
19350
19976
|
}
|
|
19351
19977
|
function asTextResponse(text) {
|
|
@@ -19435,7 +20061,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
19435
20061
|
}
|
|
19436
20062
|
}
|
|
19437
20063
|
} catch (err) {
|
|
19438
|
-
logger$
|
|
20064
|
+
logger$9.warn("Failed to compute post-action state warning:", err);
|
|
19439
20065
|
}
|
|
19440
20066
|
return `${warning}
|
|
19441
20067
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -19537,7 +20163,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
19537
20163
|
}
|
|
19538
20164
|
})()
|
|
19539
20165
|
`).catch((err) => {
|
|
19540
|
-
logger$
|
|
20166
|
+
logger$9.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
19541
20167
|
return null;
|
|
19542
20168
|
});
|
|
19543
20169
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -19624,7 +20250,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
19624
20250
|
const page = await extractContent(wc);
|
|
19625
20251
|
pageType = detectPageType(page);
|
|
19626
20252
|
} catch (err) {
|
|
19627
|
-
logger$
|
|
20253
|
+
logger$9.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
19628
20254
|
}
|
|
19629
20255
|
}
|
|
19630
20256
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -21022,7 +21648,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
21022
21648
|
void 0,
|
|
21023
21649
|
h.color
|
|
21024
21650
|
).catch(
|
|
21025
|
-
(err) => logger$
|
|
21651
|
+
(err) => logger$9.warn("Failed to restore highlight after removal:", err)
|
|
21026
21652
|
);
|
|
21027
21653
|
}
|
|
21028
21654
|
}
|
|
@@ -21870,7 +22496,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21870
22496
|
try {
|
|
21871
22497
|
page = await extractContent(wc);
|
|
21872
22498
|
} catch (err) {
|
|
21873
|
-
logger$
|
|
22499
|
+
logger$9.warn("Failed to extract page while generating suggestions:", err);
|
|
21874
22500
|
return asTextResponse(
|
|
21875
22501
|
"Could not read page. Try navigate to a working URL."
|
|
21876
22502
|
);
|
|
@@ -22479,7 +23105,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
22479
23105
|
try {
|
|
22480
23106
|
targetDomain = new URL(tab.state.url).hostname;
|
|
22481
23107
|
} catch (err) {
|
|
22482
|
-
logger$
|
|
23108
|
+
logger$9.warn("Failed to parse active tab URL for vault_status:", err);
|
|
22483
23109
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22484
23110
|
}
|
|
22485
23111
|
}
|
|
@@ -22545,7 +23171,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22545
23171
|
try {
|
|
22546
23172
|
hostname = new URL(tab.state.url).hostname;
|
|
22547
23173
|
} catch (err) {
|
|
22548
|
-
logger$
|
|
23174
|
+
logger$9.warn("Failed to parse active tab URL for vault_login:", err);
|
|
22549
23175
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22550
23176
|
}
|
|
22551
23177
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -22639,7 +23265,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
22639
23265
|
try {
|
|
22640
23266
|
hostname = new URL(tab.state.url).hostname;
|
|
22641
23267
|
} catch (err) {
|
|
22642
|
-
logger$
|
|
23268
|
+
logger$9.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
22643
23269
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
22644
23270
|
}
|
|
22645
23271
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -22937,7 +23563,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22937
23563
|
});
|
|
22938
23564
|
mcpAuthToken = getPersistentMcpAuthToken();
|
|
22939
23565
|
return new Promise((resolve) => {
|
|
22940
|
-
const server = http.createServer(async (req, res) => {
|
|
23566
|
+
const server = http$1.createServer(async (req, res) => {
|
|
22941
23567
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
22942
23568
|
if (url.pathname !== "/mcp") {
|
|
22943
23569
|
res.writeHead(404);
|
|
@@ -22979,7 +23605,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22979
23605
|
await mcpServer.connect(transport);
|
|
22980
23606
|
await transport.handleRequest(req, res);
|
|
22981
23607
|
} catch (error) {
|
|
22982
|
-
logger$
|
|
23608
|
+
logger$9.error("Error handling request:", error);
|
|
22983
23609
|
if (!res.headersSent) {
|
|
22984
23610
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22985
23611
|
res.end(
|
|
@@ -22998,7 +23624,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22998
23624
|
};
|
|
22999
23625
|
server.once("error", (error) => {
|
|
23000
23626
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
23001
|
-
logger$
|
|
23627
|
+
logger$9.error("Server error:", error);
|
|
23002
23628
|
clearMcpAuthFile();
|
|
23003
23629
|
setMcpHealth({
|
|
23004
23630
|
configuredPort: port,
|
|
@@ -23030,7 +23656,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
23030
23656
|
message: `MCP server listening on ${endpoint}.`
|
|
23031
23657
|
});
|
|
23032
23658
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23033
|
-
logger$
|
|
23659
|
+
logger$9.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
23034
23660
|
}
|
|
23035
23661
|
if (mcpAuthToken) {
|
|
23036
23662
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -23069,7 +23695,7 @@ function stopMcpServer() {
|
|
|
23069
23695
|
message: "MCP server is stopped."
|
|
23070
23696
|
});
|
|
23071
23697
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23072
|
-
logger$
|
|
23698
|
+
logger$9.info("Server stopped");
|
|
23073
23699
|
}
|
|
23074
23700
|
resolve();
|
|
23075
23701
|
});
|
|
@@ -23090,7 +23716,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
23090
23716
|
function isSafeAutomationKitId(id) {
|
|
23091
23717
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
23092
23718
|
}
|
|
23093
|
-
const logger$
|
|
23719
|
+
const logger$8 = createLogger("KitRegistry");
|
|
23094
23720
|
function getUserKitsDir() {
|
|
23095
23721
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
23096
23722
|
}
|
|
@@ -23128,10 +23754,10 @@ function getInstalledKits() {
|
|
|
23128
23754
|
if (isValidKit(parsed)) {
|
|
23129
23755
|
kits.push(parsed);
|
|
23130
23756
|
} else {
|
|
23131
|
-
logger$
|
|
23757
|
+
logger$8.warn(`Skipping invalid kit file: ${file}`);
|
|
23132
23758
|
}
|
|
23133
23759
|
} catch (err) {
|
|
23134
|
-
logger$
|
|
23760
|
+
logger$8.warn(`Failed to read kit file: ${file}`, err);
|
|
23135
23761
|
}
|
|
23136
23762
|
}
|
|
23137
23763
|
return kits;
|
|
@@ -23203,7 +23829,7 @@ function uninstallKit(id, scheduledKitIds) {
|
|
|
23203
23829
|
return errorResult("Failed to remove the kit file.");
|
|
23204
23830
|
}
|
|
23205
23831
|
}
|
|
23206
|
-
const logger$
|
|
23832
|
+
const logger$7 = createLogger("Scheduler");
|
|
23207
23833
|
let jobs = [];
|
|
23208
23834
|
let removeIdleListener = null;
|
|
23209
23835
|
let broadcastFn = null;
|
|
@@ -23228,7 +23854,7 @@ function saveJobs() {
|
|
|
23228
23854
|
try {
|
|
23229
23855
|
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
23230
23856
|
} catch (err) {
|
|
23231
|
-
logger$
|
|
23857
|
+
logger$7.warn("Failed to save jobs:", err);
|
|
23232
23858
|
}
|
|
23233
23859
|
}
|
|
23234
23860
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -23350,7 +23976,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
23350
23976
|
};
|
|
23351
23977
|
startActivity();
|
|
23352
23978
|
if (!settings2.chatProvider) {
|
|
23353
|
-
logger$
|
|
23979
|
+
logger$7.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
23354
23980
|
appendActivity(
|
|
23355
23981
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
23356
23982
|
);
|
|
@@ -23358,7 +23984,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
23358
23984
|
return;
|
|
23359
23985
|
}
|
|
23360
23986
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
23361
|
-
logger$
|
|
23987
|
+
logger$7.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
23362
23988
|
}
|
|
23363
23989
|
try {
|
|
23364
23990
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -23411,7 +24037,7 @@ function tick(windowState, runtime2) {
|
|
|
23411
24037
|
saveJobs();
|
|
23412
24038
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
23413
24039
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
23414
|
-
logger$
|
|
24040
|
+
logger$7.warn("Unexpected error firing job:", err);
|
|
23415
24041
|
}).finally(fireNext);
|
|
23416
24042
|
};
|
|
23417
24043
|
fireNext();
|
|
@@ -24521,7 +25147,7 @@ function createFindInPageBridge(tabManager, chromeView) {
|
|
|
24521
25147
|
}
|
|
24522
25148
|
};
|
|
24523
25149
|
}
|
|
24524
|
-
const logger$
|
|
25150
|
+
const logger$6 = createLogger("PrivateWindow");
|
|
24525
25151
|
const privateWindows = /* @__PURE__ */ new Set();
|
|
24526
25152
|
function layoutPrivateViews(state2) {
|
|
24527
25153
|
const { window: win, chromeView, tabManager } = state2;
|
|
@@ -24744,7 +25370,7 @@ function createPrivateWindow() {
|
|
|
24744
25370
|
privateSession.clearStorageData(),
|
|
24745
25371
|
privateSession.clearCache()
|
|
24746
25372
|
]).catch((error) => {
|
|
24747
|
-
logger$
|
|
25373
|
+
logger$6.warn("Failed to clear private browsing session:", error);
|
|
24748
25374
|
});
|
|
24749
25375
|
});
|
|
24750
25376
|
privateWindows.add(state2);
|
|
@@ -24754,7 +25380,7 @@ function createPrivateWindow() {
|
|
|
24754
25380
|
});
|
|
24755
25381
|
loadPrivateRenderer(chromeView);
|
|
24756
25382
|
win.show();
|
|
24757
|
-
logger$
|
|
25383
|
+
logger$6.info("Private browsing window opened");
|
|
24758
25384
|
return state2;
|
|
24759
25385
|
}
|
|
24760
25386
|
const secondaryWindows = /* @__PURE__ */ new Set();
|
|
@@ -25368,6 +25994,48 @@ function registerSecurityHandlers(tabManager) {
|
|
|
25368
25994
|
tabManager.goBackToSafety(tabId);
|
|
25369
25995
|
});
|
|
25370
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
|
+
}
|
|
25371
26039
|
let activeChatProvider = null;
|
|
25372
26040
|
const logger$4 = createLogger("IPC");
|
|
25373
26041
|
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
@@ -25941,6 +26609,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
25941
26609
|
registerVaultHandlers();
|
|
25942
26610
|
registerHumanVaultHandlers();
|
|
25943
26611
|
registerWindowControlHandlers(mainWindow);
|
|
26612
|
+
registerCodexHandlers();
|
|
25944
26613
|
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, () => {
|
|
25945
26614
|
return getInstalledKits();
|
|
25946
26615
|
});
|