sneakoscope 8.1.0 → 8.1.2

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.
Files changed (29) hide show
  1. package/README.md +1 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/install-helpers-codex-lb-chain.js +6 -6
  5. package/dist/cli/install-helpers-codex-lb-config.js +24 -3
  6. package/dist/cli/install-helpers.js +36 -9
  7. package/dist/commands/codex-lb.js +44 -22
  8. package/dist/commands/doctor.js +5 -5
  9. package/dist/config/skills-manifest.json +1 -1
  10. package/dist/core/codex-app.js +5 -5
  11. package/dist/core/codex-control/codex-task-runner.js +1 -1
  12. package/dist/core/codex-lb/codex-lb-env.js +4 -4
  13. package/dist/core/codex-lb/desktop-controller.js +36 -19
  14. package/dist/core/codex-lb/desktop-mode.js +1 -1
  15. package/dist/core/codex-lb/probes/bridge-probe.js +10 -15
  16. package/dist/core/codex-lb/probes/cli-image-tool-probe.js +7 -4
  17. package/dist/core/image-ux-review/imagegen-adapter.js +2 -4
  18. package/dist/core/imagegen/imagegen-capability.js +4 -6
  19. package/dist/core/init.js +1 -1
  20. package/dist/core/provider/provider-context.js +2 -2
  21. package/dist/core/version.js +1 -1
  22. package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +8 -5
  23. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +68 -16
  24. package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +3 -2
  25. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +46 -40
  26. package/dist/scripts/codex-lb-fast-mode-truth-check.js +3 -3
  27. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +2 -9
  28. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +2 -2
  29. package/package.json +1 -1
package/README.md CHANGED
@@ -22,7 +22,7 @@ Proof-first orchestration for Codex CLI, ChatGPT Desktop, AI coding agents, mult
22
22
  Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
23
23
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
24
24
 
25
- This README documents package **SKS 8.1.0** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
25
+ This README documents package **SKS 8.1.2** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
26
26
 
27
27
  Use the official latest stable SKS and Codex CLI releases. The Codex compatibility SSOT is always the **current latest stable** host; capability probes measure what that host can actually do. Product docs do not crown a fixed `0.x.y` string as SSOT (release pins and schema directories are measured artifacts for the current package, not a permanent product version claim). Menu Bar / Center induce updates to the latest stable build. Run `sks update-check` for what is installed and read the capability report for what is supported. Install SSOT is npm `sneakoscope@latest`; PATH `sks` and Menu Bar stamped generation must match that version or gates fail. It resolves managed SKS skills from the authoritative global install, preserves a runnable Naruto child slot when `max_threads=2`, and keeps Menu Bar repair transactional so stamped generations remain verifiable. Naruto uses stable opt-in multi-agent V2 when the host exposes it (Codex official multi-agent wrap-only; SKS does not reimplement a parallel runtime). Local code search is mode-separated (`sks search files|text|structure|symbol|context`); `context` is answered by the compiled TriWiki Context Graph (`context-graph.json` is exhaustive authority; `context-pack.json` and managed `AGENTS.md` are bounded projections) — see [docs/architecture/context-graph.md](docs/architecture/context-graph.md) and [docs/PRODUCT-CONTRACT.md](docs/PRODUCT-CONTRACT.md). See [CHANGELOG.md](CHANGELOG.md).
28
28
 
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "8.1.0"
262
+ version = "8.1.2"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "8.1.0"
3
+ version = "8.1.2"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -172,13 +172,13 @@ function codexLbServiceTierEvidence(...responses) {
172
172
  fast_actual: actual === CODEX_LB_CANONICAL_FAST_SERVICE_TIER || effective === CODEX_LB_CANONICAL_FAST_SERVICE_TIER
173
173
  };
174
174
  }
175
- function codexLbGatewayHeaders(apiKey, authTransport = 'x-codex-lb-api-key') {
176
- const credentialHeader = String(authTransport).startsWith('authorization-bearer')
177
- ? { authorization: `Bearer ${String(apiKey)}` }
178
- : { 'X-Codex-LB-API-Key': String(apiKey) };
175
+ function codexLbGatewayHeaders(apiKey, authTransport = 'authorization-bearer-compat') {
176
+ const credentialHeader = authTransport === 'x-codex-lb-api-key'
177
+ ? { 'X-Codex-LB-API-Key': String(apiKey) }
178
+ : { authorization: `Bearer ${String(apiKey)}` };
179
179
  return { ...credentialHeader, 'content-type': 'application/json' };
180
180
  }
181
- async function fetchCodexLbResponse(fetchImpl, endpoint, apiKey, body, timeoutMs, authTransport = 'x-codex-lb-api-key') {
181
+ async function fetchCodexLbResponse(fetchImpl, endpoint, apiKey, body, timeoutMs, authTransport = 'authorization-bearer-compat') {
182
182
  const controller = new AbortController();
183
183
  const timer = setTimeout(() => controller.abort(), timeoutMs).unref?.();
184
184
  try {
@@ -436,7 +436,7 @@ export async function checkCodexLbResponseChain(status = {}, opts = {}) {
436
436
  if (!model)
437
437
  return { ok: true, status: 'skipped', skipped: true, reason: 'model_unselected_use_explicit_model_or_codex_catalog' };
438
438
  const timeoutMs = Number(opts.timeoutMs || env.SKS_CODEX_LB_CHAIN_CHECK_TIMEOUT_MS || 8000);
439
- const gatewayAuthTransport = opts.gatewayAuthTransport || 'x-codex-lb-api-key';
439
+ const gatewayAuthTransport = opts.gatewayAuthTransport || 'authorization-bearer-compat';
440
440
  const serviceTier = opts.fastMode === true || opts.serviceTier === 'fast' || opts.serviceTier === CODEX_LB_CANONICAL_FAST_SERVICE_TIER
441
441
  ? CODEX_LB_CANONICAL_FAST_SERVICE_TIER
442
442
  : null;
@@ -17,6 +17,25 @@ export const CODEX_LB_MODEL_CATALOG_MARKER = '# sks-codex-lb-managed-model-catal
17
17
  export const CODEX_LB_PROVIDER_SELECTION_MARKER = '# sks-codex-lb-managed-provider-selection';
18
18
  export const CODEX_LB_OAUTH_SELECTION_MARKER = '# sks-codex-lb-managed-oauth-selection';
19
19
  export const LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER = '# sks-codex-lb-managed-openai-base-url';
20
+ const SKS_SWITCHABLE_THIRD_PARTY_PROVIDER_IDS = ['openrouter', 'sks-router'];
21
+ const SKS_THIRD_PARTY_CATALOG_BASENAMES = ['sks-openrouter-catalog.json', 'opencodex-catalog.json'];
22
+ export function releaseSksManagedThirdPartySelection(text) {
23
+ let next = String(text || '');
24
+ const selected = topLevelTomlString(next, 'model_provider');
25
+ if (!selected || !SKS_SWITCHABLE_THIRD_PARTY_PROVIDER_IDS.includes(selected)) {
26
+ return next;
27
+ }
28
+ if (!new RegExp(`(^|\\n)\\[model_providers\\.${escapeRegExp(selected)}\\]`).test(next))
29
+ return next;
30
+ next = removeTopLevelTomlKeyIfValue(next, 'model_provider', selected);
31
+ const catalog = topLevelTomlString(next, 'model_catalog_json');
32
+ if (catalog
33
+ && !topLevelHasLine(next, CODEX_LB_MODEL_CATALOG_MARKER)
34
+ && SKS_THIRD_PARTY_CATALOG_BASENAMES.some((name) => catalog.endsWith(`/${name}`) || catalog === name)) {
35
+ next = removeTopLevelTomlKey(next, 'model_catalog_json');
36
+ }
37
+ return next;
38
+ }
20
39
  export function removeCodexLbOrphanManagedMarkers(text) {
21
40
  const source = String(text || '');
22
41
  const selectedProvider = topLevelTomlString(source, 'model_provider');
@@ -42,7 +61,7 @@ export function removeCodexLbOrphanManagedMarkers(text) {
42
61
  };
43
62
  }
44
63
  export function upsertCodexLbNativeDesktopConfig(text, input) {
45
- let next = removeCodexLbOrphanManagedMarkers(String(text || '')).text;
64
+ let next = releaseSksManagedThirdPartySelection(removeCodexLbOrphanManagedMarkers(String(text || '')).text);
46
65
  const selectedProvider = topLevelTomlString(next, 'model_provider');
47
66
  if (selectedProvider && selectedProvider !== 'openai' && selectedProvider !== 'codex-lb') {
48
67
  throw new Error('codex_lb_user_owned_model_provider_conflict');
@@ -89,7 +108,9 @@ export function upsertCodexLbCliProviderConfig(text, input) {
89
108
  if (topLevelHasLine(cleaned, LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER)) {
90
109
  throw new Error('codex_lb_legacy_desktop_config_requires_migration');
91
110
  }
92
- const cliBase = removeCodexLbManagedDesktopConfig(cleaned);
111
+ const cliBase = input.selectGlobally === true
112
+ ? releaseSksManagedThirdPartySelection(removeCodexLbManagedDesktopConfig(cleaned))
113
+ : removeCodexLbManagedDesktopConfig(cleaned);
93
114
  const claimedSelection = input.selectGlobally === true
94
115
  && topLevelTomlString(cliBase, 'model_provider') === 'codex-lb'
95
116
  && !topLevelHasLine(cliBase, CODEX_LB_PROVIDER_SELECTION_MARKER)
@@ -161,7 +182,7 @@ function cliProviderBlock(remoteBaseUrl) {
161
182
  'name = "codex-lb"',
162
183
  `base_url = ${JSON.stringify(remoteBaseUrl)}`,
163
184
  'wire_api = "responses"',
164
- 'env_http_headers = { "X-Codex-LB-API-Key" = "CODEX_LB_API_KEY" }',
185
+ 'env_key = "CODEX_LB_API_KEY"',
165
186
  'supports_websockets = true',
166
187
  'requires_openai_auth = false'
167
188
  ].join('\n');
@@ -28,7 +28,7 @@ import { canAskYesNo, compareVersions, hasCodexUnstableFeatureWarningSuppression
28
28
  import { printCodexLbSetupWarnings } from './codex-lb-setup-warning-output.js';
29
29
  import { checkCodexLbResponseChain } from './install-helpers-codex-lb-chain.js';
30
30
  import { CODEX_LB_CANONICAL_FAST_SERVICE_TIER, CODEX_LB_PROVIDER_ENV_KEY, askPostinstallQuestion, codexAuthChatgptBackupPath, codexAuthPath, codexLbConfigPath, codexLbEnvPath, hasTopLevelCodexLbSelected, normalizeCodexLbBaseUrl, parseCodexLbEnvKey, redactSecretText } from './install-helpers-codex-lb-shared.js';
31
- import { CODEX_LB_DESKTOP_BRIDGE_MARKER, CODEX_LB_DESKTOP_COMPAT_MARKER, CODEX_LB_PROVIDER_SELECTION_MARKER, appliedCodexLbPersistenceModes, captureCodexLbSetupWriteState, codexLbSharedOpenAiRoutingState, detectCodexLbSetupDrift, ensureGlobalCodexAppGlmProfile, ensureStoredOpenRouterProviderDuringInstall, hardenCodexLbSetupRecoveryPath, removeCodexLbSetupRecoveryPath, removeCodexLbOrphanManagedMarkers, removeCodexLbSharedOpenAiRouting, removeCodexLbManagedDesktopConfig, restoreCodexLbSetupWriteState, sha256Text, shellSingleQuote, upsertCodexAppGlmConfig, upsertCodexLbConfig, upsertCodexLbCliProviderConfig, upsertCodexLbCompatDesktopConfig, upsertCodexLbNativeDesktopConfig, upsertCodexLbSharedOpenAiRouting, writeCodexLbSetupFileIfUnchanged } from './install-helpers-codex-lb-config.js';
31
+ import { CODEX_LB_DESKTOP_BRIDGE_MARKER, CODEX_LB_DESKTOP_COMPAT_MARKER, CODEX_LB_PROVIDER_SELECTION_MARKER, appliedCodexLbPersistenceModes, captureCodexLbSetupWriteState, codexLbSharedOpenAiRoutingState, detectCodexLbSetupDrift, ensureGlobalCodexAppGlmProfile, ensureStoredOpenRouterProviderDuringInstall, hardenCodexLbSetupRecoveryPath, removeCodexLbSetupRecoveryPath, removeCodexLbOrphanManagedMarkers, removeCodexLbSharedOpenAiRouting, releaseSksManagedThirdPartySelection, removeCodexLbManagedDesktopConfig, restoreCodexLbSetupWriteState, sha256Text, shellSingleQuote, upsertCodexAppGlmConfig, upsertCodexLbConfig, upsertCodexLbCliProviderConfig, upsertCodexLbCompatDesktopConfig, upsertCodexLbNativeDesktopConfig, upsertCodexLbSharedOpenAiRouting, writeCodexLbSetupFileIfUnchanged } from './install-helpers-codex-lb-config.js';
32
32
  import { detectLegacyCodexLbDesktopState } from '../core/codex-lb/legacy-migration.js';
33
33
  import { ensureCodexImagegenDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureGlobalContext7DuringInstall, ensureGlobalGetdesignSkillDuringInstall, ensureSksCommandDuringInstall } from './install-helpers-install-support.js';
34
34
  import { escapeRegExp } from '../core/text/regex.js';
@@ -416,7 +416,7 @@ export async function configureCodexLbDesktopRouting(opts) {
416
416
  })
417
417
  : mode === 'desktop-dual-auth-compat'
418
418
  ? upsertCodexLbCompatDesktopConfig(current, { remoteBaseUrl: String(opts.remoteBaseUrl) })
419
- : removeCodexLbManagedDesktopConfig(current);
419
+ : releaseSksManagedThirdPartySelection(removeCodexLbManagedDesktopConfig(current));
420
420
  }
421
421
  catch (error) {
422
422
  return {
@@ -494,10 +494,38 @@ export async function configureCodexLbCliProvider(opts) {
494
494
  const authPath = opts.authPath || codexAuthPath(home);
495
495
  const authBefore = await captureCodexAuthSnapshot({ home, authPath });
496
496
  const current = await readText(configPath, '');
497
- const next = upsertCodexLbCliProviderConfig(current, {
498
- remoteBaseUrl: opts.remoteBaseUrl,
499
- ...(opts.selectGlobally === undefined ? {} : { selectGlobally: opts.selectGlobally })
500
- });
497
+ let next;
498
+ try {
499
+ next = upsertCodexLbCliProviderConfig(current, {
500
+ remoteBaseUrl: opts.remoteBaseUrl,
501
+ ...(opts.selectGlobally === undefined ? {} : { selectGlobally: opts.selectGlobally })
502
+ });
503
+ }
504
+ catch (error) {
505
+ const message = error instanceof Error ? error.message : String(error);
506
+ return {
507
+ schema: 'sks.codex-lb-cli-provider.v1',
508
+ ok: false,
509
+ status: message,
510
+ mode: 'cli-provider',
511
+ identity_plane: 'unchanged',
512
+ routing_plane: 'cli_provider',
513
+ oauth_preserved: true,
514
+ auth_mutated: false,
515
+ config_path: configPath,
516
+ auth_path: authPath,
517
+ auth_before: authBefore,
518
+ auth_after: authBefore,
519
+ blockers: [message],
520
+ guidance: message === 'codex_lb_user_owned_model_provider_conflict'
521
+ ? [
522
+ 'config.toml has a model_provider line SKS does not manage.',
523
+ 'Remove or change that line yourself, then retry Use Codex LB.'
524
+ ]
525
+ : [],
526
+ backup_path: null
527
+ };
528
+ }
501
529
  const write = await safeWriteCodexConfigToml(configPath, current, next, 'codex-lb-cli-provider', { verifyUnchangedBeforeWrite: true });
502
530
  const authAfter = await captureCodexAuthSnapshot({ home, authPath });
503
531
  try {
@@ -1893,9 +1921,8 @@ function codexLbProviderEnvKey(text = '') {
1893
1921
  }
1894
1922
  function codexLbProviderHasSeparateGatewayAuth(text = '') {
1895
1923
  const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
1896
- return /X-Codex-LB-API-Key/.test(block)
1897
- && /CODEX_LB_API_KEY/.test(block)
1898
- && !/(^|\n)\s*env_key\s*=/.test(block);
1924
+ return /(^|\n)\s*env_key\s*=\s*"CODEX_LB_API_KEY"\s*(?:#.*)?(?=\n|$)/.test(block)
1925
+ && !/X-Codex-LB-API-Key/.test(block);
1899
1926
  }
1900
1927
  function codexLbFastModeConfigStatus(text = '') {
1901
1928
  const globalServiceTier = topLevelTomlString(text, 'service_tier');
@@ -67,7 +67,10 @@ export async function run(command, args = []) {
67
67
  baseUrl: loadedEnv.base_url,
68
68
  apiKey: loadedEnv.secret_api_key,
69
69
  credentialBindingBlockers: loadedEnv.credential_binding.blockers,
70
- gatewayAuthTransport: options.gatewayAuthTransport || loadedEnv.gateway_auth_transport || undefined
70
+ gatewayAuthTransport: options.gatewayAuthTransport
71
+ || (directGatewayProbeTransport(status, loadedEnv) === 'x-codex-lb-api-key'
72
+ ? 'x-codex-lb-api-key'
73
+ : 'authorization-bearer-compat')
71
74
  });
72
75
  if (!result.ok)
73
76
  process.exitCode = 1;
@@ -110,7 +113,7 @@ export async function run(command, args = []) {
110
113
  selected: status.selected === true,
111
114
  baseUrl: loadedEnv.base_url,
112
115
  apiKey: loadedEnv.secret_api_key,
113
- authTransport: 'x-codex-lb-api-key'
116
+ authTransport: directGatewayProbeTransport(status, loadedEnv)
114
117
  }, {
115
118
  home: path.dirname(path.dirname(status.env_path))
116
119
  });
@@ -149,7 +152,7 @@ export async function run(command, args = []) {
149
152
  selected: status.selected === true,
150
153
  baseUrl: loadedEnv.base_url,
151
154
  apiKey: loadedEnv.secret_api_key,
152
- authTransport: 'x-codex-lb-api-key'
155
+ authTransport: directGatewayProbeTransport(status, loadedEnv)
153
156
  }, {
154
157
  home: path.dirname(path.dirname(status.env_path))
155
158
  });
@@ -428,6 +431,25 @@ export async function run(command, args = []) {
428
431
  }
429
432
  if (action === 'setup' || action === 'reconfigure') {
430
433
  const options = await codexLbSetupOptions(args);
434
+ if (options.desktopMode === 'cli-provider' && options.gatewayAuthTransport === 'x-codex-lb-api-key') {
435
+ const result = {
436
+ schema: 'sks.codex-lb-setup.v1',
437
+ ok: false,
438
+ status: 'custom_header_transport_requires_desktop_bridge',
439
+ guidance: [
440
+ 'The atomic CLI provider always sends Authorization: Bearer (Codex env_key).',
441
+ 'For a gateway that only accepts the X-Codex-LB-API-Key header, use Desktop Bridge mode:',
442
+ 'sks codex-lb setup --host <domain> --desktop-mode desktop-full --gateway-auth custom-header --api-key-stdin --yes'
443
+ ]
444
+ };
445
+ if (flag(args, '--json')) {
446
+ process.exitCode = 1;
447
+ return printJson(result);
448
+ }
449
+ console.error('codex-lb setup rejected: --gateway-auth custom-header only applies to Desktop Bridge mode; the CLI provider always uses Authorization: Bearer.');
450
+ process.exitCode = 1;
451
+ return;
452
+ }
431
453
  const plan = buildCodexLbSetupPlan({
432
454
  host_or_base_url: options.host || '',
433
455
  api_key_source: options.apiKeySource,
@@ -546,13 +568,6 @@ export async function run(command, args = []) {
546
568
  gatewayAuthTransport: options.gatewayAuthTransport
547
569
  });
548
570
  }
549
- else if (result.ok && options.desktopMode === 'desktop-dual-auth-compat') {
550
- desktopActivation = await activateCodexLbDesktopMode({
551
- ...controllerOptions(args),
552
- mode: 'desktop-dual-auth-compat',
553
- gatewayAuthTransport: 'x-codex-lb-api-key'
554
- });
555
- }
556
571
  const capabilities = options.health && result.ok
557
572
  ? await buildCodexLbDesktopCapabilities({
558
573
  ...controllerOptions(args),
@@ -566,7 +581,7 @@ export async function run(command, args = []) {
566
581
  selected: result.codex_lb?.selected === true,
567
582
  baseUrl: result.base_url || null,
568
583
  apiKey: options.apiKey,
569
- authTransport: 'x-codex-lb-api-key'
584
+ authTransport: 'authorization-bearer'
570
585
  }, {
571
586
  home: path.dirname(path.dirname(result.env_path))
572
587
  })
@@ -1043,19 +1058,26 @@ function normalizeDesktopSetupMode(value) {
1043
1058
  }
1044
1059
  throw new Error(`unsupported_codex_lb_desktop_mode:${normalized}`);
1045
1060
  }
1061
+ function directGatewayProbeTransport(status, loadedEnv) {
1062
+ return status?.desktop_mode === 'desktop-native-bridge'
1063
+ && loadedEnv?.gateway_auth_transport === 'x-codex-lb-api-key'
1064
+ ? 'x-codex-lb-api-key'
1065
+ : 'authorization-bearer';
1066
+ }
1046
1067
  function normalizeGatewayAuthChoice(value) {
1047
1068
  const normalized = String(value || '').trim().toLowerCase();
1048
1069
  if (!normalized
1049
- || normalized === 'custom-header'
1050
- || normalized === 'header'
1051
- || normalized === 'x-codex-lb-api-key') {
1052
- return 'x-codex-lb-api-key';
1053
- }
1054
- if (normalized === 'bearer'
1070
+ || normalized === 'bearer'
1055
1071
  || normalized === 'bearer-compat'
1072
+ || normalized === 'authorization-bearer'
1056
1073
  || normalized === 'authorization-bearer-compat') {
1057
1074
  return 'authorization-bearer-compat';
1058
1075
  }
1076
+ if (normalized === 'custom-header'
1077
+ || normalized === 'header'
1078
+ || normalized === 'x-codex-lb-api-key') {
1079
+ return 'x-codex-lb-api-key';
1080
+ }
1059
1081
  throw new Error(`unsupported_codex_lb_gateway_auth_transport:${normalized}`);
1060
1082
  }
1061
1083
  function asRecord(value) {
@@ -1088,7 +1110,7 @@ async function codexLbSetupOptions(args = []) {
1088
1110
  let desktopMode = normalizeDesktopSetupMode(readOption(args, '--desktop-mode', readOption(args, '--mode', 'cli-provider')));
1089
1111
  let gatewayAuthTransport = parseCodexLbGatewayAuthTransport(normalizeGatewayAuthChoice(flag(args, '--compat-bearer')
1090
1112
  ? 'authorization-bearer-compat'
1091
- : readOption(args, '--gateway-auth', readOption(args, '--gateway-auth-transport', 'x-codex-lb-api-key'))));
1113
+ : readOption(args, '--gateway-auth', readOption(args, '--gateway-auth-transport', 'bearer-compat'))));
1092
1114
  let keychain = flag(args, '--keychain');
1093
1115
  if (flag(args, '--api-key-stdin'))
1094
1116
  apiKey = (await readStdin()).trim();
@@ -1112,13 +1134,13 @@ async function codexLbSetupOptions(args = []) {
1112
1134
  host ||= (await ask('1. codex-lb domain or base URL?\n Example: lb.example.com or https://lb.example.com/backend-api/codex\n> ')).trim();
1113
1135
  apiKey ||= (await askHidden('2. API key?\n Input hidden. Value will be stored securely and never printed.\n> ')).trim();
1114
1136
  apiKeySource = 'hidden_prompt';
1115
- desktopMode = normalizeDesktopSetupMode((await ask('3. Configure which mode? [desktop-full/cli-only] (desktop-full)\n> ')).trim()
1116
- || 'desktop-full');
1137
+ desktopMode = normalizeDesktopSetupMode((await ask('3. Configure which mode? [cli-only/desktop-full] (cli-only)\n> ')).trim()
1138
+ || 'cli-only');
1117
1139
  if (desktopMode === 'desktop-native-bridge') {
1118
- gatewayAuthTransport = parseCodexLbGatewayAuthTransport(normalizeGatewayAuthChoice((await ask('4. Gateway key transport? [custom-header/bearer-compat] (custom-header)\n> ')).trim()));
1140
+ gatewayAuthTransport = parseCodexLbGatewayAuthTransport(normalizeGatewayAuthChoice((await ask('4. Gateway key transport? [bearer-compat/custom-header] (bearer-compat)\n> ')).trim()));
1119
1141
  }
1120
1142
  else {
1121
- gatewayAuthTransport = 'x-codex-lb-api-key';
1143
+ gatewayAuthTransport = 'authorization-bearer-compat';
1122
1144
  }
1123
1145
  writeEnvFile = parseYesNo(await ask('5. Write shell env loader to ~/.codex/sks-codex-lb.env? [Y/n] '), true);
1124
1146
  keychain = false;
@@ -580,7 +580,7 @@ export async function codexLbRoutingTruthForStatus(status, deps = {}) {
580
580
  const baseUrl = loaded.base_url || status.base_url || null;
581
581
  const configuredHost = publicUrlHost(baseUrl);
582
582
  const authTransport = mode === 'cli-provider'
583
- ? 'x-codex-lb-api-key'
583
+ ? 'authorization-bearer'
584
584
  : doctorBridgeRoutingTransport(status, receipt);
585
585
  const contextReceipt = await readCodexLbRoutingTruthReceipt({
586
586
  ...receiptOptions,
@@ -609,7 +609,7 @@ export async function codexLbRoutingTruthForStatus(status, deps = {}) {
609
609
  baseUrl: status.base_url || null,
610
610
  apiKey: null,
611
611
  authTransport: mode === 'cli-provider'
612
- ? 'x-codex-lb-api-key'
612
+ ? 'authorization-bearer'
613
613
  : doctorBridgeRoutingTransport(status, receipt),
614
614
  measure: false
615
615
  }, {
@@ -627,9 +627,9 @@ function doctorBridgeRoutingTransport(status, receipt) {
627
627
  || receipt.auth_transport === 'x-codex-lb-api-key')) {
628
628
  return receipt.auth_transport;
629
629
  }
630
- return status?.gateway_auth_transport === 'authorization-bearer-compat'
631
- ? 'authorization-bearer'
632
- : 'x-codex-lb-api-key';
630
+ return status?.gateway_auth_transport === 'x-codex-lb-api-key'
631
+ ? 'x-codex-lb-api-key'
632
+ : 'authorization-bearer';
633
633
  }
634
634
  function publicUrlHost(value) {
635
635
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "8.1.0",
3
+ "package_version": "8.1.2",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -943,8 +943,8 @@ export async function codexProviderModelUiStatus(opts = {}) {
943
943
  const codexLbCliProviderContractOk = codexLbProviderPresent
944
944
  && hasTomlString(codexLbProvider, 'name', 'codex-lb')
945
945
  && hasTomlString(codexLbProvider, 'wire_api', 'responses')
946
- && /"X-Codex-LB-API-Key"\s*=\s*"CODEX_LB_API_KEY"/.test(codexLbProvider)
947
- && !/(?:^|\n)\s*env_key\s*=/.test(codexLbProvider)
946
+ && /(?:^|\n)\s*env_key\s*=\s*"CODEX_LB_API_KEY"\s*(?:#.*)?(?=\n|$)/.test(codexLbProvider)
947
+ && !/X-Codex-LB-API-Key/.test(codexLbProvider)
948
948
  && hasTomlBoolean(codexLbProvider, 'supports_websockets', true)
949
949
  && hasTomlBoolean(codexLbProvider, 'requires_openai_auth', false);
950
950
  const codexLbSelectedDefault = /(?:^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevelToml(globalConfig));
@@ -1185,9 +1185,9 @@ function inferCodexLbDesktopModeForUi(config) {
1185
1185
  : 'disabled';
1186
1186
  }
1187
1187
  function normalizeCodexLbGatewayAuthTransportForUi(value) {
1188
- return value === 'authorization-bearer-compat'
1189
- ? 'authorization-bearer-compat'
1190
- : 'x-codex-lb-api-key';
1188
+ return value === 'x-codex-lb-api-key'
1189
+ ? 'x-codex-lb-api-key'
1190
+ : 'authorization-bearer-compat';
1191
1191
  }
1192
1192
  async function findDefaultPluginSource(plugin, { home, configText }) {
1193
1193
  const cached = await findPluginCache(plugin.name, { home });
@@ -595,7 +595,7 @@ async function ensurePythonCodexLbConfig(env, config) {
595
595
  'name = "codex-lb"',
596
596
  `base_url = ${tomlQuote(lbBaseUrl)}`,
597
597
  'wire_api = "responses"',
598
- 'env_http_headers = { "X-Codex-LB-API-Key" = "CODEX_LB_API_KEY" }',
598
+ 'env_key = "CODEX_LB_API_KEY"',
599
599
  'supports_websockets = true',
600
600
  'requires_openai_auth = false',
601
601
  ''
@@ -58,10 +58,10 @@ export async function readCodexLbModelCatalog(opts = {}) {
58
58
  };
59
59
  }
60
60
  const fetchImpl = opts.fetchImpl || fetch;
61
- const gatewayAuthTransport = opts.gatewayAuthTransport || 'x-codex-lb-api-key';
62
- const gatewayHeaders = gatewayAuthTransport === 'authorization-bearer-compat'
63
- ? { Authorization: `Bearer ${loaded.secret_api_key}` }
64
- : { 'X-Codex-LB-API-Key': loaded.secret_api_key };
61
+ const gatewayAuthTransport = opts.gatewayAuthTransport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT;
62
+ const gatewayHeaders = gatewayAuthTransport === 'x-codex-lb-api-key'
63
+ ? { 'X-Codex-LB-API-Key': loaded.secret_api_key }
64
+ : { Authorization: `Bearer ${loaded.secret_api_key}` };
65
65
  try {
66
66
  const response = await fetchImpl(`${loaded.base_url}/models`, {
67
67
  headers: gatewayHeaders,
@@ -119,11 +119,11 @@ export async function codexLbDesktopStatusV2(options = {}) {
119
119
  const routingActive = configuredRoutingActive
120
120
  && codexLbRoutingTruthIsActive(routingTruth);
121
121
  const diagnosticOk = uniqueBlockers.length === 0 && !overallBlocked;
122
- const effectiveGatewayAuthTransport = context.mode === 'cli-provider'
123
- ? 'x-codex-lb-api-key'
124
- : context.mode === 'desktop-native-bridge' || context.mode === 'desktop-dual-auth-compat'
125
- ? context.gatewayAuthTransport
126
- : null;
122
+ const effectiveGatewayAuthTransport = context.mode === 'disabled'
123
+ ? null
124
+ : context.mode === 'cli-provider'
125
+ ? 'authorization-bearer-compat'
126
+ : context.gatewayAuthTransport;
127
127
  return {
128
128
  schema: CODEX_LB_STATUS_SCHEMA_V2,
129
129
  ok: diagnosticOk && routingActive,
@@ -194,7 +194,8 @@ export async function activateCodexLbDesktopMode(input) {
194
194
  mode: input.mode,
195
195
  identity_plane: 'unchanged',
196
196
  routing_plane: 'unchanged',
197
- gateway_auth_transport: 'x-codex-lb-api-key',
197
+ gateway_auth_transport: input.gatewayAuthTransport
198
+ || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT,
198
199
  oauth_preserved: true,
199
200
  bridge_started: false,
200
201
  config_committed: false,
@@ -538,12 +539,13 @@ export async function disableCodexLbDesktopRouting(input = {}) {
538
539
  export async function configureCodexLbCliMode(input = {}) {
539
540
  const context = await loadDesktopContext(input);
540
541
  const selectedBefore = topLevelTomlString(context.config, 'model_provider') === 'codex-lb';
542
+ const cliAuthTransport = routingTruthAuthTransport('cli-provider', context.gatewayAuthTransport);
541
543
  const routingTruth = await measureAndWriteCodexLbRoutingTruth({
542
544
  mode: 'cli-provider',
543
545
  selected: true,
544
546
  baseUrl: context.loadedEnv.base_url,
545
547
  apiKey: context.loadedEnv.secret_api_key,
546
- authTransport: 'x-codex-lb-api-key',
548
+ authTransport: cliAuthTransport,
547
549
  ...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}),
548
550
  ...(input.capabilityTimeoutMs ? { timeoutMs: input.capabilityTimeoutMs } : {})
549
551
  }, {
@@ -725,6 +727,7 @@ export async function buildCodexLbDesktopCapabilities(input = {}) {
725
727
  baseUrl: target.baseUrl,
726
728
  apiKey: context.loadedEnv.secret_api_key || '',
727
729
  model,
730
+ gatewayAuthTransport: target.effectiveTransport,
728
731
  fetchImpl: input.fetchImpl || globalThis.fetch,
729
732
  ...(envTimeout > 0 ? { timeoutMs: envTimeout } : {})
730
733
  });
@@ -752,11 +755,11 @@ export async function buildCodexLbDesktopCapabilities(input = {}) {
752
755
  oauthPreserved: context.oauthPresent,
753
756
  manifest,
754
757
  gatewayAuth: {
755
- transport: cliPlane ? 'x-codex-lb-api-key' : context.gatewayAuthTransport,
758
+ transport: cliPlane ? 'authorization-bearer-compat' : context.gatewayAuthTransport,
756
759
  configured: context.loadedEnv.configured,
757
760
  observed: gatewayObserved,
758
761
  fixture,
759
- legacyCompatibilityExplicit: context.gatewayAuthTransport === 'authorization-bearer-compat',
762
+ legacyCompatibilityExplicit: false,
760
763
  blockers: [
761
764
  ...(gatewayAuthRejected
762
765
  ? [`codex_lb_gateway_auth_rejected_for_transport:${target?.effectiveTransport || context.gatewayAuthTransport}`]
@@ -1189,8 +1192,8 @@ function providerStatus(config, mode) {
1189
1192
  const cliContractOk = Boolean(provider)
1190
1193
  && hasTomlString(provider, 'name', 'codex-lb')
1191
1194
  && hasTomlString(provider, 'wire_api', 'responses')
1192
- && /"X-Codex-LB-API-Key"\s*=\s*"CODEX_LB_API_KEY"/.test(provider)
1193
- && !/(?:^|\n)\s*env_key\s*=/.test(provider)
1195
+ && /(?:^|\n)\s*env_key\s*=\s*"CODEX_LB_API_KEY"\s*$/m.test(provider)
1196
+ && !/"X-Codex-LB-API-Key"\s*=\s*"CODEX_LB_API_KEY"/.test(provider)
1194
1197
  && hasTomlBoolean(provider, 'requires_openai_auth', false)
1195
1198
  && hasTomlBoolean(provider, 'supports_websockets', true);
1196
1199
  if (mode === 'cli-provider') {
@@ -1477,9 +1480,10 @@ function capabilityTarget(context) {
1477
1480
  effectiveTransport: context.gatewayAuthTransport
1478
1481
  };
1479
1482
  }
1480
- const effectiveTransport = context.mode === 'cli-provider'
1483
+ const effectiveTransport = context.mode !== 'cli-provider'
1484
+ && context.gatewayAuthTransport === 'x-codex-lb-api-key'
1481
1485
  ? 'x-codex-lb-api-key'
1482
- : context.gatewayAuthTransport;
1486
+ : 'authorization-bearer-compat';
1483
1487
  const headers = effectiveTransport === 'x-codex-lb-api-key'
1484
1488
  ? { 'X-Codex-LB-API-Key': context.loadedEnv.secret_api_key }
1485
1489
  : { Authorization: `Bearer ${context.loadedEnv.secret_api_key}` };
@@ -1651,7 +1655,20 @@ function statusGuidance(mode, configured, blockers) {
1651
1655
  `The gateway rejected the configured auth transport (${rejectedTransport}).`,
1652
1656
  rejectedTransport === 'x-codex-lb-api-key'
1653
1657
  ? 'If the gateway expects an Authorization header, re-run setup with: --gateway-auth bearer-compat'
1654
- : 'If the gateway expects the custom header, re-run setup with: --gateway-auth custom-header'
1658
+ : 'If the gateway expects the custom header, use Desktop Bridge mode with: --gateway-auth custom-header (the atomic CLI provider is always Authorization: Bearer)'
1659
+ ];
1660
+ }
1661
+ if (blockers.includes('codex_lb_auth_rejected')) {
1662
+ return [
1663
+ 'The gateway rejected the API key (HTTP 401/403) over Authorization: Bearer.',
1664
+ 'Reconnect the Codex LB credential in Center, or re-run: sks codex-lb setup --host <domain> --api-key-stdin --yes',
1665
+ 'Then re-verify with: sks codex-lb connect-test'
1666
+ ];
1667
+ }
1668
+ if (blockers.includes('codex_lb_endpoint_unreachable')) {
1669
+ return [
1670
+ 'The gateway host could not be reached (network, DNS, or TLS).',
1671
+ 'Verify the host, then re-run: sks codex-lb connect-test'
1655
1672
  ];
1656
1673
  }
1657
1674
  if (mode === 'desktop-native-bridge') {
@@ -1697,11 +1714,11 @@ function desktopCompatModeUnavailable(mode) {
1697
1714
  return mode === 'desktop-dual-auth-compat';
1698
1715
  }
1699
1716
  function routingTruthAuthTransport(mode, gatewayAuthTransport) {
1700
- if (mode === 'cli-provider'
1701
- || (mode === 'desktop-native-bridge' && gatewayAuthTransport === 'x-codex-lb-api-key')) {
1702
- return 'x-codex-lb-api-key';
1703
- }
1704
- return 'authorization-bearer';
1717
+ if (mode !== 'desktop-native-bridge')
1718
+ return 'authorization-bearer';
1719
+ return gatewayAuthTransport === 'x-codex-lb-api-key'
1720
+ ? 'x-codex-lb-api-key'
1721
+ : 'authorization-bearer';
1705
1722
  }
1706
1723
  function publicHost(baseUrl) {
1707
1724
  try {
@@ -9,7 +9,7 @@ export const CODEX_LB_GATEWAY_AUTH_TRANSPORTS = [
9
9
  'authorization-bearer-compat'
10
10
  ];
11
11
  export const DEFAULT_CODEX_LB_DESKTOP_MODE = 'desktop-native-bridge';
12
- export const DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT = 'x-codex-lb-api-key';
12
+ export const DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT = 'authorization-bearer-compat';
13
13
  export function parseCodexLbDesktopMode(value) {
14
14
  const normalized = String(value ?? '').trim();
15
15
  if (CODEX_LB_DESKTOP_MODES.includes(normalized)) {
@@ -48,28 +48,23 @@ export function runBridgeProbe(input) {
48
48
  }
49
49
  export function gatewayAuthTransportEvidence(input) {
50
50
  const transport = input.transport || 'unknown';
51
- const preferred = transport === 'x-codex-lb-api-key';
52
- const standardBearer = transport === 'authorization-bearer';
53
- const legacyExplicit = transport === 'authorization-bearer-compat'
54
- && input.legacyCompatibilityExplicit === true;
51
+ const preferredBearer = transport === 'authorization-bearer-compat'
52
+ || transport === 'authorization-bearer';
53
+ const customHeader = transport === 'x-codex-lb-api-key';
55
54
  return probeEvidence({
56
55
  configured: input.configured,
57
56
  attempted: input.observed,
58
- verified: input.observed === true && (preferred || standardBearer || legacyExplicit),
57
+ verified: input.observed === true && (preferredBearer || customHeader),
59
58
  fixture: input.fixture,
60
59
  source: input.observed ? 'transport' : 'config',
61
- blockers: [
62
- ...(input.blockers || []),
63
- ...(transport === 'authorization-bearer-compat' && !input.legacyCompatibilityExplicit
64
- ? ['legacy_gateway_auth_compatibility_not_explicit']
65
- : [])
66
- ],
67
- warnings: legacyExplicit ? ['legacy_authorization_bearer_compatibility_active'] : [],
60
+ blockers: [...(input.blockers || [])],
61
+ warnings: customHeader ? ['custom_x_codex_lb_api_key_transport_active'] : [],
68
62
  evidence: {
69
63
  configured_gateway_auth_transport: transport,
70
- preferred_custom_header: preferred,
71
- standard_authorization_bearer: standardBearer,
72
- legacy_authorization_bearer_compatibility: legacyExplicit,
64
+ preferred_custom_header: customHeader,
65
+ preferred_authorization_bearer: preferredBearer,
66
+ standard_authorization_bearer: transport === 'authorization-bearer',
67
+ legacy_authorization_bearer_compatibility: false,
73
68
  silent_fallback: false,
74
69
  observed: input.observed === true
75
70
  }
@@ -18,10 +18,13 @@ export async function probeCodexLbCliImageGeneration(input) {
18
18
  }
19
19
  const endpoint = `${String(input.baseUrl || '').replace(/\/+$/, '')}/responses`;
20
20
  const timeoutMs = input.timeoutMs || DEFAULT_CLI_IMAGE_PROBE_TIMEOUT_MS;
21
- const forced = await postImageProbe(fetchImpl, endpoint, input.apiKey, input.model, timeoutMs, true);
21
+ const authHeaders = input.gatewayAuthTransport === 'x-codex-lb-api-key'
22
+ ? { 'X-Codex-LB-API-Key': input.apiKey }
23
+ : { authorization: `Bearer ${input.apiKey}` };
24
+ const forced = await postImageProbe(fetchImpl, endpoint, authHeaders, input.model, timeoutMs, true);
22
25
  const forcedToolChoiceRejected = !forced.ok && /tool_choice/.test(String(forced.errorText || ''));
23
26
  const attempt = forcedToolChoiceRejected
24
- ? await postImageProbe(fetchImpl, endpoint, input.apiKey, input.model, timeoutMs, false)
27
+ ? await postImageProbe(fetchImpl, endpoint, authHeaders, input.model, timeoutMs, false)
25
28
  : forced;
26
29
  if (attempt.timedOut) {
27
30
  return result({
@@ -69,14 +72,14 @@ export async function probeCodexLbCliImageGeneration(input) {
69
72
  error: null
70
73
  });
71
74
  }
72
- async function postImageProbe(fetchImpl, endpoint, apiKey, model, timeoutMs, forceTool) {
75
+ async function postImageProbe(fetchImpl, endpoint, authHeaders, model, timeoutMs, forceTool) {
73
76
  const controller = new AbortController();
74
77
  const timer = setTimeout(() => controller.abort(), timeoutMs);
75
78
  try {
76
79
  const response = await fetchImpl(endpoint, {
77
80
  method: 'POST',
78
81
  headers: {
79
- 'X-Codex-LB-API-Key': apiKey,
82
+ ...authHeaders,
80
83
  'content-type': 'application/json'
81
84
  },
82
85
  body: JSON.stringify({
@@ -599,7 +599,7 @@ export async function generateGptImage2CalloutReview(input, opts = {}) {
599
599
  codexLb: allowCodexLbApiFallback ? opts.openai?.codexLb || capability?.codex_lb || null : null,
600
600
  codexLbTarget,
601
601
  codexLbAuthTransport: capability?.codex_lb?.cli_contract === true
602
- ? 'x-codex-lb-api-key'
602
+ ? 'authorization-bearer'
603
603
  : codexLbTarget?.auth_transport,
604
604
  allowCodexLbApiFallback
605
605
  };
@@ -689,9 +689,7 @@ function codexLbImagesApiAuth(opts, codexLb, target) {
689
689
  return {
690
690
  apiKey: codexLbKey || null,
691
691
  auth_source: envKey,
692
- auth_transport: opts.codexLbAuthTransport || target?.auth_transport || (codexLb?.cli_contract === true
693
- ? 'x-codex-lb-api-key'
694
- : 'authorization-bearer'),
692
+ auth_transport: opts.codexLbAuthTransport || target?.auth_transport || 'authorization-bearer',
695
693
  api_key_source: target?.api_key_source || null,
696
694
  responses_model: responsesImagegenModel(opts) || target?.model || '',
697
695
  responses_model_source: responsesImagegenModel(opts) ? 'explicit' : target?.model_source || null,
@@ -109,12 +109,12 @@ async function detectCodexLbImagegenAuth(opts = {}, env = process.env) {
109
109
  const requiresOpenAiAuth = tomlBoolean(block, 'requires_openai_auth');
110
110
  const bearerEnvKey = tomlString(block, 'env_key');
111
111
  const gatewayHeaderEnvKey = tomlEnvHttpHeader(block, 'X-Codex-LB-API-Key');
112
- const envKey = gatewayHeaderEnvKey || bearerEnvKey;
112
+ const envKey = bearerEnvKey || gatewayHeaderEnvKey;
113
113
  const cliContract = providerConfigured
114
114
  && tomlString(block, 'name') === 'codex-lb'
115
115
  && tomlString(block, 'wire_api') === 'responses'
116
- && gatewayHeaderEnvKey === 'CODEX_LB_API_KEY'
117
- && !bearerEnvKey
116
+ && bearerEnvKey === 'CODEX_LB_API_KEY'
117
+ && !gatewayHeaderEnvKey
118
118
  && requiresOpenAiAuth === false;
119
119
  const baseUrl = tomlString(block, 'base_url') || String(env.CODEX_LB_BASE_URL || '').trim();
120
120
  const envPath = opts.codexLbEnvPath || path.join(codexHome, 'sks-codex-lb.env');
@@ -147,9 +147,7 @@ async function detectCodexLbImagegenAuth(opts = {}, env = process.env) {
147
147
  receiptPath: routingTruthReceiptPath
148
148
  }).catch(() => null);
149
149
  const configuredHost = publicUrlHost(baseUrl);
150
- const routingAuthTransport = cliContract
151
- ? 'x-codex-lb-api-key'
152
- : 'authorization-bearer';
150
+ const routingAuthTransport = 'authorization-bearer';
153
151
  const routingTruthContextMatches = routingTruth?.selected === selected
154
152
  && routingTruth?.configured_host === configuredHost
155
153
  && routingTruth?.auth_transport === routingAuthTransport;
package/dist/core/init.js CHANGED
@@ -695,7 +695,7 @@ export async function initProject(root, opts = {}) {
695
695
  next = shouldSelectCodexLb
696
696
  ? upsertTopLevelTomlString(next, 'model_provider', 'codex-lb')
697
697
  : removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
698
- next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "codex-lb"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_http_headers = { "X-Codex-LB-API-Key" = "CODEX_LB_API_KEY" }\nsupports_websockets = true\nrequires_openai_auth = false`);
698
+ next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "codex-lb"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = false`);
699
699
  return `${next.trim()}\n`;
700
700
  }
701
701
  function parseCodexLbEnvKey(text = '') {
@@ -14,8 +14,8 @@ export async function resolveProviderContext(input = {}) {
14
14
  const codexLbEnvKey = codexLbGatewayHeaderEnvKey || codexLbBearerEnvKey;
15
15
  const codexLbRequiresOpenAiAuth = codexLbProviderRequiresOpenAiAuth(configText);
16
16
  const codexLbProviderValid = codexLbProviderBlockPresent
17
- && codexLbGatewayHeaderEnvKey === 'CODEX_LB_API_KEY'
18
- && codexLbBearerEnvKey === null
17
+ && codexLbBearerEnvKey === 'CODEX_LB_API_KEY'
18
+ && codexLbGatewayHeaderEnvKey === null
19
19
  && codexLbRequiresOpenAiAuth === false;
20
20
  const openaiKey = Boolean(String(env.OPENAI_API_KEY || '').trim());
21
21
  const lbKey = Boolean(String((codexLbEnvKey ? env[codexLbEnvKey] : env.CODEX_LB_API_KEY) || env.CODEX_LB_API_KEY || '').trim());
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '8.1.0';
1
+ export const PACKAGE_VERSION = '8.1.2';
@@ -79,6 +79,7 @@ extension ProvidersViewController {
79
79
  _ = self.operations.update(snapshot, state: proved ? .succeeded : .failed, stage: "complete", progress: 1, summary: proved ? "Codex LB connection proved" : "Codex LB connection proof failed")
80
80
  if let proof = proof, result.code == 0 {
81
81
  self.codexLbSelectedNow = true
82
+ self.codexLbProvedNow = true
82
83
  self.cliProviderStatus.stringValue = "Codex LB mode selected · connection proved by one live response."
83
84
  ControlKit.setBadge(self.activeProviderBadge, text: "Codex LB · active · model \(proof.model) · \(proof.latencyMs) ms", tone: .ok)
84
85
  self.renderCliConnectStages(
@@ -91,14 +92,16 @@ extension ProvidersViewController {
91
92
  )
92
93
  return
93
94
  }
95
+ self.codexLbProvedNow = false
94
96
  let selected = self.codexLbSelectedNow
97
+ let code = self.publicFailureCode(parsed)
95
98
  let failure = parsed.map(CodexLbConnectTestTruth.validationFailure(from:))
96
99
  ?? "structured connect-test JSON was not returned"
97
100
  self.cliProviderStatus.stringValue = selected
98
- ? "Codex LB mode is selected, but connection proof failed · \(failure)."
99
- : "Codex LB connection proof failed and provider selection is unconfirmed · \(failure)."
101
+ ? "Codex LB mode is selected, but connection proof failed · \(code) · \(failure)."
102
+ : "Codex LB connection proof failed and provider selection is unconfirmed · \(code) · \(failure)."
100
103
  if selected {
101
- ControlKit.setBadge(self.activeProviderBadge, text: "Codex LB · selected · connection proof failed", tone: .warning)
104
+ ControlKit.setBadge(self.activeProviderBadge, text: "Codex LB · selected · \(code) · connection proof failed", tone: .warning)
102
105
  }
103
106
  self.renderCliConnectStages(
104
107
  progress: 3,
@@ -106,8 +109,8 @@ extension ProvidersViewController {
106
109
  request: "completed without valid proof",
107
110
  response: "failed",
108
111
  result: selected
109
- ? "Mode is selected, but connection proof failed · \(failure). Use Run Connect Test to retry."
110
- : "Connection proof failed · \(failure). Confirm the provider, then retry.",
112
+ ? "\(code) · Mode is selected, but connection proof failed · \(failure). Use Run Connect Test to retry."
113
+ : "\(code) · Connection proof failed · \(failure). Confirm the provider, then retry.",
111
114
  tone: .systemRed
112
115
  )
113
116
  }
@@ -4,7 +4,7 @@ extension ProvidersViewController {
4
4
  func makeActiveProviderCard() -> NSBox {
5
5
  NativeView.card(
6
6
  title: "Active Provider",
7
- subtitle: "Shows the live Desktop mode: Codex LB, ChatGPT OAuth, OpenRouter, or Multi-Provider Router. Switch with Use Codex LB or Use ChatGPT OAuth Only below.",
7
+ subtitle: "Live provider path. Codex LB uses Authorization: Bearer. Switch with Use Codex LB, OpenRouter Activate, or Advanced · Use ChatGPT OAuth Only.",
8
8
  views: [activeProviderBadge, oauthCredentialStatus]
9
9
  )
10
10
  }
@@ -22,12 +22,17 @@ extension ProvidersViewController {
22
22
  ControlKit.setBadge(
23
23
  activeProviderBadge,
24
24
  text: chatgptOauthPresentNow
25
- ? "Codex LB mode · ChatGPT OAuth + built-in OpenAI via bridge"
26
- : "Codex LB mode enabled · ChatGPT OAuth missing — run codex login",
25
+ ? "Codex LB · Desktop Bridge · ChatGPT OAuth + built-in OpenAI"
26
+ : "Codex LB · Desktop Bridge · ChatGPT OAuth missing — run codex login",
27
27
  tone: chatgptOauthPresentNow ? .ok : .warning
28
28
  )
29
29
  } else if codexLbSelectedNow {
30
- ControlKit.setBadge(activeProviderBadge, text: "Legacy Codex LB provider selection · migration required", tone: .warning)
30
+ // Measured CLI selection is not legacy migration (that badge comes only from refresh snapshot markers).
31
+ if codexLbProvedNow {
32
+ ControlKit.setBadge(activeProviderBadge, text: "Codex LB · CLI · verified", tone: .ok)
33
+ } else {
34
+ ControlKit.setBadge(activeProviderBadge, text: "Codex LB · CLI · selected · connection unproved", tone: .warning)
35
+ }
31
36
  } else if chatgptOauthPresentNow {
32
37
  ControlKit.setBadge(activeProviderBadge, text: "ChatGPT OAuth mode · built-in OpenAI models", tone: .neutral)
33
38
  } else {
@@ -129,7 +134,7 @@ extension ProvidersViewController {
129
134
  let summary = self.describeOpenRouterStatus(json)
130
135
  self.openRouterStatus.stringValue = result.code == 0
131
136
  ? summary
132
- : "\(summary) \(self.structuredPublicDetail(json, fallback: result.output))"
137
+ : "\(summary) \(self.structuredPublicDetail(json, fallback: result.output, codePrefix: "E-OR"))"
133
138
  }
134
139
  self.openRouterCredentialStatus.textColor = keyValidated ? .secondaryLabelColor : .systemOrange
135
140
  if !keyValidated { self.clearOpenRouterModels(reason: "Credential not validated · model and child-agent lists are hidden. Choose Reconnect, then Refresh Models.") }
@@ -170,7 +175,7 @@ extension ProvidersViewController {
170
175
  return
171
176
  }
172
177
  guard result.code == 0, json["ok"] as? Bool == true else {
173
- self.clearOpenRouterModels(reason: "Catalog unavailable · \(self.structuredPublicDetail(json, fallback: result.output)) No model list is exposed.")
178
+ self.clearOpenRouterModels(reason: "Catalog unavailable · \(self.structuredPublicDetail(json, fallback: result.output, codePrefix: "E-OR")) No model list is exposed.")
174
179
  return
175
180
  }
176
181
  guard json["authenticated"] as? Bool == true else {
@@ -264,7 +269,8 @@ extension ProvidersViewController {
264
269
  credential: .openRouterApiKey,
265
270
  statusLabel: openRouterStatus,
266
271
  successSummary: "OpenRouter key saved",
267
- failSummary: "OpenRouter key save failed"
272
+ failSummary: "OpenRouter key save failed",
273
+ codePrefix: "E-OR"
268
274
  )
269
275
  }
270
276
 
@@ -289,7 +295,7 @@ extension ProvidersViewController {
289
295
  let json = self.json(result.output)
290
296
  let ok = result.code == 0 && json?["ok"] as? Bool == true
291
297
  let status = json?["status"] as? String ?? (ok ? "connected" : "failed")
292
- let detail = self.structuredPublicDetail(json, fallback: result.output)
298
+ let detail = self.structuredPublicDetail(json, fallback: result.output, codePrefix: "E-OR")
293
299
  _ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "OpenRouter connection ready" : "OpenRouter connection needs action")
294
300
  self.openRouterStatus.stringValue = ok
295
301
  ? "Connection test passed · \(model) · \(status). Next: activate it if you want it as the main model."
@@ -347,7 +353,7 @@ extension ProvidersViewController {
347
353
  let json = self.json(result.output)
348
354
  let ok = result.code == 0 && json?["ok"] as? Bool == true
349
355
  let status = json?["status"] as? String ?? (ok ? "restored" : "failed")
350
- let detail = self.structuredPublicDetail(json, fallback: result.output)
356
+ let detail = self.structuredPublicDetail(json, fallback: result.output, codePrefix: "E-OR")
351
357
  // Routing rolls back before the sidebar retag, so a blocked retag is
352
358
  // partial success: the snapshot is kept and re-running finishes it.
353
359
  let sidebarBlocked = status.hasPrefix("restored_sidebar")
@@ -415,7 +421,7 @@ extension ProvidersViewController {
415
421
  } else if configApplied {
416
422
  self.openRouterStatus.stringValue = "Configuration saved · main model \(model) is selected, but Codex App did not restart. Next: reopen Codex App, then verify status."
417
423
  } else {
418
- self.openRouterStatus.stringValue = "Activation incomplete · requested \(model), observed \(activeModel ?? "unknown") · \(self.structuredPublicDetail(activationJson, fallback: activation.output))"
424
+ self.openRouterStatus.stringValue = "Activation incomplete · requested \(model), observed \(activeModel ?? "unknown") · \(self.structuredPublicDetail(activationJson, fallback: activation.output, codePrefix: "E-OR"))"
419
425
  }
420
426
  self.refreshOpenRouterStatus()
421
427
  }
@@ -426,14 +432,60 @@ extension ProvidersViewController {
426
432
  openRouterModelField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
427
433
  }
428
434
 
429
- func structuredPublicDetail(_ json: [String: Any]?, fallback: String) -> String {
435
+ /// One shared failure format: stable code · HTTP · transport · public message · Next: one action.
436
+ /// `codePrefix` keeps codes honest per surface (E-LB / E-OR); the neutral default next-step keeps OpenRouter failures from advertising Codex LB recovery actions.
437
+ func structuredPublicDetail(_ json: [String: Any]?, fallback: String, codePrefix: String = "E-LB", fallbackNext: String = "review the key, model id, and network, then retry") -> String {
438
+ let code = publicFailureCode(json, codePrefix: codePrefix)
439
+ let http = publicHttpStatus(json).map { "HTTP \($0)" }
440
+ let transport = publicAuthTransport(json)
441
+ let error = publicError(json)
442
+ var parts: [String] = [code]
443
+ if let http { parts.append(http) }
444
+ if let transport { parts.append("transport \(transport)") }
445
+ if let error, !error.isEmpty { parts.append(error) }
430
446
  if let blockers = json?["blockers"] as? [String], !blockers.isEmpty {
431
- let error = publicError(json).map { " · \($0)" } ?? ""
432
- return "Reason: \(blockers.joined(separator: ", ").replacingOccurrences(of: "_", with: " "))\(error). Next: \((json?["hint"] as? String) ?? "review the key, model id, and network, then retry")."
447
+ parts.append(blockers.prefix(2).joined(separator: ", "))
448
+ }
449
+ let next = firstGuidance(json) ?? (json?["hint"] as? String) ?? fallbackNext
450
+ return "\(parts.joined(separator: " · ")). Next: \(next)."
451
+ }
452
+
453
+ func publicFailureCode(_ json: [String: Any]?, codePrefix: String = "E-LB") -> String {
454
+ let blockers = json?["blockers"] as? [String] ?? []
455
+ let truth = json?["routing_truth"] as? [String: Any]
456
+ let truthBlockers = truth?["blockers"] as? [String] ?? []
457
+ let all = blockers + truthBlockers
458
+ if all.contains(where: { $0.contains("auth_rejected") || $0.contains("gateway_auth_rejected") }) {
459
+ return "\(codePrefix)-AUTH"
460
+ }
461
+ if all.contains(where: { $0.contains("unreachable") }) { return "\(codePrefix)-UNREACHABLE" }
462
+ if all.contains(where: { $0.contains("legacy") && $0.contains("migration") }) { return "\(codePrefix)-LEGACY-MIGRATE" }
463
+ if let http = publicHttpStatus(json), http >= 400 { return "\(codePrefix)-HTTP-\(http)" }
464
+ if json == nil || json?["ok"] as? Bool == false { return "\(codePrefix)-FAILED" }
465
+ return "\(codePrefix)-INFO"
466
+ }
467
+
468
+ func publicHttpStatus(_ json: [String: Any]?) -> Int? {
469
+ if let value = json?["http_status"] as? Int { return value }
470
+ if let value = (json?["http_status"] as? NSNumber)?.intValue { return value }
471
+ let truth = json?["routing_truth"] as? [String: Any]
472
+ if let value = truth?["http_status"] as? Int { return value }
473
+ return (truth?["http_status"] as? NSNumber)?.intValue
474
+ }
475
+
476
+ func publicAuthTransport(_ json: [String: Any]?) -> String? {
477
+ if let value = json?["gateway_auth_transport"] as? String, !value.isEmpty { return value }
478
+ if let value = json?["auth_transport"] as? String, !value.isEmpty { return value }
479
+ let truth = json?["routing_truth"] as? [String: Any]
480
+ if let value = truth?["auth_transport"] as? String, !value.isEmpty { return value }
481
+ return nil
482
+ }
483
+
484
+ func firstGuidance(_ json: [String: Any]?) -> String? {
485
+ if let rows = json?["guidance"] as? [String] {
486
+ return rows.first { !$0.isEmpty }
433
487
  }
434
- if let error = publicError(json), !error.isEmpty { return "Reason: \(error). Next: review the key, model id, and network, then retry." }
435
- if let hint = json?["hint"] as? String, !hint.isEmpty { return "Next: \(hint)." }
436
- return "Next: \(NativeView.redactPreview(fallback))"
488
+ return nil
437
489
  }
438
490
 
439
491
  func publicError(_ json: [String: Any]?) -> String? {
@@ -198,7 +198,7 @@ extension ProvidersViewController {
198
198
  }
199
199
  }
200
200
 
201
- func promptForSecretKey(window: NSWindow, sheetTitle: String, sheetMessage: String, placeholder: String, args: [String], kind: String, title: String, credential: SKSKeychainCredential, statusLabel: NSTextField, successSummary: String, failSummary: String) {
201
+ func promptForSecretKey(window: NSWindow, sheetTitle: String, sheetMessage: String, placeholder: String, args: [String], kind: String, title: String, credential: SKSKeychainCredential, statusLabel: NSTextField, successSummary: String, failSummary: String, codePrefix: String = "E-LB") {
202
202
  AlertFactory.textSheet(window: window, title: sheetTitle, message: sheetMessage, secure: true, placeholder: placeholder) { [weak self] key in
203
203
  guard let self = self, let key = key else { return }
204
204
  guard !self.busy else { statusLabel.stringValue = "Another provider action is already running."; return }
@@ -229,7 +229,7 @@ extension ProvidersViewController {
229
229
  }
230
230
  statusLabel.stringValue = "Configuration saved, but Keychain was not applied · \(reason). Authentication Required: choose Reconnect after repairing Keychain."
231
231
  } else {
232
- statusLabel.stringValue = "\(failSummary) · \(self.structuredPublicDetail(parsed, fallback: result.output))"
232
+ statusLabel.stringValue = "\(failSummary) · \(self.structuredPublicDetail(parsed, fallback: result.output, codePrefix: codePrefix))"
233
233
  }
234
234
  self.refresh()
235
235
  }
@@ -288,6 +288,7 @@ extension ProvidersViewController {
288
288
  let bridgeSelected = expectedMode == "desktop-native-bridge"
289
289
  self.desktopFullRoutingNow = false
290
290
  self.codexLbSelectedNow = false
291
+ self.codexLbProvedNow = false
291
292
  self.chatgptOauthPresentNow = routing?.chatgptOauthPresent ?? self.chatgptOauthPresentNow
292
293
  self.renderActiveProviderSummary()
293
294
  if bridgeSelected { self.renderMeasuredRoutingBadge(nil, routeExpected: true) }
@@ -55,6 +55,9 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
55
55
  var capabilityRows: [String: NSTextField] = [:]
56
56
  var desktopFullRoutingNow = false
57
57
  var codexLbSelectedNow = false
58
+ // True only after a measured verified route or a live connection proof, so
59
+ // unrelated status probes never downgrade a proven badge to "unproved".
60
+ var codexLbProvedNow = false
58
61
  var chatgptOauthPresentNow = false
59
62
  var openRouterSelectedNow = false
60
63
  var openRouterCredentialValidatedNow = false
@@ -115,17 +118,11 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
115
118
  providerApplyStatus.setAccessibilityIdentifier("sks-center-provider-apply-status")
116
119
  providerApplyStatus.setAccessibilityLabel("Provider apply stage receipts")
117
120
  actionButtons = [enableDesktop, verifyDesktop, disableDesktop, configureCli, reconnectOpenRouter, openCodexSignIn, useCli, testCli, copyCli, fastOn, fastOff]
118
- let desktop = NativeView.card(
119
- title: "Advanced · Desktop Bridge / ChatGPT Identity Mode",
120
- subtitle: "Optional bridge mode: keeps ChatGPT OAuth identity and the built-in OpenAI provider while routing model traffic through the local codex-lb bridge. Use ChatGPT OAuth Only removes bridge routing without touching login.",
121
- views: [providerStatus, ControlKit.actionRow([enableDesktop, verifyDesktop], trailing: [disableDesktop])]
122
- )
123
121
  let cli = NativeView.card(
124
122
  title: "Codex LB · Credentials & Primary Provider",
125
- subtitle: "Saves the official Center Codex LB host and API key (sks-codex-lb store). Use Codex LB applies the atomic CLI provider path no shell source or twin env files.",
123
+ subtitle: "Saves host + API key, then Use Codex LB selects the atomic CLI provider (Authorization: Bearer via CODEX_LB_API_KEY). No transport picker.",
126
124
  views: [
127
125
  cliProviderStatus, codexLbKeychainStatus,
128
- NativeView.detail("One-off command: \(cliLaunchCommand)"),
129
126
  ControlKit.actionRow([configureCli, useCli]),
130
127
  makeCliConnectProgressView(),
131
128
  ControlKit.actionRow([testCli, copyCli])
@@ -142,19 +139,24 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
142
139
  )
143
140
  view = NativeView.page([
144
141
  NativeView.row([NativeView.title("Providers & Models"), globalSpinner]),
145
- NativeView.detail("Codex always sees its native OpenAI identity through a local loopback proxy. SKS selects exactly one internal mode: Codex LB, OpenRouter, or ChatGPT OAuth. Secrets are sent through stdin and never written to operation logs."),
146
- NativeView.card(
147
- title: "Authentication Recovery",
148
- subtitle: "Background refresh and menu restarts never open authentication UI. Missing, locked, damaged, or inaccessible credentials stay in an Authentication Required state until you choose a reconnect action.",
149
- views: [codexLbKeychainStatus, openRouterKeychainStatus, oauthCredentialStatus, NativeView.row([configureCli, reconnectOpenRouter, openCodexSignIn])]
150
- ),
142
+ NativeView.detail("SKS selects one provider path: Codex LB (Authorization: Bearer), OpenRouter, or ChatGPT OAuth. Secrets go through stdin and are never written to operation logs."),
143
+ makeActiveProviderCard(),
144
+ cli,
145
+ makeOpenRouterCard(),
151
146
  NativeView.card(
152
- title: "Provider Apply Stages",
153
- subtitle: "Configuration, proxy, catalog, and new-session readiness are independent. Existing sessions keep their pinned copy; a partial failure never appears as silent success.",
154
- views: [providerApplyStatus]
147
+ title: "Advanced",
148
+ subtitle: "You rarely need this section. Desktop Bridge is only for keeping ChatGPT sign-in while routing through codex-lb (or a custom-header-only gateway) if Use Codex LB above works, skip it.",
149
+ views: [
150
+ oauthCredentialStatus,
151
+ NativeView.row([openCodexSignIn]),
152
+ providerStatus,
153
+ ControlKit.actionRow([enableDesktop, verifyDesktop], trailing: [disableDesktop]),
154
+ providerApplyStatus,
155
+ makeCapabilityMatrixCard()
156
+ ]
155
157
  ),
156
- makeActiveProviderCard(), cli, desktop, makeCapabilityMatrixCard(), makeOpenRouterCard(),
157
- makeRoleModelsCard(), fast
158
+ makeRoleModelsCard(),
159
+ fast
158
160
  ])
159
161
  }
160
162
  func refreshOnAppear() { refresh() }
@@ -211,8 +213,20 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
211
213
  : "Authentication Required: ChatGPT OAuth is not connected. Choose Open Codex sign-in; no login window was opened automatically."
212
214
  self.oauthCredentialStatus.textColor = snapshot.chatgptOauthPresent ? .secondaryLabelColor : .systemOrange
213
215
  self.renderCatalogSyncStatus(json)
214
- self.renderActiveProviderSummary()
215
- self.renderMeasuredRoutingBadge(measuredRoute, routeExpected: snapshot.desktopFullRouting || snapshot.legacyCodexLbSelected)
216
+ self.codexLbProvedNow = snapshot.mode == "cli-provider" && measuredRoute?.active == true
217
+ // Single decision ladder — exactly one badge writer per refresh.
218
+ if snapshot.legacyCodexLbSelected {
219
+ ControlKit.setBadge(self.activeProviderBadge, text: "E-LB-LEGACY-MIGRATE · run migrate-legacy-desktop", tone: .warning)
220
+ } else if snapshot.mode == "cli-provider", let route = measuredRoute, route.selected, !route.active {
221
+ let authFail = route.status == "auth_rejected" || route.blockers.contains(where: { $0.contains("auth") })
222
+ let code = authFail ? "E-LB-AUTH" : "E-LB-DEGRADED"
223
+ let http = route.httpStatus.map { "HTTP \($0)" } ?? route.status
224
+ ControlKit.setBadge(self.activeProviderBadge, text: "Codex LB · CLI · \(code) · \(http)", tone: .warning)
225
+ } else if snapshot.desktopFullRouting {
226
+ self.renderMeasuredRoutingBadge(measuredRoute, routeExpected: true)
227
+ } else {
228
+ self.renderActiveProviderSummary()
229
+ }
216
230
  if !self.busy {
217
231
  self.providerStatus.stringValue = self.describeDesktopStatus(snapshot)
218
232
  self.cliProviderStatus.stringValue = self.describeCliStatus(snapshot, measuredRoute: measuredRoute)
@@ -242,7 +256,10 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
242
256
  let oauth = snapshot.chatgptOauthPresent
243
257
  ? "ChatGPT OAuth present"
244
258
  : "ChatGPT OAuth missing — sign in with ChatGPT"
245
- return "Codex LB mode: enabled · \(oauth) · built-in OpenAI via bridge. Run Verify Capabilities for evidence."
259
+ return "Desktop Bridge mode: enabled · \(oauth) · built-in OpenAI via bridge. Run Verify Capabilities for evidence."
260
+ }
261
+ if snapshot.mode == "cli-provider" {
262
+ return "Desktop Bridge: off · the Codex LB CLI provider path handles routing, so the bridge is not used. Desktop Bridge Mode stays an optional alternative."
246
263
  }
247
264
  if snapshot.mode == "disabled" {
248
265
  return snapshot.chatgptOauthPresent
@@ -357,23 +374,9 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
357
374
  }
358
375
  @objc private func setDomainAndKey() {
359
376
  guard let window = view.window else { return }
360
- AlertFactory.textSheet(window: window, title: "Codex LB Domain", message: "Enter a hostname or full base URL. https:// is optional — SKS adds https:// and /backend-api/codex when missing.\nExamples: lb.example.com or https://lb.example.com", secure: false, placeholder: "https://lb.example.com") { [weak self] host in
377
+ AlertFactory.textSheet(window: window, title: "Codex LB Domain", message: "Enter a hostname or full base URL. https:// is optional — SKS adds https:// and /backend-api/codex when missing.\nExamples: lb.example.com or https://lb.example.com\n\nSKS always sends the API key as Authorization: Bearer (Codex env_key). There is no custom-header transport picker.", secure: false, placeholder: "https://lb.example.com") { [weak self] host in
361
378
  guard let self = self, let host = host else { return }
362
- // The transport is an explicit operator choice, never guessed: a gateway
363
- // that wants `Authorization: Bearer` rejects the custom header with 401,
364
- // which Desktop surfaces as "codex-lb auth not recognized".
365
- AlertFactory.choiceSheet(
366
- window: window,
367
- title: "Codex LB Gateway Key Transport",
368
- message: "How does this codex-lb gateway expect the API key? Choose Authorization bearer if the gateway answers 401 \"Missing API key in Authorization header\" to the custom header.",
369
- choices: [
370
- ("custom-header", "X-Codex-LB-API-Key custom header (default)"),
371
- ("bearer-compat", "Authorization: Bearer compatibility")
372
- ]
373
- ) { [weak self] transport in
374
- guard let self = self, let transport = transport else { return }
375
- self.promptForKey(window: window, args: ["codex-lb", "setup", "--host", host, "--gateway-auth", transport, "--api-key-stdin", "--yes", "--write-env-file", "--json"], kind: "codex-lb-center-setup", title: "Save Codex LB credentials")
376
- }
379
+ self.promptForKey(window: window, args: ["codex-lb", "setup", "--host", host, "--gateway-auth", "bearer-compat", "--api-key-stdin", "--yes", "--write-env-file", "--json"], kind: "codex-lb-center-setup", title: "Save Codex LB credentials")
377
380
  }
378
381
  }
379
382
  private func promptForKey(window: NSWindow, args: [String], kind: String, title: String) {
@@ -448,20 +451,23 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
448
451
  _ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "CLI provider configured" : "CLI provider configuration needs action")
449
452
  guard ok else {
450
453
  self.setBusy(false)
451
- let detail = self.structuredPublicDetail(parsed, fallback: result.output)
454
+ // structuredPublicDetail already leads with the stable code.
455
+ let detail = self.structuredPublicDetail(parsed, fallback: result.output, fallbackNext: "Reconnect Codex LB credential, then Use Codex LB / Run Connect Test")
456
+ let code = self.publicFailureCode(parsed)
452
457
  self.cliProviderStatus.stringValue = "CLI provider was not confirmed · \(detail)"
453
458
  self.renderCliConnectStages(
454
459
  progress: 1,
455
- configuration: "failed",
460
+ configuration: "failed · \(code)",
456
461
  request: "not run",
457
462
  response: "not received",
458
- result: "Codex LB mode was not confirmed. Connection proof did not run.",
463
+ result: detail,
459
464
  tone: .systemRed
460
465
  )
461
466
  return
462
467
  }
463
468
  self.setBusy(false)
464
469
  self.codexLbSelectedNow = true
470
+ self.codexLbProvedNow = false
465
471
  self.desktopFullRoutingNow = false
466
472
  self.openRouterSelectedNow = false
467
473
  self.routerSelectedNow = false
@@ -16,7 +16,7 @@ await fs.writeFile(path.join(home, '.codex', 'config.toml'), [
16
16
  'name = "codex-lb"',
17
17
  'base_url = "https://lb.example.test/backend-api/codex"',
18
18
  'wire_api = "responses"',
19
- 'env_http_headers = { "X-Codex-LB-API-Key" = "CODEX_LB_API_KEY" }',
19
+ 'env_key = "CODEX_LB_API_KEY"',
20
20
  'supports_websockets = true',
21
21
  'requires_openai_auth = false',
22
22
  ''
@@ -44,7 +44,7 @@ const requestedOnly = await runFastCheck({
44
44
  const ok = chain.ok === true
45
45
  && chain.status === 'fast_verified'
46
46
  && calls[0]?.body?.service_tier === 'priority'
47
- && calls.every((call) => call.authorization === null && call.gatewayKey === 'sk-fixture')
47
+ && calls.every((call) => call.authorization === 'Bearer sk-fixture' && call.gatewayKey === null)
48
48
  && requestedOnly.ok === false
49
49
  && requestedOnly.status === 'fast_requested_but_actual_unverified'
50
50
  && requestedOnly.blockers.includes('codex_lb_actual_fast_service_tier_unverified');
@@ -52,7 +52,7 @@ console.log(JSON.stringify({
52
52
  schema: 'sks.codex-lb-fast-mode-truth-check.v1',
53
53
  ok,
54
54
  priority_request_sent: calls[0]?.body?.service_tier === 'priority',
55
- custom_header_transport_verified: calls.every((call) => call.authorization === null && call.gatewayKey === 'sk-fixture'),
55
+ bearer_transport_verified: calls.every((call) => call.authorization === 'Bearer sk-fixture' && call.gatewayKey === null),
56
56
  verified_case: chain,
57
57
  requested_only_case: requestedOnly,
58
58
  blockers: ok ? [] : ['codex_lb_fast_mode_truth_check_failed']
@@ -124,9 +124,9 @@ function assertConfig(text, label, options = {}) {
124
124
  ...(hasTable(text, 'model_providers.codex-lb') ? [] : ['codex_lb_provider_table_missing']),
125
125
  ...(tableKey(text, 'model_providers.codex-lb', 'name') === 'codex-lb' ? [] : ['cli_provider_name_not_codex_lb']),
126
126
  ...(tableKey(text, 'model_providers.codex-lb', 'requires_openai_auth') === 'false' ? [] : ['cli_provider_requires_openai_auth_not_false']),
127
- ...(tableEnvHttpHeader(text, 'model_providers.codex-lb', 'X-Codex-LB-API-Key') === 'CODEX_LB_API_KEY'
127
+ ...(tableKey(text, 'model_providers.codex-lb', 'env_key') === 'CODEX_LB_API_KEY'
128
128
  ? []
129
- : ['cli_provider_gateway_header_env_missing']),
129
+ : ['cli_provider_env_key_missing']),
130
130
  ...(topLevelKey(text, 'model_provider') === 'openai' ? [] : ['desktop_model_provider_must_be_openai']),
131
131
  ...(expectBridge && topLevelKey(text, 'openai_base_url') !== bridgeBaseUrl ? ['native_bridge_base_url_missing'] : []),
132
132
  ...(!expectBridge && topLevelKey(text, 'openai_base_url') ? ['disabled_routing_still_has_openai_base_url'] : []),
@@ -141,13 +141,6 @@ function hasLegacyFastModeTables(text) {
141
141
  function hasTable(text, table) {
142
142
  return new RegExp(`(^|\\n)\\[${escapeRegExp(table)}\\](?=\\n|$)`).test(text);
143
143
  }
144
- function tableEnvHttpHeader(text, table, header) {
145
- const tableMatch = text.match(new RegExp(`(^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`));
146
- const body = tableMatch?.[2] || '';
147
- const match = body.match(/(?:^|\n)\s*env_http_headers\s*=\s*\{([^}]*)\}/);
148
- const entries = match?.[1] || '';
149
- return entries.match(new RegExp(`"${escapeRegExp(header)}"\\s*=\\s*"([^"]+)"`))?.[1] || '';
150
- }
151
144
  function tableKey(text, table, key) {
152
145
  const match = text.match(new RegExp(`(^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`));
153
146
  const block = match?.[2] || '';
@@ -152,9 +152,9 @@ function assertFastProfile(text, label, expectBridge = true) {
152
152
  ...(parsed.model_provider === 'openai' ? [] : ['desktop_model_provider_must_be_openai']),
153
153
  ...(provider.name === 'codex-lb' ? [] : ['codex_lb_cli_provider_name_mismatch']),
154
154
  ...(provider.requires_openai_auth === false ? [] : ['codex_lb_cli_requires_openai_auth_not_false']),
155
- ...(provider.env_http_headers?.['X-Codex-LB-API-Key'] === 'CODEX_LB_API_KEY'
155
+ ...(provider.env_key === 'CODEX_LB_API_KEY'
156
156
  ? []
157
- : ['codex_lb_cli_gateway_header_env_missing']),
157
+ : ['codex_lb_cli_env_key_missing']),
158
158
  ...(provider.wire_api === 'responses' ? [] : ['codex_lb_wire_api_not_responses']),
159
159
  ...(expectBridge && parsed.openai_base_url === bridgeBaseUrl ? [] : expectBridge ? ['native_bridge_base_url_missing'] : []),
160
160
  ...(!expectBridge && parsed.openai_base_url !== undefined ? ['disabled_routing_still_has_openai_base_url'] : []),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "8.1.0",
4
+ "version": "8.1.2",
5
5
  "description": "Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",