@quanta-intellect/vessel-browser 0.1.114 → 0.1.116

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
@@ -81,7 +81,7 @@ const defaults = {
81
81
  const SAVE_DEBOUNCE_MS$6 = 150;
82
82
  const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
83
83
  const CODEX_TOKENS_FILENAME = "vessel-codex-tokens";
84
- const logger$p = createLogger("Settings");
84
+ const logger$r = createLogger("Settings");
85
85
  const SETTABLE_KEYS = new Set(Object.keys(defaults));
86
86
  let settings = null;
87
87
  let settingsIssues = [];
@@ -289,7 +289,7 @@ function persistNow() {
289
289
  JSON.stringify(buildPersistedSettings(settings), null, 2),
290
290
  { encoding: "utf-8", mode: 384 }
291
291
  )
292
- ).then(() => fs.promises.chmod(getSettingsPath(), 384).catch(() => void 0)).catch((err) => logger$p.error("Failed to save settings:", err));
292
+ ).then(() => fs.promises.chmod(getSettingsPath(), 384).catch(() => void 0)).catch((err) => logger$r.error("Failed to save settings:", err));
293
293
  }
294
294
  function saveSettings() {
295
295
  saveDirty = true;
@@ -420,7 +420,7 @@ function loadTrustedAppURL(wc, url) {
420
420
  }
421
421
  const MAX_CUSTOM_HISTORY = 50;
422
422
  const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
423
- const logger$o = createLogger("Tab");
423
+ const logger$q = createLogger("Tab");
424
424
  const sessionCertExceptions = /* @__PURE__ */ new WeakMap();
425
425
  const sessionsWithVerifyProc = /* @__PURE__ */ new WeakSet();
426
426
  const CERT_VERIFY_TRUST = 0;
@@ -486,7 +486,7 @@ class Tab {
486
486
  guardedLoadURL(url, options) {
487
487
  const blockReason = this.getNavigationBlockReason(url);
488
488
  if (blockReason) {
489
- logger$o.warn(blockReason);
489
+ logger$q.warn(blockReason);
490
490
  return blockReason;
491
491
  }
492
492
  void this.view.webContents.loadURL(url, options);
@@ -570,7 +570,7 @@ class Tab {
570
570
  wc.setWindowOpenHandler(({ url, disposition }) => {
571
571
  const error = this.getNavigationBlockReason(url);
572
572
  if (error) {
573
- logger$o.warn(error);
573
+ logger$q.warn(error);
574
574
  return { action: "deny" };
575
575
  }
576
576
  this.onOpenUrl?.({
@@ -584,7 +584,7 @@ class Tab {
584
584
  const error = this.getNavigationBlockReason(url);
585
585
  if (!error) return;
586
586
  event.preventDefault();
587
- logger$o.warn(`${context}: ${error}`);
587
+ logger$q.warn(`${context}: ${error}`);
588
588
  };
589
589
  wc.on("will-navigate", (event, url) => {
590
590
  blockNavigation(event, url, "Blocked top-level navigation");
@@ -668,7 +668,7 @@ class Tab {
668
668
  ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
669
669
  ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
670
670
  ::-webkit-scrollbar-corner { background: transparent; }
671
- `).catch((err) => logger$o.warn("Failed to inject scrollbar CSS:", err));
671
+ `).catch((err) => logger$q.warn("Failed to inject scrollbar CSS:", err));
672
672
  });
673
673
  wc.on("page-favicon-updated", (_, favicons) => {
674
674
  this._state.favicon = favicons[0] || "";
@@ -704,7 +704,7 @@ class Tab {
704
704
  ).then((highlightedText) => {
705
705
  this.buildContextMenu(wc, params, highlightedText.trim());
706
706
  }).catch((err) => {
707
- logger$o.warn("Failed to inspect highlighted text for context menu:", err);
707
+ logger$q.warn("Failed to inspect highlighted text for context menu:", err);
708
708
  this.buildContextMenu(wc, params, "");
709
709
  });
710
710
  });
@@ -905,7 +905,7 @@ class Tab {
905
905
  "document.documentElement.outerHTML"
906
906
  );
907
907
  } catch (err) {
908
- logger$o.warn("Failed to retrieve page source:", err);
908
+ logger$q.warn("Failed to retrieve page source:", err);
909
909
  return;
910
910
  }
911
911
  const escaped = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1033,7 +1033,7 @@ class Tab {
1033
1033
  document.addEventListener('mouseup', window.__vesselHighlightHandler);
1034
1034
  }
1035
1035
  })()
1036
- `).catch((err) => logger$o.warn("Failed to inject highlight listener:", err));
1036
+ `).catch((err) => logger$q.warn("Failed to inject highlight listener:", err));
1037
1037
  } else {
1038
1038
  void wc.executeJavaScript(`
1039
1039
  (function() {
@@ -1044,7 +1044,7 @@ class Tab {
1044
1044
  delete window.__vesselHighlightHandler;
1045
1045
  }
1046
1046
  })()
1047
- `).catch((err) => logger$o.warn("Failed to remove highlight listener:", err));
1047
+ `).catch((err) => logger$q.warn("Failed to remove highlight listener:", err));
1048
1048
  }
1049
1049
  }
1050
1050
  get webContentsId() {
@@ -1081,7 +1081,7 @@ const SEARCH_ENGINE_PRESETS = {
1081
1081
  ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
1082
1082
  kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
1083
1083
  };
1084
- const logger$n = createLogger("JsonPersistence");
1084
+ const logger$p = createLogger("JsonPersistence");
1085
1085
  function canUseSafeStorage() {
1086
1086
  try {
1087
1087
  return electron.safeStorage.isEncryptionAvailable();
@@ -1146,7 +1146,7 @@ function createDebouncedJsonPersistence({
1146
1146
  data,
1147
1147
  typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
1148
1148
  )
1149
- ).then(() => fs.promises.chmod(filePath2, 384).catch(() => void 0)).catch((err) => logger$n.error(`Failed to save ${logLabel}:`, err));
1149
+ ).then(() => fs.promises.chmod(filePath2, 384).catch(() => void 0)).catch((err) => logger$p.error(`Failed to save ${logLabel}:`, err));
1150
1150
  };
1151
1151
  const schedule = () => {
1152
1152
  saveDirty2 = true;
@@ -2838,7 +2838,7 @@ function destroySession(tabId) {
2838
2838
  sessions.delete(tabId);
2839
2839
  }
2840
2840
  }
2841
- const logger$m = createLogger("TabManager");
2841
+ const logger$o = createLogger("TabManager");
2842
2842
  function sanitizePdfFilename(title) {
2843
2843
  const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
2844
2844
  const base = (clean || "Vessel Page").replace(/\.pdf$/i, "");
@@ -3238,7 +3238,7 @@ class TabManager {
3238
3238
  }));
3239
3239
  if (entries.length > 0) {
3240
3240
  void highlightBatchOnPage(wc, entries).catch(
3241
- (err) => logger$m.warn("Failed to batch highlight:", err)
3241
+ (err) => logger$o.warn("Failed to batch highlight:", err)
3242
3242
  );
3243
3243
  }
3244
3244
  }
@@ -3260,12 +3260,12 @@ class TabManager {
3260
3260
  const result = await captureSelectionHighlight(wc);
3261
3261
  if (result.success && result.text) {
3262
3262
  await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
3263
- (err) => logger$m.warn("Failed to capture highlight:", err)
3263
+ (err) => logger$o.warn("Failed to capture highlight:", err)
3264
3264
  );
3265
3265
  }
3266
3266
  this.highlightCaptureCallback?.(result);
3267
3267
  } catch (err) {
3268
- logger$m.warn("Failed to capture highlight from page:", err);
3268
+ logger$o.warn("Failed to capture highlight from page:", err);
3269
3269
  this.highlightCaptureCallback?.({
3270
3270
  success: false,
3271
3271
  message: "Could not capture selection"
@@ -3290,7 +3290,7 @@ class TabManager {
3290
3290
  void this.removeHighlightMarksForText(wc, text);
3291
3291
  }
3292
3292
  } catch (err) {
3293
- logger$m.warn("Failed to remove highlight from matching tab:", err);
3293
+ logger$o.warn("Failed to remove highlight from matching tab:", err);
3294
3294
  }
3295
3295
  }
3296
3296
  this.highlightCaptureCallback?.({
@@ -3321,12 +3321,12 @@ class TabManager {
3321
3321
  void 0,
3322
3322
  color
3323
3323
  ).catch(
3324
- (err) => logger$m.warn("Failed to update highlight color:", err)
3324
+ (err) => logger$o.warn("Failed to update highlight color:", err)
3325
3325
  );
3326
3326
  });
3327
3327
  }
3328
3328
  } catch (err) {
3329
- logger$m.warn("Failed to iterate highlights for color change:", err);
3329
+ logger$o.warn("Failed to iterate highlights for color change:", err);
3330
3330
  }
3331
3331
  }
3332
3332
  this.highlightCaptureCallback?.({
@@ -3381,7 +3381,7 @@ class TabManager {
3381
3381
  });
3382
3382
  })()`
3383
3383
  ).catch(
3384
- (err) => logger$m.warn("Failed to remove highlight marks:", err)
3384
+ (err) => logger$o.warn("Failed to remove highlight marks:", err)
3385
3385
  );
3386
3386
  }
3387
3387
  broadcastState(meta = { persistSession: false }) {
@@ -3614,6 +3614,10 @@ const Channels = {
3614
3614
  CODEX_CANCEL_AUTH: "codex:cancel-auth",
3615
3615
  CODEX_AUTH_STATUS: "codex:auth-status",
3616
3616
  CODEX_DISCONNECT: "codex:disconnect",
3617
+ // OpenRouter OAuth
3618
+ OPENROUTER_START_AUTH: "openrouter:start-auth",
3619
+ OPENROUTER_CANCEL_AUTH: "openrouter:cancel-auth",
3620
+ OPENROUTER_AUTH_STATUS: "openrouter:auth-status",
3617
3621
  // Updates
3618
3622
  UPDATES_CHECK: "updates:check",
3619
3623
  UPDATES_OPEN_DOWNLOAD: "updates:open-download",
@@ -4578,7 +4582,7 @@ function errorResult(error, value) {
4578
4582
  function getErrorMessage(error, fallback = "Unknown error") {
4579
4583
  return error instanceof Error && error.message ? error.message : fallback;
4580
4584
  }
4581
- const logger$l = createLogger("Premium");
4585
+ const logger$n = createLogger("Premium");
4582
4586
  const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
4583
4587
  const FREE_TOOL_ITERATION_LIMIT = 50;
4584
4588
  const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -4745,7 +4749,7 @@ async function verifySubscription$1(identifier) {
4745
4749
  });
4746
4750
  if (!res.ok) {
4747
4751
  const detail = await readApiErrorDetail(res);
4748
- logger$l.warn(
4752
+ logger$n.warn(
4749
4753
  "Verification API returned a non-OK status:",
4750
4754
  res.status,
4751
4755
  detail
@@ -4764,7 +4768,7 @@ async function verifySubscription$1(identifier) {
4764
4768
  setSetting("premium", updated);
4765
4769
  return updated;
4766
4770
  } catch (err) {
4767
- logger$l.warn("Verification failed:", err);
4771
+ logger$n.warn("Verification failed:", err);
4768
4772
  return current;
4769
4773
  }
4770
4774
  }
@@ -5376,7 +5380,7 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
5376
5380
  const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
5377
5381
  const MUTATION_SETTLE_AFTER_MS = 1500;
5378
5382
  const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
5379
- const logger$k = createLogger("Extractor");
5383
+ const logger$m = createLogger("Extractor");
5380
5384
  const EXTRACTION_CACHE_TTL_MS = 1500;
5381
5385
  const MAX_EXTRACTION_CACHE_ENTRIES = 50;
5382
5386
  const extractionCache = /* @__PURE__ */ new Map();
@@ -6143,9 +6147,9 @@ async function executeScript(webContents, script, options = {}) {
6143
6147
  const message = err instanceof Error ? err.message : String(err);
6144
6148
  const detail = `Failed to execute page script${label} on ${url}: ${message}`;
6145
6149
  if (options.warnOnFailure) {
6146
- logger$k.warn(detail);
6150
+ logger$m.warn(detail);
6147
6151
  } else {
6148
- logger$k.debug(detail);
6152
+ logger$m.debug(detail);
6149
6153
  }
6150
6154
  return null;
6151
6155
  } finally {
@@ -6254,7 +6258,7 @@ async function estimateExtractionTimeout(webContents) {
6254
6258
  return EXTRACT_TIMEOUT_BASE_MS + extra;
6255
6259
  }
6256
6260
  } catch (err) {
6257
- logger$k.warn("Failed to estimate extraction timeout, using base timeout:", err);
6261
+ logger$m.warn("Failed to estimate extraction timeout, using base timeout:", err);
6258
6262
  }
6259
6263
  return EXTRACT_TIMEOUT_BASE_MS;
6260
6264
  }
@@ -7481,8 +7485,9 @@ const PROVIDERS = {
7481
7485
  openrouter: {
7482
7486
  id: "openrouter",
7483
7487
  name: "OpenRouter",
7484
- defaultModel: "anthropic/claude-sonnet-4",
7488
+ defaultModel: "openrouter/free",
7485
7489
  models: [
7490
+ "openrouter/free",
7486
7491
  "anthropic/claude-sonnet-4",
7487
7492
  "anthropic/claude-haiku-4",
7488
7493
  "openai/gpt-4o",
@@ -8028,7 +8033,7 @@ function recoverNarratedActionToolCalls(text, availableToolNames) {
8028
8033
  }
8029
8034
  return recovered;
8030
8035
  }
8031
- const logger$j = createLogger("OpenAIProvider");
8036
+ const logger$l = createLogger("OpenAIProvider");
8032
8037
  function shouldDebugAgentLoop() {
8033
8038
  const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
8034
8039
  return value === "1" || value === "true";
@@ -8296,9 +8301,9 @@ function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage)
8296
8301
  function logAgentLoopDebug(payload) {
8297
8302
  if (!shouldDebugAgentLoop()) return;
8298
8303
  try {
8299
- logger$j.info(`[agent-debug] ${JSON.stringify(payload)}`);
8304
+ logger$l.info(`[agent-debug] ${JSON.stringify(payload)}`);
8300
8305
  } catch (err) {
8301
- logger$j.warn("Failed to serialize debug payload:", err);
8306
+ logger$l.warn("Failed to serialize debug payload:", err);
8302
8307
  }
8303
8308
  }
8304
8309
  function formatOpenAICompatErrorMessage(providerId, message) {
@@ -8732,28 +8737,184 @@ async function openExternalAllowlisted(url, rule) {
8732
8737
  }
8733
8738
  await electron.shell.openExternal(parsed.toString());
8734
8739
  }
8735
- const logger$i = createLogger("CodexOAuth");
8736
- const ISSUER = "https://auth.openai.com";
8737
- const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
8738
- const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
8739
- const AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
8740
- const PREFERRED_PORT = 1455;
8741
- const FALLBACK_PORT = 1457;
8742
- let activeFlow = null;
8743
8740
  function base64url(buffer) {
8744
8741
  return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
8745
8742
  }
8746
8743
  function generatePkce() {
8747
8744
  const codeVerifier = base64url(crypto$1.randomBytes(64));
8748
8745
  const hash = crypto$1.createHash("sha256").update(codeVerifier).digest();
8749
- const codeChallenge = base64url(hash);
8750
- return { codeVerifier, codeChallenge };
8746
+ return {
8747
+ codeVerifier,
8748
+ codeChallenge: base64url(hash)
8749
+ };
8751
8750
  }
8752
8751
  function generateState() {
8753
8752
  return base64url(crypto$1.randomBytes(32));
8754
8753
  }
8755
- function buildAuthorizeUrl(port, pkce, state2) {
8756
- const redirectUri = `http://localhost:${port}/auth/callback`;
8754
+ function buildCallbackUrl(port, path2) {
8755
+ return `http://localhost:${port}${path2}`;
8756
+ }
8757
+ async function bindServer(server, preferredPorts) {
8758
+ for (const port of preferredPorts) {
8759
+ try {
8760
+ await new Promise((resolve, reject) => {
8761
+ const onError = (err) => {
8762
+ server.off("listening", onListening);
8763
+ reject(err);
8764
+ };
8765
+ const onListening = () => {
8766
+ server.off("error", onError);
8767
+ resolve();
8768
+ };
8769
+ server.once("error", onError);
8770
+ server.once("listening", onListening);
8771
+ server.listen(port, "127.0.0.1");
8772
+ });
8773
+ return port;
8774
+ } catch (err) {
8775
+ if (err.code === "EADDRINUSE") {
8776
+ continue;
8777
+ }
8778
+ throw err;
8779
+ }
8780
+ }
8781
+ throw new Error(
8782
+ `Could not bind ${preferredPorts.join(", ")} callback ports`
8783
+ );
8784
+ }
8785
+ function createLocalPkceOAuthFlow(config) {
8786
+ let activeFlow = null;
8787
+ const cancel = () => {
8788
+ if (!activeFlow) return;
8789
+ activeFlow.server.close();
8790
+ clearTimeout(activeFlow.timeout);
8791
+ try {
8792
+ activeFlow.onStatus("idle");
8793
+ } catch {
8794
+ config.logger.warn(`${config.name} OAuth cancel status callback failed`);
8795
+ }
8796
+ activeFlow = null;
8797
+ };
8798
+ const start = (onStatus) => {
8799
+ if (activeFlow) {
8800
+ throw new Error(`${config.name} auth flow already in progress`);
8801
+ }
8802
+ const pkce = generatePkce();
8803
+ const state2 = generateState();
8804
+ const callbackPath = config.callbackPath(state2);
8805
+ return new Promise((resolve, reject) => {
8806
+ let settled = false;
8807
+ let boundPort = 0;
8808
+ const safeOnStatus = (status, error) => {
8809
+ try {
8810
+ onStatus(status, error);
8811
+ } catch {
8812
+ config.logger.warn(`${config.name} OAuth status callback failed`);
8813
+ }
8814
+ };
8815
+ const cleanup = () => {
8816
+ clearTimeout(activeFlow?.timeout);
8817
+ activeFlow?.server.close();
8818
+ activeFlow = null;
8819
+ };
8820
+ const wrappedResolve = (result) => {
8821
+ if (settled) return;
8822
+ settled = true;
8823
+ cleanup();
8824
+ safeOnStatus("connected");
8825
+ resolve(result);
8826
+ };
8827
+ const wrappedReject = (err) => {
8828
+ if (settled) return;
8829
+ settled = true;
8830
+ cleanup();
8831
+ safeOnStatus("error", err.message);
8832
+ reject(err);
8833
+ };
8834
+ const server = http.createServer(async (req, res) => {
8835
+ const url = new URL(req.url || "/", `http://localhost:${boundPort}`);
8836
+ if (url.pathname === callbackPath) {
8837
+ const authError = config.authErrorMessage?.(url) || url.searchParams.get("error");
8838
+ if (authError) {
8839
+ res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
8840
+ res.end(`Authorization failed: ${authError}`);
8841
+ wrappedReject(new Error(authError));
8842
+ return;
8843
+ }
8844
+ if (config.readState(url) !== state2) {
8845
+ res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
8846
+ res.end("State mismatch. Please try again.");
8847
+ wrappedReject(new Error("State mismatch"));
8848
+ return;
8849
+ }
8850
+ const code = url.searchParams.get("code");
8851
+ if (!code) {
8852
+ res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
8853
+ res.end("Missing authorization code.");
8854
+ wrappedReject(new Error("Missing authorization code"));
8855
+ return;
8856
+ }
8857
+ try {
8858
+ safeOnStatus("exchanging");
8859
+ const result = await config.exchangeCode({
8860
+ code,
8861
+ codeVerifier: pkce.codeVerifier,
8862
+ callbackUrl: buildCallbackUrl(boundPort, callbackPath),
8863
+ port: boundPort
8864
+ });
8865
+ res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
8866
+ res.end(config.successHtml(result));
8867
+ wrappedResolve(result);
8868
+ } catch (err) {
8869
+ const message = err instanceof Error ? err.message : "Unknown error";
8870
+ res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
8871
+ res.end(`${config.name} setup failed: ${message}`);
8872
+ wrappedReject(err instanceof Error ? err : new Error(`${config.name} setup failed`));
8873
+ }
8874
+ return;
8875
+ }
8876
+ res.writeHead(404, { Connection: "close" });
8877
+ res.end("Not found");
8878
+ });
8879
+ const timeout = setTimeout(() => {
8880
+ wrappedReject(new Error(`${config.name} setup timed out after 5 minutes`));
8881
+ }, config.timeoutMs);
8882
+ activeFlow = {
8883
+ server,
8884
+ timeout,
8885
+ onStatus
8886
+ };
8887
+ bindServer(server, config.preferredPorts).then((port) => {
8888
+ if (settled || !activeFlow) return;
8889
+ boundPort = port;
8890
+ const callbackUrl = buildCallbackUrl(port, callbackPath);
8891
+ const authUrl = config.buildAuthorizeUrl({
8892
+ port,
8893
+ pkce,
8894
+ state: state2,
8895
+ callbackUrl
8896
+ });
8897
+ safeOnStatus("waiting");
8898
+ openExternalAllowlisted(authUrl, { hosts: [...config.openHosts] }).catch((err) => {
8899
+ config.logger.warn(`Failed to open ${config.name} auth URL:`, err);
8900
+ });
8901
+ }).catch(wrappedReject);
8902
+ });
8903
+ };
8904
+ return {
8905
+ start,
8906
+ cancel,
8907
+ isInProgress: () => activeFlow !== null
8908
+ };
8909
+ }
8910
+ const logger$k = createLogger("CodexOAuth");
8911
+ const ISSUER = "https://auth.openai.com";
8912
+ const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
8913
+ const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
8914
+ const AUTH_TIMEOUT_MS$1 = 5 * 60 * 1e3;
8915
+ const PREFERRED_PORT$1 = 1455;
8916
+ const FALLBACK_PORT$1 = 1457;
8917
+ function buildAuthorizeUrl(redirectUri, pkce, state2) {
8757
8918
  const params = new URLSearchParams({
8758
8919
  response_type: "code",
8759
8920
  client_id: CLIENT_ID,
@@ -8872,172 +9033,36 @@ async function refreshAccessToken(tokens) {
8872
9033
  };
8873
9034
  return refreshedTokens;
8874
9035
  }
8875
- function startServer(port, pkce, expectedState, resolve, reject) {
8876
- const server = http.createServer(async (req, res) => {
8877
- const url = new URL(req.url || "/", `http://localhost:${port}`);
8878
- if (url.pathname === "/auth/callback") {
8879
- const state2 = url.searchParams.get("state");
8880
- const code = url.searchParams.get("code");
8881
- const error = url.searchParams.get("error");
8882
- const errorDescription = url.searchParams.get("error_description");
8883
- if (error) {
8884
- res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
8885
- const msg = errorDescription || error;
8886
- res.end(`Authorization failed: ${msg}`);
8887
- reject(new Error(msg));
8888
- return;
8889
- }
8890
- if (state2 !== expectedState) {
8891
- res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
8892
- res.end("State mismatch. Please try again.");
8893
- reject(new Error("State mismatch"));
8894
- return;
8895
- }
8896
- if (!code) {
8897
- res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
8898
- res.end("Missing authorization code.");
8899
- reject(new Error("Missing authorization code"));
8900
- return;
8901
- }
8902
- try {
8903
- activeFlow?.onStatus("exchanging");
8904
- const redirectUri = `http://localhost:${activeFlow?.port ?? port}/auth/callback`;
8905
- const tokens = await exchangeCodeForTokens(code, redirectUri, pkce.codeVerifier);
8906
- res.writeHead(302, {
8907
- Location: `/success?email=${encodeURIComponent(tokens.accountEmail || tokens.accountId)}`,
8908
- Connection: "close"
8909
- });
8910
- res.end();
8911
- resolve(tokens);
8912
- } catch (err) {
8913
- res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
8914
- res.end(`Token exchange failed: ${err instanceof Error ? err.message : "Unknown error"}`);
8915
- reject(err instanceof Error ? err : new Error("Token exchange failed"));
8916
- }
8917
- return;
8918
- }
8919
- if (url.pathname === "/success") {
8920
- const email = url.searchParams.get("email") || "";
8921
- res.writeHead(200, { "Content-Type": "text/html", "Connection": "close" });
8922
- res.end(`<!DOCTYPE html>
8923
- <html><head><meta charset="utf-8"><title>Vessel — Signed In</title>
8924
- <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>
8925
- <body><div style="text-align:center"><h1>✓ Signed In</h1>
8926
- <p>Connected as ${escapeHtml(email)}</p><p>You can close this tab.</p></div></body></html>`);
8927
- return;
8928
- }
8929
- if (url.pathname === "/cancel") {
8930
- res.writeHead(200, { "Content-Type": "text/plain", "Connection": "close" });
8931
- res.end("Login cancelled");
8932
- reject(new Error("Login cancelled by user"));
8933
- return;
8934
- }
8935
- res.writeHead(404, { "Connection": "close" });
8936
- res.end("Not found");
8937
- });
8938
- return server;
8939
- }
8940
9036
  function escapeHtml(text) {
8941
9037
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8942
9038
  }
8943
- async function bindServer(server) {
8944
- const allowedPorts = [PREFERRED_PORT, FALLBACK_PORT];
8945
- for (const port of allowedPorts) {
8946
- try {
8947
- await new Promise((resolve, reject) => {
8948
- const onError = (err) => {
8949
- server.off("listening", onListening);
8950
- reject(err);
8951
- };
8952
- const onListening = () => {
8953
- server.off("error", onError);
8954
- resolve();
8955
- };
8956
- server.once("error", onError);
8957
- server.once("listening", onListening);
8958
- server.listen(port, "127.0.0.1");
8959
- });
8960
- return port;
8961
- } catch (err) {
8962
- if (err.code === "EADDRINUSE") {
8963
- continue;
8964
- }
8965
- throw err;
8966
- }
8967
- }
8968
- throw new Error(
8969
- `Could not bind Codex OAuth callback server to registered ports ${allowedPorts.join(", ")}`
8970
- );
8971
- }
9039
+ const codexOAuth = createLocalPkceOAuthFlow({
9040
+ name: "Codex",
9041
+ logger: logger$k,
9042
+ preferredPorts: [PREFERRED_PORT$1, FALLBACK_PORT$1],
9043
+ timeoutMs: AUTH_TIMEOUT_MS$1,
9044
+ callbackPath: () => "/auth/callback",
9045
+ readState: (url) => url.searchParams.get("state"),
9046
+ authErrorMessage: (url) => url.searchParams.get("error_description") || url.searchParams.get("error"),
9047
+ buildAuthorizeUrl: ({ callbackUrl, pkce, state: state2 }) => buildAuthorizeUrl(callbackUrl, pkce, state2),
9048
+ exchangeCode: ({ code, callbackUrl, codeVerifier }) => exchangeCodeForTokens(code, callbackUrl, codeVerifier),
9049
+ successHtml: (tokens) => {
9050
+ const label = escapeHtml(tokens.accountEmail || tokens.accountId);
9051
+ return `<!DOCTYPE html>
9052
+ <html><head><meta charset="utf-8"><title>Vessel — Signed In</title>
9053
+ <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>
9054
+ <body><div style="text-align:center"><h1>Signed In</h1>
9055
+ <p>Connected as ${label}</p><p>You can close this tab.</p></div></body></html>`;
9056
+ },
9057
+ openHosts: ["auth.openai.com"]
9058
+ });
8972
9059
  async function startCodexOAuth(onStatus) {
8973
- if (activeFlow) {
8974
- throw new Error("Auth flow already in progress");
8975
- }
8976
- const pkce = generatePkce();
8977
- const state2 = generateState();
8978
- return new Promise((resolve, reject) => {
8979
- let settled = false;
8980
- const safeOnStatus = (status, error) => {
8981
- try {
8982
- onStatus(status, error);
8983
- } catch {
8984
- logger$i.warn("Codex OAuth status callback failed — window may be closed");
8985
- }
8986
- };
8987
- const wrappedResolve = (tokens) => {
8988
- if (settled) return;
8989
- settled = true;
8990
- cleanup();
8991
- safeOnStatus("connected");
8992
- resolve(tokens);
8993
- };
8994
- const wrappedReject = (err) => {
8995
- if (settled) return;
8996
- settled = true;
8997
- cleanup();
8998
- safeOnStatus("error", err.message);
8999
- reject(err);
9000
- };
9001
- const server = startServer(0, pkce, state2, wrappedResolve, wrappedReject);
9002
- const timeout = setTimeout(() => {
9003
- wrappedReject(new Error("Auth flow timed out after 5 minutes"));
9004
- }, AUTH_TIMEOUT_MS);
9005
- activeFlow = {
9006
- state: state2,
9007
- codeVerifier: pkce.codeVerifier,
9008
- port: 0,
9009
- server,
9010
- timeout,
9011
- onStatus
9012
- };
9013
- const cleanup = () => {
9014
- if (activeFlow?.timeout) clearTimeout(activeFlow.timeout);
9015
- activeFlow?.server.close();
9016
- activeFlow = null;
9017
- };
9018
- bindServer(server).then((port) => {
9019
- if (settled) return;
9020
- activeFlow.port = port;
9021
- const authUrl = buildAuthorizeUrl(port, pkce, state2);
9022
- safeOnStatus("waiting");
9023
- openExternalAllowlisted(authUrl, { hosts: ["auth.openai.com"] }).catch((err) => {
9024
- logger$i.warn("Failed to open browser, user will need the URL:", err);
9025
- });
9026
- }).catch(wrappedReject);
9027
- });
9060
+ return codexOAuth.start(onStatus);
9028
9061
  }
9029
9062
  function cancelCodexOAuth() {
9030
- if (!activeFlow) return;
9031
- activeFlow.server.close();
9032
- if (activeFlow.timeout) clearTimeout(activeFlow.timeout);
9033
- try {
9034
- activeFlow.onStatus("idle");
9035
- } catch {
9036
- logger$i.warn("Codex OAuth cancel status callback failed — window may be closed");
9037
- }
9038
- activeFlow = null;
9063
+ codexOAuth.cancel();
9039
9064
  }
9040
- const logger$h = createLogger("CodexProvider");
9065
+ const logger$j = createLogger("CodexProvider");
9041
9066
  const REFRESH_WINDOW_MS = 5 * 60 * 1e3;
9042
9067
  const CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api/codex";
9043
9068
  const CODEX_CLIENT_VERSION = "0.129.0";
@@ -9102,7 +9127,7 @@ class CodexProvider {
9102
9127
  async ensureFreshTokens() {
9103
9128
  if (Date.now() < this.tokens.expiresAt - REFRESH_WINDOW_MS) return;
9104
9129
  try {
9105
- logger$h.info("Refreshing Codex access token");
9130
+ logger$j.info("Refreshing Codex access token");
9106
9131
  const fresh = await refreshAccessToken(this.tokens);
9107
9132
  this.tokens = fresh;
9108
9133
  writeStoredCodexTokens(fresh);
@@ -9250,7 +9275,7 @@ class CodexProvider {
9250
9275
  } catch (err) {
9251
9276
  if (err.name !== "AbortError") {
9252
9277
  const msg = err instanceof Error ? err.message : String(err);
9253
- logger$h.error("Codex streamQuery error:", err);
9278
+ logger$j.error("Codex streamQuery error:", err);
9254
9279
  onChunk(`
9255
9280
 
9256
9281
  [Error: ${msg}]`);
@@ -9318,7 +9343,7 @@ class CodexProvider {
9318
9343
  } catch (err) {
9319
9344
  if (err.name !== "AbortError") {
9320
9345
  const msg = err instanceof Error ? err.message : String(err);
9321
- logger$h.error("Codex streamAgentQuery error:", err);
9346
+ logger$j.error("Codex streamAgentQuery error:", err);
9322
9347
  onChunk(`
9323
9348
 
9324
9349
  [Error: ${msg}]`);
@@ -9510,7 +9535,7 @@ function createProvider(config) {
9510
9535
  return new OpenAICompatProvider(normalized);
9511
9536
  }
9512
9537
  const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
9513
- const logger$g = createLogger("DevTrace");
9538
+ const logger$i = createLogger("DevTrace");
9514
9539
  let cachedFactory;
9515
9540
  function createNoopTraceSession() {
9516
9541
  return {
@@ -9543,7 +9568,7 @@ function loadLocalFactory() {
9543
9568
  return cachedFactory;
9544
9569
  }
9545
9570
  } catch (err) {
9546
- logger$g.warn("Failed to load local trace logger:", err);
9571
+ logger$i.warn("Failed to load local trace logger:", err);
9547
9572
  }
9548
9573
  }
9549
9574
  return cachedFactory;
@@ -13671,7 +13696,7 @@ function formatDeadLinkMessage(label, result) {
13671
13696
  const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
13672
13697
  return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
13673
13698
  }
13674
- const logger$f = createLogger("Screenshot");
13699
+ const logger$h = createLogger("Screenshot");
13675
13700
  const SCREENSHOT_RETRY_COUNT = 3;
13676
13701
  const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
13677
13702
  async function captureScreenshot(wc) {
@@ -13693,7 +13718,7 @@ async function captureScreenshot(wc) {
13693
13718
  }
13694
13719
  }
13695
13720
  } catch (err) {
13696
- logger$f.debug(
13721
+ logger$h.debug(
13697
13722
  `capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
13698
13723
  getErrorMessage(err)
13699
13724
  );
@@ -14669,7 +14694,7 @@ class TabMutex {
14669
14694
  return run;
14670
14695
  }
14671
14696
  }
14672
- const logger$e = createLogger("PageActions");
14697
+ const logger$g = createLogger("PageActions");
14673
14698
  function getBookmarkMetadataFromArgs(args) {
14674
14699
  return normalizeBookmarkMetadata({
14675
14700
  intent: args.intent ?? args.intent,
@@ -14855,7 +14880,7 @@ async function executePageScript(wc, script, options) {
14855
14880
  return result;
14856
14881
  } catch (err) {
14857
14882
  const label = options?.label ? ` (${options.label})` : "";
14858
- logger$e.warn(`Failed to execute page script${label}:`, err);
14883
+ logger$g.warn(`Failed to execute page script${label}:`, err);
14859
14884
  return null;
14860
14885
  } finally {
14861
14886
  if (timer) {
@@ -14956,7 +14981,7 @@ Search results snapshot:
14956
14981
  ${truncated}`;
14957
14982
  }
14958
14983
  } catch (err) {
14959
- logger$e.warn("Failed to build post-search summary, falling back to nav summary:", err);
14984
+ logger$g.warn("Failed to build post-search summary, falling back to nav summary:", err);
14960
14985
  }
14961
14986
  const fallback = await getPostNavSummary(wc);
14962
14987
  return fallback ? `${fallback}
@@ -14979,7 +15004,7 @@ Page snapshot after navigation:
14979
15004
  ${truncated}`;
14980
15005
  }
14981
15006
  } catch (err) {
14982
- logger$e.warn("Failed to build post-click navigation summary:", err);
15007
+ logger$g.warn("Failed to build post-click navigation summary:", err);
14983
15008
  }
14984
15009
  return "";
14985
15010
  }
@@ -15473,7 +15498,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
15473
15498
  }
15474
15499
  }
15475
15500
  } catch (err) {
15476
- logger$e.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
15501
+ logger$g.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
15477
15502
  }
15478
15503
  if (snapshot2.url && snapshot2.url !== wc.getURL()) {
15479
15504
  try {
@@ -15482,7 +15507,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
15482
15507
  await waitForLoad(wc, 3e3);
15483
15508
  return;
15484
15509
  } catch (err) {
15485
- logger$e.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
15510
+ logger$g.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
15486
15511
  }
15487
15512
  }
15488
15513
  if (snapshot2.url) {
@@ -15490,7 +15515,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
15490
15515
  await wc.reload();
15491
15516
  await waitForLoad(wc, 3e3);
15492
15517
  } catch (err) {
15493
- logger$e.warn("Failed to restore locale via page reload:", err);
15518
+ logger$g.warn("Failed to restore locale via page reload:", err);
15494
15519
  }
15495
15520
  }
15496
15521
  }
@@ -15760,7 +15785,7 @@ ${postActivationOverlayHint}`;
15760
15785
  return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
15761
15786
  }
15762
15787
  } catch (err) {
15763
- logger$e.warn("Failed href fallback after click, returning generic click result:", err);
15788
+ logger$g.warn("Failed href fallback after click, returning generic click result:", err);
15764
15789
  }
15765
15790
  }
15766
15791
  }
@@ -15805,7 +15830,7 @@ async function tryAutoDismissCartDialog(wc) {
15805
15830
  return result;
15806
15831
  }
15807
15832
  } catch (err) {
15808
- logger$e.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
15833
+ logger$g.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
15809
15834
  }
15810
15835
  return null;
15811
15836
  }
@@ -18088,7 +18113,7 @@ async function executeAction(name, args, ctx) {
18088
18113
  )
18089
18114
  ]);
18090
18115
  } catch (err) {
18091
- logger$e.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
18116
+ logger$g.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
18092
18117
  content = null;
18093
18118
  }
18094
18119
  if (!content || content.content.length === 0) {
@@ -18105,12 +18130,12 @@ async function executeAction(name, args, ctx) {
18105
18130
  new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
18106
18131
  ]);
18107
18132
  } catch (err) {
18108
- logger$e.warn("Failed to re-extract content after iframe consent dismissal:", err);
18133
+ logger$g.warn("Failed to re-extract content after iframe consent dismissal:", err);
18109
18134
  content = null;
18110
18135
  }
18111
18136
  }
18112
18137
  } catch (err) {
18113
- logger$e.warn("Failed iframe consent dismissal during read_page recovery:", err);
18138
+ logger$g.warn("Failed iframe consent dismissal during read_page recovery:", err);
18114
18139
  }
18115
18140
  }
18116
18141
  if (content && content.content.length > 0) {
@@ -18523,7 +18548,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
18523
18548
  try {
18524
18549
  page = await extractContent(wc);
18525
18550
  } catch (err) {
18526
- logger$e.warn("Failed to extract content for suggest:", err);
18551
+ logger$g.warn("Failed to extract content for suggest:", err);
18527
18552
  return "Could not read page. Try navigate to a working URL.";
18528
18553
  }
18529
18554
  const suggestions = [];
@@ -19972,7 +19997,7 @@ Exception: ${result.exceptionDetails}`);
19972
19997
  }
19973
19998
  );
19974
19999
  }
19975
- const logger$d = createLogger("VaultShared");
20000
+ const logger$f = createLogger("VaultShared");
19976
20001
  const ALGORITHM = "aes-256-gcm";
19977
20002
  const IV_LENGTH = 12;
19978
20003
  const AUTH_TAG_LENGTH = 16;
@@ -20066,7 +20091,7 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
20066
20091
  cachedEntries = JSON.parse(json);
20067
20092
  return cachedEntries;
20068
20093
  } catch (err) {
20069
- logger$d.error("Failed to load vault:", err);
20094
+ logger$f.error("Failed to load vault:", err);
20070
20095
  throw new Error("Could not unlock the vault. Check OS secret storage availability.");
20071
20096
  }
20072
20097
  }
@@ -20149,7 +20174,7 @@ function createAuditLog(filename, maxEntries) {
20149
20174
  } catch {
20150
20175
  }
20151
20176
  } catch (err) {
20152
- logger$d.error("Failed to write audit log:", err);
20177
+ logger$f.error("Failed to write audit log:", err);
20153
20178
  }
20154
20179
  }
20155
20180
  function readAuditLog2(limit = 100) {
@@ -20159,7 +20184,7 @@ function createAuditLog(filename, maxEntries) {
20159
20184
  const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
20160
20185
  return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
20161
20186
  } catch (err) {
20162
- logger$d.error("Failed to read audit log:", err);
20187
+ logger$f.error("Failed to read audit log:", err);
20163
20188
  return [];
20164
20189
  }
20165
20190
  }
@@ -20263,7 +20288,7 @@ async function requestConsent(request) {
20263
20288
  }
20264
20289
  const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
20265
20290
  const MAX_ENTRIES = 1e3;
20266
- const logger$c = createLogger("VaultAudit");
20291
+ const logger$e = createLogger("VaultAudit");
20267
20292
  function getAuditPath() {
20268
20293
  return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
20269
20294
  }
@@ -20277,7 +20302,7 @@ function appendAuditEntry(entry) {
20277
20302
  });
20278
20303
  fs$1.chmodSync(auditPath, 384);
20279
20304
  } catch (err) {
20280
- logger$c.error("Failed to write audit log:", err);
20305
+ logger$e.error("Failed to write audit log:", err);
20281
20306
  }
20282
20307
  }
20283
20308
  function readAuditLog$1(limit = 100) {
@@ -20287,7 +20312,7 @@ function readAuditLog$1(limit = 100) {
20287
20312
  const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
20288
20313
  return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
20289
20314
  } catch (err) {
20290
- logger$c.error("Failed to read audit log:", err);
20315
+ logger$e.error("Failed to read audit log:", err);
20291
20316
  return [];
20292
20317
  }
20293
20318
  }
@@ -20455,7 +20480,7 @@ async function requestHumanVaultConsent(request) {
20455
20480
  }
20456
20481
  let httpServer = null;
20457
20482
  let mcpAuthToken = null;
20458
- const logger$b = createLogger("MCP");
20483
+ const logger$d = createLogger("MCP");
20459
20484
  const MCP_AUTH_FILENAME = "mcp-auth.json";
20460
20485
  function getMcpAuthFilePath() {
20461
20486
  const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
@@ -20492,7 +20517,7 @@ function writeMcpAuthFile(endpoint, token) {
20492
20517
  );
20493
20518
  fs$1.chmodSync(filePath2, 384);
20494
20519
  } catch (err) {
20495
- logger$b.warn("Failed to write auth file:", err);
20520
+ logger$d.warn("Failed to write auth file:", err);
20496
20521
  }
20497
20522
  }
20498
20523
  function clearMcpAuthFile() {
@@ -20518,7 +20543,7 @@ function clearMcpAuthFile() {
20518
20543
  );
20519
20544
  fs$1.chmodSync(filePath2, 384);
20520
20545
  } catch (err) {
20521
- logger$b.warn("Failed to clear auth file:", err);
20546
+ logger$d.warn("Failed to clear auth file:", err);
20522
20547
  }
20523
20548
  }
20524
20549
  function regenerateMcpAuthToken() {
@@ -20628,7 +20653,7 @@ async function getPostActionState(tabManager, name) {
20628
20653
  }
20629
20654
  }
20630
20655
  } catch (err) {
20631
- logger$b.warn("Failed to compute post-action state warning:", err);
20656
+ logger$d.warn("Failed to compute post-action state warning:", err);
20632
20657
  }
20633
20658
  return `${warning}
20634
20659
  [state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
@@ -20733,7 +20758,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
20733
20758
  }
20734
20759
  })()
20735
20760
  `).catch((err) => {
20736
- logger$b.warn("Failed to gather wait_for timeout diagnostic:", err);
20761
+ logger$d.warn("Failed to gather wait_for timeout diagnostic:", err);
20737
20762
  return null;
20738
20763
  });
20739
20764
  if (typeof diagnostic === "string" && diagnostic.trim()) {
@@ -20820,7 +20845,7 @@ function registerTools(server, tabManager, runtime2) {
20820
20845
  const page = await extractContent(wc);
20821
20846
  pageType = detectPageType(page);
20822
20847
  } catch (err) {
20823
- logger$b.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
20848
+ logger$d.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
20824
20849
  }
20825
20850
  }
20826
20851
  const scored = TOOL_DEFINITIONS.map((def) => {
@@ -22220,7 +22245,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
22220
22245
  void 0,
22221
22246
  h.color
22222
22247
  ).catch(
22223
- (err) => logger$b.warn("Failed to restore highlight after removal:", err)
22248
+ (err) => logger$d.warn("Failed to restore highlight after removal:", err)
22224
22249
  );
22225
22250
  }
22226
22251
  }
@@ -23076,7 +23101,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
23076
23101
  try {
23077
23102
  page = await extractContent(wc);
23078
23103
  } catch (err) {
23079
- logger$b.warn("Failed to extract page while generating suggestions:", err);
23104
+ logger$d.warn("Failed to extract page while generating suggestions:", err);
23080
23105
  return asTextResponse(
23081
23106
  "Could not read page. Try navigate to a working URL."
23082
23107
  );
@@ -23687,7 +23712,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
23687
23712
  try {
23688
23713
  targetDomain = new URL(tab.state.url).hostname;
23689
23714
  } catch (err) {
23690
- logger$b.warn("Failed to parse active tab URL for vault_status:", err);
23715
+ logger$d.warn("Failed to parse active tab URL for vault_status:", err);
23691
23716
  return asErrorTextResponse("Could not parse active tab URL");
23692
23717
  }
23693
23718
  }
@@ -23755,7 +23780,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23755
23780
  try {
23756
23781
  hostname = new URL(tab.state.url).hostname;
23757
23782
  } catch (err) {
23758
- logger$b.warn("Failed to parse active tab URL for vault_login:", err);
23783
+ logger$d.warn("Failed to parse active tab URL for vault_login:", err);
23759
23784
  return asErrorTextResponse("Could not parse active tab URL");
23760
23785
  }
23761
23786
  const matches = findEntriesForDomain(`https://${hostname}`);
@@ -23851,7 +23876,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23851
23876
  try {
23852
23877
  hostname = new URL(tab.state.url).hostname;
23853
23878
  } catch (err) {
23854
- logger$b.warn("Failed to parse active tab URL for vault_totp:", err);
23879
+ logger$d.warn("Failed to parse active tab URL for vault_totp:", err);
23855
23880
  return asErrorTextResponse("Could not parse active tab URL");
23856
23881
  }
23857
23882
  const matches = findEntriesForDomain(`https://${hostname}`);
@@ -24199,7 +24224,7 @@ function startMcpServer(tabManager, runtime2, port) {
24199
24224
  await mcpServer.connect(transport);
24200
24225
  await transport.handleRequest(req, res);
24201
24226
  } catch (error) {
24202
- logger$b.error("Error handling request:", error);
24227
+ logger$d.error("Error handling request:", error);
24203
24228
  if (!res.headersSent) {
24204
24229
  res.writeHead(500, { "Content-Type": "application/json" });
24205
24230
  res.end(
@@ -24218,7 +24243,7 @@ function startMcpServer(tabManager, runtime2, port) {
24218
24243
  };
24219
24244
  server.once("error", (error) => {
24220
24245
  const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
24221
- logger$b.error("Server error:", error);
24246
+ logger$d.error("Server error:", error);
24222
24247
  clearMcpAuthFile();
24223
24248
  setMcpHealth({
24224
24249
  configuredPort: port,
@@ -24250,7 +24275,7 @@ function startMcpServer(tabManager, runtime2, port) {
24250
24275
  message: `MCP server listening on ${endpoint}.`
24251
24276
  });
24252
24277
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
24253
- logger$b.info(`Server listening on ${endpoint} (auth enabled)`);
24278
+ logger$d.info(`Server listening on ${endpoint} (auth enabled)`);
24254
24279
  }
24255
24280
  if (mcpAuthToken) {
24256
24281
  writeMcpAuthFile(endpoint, mcpAuthToken);
@@ -24289,7 +24314,7 @@ function stopMcpServer() {
24289
24314
  message: "MCP server is stopped."
24290
24315
  });
24291
24316
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
24292
- logger$b.info("Server stopped");
24317
+ logger$d.info("Server stopped");
24293
24318
  }
24294
24319
  resolve();
24295
24320
  });
@@ -24310,7 +24335,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
24310
24335
  function isSafeAutomationKitId(id) {
24311
24336
  return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
24312
24337
  }
24313
- const logger$a = createLogger("KitRegistry");
24338
+ const logger$c = createLogger("KitRegistry");
24314
24339
  function getUserKitsDir() {
24315
24340
  return path$1.join(electron.app.getPath("userData"), "kits");
24316
24341
  }
@@ -24348,10 +24373,10 @@ function getInstalledKits() {
24348
24373
  if (isValidKit(parsed)) {
24349
24374
  kits.push(parsed);
24350
24375
  } else {
24351
- logger$a.warn(`Skipping invalid kit file: ${file}`);
24376
+ logger$c.warn(`Skipping invalid kit file: ${file}`);
24352
24377
  }
24353
24378
  } catch (err) {
24354
- logger$a.warn(`Failed to read kit file: ${file}`, err);
24379
+ logger$c.warn(`Failed to read kit file: ${file}`, err);
24355
24380
  }
24356
24381
  }
24357
24382
  return kits;
@@ -24460,7 +24485,7 @@ function getActiveTabInfo(tabManager) {
24460
24485
  if (wc.isDestroyed()) return null;
24461
24486
  return { tab, wc };
24462
24487
  }
24463
- const logger$9 = createLogger("Scheduler");
24488
+ const logger$b = createLogger("Scheduler");
24464
24489
  let jobs = [];
24465
24490
  let pollInterval = null;
24466
24491
  let alignStartTimeout = null;
@@ -24493,7 +24518,7 @@ function saveJobs() {
24493
24518
  });
24494
24519
  fs$1.chmodSync(jobsPath, 384);
24495
24520
  } catch (err) {
24496
- logger$9.warn("Failed to save jobs:", err);
24521
+ logger$b.warn("Failed to save jobs:", err);
24497
24522
  }
24498
24523
  }
24499
24524
  function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
@@ -24617,7 +24642,7 @@ async function fireJob(job, windowState, runtime2) {
24617
24642
  };
24618
24643
  startActivity();
24619
24644
  if (!settings2.chatProvider) {
24620
- logger$9.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
24645
+ logger$b.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
24621
24646
  appendActivity(
24622
24647
  "Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
24623
24648
  );
@@ -24625,7 +24650,7 @@ async function fireJob(job, windowState, runtime2) {
24625
24650
  return;
24626
24651
  }
24627
24652
  if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
24628
- logger$9.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
24653
+ logger$b.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
24629
24654
  }
24630
24655
  try {
24631
24656
  const provider = createProvider(settings2.chatProvider);
@@ -24678,7 +24703,7 @@ function tick(windowState, runtime2) {
24678
24703
  saveJobs();
24679
24704
  broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
24680
24705
  void fireJob(job, windowState, runtime2).catch((err) => {
24681
- logger$9.warn("Unexpected error firing job:", err);
24706
+ logger$b.warn("Unexpected error firing job:", err);
24682
24707
  }).finally(fireNext);
24683
24708
  };
24684
24709
  fireNext();
@@ -25280,7 +25305,7 @@ function renderReportAsMarkdown(report, traces) {
25280
25305
  }
25281
25306
  return sections.join("\n");
25282
25307
  }
25283
- const logger$8 = createLogger("ResearchIPC");
25308
+ const logger$a = createLogger("ResearchIPC");
25284
25309
  function registerResearchHandlers(getOrchestrator) {
25285
25310
  electron.ipcMain.handle(Channels.RESEARCH_STATE_GET, () => {
25286
25311
  return getOrchestrator().getState();
@@ -25299,7 +25324,7 @@ function registerResearchHandlers(getOrchestrator) {
25299
25324
  await getOrchestrator().startBrief(trimmedQuery);
25300
25325
  return { accepted: true };
25301
25326
  } catch (err) {
25302
- logger$8.error("RESEARCH_START_BRIEF failed", err);
25327
+ logger$a.error("RESEARCH_START_BRIEF failed", err);
25303
25328
  return { accepted: false, reason: "error" };
25304
25329
  }
25305
25330
  }
@@ -25316,7 +25341,7 @@ function registerResearchHandlers(getOrchestrator) {
25316
25341
  orchestrator.confirmBrief();
25317
25342
  return { accepted: true };
25318
25343
  } catch (err) {
25319
- logger$8.error("RESEARCH_CONFIRM_BRIEF failed", err);
25344
+ logger$a.error("RESEARCH_CONFIRM_BRIEF failed", err);
25320
25345
  return { accepted: false, reason: "error" };
25321
25346
  }
25322
25347
  });
@@ -25337,11 +25362,11 @@ function registerResearchHandlers(getOrchestrator) {
25337
25362
  options.includeTraces
25338
25363
  );
25339
25364
  orchestrator.executeSubAgents().catch((err) => {
25340
- logger$8.error("Background sub-agent execution failed", err);
25365
+ logger$a.error("Background sub-agent execution failed", err);
25341
25366
  });
25342
25367
  return { accepted: true };
25343
25368
  } catch (err) {
25344
- logger$8.error("RESEARCH_APPROVE_OBJECTIVES failed", err);
25369
+ logger$a.error("RESEARCH_APPROVE_OBJECTIVES failed", err);
25345
25370
  return { accepted: false, reason: "error" };
25346
25371
  }
25347
25372
  }
@@ -25391,7 +25416,7 @@ function registerResearchHandlers(getOrchestrator) {
25391
25416
  await promises.writeFile(filePath2, markdown, "utf-8");
25392
25417
  return { accepted: true, savedPath: filePath2 };
25393
25418
  } catch (err) {
25394
- logger$8.error("RESEARCH_EXPORT_REPORT failed", err);
25419
+ logger$a.error("RESEARCH_EXPORT_REPORT failed", err);
25395
25420
  return { accepted: false, reason: "error" };
25396
25421
  }
25397
25422
  });
@@ -25486,7 +25511,7 @@ RULES:
25486
25511
  4. Omit empty arrays entirely (contradictions, gaps) — do not include "contradictions": [] if there are none.
25487
25512
  5. Do not use emojis.`;
25488
25513
  }
25489
- const logger$7 = createLogger("ResearchOrchestrator");
25514
+ const logger$9 = createLogger("ResearchOrchestrator");
25490
25515
  const MAX_THREADS = 5;
25491
25516
  const MAX_TRACE_ARGS_CHARS = 1200;
25492
25517
  const MAX_TRACE_RESULT_CHARS = 2e3;
@@ -25675,7 +25700,7 @@ class ResearchOrchestrator {
25675
25700
  }
25676
25701
  stopAndSynthesizeCurrentFindings() {
25677
25702
  if (this.state.phase !== "executing") {
25678
- logger$7.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
25703
+ logger$9.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
25679
25704
  return;
25680
25705
  }
25681
25706
  this.stopRequested = true;
@@ -25709,23 +25734,23 @@ class ResearchOrchestrator {
25709
25734
  async startBrief(userQuery) {
25710
25735
  const query = userQuery.trim();
25711
25736
  if (!query) {
25712
- logger$7.warn("Ignoring empty Research Desk query");
25737
+ logger$9.warn("Ignoring empty Research Desk query");
25713
25738
  return;
25714
25739
  }
25715
25740
  if (this.state.phase !== "idle") {
25716
- logger$7.warn("Research already in progress, ignoring startBrief");
25741
+ logger$9.warn("Research already in progress, ignoring startBrief");
25717
25742
  return;
25718
25743
  }
25719
25744
  this.state = this.initialState();
25720
25745
  this.state.originalQuery = query;
25721
25746
  this.state.startedAt = (/* @__PURE__ */ new Date()).toISOString();
25722
25747
  this.setPhase("briefing");
25723
- logger$7.info(`Brief started for query: ${query.slice(0, 120)}`);
25748
+ logger$9.info(`Brief started for query: ${query.slice(0, 120)}`);
25724
25749
  }
25725
25750
  // ── phase: briefing → planning ─────────────────────────────────
25726
25751
  confirmBrief() {
25727
25752
  if (this.state.phase !== "briefing") {
25728
- logger$7.warn("Not in briefing phase, ignoring confirmBrief");
25753
+ logger$9.warn("Not in briefing phase, ignoring confirmBrief");
25729
25754
  return;
25730
25755
  }
25731
25756
  this.setPhase("planning");
@@ -25733,7 +25758,7 @@ class ResearchOrchestrator {
25733
25758
  // ── phase: planning → awaiting_approval ────────────────────────
25734
25759
  setObjectives(objectives) {
25735
25760
  if (this.state.phase !== "planning") {
25736
- logger$7.warn("Not in planning phase, ignoring setObjectives");
25761
+ logger$9.warn("Not in planning phase, ignoring setObjectives");
25737
25762
  return;
25738
25763
  }
25739
25764
  const threads = objectives.threads.slice(0, MAX_THREADS).map(mergeBlockedSourceDomains);
@@ -25762,11 +25787,11 @@ class ResearchOrchestrator {
25762
25787
  try {
25763
25788
  const parsed = JSON.parse(json);
25764
25789
  if (typeof parsed.researchQuestion !== "string" || !parsed.researchQuestion.trim()) {
25765
- logger$7.warn("Missing researchQuestion in objectives JSON");
25790
+ logger$9.warn("Missing researchQuestion in objectives JSON");
25766
25791
  return false;
25767
25792
  }
25768
25793
  if (!Array.isArray(parsed.threads) || parsed.threads.length === 0) {
25769
- logger$7.warn("Missing or empty threads array in objectives JSON");
25794
+ logger$9.warn("Missing or empty threads array in objectives JSON");
25770
25795
  return false;
25771
25796
  }
25772
25797
  const threads = parsed.threads.map((t, i) => {
@@ -25784,7 +25809,7 @@ class ResearchOrchestrator {
25784
25809
  };
25785
25810
  }).filter((thread) => thread.question && thread.searchQueries.length > 0).slice(0, MAX_THREADS);
25786
25811
  if (threads.length === 0) {
25787
- logger$7.warn("Objectives JSON did not contain any valid research threads");
25812
+ logger$9.warn("Objectives JSON did not contain any valid research threads");
25788
25813
  return false;
25789
25814
  }
25790
25815
  const objectives = {
@@ -25795,17 +25820,17 @@ class ResearchOrchestrator {
25795
25820
  totalSourceBudget: threads.reduce((sum, t) => sum + t.sourceBudget, 0)
25796
25821
  };
25797
25822
  this.setObjectives(objectives);
25798
- logger$7.info(`Parsed ${objectives.threads.length} threads from objectives`);
25823
+ logger$9.info(`Parsed ${objectives.threads.length} threads from objectives`);
25799
25824
  return true;
25800
25825
  } catch (err) {
25801
- logger$7.warn("Failed to parse objectives JSON", err);
25826
+ logger$9.warn("Failed to parse objectives JSON", err);
25802
25827
  return false;
25803
25828
  }
25804
25829
  }
25805
25830
  // ── phase: awaiting_approval → executing ───────────────────────
25806
25831
  approveObjectives(mode, includeTraces) {
25807
25832
  if (this.state.phase !== "awaiting_approval") {
25808
- logger$7.warn("Not awaiting approval, ignoring approveObjectives");
25833
+ logger$9.warn("Not awaiting approval, ignoring approveObjectives");
25809
25834
  return;
25810
25835
  }
25811
25836
  if (mode) this.state.supervisionMode = mode;
@@ -25840,7 +25865,7 @@ class ResearchOrchestrator {
25840
25865
  this.state.threads.map((thread) => {
25841
25866
  if (this.state.phase !== "executing") return null;
25842
25867
  return this.runSubAgent(thread, tabMutex).catch((err) => {
25843
- logger$7.error(`Sub-agent "${thread.label}" failed`, err);
25868
+ logger$9.error(`Sub-agent "${thread.label}" failed`, err);
25844
25869
  return {
25845
25870
  threadLabel: thread.label,
25846
25871
  threadQuestion: thread.question,
@@ -25869,7 +25894,7 @@ class ResearchOrchestrator {
25869
25894
  try {
25870
25895
  await this.synthesizeReport();
25871
25896
  } catch (err) {
25872
- logger$7.error("Auto-synthesis failed", err);
25897
+ logger$9.error("Auto-synthesis failed", err);
25873
25898
  this.state.error = `Synthesis failed: ${String(err)}`;
25874
25899
  this.setPhase("delivered");
25875
25900
  }
@@ -25980,7 +26005,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
25980
26005
  try {
25981
26006
  this.tabManager.closeTab(tabId);
25982
26007
  } catch (err) {
25983
- logger$7.warn(`Failed to close sub-agent tab ${tabId}`, err);
26008
+ logger$9.warn(`Failed to close sub-agent tab ${tabId}`, err);
25984
26009
  }
25985
26010
  }
25986
26011
  }
@@ -25989,7 +26014,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
25989
26014
  try {
25990
26015
  claims = await this.extractClaimsFromTranscript(thread, transcript);
25991
26016
  } catch (err) {
25992
- logger$7.warn(`Claim extraction failed for "${thread.label}"`, err);
26017
+ logger$9.warn(`Claim extraction failed for "${thread.label}"`, err);
25993
26018
  }
25994
26019
  }
25995
26020
  if (this.state.phase === "executing" && this.state.includeTraces) {
@@ -26079,7 +26104,7 @@ ${transcript.slice(0, 32e3)}`;
26079
26104
  (claim) => claim.claim && claim.sourceUrl && claim.extractedQuote
26080
26105
  );
26081
26106
  } catch {
26082
- logger$7.warn(`Failed to parse claims JSON for "${thread.label}"`);
26107
+ logger$9.warn(`Failed to parse claims JSON for "${thread.label}"`);
26083
26108
  return [];
26084
26109
  }
26085
26110
  }
@@ -26164,7 +26189,7 @@ ${transcript.slice(0, 32e3)}`;
26164
26189
  objectives
26165
26190
  };
26166
26191
  } catch (err) {
26167
- logger$7.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
26192
+ logger$9.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
26168
26193
  return buildFallbackReport(objectives, findings, String(err));
26169
26194
  }
26170
26195
  }
@@ -26903,7 +26928,7 @@ function createFindInPageBridge(tabManager, chromeView) {
26903
26928
  }
26904
26929
  };
26905
26930
  }
26906
- const logger$6 = createLogger("PrivateWindow");
26931
+ const logger$8 = createLogger("PrivateWindow");
26907
26932
  const privateWindows = /* @__PURE__ */ new Set();
26908
26933
  function layoutPrivateViews(state2) {
26909
26934
  const { window: win, chromeView, tabManager } = state2;
@@ -27126,7 +27151,7 @@ function createPrivateWindow() {
27126
27151
  privateSession.clearStorageData(),
27127
27152
  privateSession.clearCache()
27128
27153
  ]).catch((error) => {
27129
- logger$6.warn("Failed to clear private browsing session:", error);
27154
+ logger$8.warn("Failed to clear private browsing session:", error);
27130
27155
  });
27131
27156
  });
27132
27157
  privateWindows.add(state2);
@@ -27136,7 +27161,7 @@ function createPrivateWindow() {
27136
27161
  });
27137
27162
  loadPrivateRenderer(chromeView);
27138
27163
  win.show();
27139
- logger$6.info("Private browsing window opened");
27164
+ logger$8.info("Private browsing window opened");
27140
27165
  return state2;
27141
27166
  }
27142
27167
  const secondaryWindows = /* @__PURE__ */ new Set();
@@ -27836,7 +27861,7 @@ function registerSecurityHandlers(tabManager) {
27836
27861
  tabManager.goBackToSafety(tabId);
27837
27862
  });
27838
27863
  }
27839
- const logger$5 = createLogger("CodexIPC");
27864
+ const logger$7 = createLogger("CodexIPC");
27840
27865
  function registerCodexHandlers() {
27841
27866
  electron.ipcMain.handle(Channels.CODEX_START_AUTH, async (event) => {
27842
27867
  assertTrustedIpcSender(event);
@@ -27851,7 +27876,7 @@ function registerCodexHandlers() {
27851
27876
  try {
27852
27877
  wc.send(Channels.CODEX_AUTH_STATUS, { status, error: error || null });
27853
27878
  } catch {
27854
- logger$5.warn("Codex auth status send failed — window may be closed");
27879
+ logger$7.warn("Codex auth status send failed — window may be closed");
27855
27880
  }
27856
27881
  };
27857
27882
  try {
@@ -27863,7 +27888,7 @@ function registerCodexHandlers() {
27863
27888
  accountId: tokens.accountId
27864
27889
  };
27865
27890
  } catch (err) {
27866
- logger$5.error("Codex auth failed:", err);
27891
+ logger$7.error("Codex auth failed:", err);
27867
27892
  return {
27868
27893
  ok: false,
27869
27894
  error: err instanceof Error ? err.message : "Unknown error"
@@ -27881,6 +27906,119 @@ function registerCodexHandlers() {
27881
27906
  return { ok: true };
27882
27907
  });
27883
27908
  }
27909
+ const logger$6 = createLogger("OpenRouterOAuth");
27910
+ const AUTH_BASE_URL = "https://openrouter.ai/auth";
27911
+ const KEY_EXCHANGE_URL = "https://openrouter.ai/api/v1/auth/keys";
27912
+ const AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
27913
+ const PREFERRED_PORT = 1460;
27914
+ const FALLBACK_PORT = 1461;
27915
+ async function exchangeCodeForApiKey(code, codeVerifier) {
27916
+ const response = await fetch(KEY_EXCHANGE_URL, {
27917
+ method: "POST",
27918
+ headers: {
27919
+ "Content-Type": "application/json"
27920
+ },
27921
+ body: JSON.stringify({
27922
+ code,
27923
+ code_verifier: codeVerifier,
27924
+ code_challenge_method: "S256"
27925
+ })
27926
+ });
27927
+ if (!response.ok) {
27928
+ let errorMsg = `OpenRouter key exchange failed: ${response.status}`;
27929
+ try {
27930
+ const payload2 = await response.json();
27931
+ if (typeof payload2.error === "string") {
27932
+ errorMsg = payload2.error;
27933
+ } else if (typeof payload2.message === "string") {
27934
+ errorMsg = payload2.message;
27935
+ }
27936
+ } catch {
27937
+ }
27938
+ throw new Error(errorMsg);
27939
+ }
27940
+ const payload = await response.json();
27941
+ if (typeof payload.key !== "string" || !payload.key.trim()) {
27942
+ throw new Error("OpenRouter did not return an API key");
27943
+ }
27944
+ return payload.key.trim();
27945
+ }
27946
+ const openRouterOAuth = createLocalPkceOAuthFlow({
27947
+ name: "OpenRouter",
27948
+ logger: logger$6,
27949
+ preferredPorts: [PREFERRED_PORT, FALLBACK_PORT],
27950
+ timeoutMs: AUTH_TIMEOUT_MS,
27951
+ callbackPath: (state2) => `/auth/openrouter/callback/${state2}`,
27952
+ readState: (url) => decodeURIComponent(url.pathname.split("/").pop() || ""),
27953
+ buildAuthorizeUrl: ({ callbackUrl, pkce }) => {
27954
+ const params = new URLSearchParams({
27955
+ callback_url: callbackUrl,
27956
+ code_challenge: pkce.codeChallenge,
27957
+ code_challenge_method: "S256"
27958
+ });
27959
+ return `${AUTH_BASE_URL}?${params.toString()}`;
27960
+ },
27961
+ exchangeCode: ({ code, codeVerifier }) => exchangeCodeForApiKey(code, codeVerifier),
27962
+ successHtml: () => `<!DOCTYPE html>
27963
+ <html><head><meta charset="utf-8"><title>Vessel OpenRouter Setup</title>
27964
+ <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>
27965
+ <body><div style="text-align:center"><h1>OpenRouter connected</h1><p>Vessel is ready. You can close this tab.</p></div></body></html>`,
27966
+ openHosts: ["openrouter.ai"]
27967
+ });
27968
+ function startOpenRouterOAuth(onStatus) {
27969
+ return openRouterOAuth.start(onStatus);
27970
+ }
27971
+ function cancelOpenRouterOAuth() {
27972
+ openRouterOAuth.cancel();
27973
+ }
27974
+ const logger$5 = createLogger("OpenRouterIPC");
27975
+ function registerOpenRouterHandlers(applySettingChange) {
27976
+ electron.ipcMain.handle(Channels.OPENROUTER_START_AUTH, async (event) => {
27977
+ assertTrustedIpcSender(event);
27978
+ const wc = event.sender;
27979
+ if (!wc || wc.isDestroyed()) {
27980
+ return {
27981
+ ok: false,
27982
+ error: "No active window found for sender"
27983
+ };
27984
+ }
27985
+ const sendStatus = (status, error) => {
27986
+ try {
27987
+ wc.send(Channels.OPENROUTER_AUTH_STATUS, { status, error: error || null });
27988
+ } catch {
27989
+ logger$5.warn("OpenRouter auth status send failed - window may be closed");
27990
+ }
27991
+ };
27992
+ try {
27993
+ const apiKey = await startOpenRouterOAuth(sendStatus);
27994
+ const openRouterConfig = {
27995
+ id: "openrouter",
27996
+ apiKey,
27997
+ hasApiKey: true,
27998
+ model: PROVIDERS.openrouter.defaultModel,
27999
+ baseUrl: PROVIDERS.openrouter.defaultBaseUrl,
28000
+ reasoningEffort: "off"
28001
+ };
28002
+ await applySettingChange("chatProvider", openRouterConfig);
28003
+ return {
28004
+ ok: true,
28005
+ providerId: "openrouter",
28006
+ model: openRouterConfig.model
28007
+ };
28008
+ } catch (err) {
28009
+ logger$5.error("OpenRouter auth failed:", err);
28010
+ return {
28011
+ ok: false,
28012
+ error: err instanceof Error ? err.message : "Unknown error"
28013
+ };
28014
+ }
28015
+ });
28016
+ electron.ipcMain.handle(Channels.OPENROUTER_CANCEL_AUTH, (event) => {
28017
+ assertTrustedIpcSender(event);
28018
+ cancelOpenRouterOAuth();
28019
+ return { ok: true };
28020
+ });
28021
+ }
27884
28022
  const SUPPORT_API = process.env.VESSEL_SUPPORT_API || process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
27885
28023
  const MAX_FEEDBACK_MESSAGE_LENGTH = 5e3;
27886
28024
  const FEEDBACK_REQUEST_TIMEOUT_MS = 15e3;
@@ -28250,6 +28388,26 @@ function registerIpcHandlers(windowState, runtime2) {
28250
28388
  const count = await getActiveHighlightCountSafe();
28251
28389
  sendToRendererViews(Channels.HIGHLIGHT_COUNT_UPDATE, count);
28252
28390
  };
28391
+ const applySettingChange = async (key2, value) => {
28392
+ const updatedSettings = setSetting(key2, value);
28393
+ trackSettingChanged(key2);
28394
+ if (key2 === "approvalMode") {
28395
+ runtime2.setApprovalMode(value);
28396
+ }
28397
+ if (key2 === "mcpPort") {
28398
+ await stopMcpServer();
28399
+ await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
28400
+ }
28401
+ if (key2 === "chatProvider" && researchOrchestrator) {
28402
+ try {
28403
+ researchOrchestrator.setProvider(createProvider(value));
28404
+ } catch {
28405
+ }
28406
+ }
28407
+ const rendererSettings = getRendererSettings();
28408
+ sendToRendererViews(Channels.SETTINGS_UPDATE, rendererSettings);
28409
+ return rendererSettings;
28410
+ };
28253
28411
  runtime2.setUpdateListener((state2) => {
28254
28412
  scheduleRuntimeUpdate(state2);
28255
28413
  });
@@ -28600,24 +28758,7 @@ function registerIpcHandlers(windowState, runtime2) {
28600
28758
  throw new Error(`Unknown setting key: ${key2}`);
28601
28759
  }
28602
28760
  const settingsKey = key2;
28603
- const updatedSettings = setSetting(settingsKey, value);
28604
- trackSettingChanged(key2);
28605
- if (key2 === "approvalMode") {
28606
- runtime2.setApprovalMode(value);
28607
- }
28608
- if (key2 === "mcpPort") {
28609
- await stopMcpServer();
28610
- await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
28611
- }
28612
- if (key2 === "chatProvider" && researchOrchestrator) {
28613
- try {
28614
- researchOrchestrator.setProvider(createProvider(value));
28615
- } catch {
28616
- }
28617
- }
28618
- const rendererSettings = getRendererSettings();
28619
- sendToRendererViews(Channels.SETTINGS_UPDATE, rendererSettings);
28620
- return rendererSettings;
28761
+ return applySettingChange(settingsKey, value);
28621
28762
  });
28622
28763
  electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, (event) => {
28623
28764
  requireTrusted(event);
@@ -28806,6 +28947,7 @@ function registerIpcHandlers(windowState, runtime2) {
28806
28947
  registerHumanVaultHandlers();
28807
28948
  registerWindowControlHandlers(mainWindow);
28808
28949
  registerCodexHandlers();
28950
+ registerOpenRouterHandlers(applySettingChange);
28809
28951
  electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, (event) => {
28810
28952
  requireTrusted(event);
28811
28953
  return getInstalledKits();