@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 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;
@@ -2746,7 +2782,7 @@ function destroySession(tabId) {
2746
2782
  sessions.delete(tabId);
2747
2783
  }
2748
2784
  }
2749
- const logger$h = createLogger("TabManager");
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$h.warn("Failed to batch highlight:", err)
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$h.warn("Failed to capture highlight:", err)
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$h.warn("Failed to capture highlight from page:", err);
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$h.warn("Failed to remove highlight from matching tab:", err);
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$h.warn("Failed to update highlight color:", err)
3267
+ (err) => logger$k.warn("Failed to update highlight color:", err)
3232
3268
  );
3233
3269
  });
3234
3270
  }
3235
3271
  } catch (err) {
3236
- logger$h.warn("Failed to iterate highlights for color change:", err);
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$h.warn("Failed to remove highlight marks:", err)
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$g = createLogger("Premium");
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$g.warn(
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$g.warn("Verification failed:", err);
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$f = createLogger("Extractor");
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$f.warn("Failed to execute page script:", err);
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$f.warn("Failed to estimate extraction timeout, using base timeout:", err);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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$e = createLogger("OpenAIProvider");
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$e.info(`[agent-debug] ${JSON.stringify(payload)}`);
7928
+ logger$h.info(`[agent-debug] ${JSON.stringify(payload)}`);
7877
7929
  } catch (err) {
7878
- logger$e.warn("Failed to serialize debug payload:", err);
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, "&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
+ }
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$d = createLogger("DevTrace");
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$d.warn("Failed to load local trace logger:", err);
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$c = createLogger("Screenshot");
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$c.debug(
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$b = createLogger("PageActions");
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$b.warn(`Failed to execute page script${label}:`, err);
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$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);
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$b.warn("Failed to build post-click navigation summary:", err);
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$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);
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$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);
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$b.warn("Failed to restore locale via page reload:", err);
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$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);
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$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);
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$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);
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$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);
17088
17714
  content = null;
17089
17715
  }
17090
17716
  }
17091
17717
  } catch (err) {
17092
- 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);
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$b.warn("Failed to extract content for suggest:", err);
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$a = createLogger("VaultShared");
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$a.error("Failed to load vault:", err);
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$a.error("Failed to write audit log:", err);
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$a.error("Failed to read audit log:", err);
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$9 = createLogger("VaultAudit");
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$9.error("Failed to write audit log:", err);
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$9.error("Failed to read audit log:", err);
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$8 = createLogger("MCP");
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$8.warn("Failed to write auth file:", err);
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$8.warn("Failed to clear auth file:", err);
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$8.warn("Failed to compute post-action state warning:", err);
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$8.warn("Failed to gather wait_for timeout diagnostic:", err);
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$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);
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$8.warn("Failed to restore highlight after removal:", err)
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$8.warn("Failed to extract page while generating suggestions:", err);
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$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);
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$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);
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$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);
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$8.error("Error handling request:", error);
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$8.error("Server error:", error);
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$8.info(`Server listening on ${endpoint} (auth enabled)`);
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$8.info("Server stopped");
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$7 = createLogger("KitRegistry");
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$7.warn(`Skipping invalid kit file: ${file}`);
23757
+ logger$8.warn(`Skipping invalid kit file: ${file}`);
23132
23758
  }
23133
23759
  } catch (err) {
23134
- logger$7.warn(`Failed to read kit file: ${file}`, err);
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$6 = createLogger("Scheduler");
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$6.warn("Failed to save jobs:", err);
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$6.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
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$6.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
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$6.warn("Unexpected error firing job:", err);
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$5 = createLogger("PrivateWindow");
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$5.warn("Failed to clear private browsing session:", error);
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$5.info("Private browsing window opened");
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
  });