@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 logger$k = createLogger("Settings");
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: current.chatProvider ? {
163
- ...current.chatProvider,
198
+ chatProvider: provider ? {
199
+ ...provider,
164
200
  apiKey: "",
165
- hasApiKey: Boolean(current.chatProvider.apiKey)
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$k.error("Failed to save settings:", err));
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$j = createLogger("Tab");
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$j.warn(blockReason);
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$j.warn(error);
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$j.warn(`${context}: ${error}`);
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$j.warn("Failed to inject scrollbar CSS:", err));
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$j.warn("Failed to inspect highlighted text for context menu:", err);
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$j.warn("Failed to retrieve page source:", err);
865
+ logger$m.warn("Failed to retrieve page source:", err);
830
866
  return;
831
867
  }
832
868
  const escaped = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -954,7 +990,7 @@ class Tab {
954
990
  document.addEventListener('mouseup', window.__vesselHighlightHandler);
955
991
  }
956
992
  })()
957
- `).catch((err) => logger$j.warn("Failed to inject highlight listener:", err));
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$j.warn("Failed to remove highlight listener:", err));
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$i = createLogger("JsonPersistence");
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$i.error(`Failed to save ${logLabel}:`, err));
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 highlightOnPage(wc, resolvedSelector, text, label, durationMs, color) {
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$h = createLogger("TabManager");
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$h.warn("Failed to batch highlight:", err)
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$h.warn("Failed to capture highlight:", err)
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$h.warn("Failed to capture highlight from page:", err);
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$h.warn("Failed to remove highlight from matching tab:", err);
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$h.warn("Failed to update highlight color:", err)
3267
+ (err) => logger$k.warn("Failed to update highlight color:", err)
3228
3268
  );
3229
3269
  });
3230
3270
  }
3231
3271
  } catch (err) {
3232
- logger$h.warn("Failed to iterate highlights for color change:", err);
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$h.warn("Failed to remove highlight marks:", err)
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$g = createLogger("Premium");
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$g.warn(
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$g.warn("Verification failed:", err);
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$f = createLogger("Extractor");
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$f.warn("Failed to execute page script:", err);
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$f.warn("Failed to estimate extraction timeout, using base timeout:", err);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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$e = createLogger("OpenAIProvider");
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$e.info(`[agent-debug] ${JSON.stringify(payload)}`);
7928
+ logger$h.info(`[agent-debug] ${JSON.stringify(payload)}`);
7873
7929
  } catch (err) {
7874
- logger$e.warn("Failed to serialize debug payload:", err);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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$d = createLogger("DevTrace");
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$d.warn("Failed to load local trace logger:", err);
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$c = createLogger("Screenshot");
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$c.debug(
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$b = createLogger("PageActions");
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$b.warn(`Failed to execute page script${label}:`, err);
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$b.warn("Failed to build post-search summary, falling back to nav summary:", err);
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$b.warn("Failed to build post-click navigation summary:", err);
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$b.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
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$b.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
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$b.warn("Failed to restore locale via page reload:", err);
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$b.warn("Failed href fallback after click, returning generic click result:", err);
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$b.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
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$b.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
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$b.warn("Failed to re-extract content after iframe consent dismissal:", err);
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$b.warn("Failed iframe consent dismissal during read_page recovery:", err);
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$b.warn("Failed to extract content for suggest:", err);
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$a = createLogger("VaultShared");
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$a.error("Failed to load vault:", err);
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$a.error("Failed to write audit log:", err);
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$a.error("Failed to read audit log:", err);
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$9 = createLogger("VaultAudit");
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$9.error("Failed to write audit log:", err);
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$9.error("Failed to read audit log:", err);
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$8 = createLogger("MCP");
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$8.warn("Failed to write auth file:", err);
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$8.warn("Failed to clear auth file:", err);
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$8.warn("Failed to compute post-action state warning:", err);
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$8.warn("Failed to gather wait_for timeout diagnostic:", err);
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$8.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
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$8.warn("Failed to restore highlight after removal:", err)
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$8.warn("Failed to extract page while generating suggestions:", err);
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$8.warn("Failed to parse active tab URL for vault_status:", err);
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$8.warn("Failed to parse active tab URL for vault_login:", err);
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$8.warn("Failed to parse active tab URL for vault_totp:", err);
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$8.error("Error handling request:", error);
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$8.error("Server error:", error);
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$8.info(`Server listening on ${endpoint} (auth enabled)`);
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$8.info("Server stopped");
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$7 = createLogger("KitRegistry");
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$7.warn(`Skipping invalid kit file: ${file}`);
23757
+ logger$8.warn(`Skipping invalid kit file: ${file}`);
23128
23758
  }
23129
23759
  } catch (err) {
23130
- logger$7.warn(`Failed to read kit file: ${file}`, err);
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$6 = createLogger("Scheduler");
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$6.warn("Failed to save jobs:", err);
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$6.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
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$6.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
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$6.warn("Unexpected error firing job:", err);
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$5 = createLogger("PrivateWindow");
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$5.warn("Failed to clear private browsing session:", error);
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$5.info("Private browsing window opened");
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
  });