sneakoscope 7.5.0 → 7.6.0
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/README.md +14 -7
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +2 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
- package/dist/cli/install-helpers-codex-lb-config.js +114 -9
- package/dist/cli/install-helpers.js +729 -550
- package/dist/cli/router.js +24 -4
- package/dist/commands/codex-app.js +37 -1
- package/dist/commands/codex-lb.js +491 -300
- package/dist/config/skills-manifest.json +55 -48
- package/dist/core/align/align-route.js +127 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
- package/dist/core/codex-app/codex-app-restart.js +83 -0
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
- package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
- package/dist/core/codex-app/menubar/constants.js +5 -0
- package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
- package/dist/core/codex-app/menubar/installer.js +18 -0
- package/dist/core/codex-app/multi-provider-router.js +22 -10
- package/dist/core/codex-app/openrouter-activate.js +26 -1
- package/dist/core/codex-app.js +113 -21
- package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
- package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
- package/dist/core/codex-control/codex-task-runner.js +2 -2
- package/dist/core/codex-lb/capability-runner.js +204 -0
- package/dist/core/codex-lb/capability-types.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +9 -13
- package/dist/core/codex-lb/codex-lb-setup.js +45 -8
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
- package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
- package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
- package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
- package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
- package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
- package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
- package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
- package/dist/core/codex-lb/desktop-controller.js +1410 -0
- package/dist/core/codex-lb/desktop-mode.js +32 -0
- package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
- package/dist/core/codex-lb/desktop-service.js +613 -0
- package/dist/core/codex-lb/legacy-migration.js +575 -0
- package/dist/core/codex-lb/migration-receipt.js +246 -0
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
- package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
- package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
- package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
- package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
- package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
- package/dist/core/codex-native/core-skill-manifest.js +13 -0
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
- package/dist/core/commands/align-command.js +220 -0
- package/dist/core/commands/image-ux-review-command.js +37 -18
- package/dist/core/commands/naruto-command.js +18 -6
- package/dist/core/commands/telegram-command.js +294 -39
- package/dist/core/doctor/skill-legacy-surface.js +1 -3
- package/dist/core/evidence/evidence-freshness.js +14 -0
- package/dist/core/feature-fixtures.js +9 -1
- package/dist/core/feature-registry.js +3 -1
- package/dist/core/hooks-runtime/hook-context.js +15 -2
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
- package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +5 -3
- package/dist/core/image-ux-review/imagegen-adapter.js +10 -5
- package/dist/core/image-ux-review.js +70 -27
- package/dist/core/imagegen/codex-lb-imagegen-target.js +1 -2
- package/dist/core/imagegen/imagegen-capability.js +1 -2
- package/dist/core/init/skills.js +3 -3
- package/dist/core/init.js +1 -1
- package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
- package/dist/core/perf/release-latency-slo.js +12 -6
- package/dist/core/pipeline-internals/runtime-core.js +34 -0
- package/dist/core/pipeline-internals/runtime-gates.js +5 -2
- package/dist/core/preflight/parallel-preflight-engine.js +1 -1
- package/dist/core/proof/auto-finalize.js +98 -6
- package/dist/core/proof/route-proof-gate.js +12 -1
- package/dist/core/proof/route-proof-policy.js +8 -8
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-proof-io.js +1 -3
- package/dist/core/release/release-real-contract.js +5 -4
- package/dist/core/release/release-stamp-proof.js +1 -3
- package/dist/core/remote/audit-idempotency.js +50 -18
- package/dist/core/remote/codex-public-activity.js +283 -0
- package/dist/core/remote/index.js +1 -0
- package/dist/core/remote/ssh-worker-client.js +4 -1
- package/dist/core/remote/worker.js +206 -27
- package/dist/core/research/research-adversarial-review.js +1 -3
- package/dist/core/research/research-claim-synthesizer.js +1 -3
- package/dist/core/research/research-falsification-runner.js +1 -3
- package/dist/core/research/research-source-evidence.js +1 -3
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/dollar-manifest-lite.js +2 -0
- package/dist/core/routes.js +18 -2
- package/dist/core/secret-redaction.js +6 -0
- package/dist/core/telegram/bot-api.js +26 -6
- package/dist/core/telegram/center-snapshot.js +718 -0
- package/dist/core/telegram/config.js +157 -31
- package/dist/core/telegram/hub.js +285 -31
- package/dist/core/telegram/index.js +2 -0
- package/dist/core/telegram/ingress-spool.js +419 -0
- package/dist/core/telegram/ledgers.js +188 -29
- package/dist/core/telegram/messages.js +26 -6
- package/dist/core/telegram/owner-lock.js +6 -1
- package/dist/core/telegram/runtime-projection.js +38 -3
- package/dist/core/telegram/runtime.js +298 -63
- package/dist/core/telegram/service.js +137 -8
- package/dist/core/telegram/setup.js +96 -28
- package/dist/core/text/strings.js +3 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
- package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
- package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
- package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
- package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
- package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
- package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
- package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
- package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
- package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
- package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +1 -1
- package/dist/scripts/helper-dedup-check.js +0 -1
- package/dist/scripts/release-metadata-check.js +1 -1
- package/dist/scripts/release-real-check.js +7 -0
- package/package.json +5 -2
- package/release-gates.v2.json +2 -1
- package/runtime-required-scripts.json +30 -0
- package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
- package/dist/cli/install-helpers-codex-lb-selftest.js +0 -482
|
@@ -6,7 +6,6 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
6
6
|
import { ensureDir, exists, globalSksRoot, packageRoot, PACKAGE_VERSION, readText, runProcess, tmpdir, which, writeTextAtomic } from '../core/fsx.js';
|
|
7
7
|
import { createRequestedScopeContract } from '../core/safety/requested-scope-contract.js';
|
|
8
8
|
import { guardedPackageInstall, guardContextForRoute } from '../core/safety/mutation-guard.js';
|
|
9
|
-
import { EMPTY_CODEX_INFO, getCodexInfo } from '../core/codex-adapter.js';
|
|
10
9
|
import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.js';
|
|
11
10
|
import { initProject, installGlobalSkills } from '../core/init.js';
|
|
12
11
|
import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext7ConfigText, RECOMMENDED_SKILLS } from '../core/routes.js';
|
|
@@ -16,18 +15,21 @@ import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
|
|
|
16
15
|
import { cleanupMacLaunchSecretEnvironment } from '../core/codex-app/menubar/index.js';
|
|
17
16
|
import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
|
|
18
17
|
import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
|
|
19
|
-
import { codexLbToolCatalogPath
|
|
18
|
+
import { codexLbToolCatalogPath } from '../core/codex-lb/codex-lb-tool-catalog.js';
|
|
20
19
|
import { codexLbToolOutputRecoveryNotChecked, codexLbToolOutputRecoveryNotSelected, codexLbToolOutputRecoveryOverrideAcknowledged, probeCodexLbToolOutputRecovery } from '../core/codex-lb/codex-lb-tool-output-recovery.js';
|
|
21
20
|
import { GLM_CODEX_CONFIG_PROFILE_ID, GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES, GLM_52_OPENROUTER_MODEL } from '../core/codex-app/openrouter-provider.js';
|
|
22
21
|
import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
|
|
22
|
+
import { DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT, modeRequiresChatGptOAuth, parseCodexLbDesktopMode, parseCodexLbGatewayAuthTransport } from '../core/codex-lb/desktop-mode.js';
|
|
23
|
+
import { assertDesktopAuthUnchangedBySks, captureCodexAuthSnapshot } from '../core/codex-lb/desktop-auth-invariant.js';
|
|
23
24
|
import { extractTomlTable, writeCodexConfigGuarded } from '../core/codex/codex-config-guard.js';
|
|
24
|
-
import { ensureGlobalCodexFastModeDuringInstall, ensureTrailingNewline, normalizeCodexFastModeUiConfig, removeTopLevelTomlKeyIfValue, safeWriteCodexConfigToml,
|
|
25
|
+
import { ensureGlobalCodexFastModeDuringInstall, ensureTrailingNewline, normalizeCodexFastModeUiConfig, removeTopLevelTomlKeyIfValue, safeWriteCodexConfigToml, upsertTomlTable } from '../core/codex-runtime/codex-desktop-config-policy.js';
|
|
25
26
|
import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
|
|
26
27
|
import { repairCodexImagegen } from '../core/doctor/imagegen-repair.js';
|
|
27
|
-
import { canAskYesNo, compareVersions, hasCodexUnstableFeatureWarningSuppression, hasDeprecatedCodexHooksFeatureFlag,
|
|
28
|
+
import { canAskYesNo, compareVersions, hasCodexUnstableFeatureWarningSuppression, hasDeprecatedCodexHooksFeatureFlag, isProjectSetupCandidate } from './install-tool-helpers.js';
|
|
28
29
|
import { checkCodexLbResponseChain } from './install-helpers-codex-lb-chain.js';
|
|
29
|
-
import { CODEX_LB_CANONICAL_FAST_SERVICE_TIER, CODEX_LB_PROVIDER_ENV_KEY,
|
|
30
|
-
import { appliedCodexLbPersistenceModes, captureCodexLbSetupWriteState, codexLbSharedOpenAiRoutingState, detectCodexLbSetupDrift, ensureGlobalCodexAppGlmProfile, ensureStoredOpenRouterProviderDuringInstall, parseCodexLbEnvBaseUrl,
|
|
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, appliedCodexLbPersistenceModes, captureCodexLbSetupWriteState, codexLbSharedOpenAiRoutingState, detectCodexLbSetupDrift, ensureGlobalCodexAppGlmProfile, ensureStoredOpenRouterProviderDuringInstall, parseCodexLbEnvBaseUrl, removeCodexLbSharedOpenAiRouting, removeCodexLbManagedDesktopConfig, sha256Text, shellSingleQuote, upsertCodexAppGlmConfig, upsertCodexLbConfig, upsertCodexLbCliProviderConfig, upsertCodexLbCompatDesktopConfig, upsertCodexLbNativeDesktopConfig, upsertCodexLbSharedOpenAiRouting } from './install-helpers-codex-lb-config.js';
|
|
32
|
+
import { detectLegacyCodexLbDesktopState } from '../core/codex-lb/legacy-migration.js';
|
|
31
33
|
import { ensureCodexImagegenDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureGlobalContext7DuringInstall, ensureGlobalGetdesignSkillDuringInstall, ensureSksCommandDuringInstall } from './install-helpers-install-support.js';
|
|
32
34
|
import { escapeRegExp } from '../core/text/regex.js';
|
|
33
35
|
function packagedSksEntrypoint() {
|
|
@@ -84,19 +86,7 @@ export async function postinstall({ bootstrap, args = [] }) {
|
|
|
84
86
|
console.log(`Context7 MCP: skipped (${context7Install.reason}).`);
|
|
85
87
|
else if (context7Install.status === 'failed')
|
|
86
88
|
console.log(`Context7 MCP: auto setup failed. Run \`sks context7 setup --scope global\` or \`sks setup\`. ${context7Install.error || ''}`.trim());
|
|
87
|
-
|
|
88
|
-
if (fastModeRepair.status === 'updated')
|
|
89
|
-
console.log(`Codex App Fast mode: updated ${fastModeRepair.config_path}${fastModeRepair.backup_path ? ` (backup ${fastModeRepair.backup_path})` : ''}.`);
|
|
90
|
-
else if (fastModeRepair.status === 'present')
|
|
91
|
-
console.log('Codex App Fast mode: config already compatible.');
|
|
92
|
-
else if (fastModeRepair.status === 'unparseable_config_preserved')
|
|
93
|
-
console.log(`Codex App Fast mode: existing ${fastModeRepair.config_path} is not valid TOML — left untouched, backed up to ${fastModeRepair.backup_path}. Run \`sks doctor --fix\` to recover it.`);
|
|
94
|
-
else if (fastModeRepair.status === 'skipped_unsafe_rewrite')
|
|
95
|
-
console.log(`Codex App Fast mode: skipped (managed rewrite would not parse; ${fastModeRepair.config_path} left untouched).`);
|
|
96
|
-
else if (fastModeRepair.status === 'skipped')
|
|
97
|
-
console.log(`Codex App Fast mode: skipped (${fastModeRepair.reason}).`);
|
|
98
|
-
else if (fastModeRepair.status === 'failed')
|
|
99
|
-
console.log(`Codex App Fast mode: auto repair failed. Run \`sks setup\`. ${fastModeRepair.error || ''}`.trim());
|
|
89
|
+
console.log('Codex App Fast mode: left unchanged during install; use the explicit Fast-mode command to change it.');
|
|
100
90
|
const openRouterProviderRepair = await ensureStoredOpenRouterProviderDuringInstall();
|
|
101
91
|
if (openRouterProviderRepair.status === 'updated')
|
|
102
92
|
console.log('OpenRouter provider: repaired for the stored key (credentials and active model were preserved).');
|
|
@@ -115,7 +105,12 @@ export async function postinstall({ bootstrap, args = [] }) {
|
|
|
115
105
|
console.log(`Codex App Image Gen: blocked; run \`sks doctor --fix\`. ${(imagegenRepair.blockers || []).join(', ')}`.trim());
|
|
116
106
|
else if (imagegenRepair.status === 'skipped')
|
|
117
107
|
console.log(`Codex App Image Gen: skipped (${imagegenRepair.reason}).`);
|
|
118
|
-
const postinstallDoctor = await runPostinstallGlobalDoctorAndMarkPending(
|
|
108
|
+
const postinstallDoctor = await runPostinstallGlobalDoctorAndMarkPending({
|
|
109
|
+
env: {
|
|
110
|
+
...process.env,
|
|
111
|
+
SKS_POSTINSTALL_GLOBAL_DOCTOR: '0'
|
|
112
|
+
}
|
|
113
|
+
}).catch((err) => ({
|
|
119
114
|
ok: false,
|
|
120
115
|
doctor: null,
|
|
121
116
|
pending: null,
|
|
@@ -123,7 +118,7 @@ export async function postinstall({ bootstrap, args = [] }) {
|
|
|
123
118
|
warnings: []
|
|
124
119
|
}));
|
|
125
120
|
if (postinstallDoctor.ok)
|
|
126
|
-
console.log('SKS update migration:
|
|
121
|
+
console.log('SKS update migration: pending receipt recorded; no global Doctor repair ran during install.');
|
|
127
122
|
else
|
|
128
123
|
console.log(`SKS update migration: global Doctor did not complete; first normal command will retry. ${(postinstallDoctor.blockers || []).join(', ')}`.trim());
|
|
129
124
|
const postinstallRetention = await runPostinstallProjectRetentionCleanup(installRoot);
|
|
@@ -227,9 +222,7 @@ async function runPostinstallProjectRetentionCleanup(root) {
|
|
|
227
222
|
}
|
|
228
223
|
}
|
|
229
224
|
async function reportPostinstallCodexLbAuth(snapshot = null) {
|
|
230
|
-
const preserveActiveCodexLb = snapshot?.selected === true && snapshot?.codex_lb_key_in_shared_auth === true;
|
|
231
225
|
const codexLbAuth = await ensureCodexLbAuthDuringInstall({
|
|
232
|
-
forceCodexLbApiKeyAuth: preserveActiveCodexLb,
|
|
233
226
|
...(snapshot?.secret_api_key ? { apiKey: snapshot.secret_api_key } : {}),
|
|
234
227
|
...(snapshot?.base_url ? { baseUrl: snapshot.base_url } : {})
|
|
235
228
|
});
|
|
@@ -245,25 +238,18 @@ async function reportPostinstallCodexLbAuth(snapshot = null) {
|
|
|
245
238
|
console.log('codex-lb auth: stored key missing. Run `sks codex-lb setup --host <domain> --api-key-stdin` to repair.');
|
|
246
239
|
else if (codexLbAuth.status === 'missing_base_url')
|
|
247
240
|
console.log('codex-lb auth: stored key has no recoverable base URL. Run `sks codex-lb reconfigure --host <domain> --api-key-stdin` once.');
|
|
241
|
+
else if (codexLbAuth.status === 'legacy_migration_required')
|
|
242
|
+
console.log('codex-lb legacy Desktop auth routing was left unchanged. Migrate explicitly with `sks codex-lb migrate-legacy-desktop --restart-app`.');
|
|
248
243
|
else if (codexLbAuth.status === 'not_configured')
|
|
249
|
-
console.log('codex-lb (optional multi-account load balancer): not configured —
|
|
244
|
+
console.log('codex-lb (optional multi-account load balancer): not configured — use `sks codex-lb setup`, then choose `use-desktop-full`, `use-cli`, or `disable` explicitly.');
|
|
250
245
|
else if (codexLbAuth.status && codexLbAuth.status !== 'not_configured')
|
|
251
246
|
console.log(`codex-lb auth: repair skipped (${codexLbAuth.status}${codexLbAuth.error ? `: ${codexLbAuth.error}` : ''}).`);
|
|
252
247
|
const reconcile = codexLbAuth.auth_reconcile;
|
|
253
|
-
if (reconcile?.status === '
|
|
254
|
-
console.log(
|
|
255
|
-
}
|
|
256
|
-
else if (reconcile?.status === 'oauth_restored') {
|
|
257
|
-
console.log(`codex-lb auth: restored ChatGPT OAuth from ${reconcile.backup_path ?? 'unknown'} and cleared codex-lb provider selection.`);
|
|
258
|
-
}
|
|
259
|
-
else if (reconcile?.status === 'apikey_forced') {
|
|
260
|
-
console.log(`codex-lb auth: forced API-key auth.json for CLI-only use (OAuth backup at ${reconcile.backup_path ?? 'unknown'}).`);
|
|
248
|
+
if (reconcile?.status === 'oauth_untouched') {
|
|
249
|
+
console.log('codex-lb auth: ChatGPT OAuth/shared Codex auth was left byte-for-byte unchanged.');
|
|
261
250
|
}
|
|
262
|
-
else if (reconcile?.status === '
|
|
263
|
-
console.log(
|
|
264
|
-
}
|
|
265
|
-
else if (reconcile?.status === 'failed') {
|
|
266
|
-
console.log(`codex-lb auth: ChatGPT OAuth reconciliation could not complete (${reconcile.reason || 'unknown'}${reconcile.error ? `: ${reconcile.error}` : ''}). Run \`sks codex-lb repair\` to retry.`);
|
|
251
|
+
else if (reconcile?.status === 'legacy_migration_required') {
|
|
252
|
+
console.log('codex-lb auth: legacy shared-auth routing requires `sks codex-lb migrate-legacy-desktop --restart-app`; background install will not migrate it.');
|
|
267
253
|
}
|
|
268
254
|
if (codexLbAuth.base_url && codexLbAuth.codex_lb?.env_key_configured && canAskYesNo() && process.env.SKS_SKIP_CODEX_LB_KEY_PROMPT !== '1') {
|
|
269
255
|
const changeKey = (await askPostinstallQuestion('codex-lb key changed? Update now? [y/N] ')).trim();
|
|
@@ -330,147 +316,324 @@ async function runPostinstallBootstrap(root, bootstrap, selectedDecision) {
|
|
|
330
316
|
process.chdir(previousCwd);
|
|
331
317
|
}
|
|
332
318
|
}
|
|
319
|
+
export async function configureCodexLbDesktopRouting(opts) {
|
|
320
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
321
|
+
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
322
|
+
const authPath = opts.authPath || codexAuthPath(home);
|
|
323
|
+
const mode = parseCodexLbDesktopMode(opts.mode);
|
|
324
|
+
const gatewayAuthTransport = parseCodexLbGatewayAuthTransport(opts.gatewayAuthTransport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT);
|
|
325
|
+
const authBefore = await captureCodexAuthSnapshot({ home, authPath });
|
|
326
|
+
const identityPlane = authBefore.mode === 'chatgpt_oauth' || authBefore.mode === 'mixed'
|
|
327
|
+
? 'chatgpt_oauth'
|
|
328
|
+
: 'unavailable';
|
|
329
|
+
const base = {
|
|
330
|
+
schema: 'sks.codex-lb-desktop-routing.v1',
|
|
331
|
+
mode,
|
|
332
|
+
gateway_auth_transport: gatewayAuthTransport,
|
|
333
|
+
identity_plane: identityPlane,
|
|
334
|
+
auth_mutated: false,
|
|
335
|
+
config_path: configPath,
|
|
336
|
+
auth_path: authPath,
|
|
337
|
+
auth_before: authBefore
|
|
338
|
+
};
|
|
339
|
+
if (mode === 'cli-provider') {
|
|
340
|
+
return {
|
|
341
|
+
...base,
|
|
342
|
+
ok: false,
|
|
343
|
+
status: 'invalid_desktop_routing_input',
|
|
344
|
+
routing_plane: 'unchanged',
|
|
345
|
+
oauth_preserved: true,
|
|
346
|
+
auth_after: authBefore,
|
|
347
|
+
blockers: ['cli_provider_is_not_a_desktop_routing_mode']
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (modeRequiresChatGptOAuth(mode) && identityPlane !== 'chatgpt_oauth') {
|
|
351
|
+
return {
|
|
352
|
+
...base,
|
|
353
|
+
ok: false,
|
|
354
|
+
status: 'desktop_oauth_required',
|
|
355
|
+
routing_plane: 'unchanged',
|
|
356
|
+
oauth_preserved: false,
|
|
357
|
+
auth_after: authBefore,
|
|
358
|
+
blockers: ['chatgpt_oauth_required_for_desktop']
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (mode === 'desktop-dual-auth-compat' && gatewayAuthTransport !== 'x-codex-lb-api-key') {
|
|
362
|
+
return {
|
|
363
|
+
...base,
|
|
364
|
+
ok: false,
|
|
365
|
+
status: 'desktop_gateway_auth_transport_unsupported',
|
|
366
|
+
routing_plane: 'unchanged',
|
|
367
|
+
oauth_preserved: true,
|
|
368
|
+
auth_after: authBefore,
|
|
369
|
+
blockers: ['desktop_compat_cannot_use_authorization_bearer_without_replacing_oauth']
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (mode === 'desktop-native-bridge' && (!opts.bridgeBaseUrl || !opts.remoteBaseUrl)) {
|
|
373
|
+
return {
|
|
374
|
+
...base,
|
|
375
|
+
ok: false,
|
|
376
|
+
status: 'invalid_desktop_routing_input',
|
|
377
|
+
routing_plane: 'unchanged',
|
|
378
|
+
oauth_preserved: true,
|
|
379
|
+
auth_after: authBefore,
|
|
380
|
+
blockers: [
|
|
381
|
+
...(!opts.bridgeBaseUrl ? ['missing_bridge_base_url'] : []),
|
|
382
|
+
...(!opts.remoteBaseUrl ? ['missing_remote_base_url'] : [])
|
|
383
|
+
]
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (mode === 'desktop-dual-auth-compat' && !opts.remoteBaseUrl) {
|
|
387
|
+
return {
|
|
388
|
+
...base,
|
|
389
|
+
ok: false,
|
|
390
|
+
status: 'invalid_desktop_routing_input',
|
|
391
|
+
routing_plane: 'unchanged',
|
|
392
|
+
oauth_preserved: true,
|
|
393
|
+
auth_after: authBefore,
|
|
394
|
+
blockers: ['missing_remote_base_url']
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const current = await readText(configPath, '');
|
|
398
|
+
let next;
|
|
399
|
+
try {
|
|
400
|
+
next = mode === 'desktop-native-bridge'
|
|
401
|
+
? upsertCodexLbNativeDesktopConfig(current, {
|
|
402
|
+
bridgeBaseUrl: String(opts.bridgeBaseUrl),
|
|
403
|
+
remoteBaseUrl: String(opts.remoteBaseUrl)
|
|
404
|
+
})
|
|
405
|
+
: mode === 'desktop-dual-auth-compat'
|
|
406
|
+
? upsertCodexLbCompatDesktopConfig(current, { remoteBaseUrl: String(opts.remoteBaseUrl) })
|
|
407
|
+
: removeCodexLbManagedDesktopConfig(current);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
return {
|
|
411
|
+
...base,
|
|
412
|
+
ok: false,
|
|
413
|
+
status: 'failed',
|
|
414
|
+
routing_plane: 'unchanged',
|
|
415
|
+
oauth_preserved: true,
|
|
416
|
+
auth_after: authBefore,
|
|
417
|
+
blockers: ['desktop_config_conflict'],
|
|
418
|
+
error: error instanceof Error ? error.message : String(error)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const write = await safeWriteCodexConfigToml(configPath, current, next, 'codex-lb-desktop-routing', { verifyUnchangedBeforeWrite: true });
|
|
422
|
+
if (!write.ok) {
|
|
423
|
+
return {
|
|
424
|
+
...base,
|
|
425
|
+
ok: false,
|
|
426
|
+
status: 'failed',
|
|
427
|
+
routing_plane: 'unchanged',
|
|
428
|
+
oauth_preserved: true,
|
|
429
|
+
auth_after: authBefore,
|
|
430
|
+
backup_path: write.backup_path,
|
|
431
|
+
blockers: ['desktop_config_write_failed'],
|
|
432
|
+
error: write.status
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
const authAfter = await captureCodexAuthSnapshot({ home, authPath });
|
|
436
|
+
try {
|
|
437
|
+
await assertDesktopAuthUnchangedBySks(authBefore, authAfter);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
const writtenText = await readText(configPath, '');
|
|
441
|
+
const rollbackWrite = await safeWriteCodexConfigToml(configPath, writtenText, current, 'codex-lb-desktop-routing-auth-invariant-rollback', { verifyUnchangedBeforeWrite: true });
|
|
442
|
+
return {
|
|
443
|
+
...base,
|
|
444
|
+
ok: false,
|
|
445
|
+
status: 'failed',
|
|
446
|
+
routing_plane: 'unchanged',
|
|
447
|
+
oauth_preserved: false,
|
|
448
|
+
auth_after: authAfter,
|
|
449
|
+
backup_path: write.backup_path,
|
|
450
|
+
rollback: {
|
|
451
|
+
ok: rollbackWrite.ok,
|
|
452
|
+
status: rollbackWrite.status,
|
|
453
|
+
config_restored: rollbackWrite.ok
|
|
454
|
+
},
|
|
455
|
+
blockers: ['desktop_auth_byte_invariant_failed', ...(rollbackWrite.ok ? [] : ['desktop_config_rollback_failed'])],
|
|
456
|
+
error: error instanceof Error ? error.message : String(error)
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const routingPlane = mode === 'desktop-native-bridge'
|
|
460
|
+
? 'desktop_native_bridge'
|
|
461
|
+
: mode === 'desktop-dual-auth-compat'
|
|
462
|
+
? 'desktop_compat_provider'
|
|
463
|
+
: 'disabled';
|
|
464
|
+
return {
|
|
465
|
+
...base,
|
|
466
|
+
ok: true,
|
|
467
|
+
status: mode === 'disabled'
|
|
468
|
+
? 'desktop_routing_disabled'
|
|
469
|
+
: write.changed === true
|
|
470
|
+
? 'desktop_routing_configured'
|
|
471
|
+
: 'desktop_routing_present',
|
|
472
|
+
routing_plane: routingPlane,
|
|
473
|
+
oauth_preserved: true,
|
|
474
|
+
auth_after: authAfter,
|
|
475
|
+
backup_path: write.backup_path,
|
|
476
|
+
blockers: []
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
export async function configureCodexLbCliProvider(opts) {
|
|
480
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
481
|
+
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
482
|
+
const authPath = opts.authPath || codexAuthPath(home);
|
|
483
|
+
const authBefore = await captureCodexAuthSnapshot({ home, authPath });
|
|
484
|
+
const current = await readText(configPath, '');
|
|
485
|
+
const next = upsertCodexLbCliProviderConfig(current, {
|
|
486
|
+
remoteBaseUrl: opts.remoteBaseUrl,
|
|
487
|
+
...(opts.selectGlobally === undefined ? {} : { selectGlobally: opts.selectGlobally })
|
|
488
|
+
});
|
|
489
|
+
const write = await safeWriteCodexConfigToml(configPath, current, next, 'codex-lb-cli-provider', { verifyUnchangedBeforeWrite: true });
|
|
490
|
+
const authAfter = await captureCodexAuthSnapshot({ home, authPath });
|
|
491
|
+
try {
|
|
492
|
+
await assertDesktopAuthUnchangedBySks(authBefore, authAfter);
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return {
|
|
496
|
+
schema: 'sks.codex-lb-cli-provider.v1',
|
|
497
|
+
ok: false,
|
|
498
|
+
status: 'auth_changed_during_cli_provider_write',
|
|
499
|
+
mode: 'cli-provider',
|
|
500
|
+
identity_plane: 'unchanged',
|
|
501
|
+
routing_plane: 'cli_provider',
|
|
502
|
+
oauth_preserved: false,
|
|
503
|
+
auth_mutated: false,
|
|
504
|
+
config_path: configPath,
|
|
505
|
+
auth_path: authPath,
|
|
506
|
+
auth_before: authBefore,
|
|
507
|
+
auth_after: authAfter,
|
|
508
|
+
blockers: ['shared_auth_byte_invariant_failed'],
|
|
509
|
+
backup_path: write.backup_path
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
schema: 'sks.codex-lb-cli-provider.v1',
|
|
514
|
+
ok: write.ok,
|
|
515
|
+
status: write.ok ? (write.changed ? 'configured' : 'present') : write.status,
|
|
516
|
+
mode: 'cli-provider',
|
|
517
|
+
identity_plane: 'unchanged',
|
|
518
|
+
routing_plane: 'cli_provider',
|
|
519
|
+
oauth_preserved: true,
|
|
520
|
+
auth_mutated: false,
|
|
521
|
+
config_path: configPath,
|
|
522
|
+
auth_path: authPath,
|
|
523
|
+
auth_before: authBefore,
|
|
524
|
+
auth_after: authAfter,
|
|
525
|
+
blockers: write.ok ? [] : ['cli_provider_config_write_failed'],
|
|
526
|
+
backup_path: write.backup_path
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function hasTopLevelMarker(text, marker) {
|
|
530
|
+
const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
|
|
531
|
+
return topLevel.split(/\r?\n/).some((line) => line.trim() === marker);
|
|
532
|
+
}
|
|
533
|
+
function managedCodexLbDesktopMode(text) {
|
|
534
|
+
if (hasTopLevelMarker(text, CODEX_LB_DESKTOP_BRIDGE_MARKER))
|
|
535
|
+
return 'desktop-native-bridge';
|
|
536
|
+
if (hasTopLevelMarker(text, CODEX_LB_DESKTOP_COMPAT_MARKER))
|
|
537
|
+
return 'desktop-dual-auth-compat';
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
function codexLbRoutingPlane(mode) {
|
|
541
|
+
if (mode === 'desktop-native-bridge')
|
|
542
|
+
return 'desktop_native_bridge';
|
|
543
|
+
if (mode === 'desktop-dual-auth-compat')
|
|
544
|
+
return 'desktop_compat_provider';
|
|
545
|
+
return 'cli_provider';
|
|
546
|
+
}
|
|
333
547
|
async function capturePostinstallCodexLbConfigSnapshot(home = process.env.HOME || os.homedir()) {
|
|
334
548
|
const configPath = codexLbConfigPath(home);
|
|
335
549
|
const envPath = codexLbEnvPath(home);
|
|
336
550
|
const authPath = codexAuthPath(home);
|
|
337
551
|
const config = await readText(configPath, '');
|
|
338
552
|
const envText = await readText(envPath, '');
|
|
339
|
-
const
|
|
340
|
-
const authText = authExisted ? await readText(authPath, '') : '';
|
|
553
|
+
const auth = await captureCodexAuthSnapshot({ home, authPath });
|
|
341
554
|
const envLoad = await loadCodexLbEnv({ home, envPath });
|
|
342
555
|
const envKey = envLoad.secret_api_key || '';
|
|
343
556
|
const providerConfigured = /\[model_providers\.codex-lb\]/.test(config);
|
|
344
557
|
const baseUrl = envLoad.base_url || codexLbProviderBaseUrl(config) || parseCodexLbEnvBaseUrl(envText);
|
|
345
|
-
|
|
558
|
+
const desktopMode = managedCodexLbDesktopMode(config);
|
|
559
|
+
const bridgeBaseUrl = desktopMode === 'desktop-native-bridge'
|
|
560
|
+
? topLevelTomlString(config, 'openai_base_url')
|
|
561
|
+
: '';
|
|
562
|
+
if (!envKey && !providerConfigured && !baseUrl && !desktopMode)
|
|
346
563
|
return null;
|
|
347
564
|
return {
|
|
348
565
|
config_path: configPath,
|
|
349
566
|
env_path: envPath,
|
|
350
567
|
auth_path: authPath,
|
|
351
568
|
base_url: baseUrl ? normalizeCodexLbBaseUrl(baseUrl) : null,
|
|
569
|
+
bridge_base_url: bridgeBaseUrl || null,
|
|
570
|
+
desktop_mode: desktopMode,
|
|
352
571
|
selected: hasTopLevelCodexLbSelected(config),
|
|
353
|
-
auth_mode:
|
|
354
|
-
|
|
572
|
+
auth_mode: auth.mode,
|
|
573
|
+
auth_sha256: auth.sha256,
|
|
355
574
|
credential_source: envLoad.source,
|
|
356
575
|
credential_fingerprint: envLoad.api_key.fingerprint,
|
|
357
576
|
credential_binding: envLoad.credential_binding,
|
|
358
|
-
secret_api_key: envKey || null
|
|
359
|
-
auth_existed: authExisted,
|
|
360
|
-
auth_text: authText
|
|
577
|
+
secret_api_key: envKey || null
|
|
361
578
|
};
|
|
362
579
|
}
|
|
363
580
|
async function restorePostinstallCodexLbConfigSnapshot(snapshot) {
|
|
364
581
|
if (!snapshot)
|
|
365
582
|
return { status: 'skipped', reason: 'no_snapshot' };
|
|
366
583
|
let configRestored = false;
|
|
584
|
+
let configStatus = 'present';
|
|
367
585
|
if (snapshot.base_url) {
|
|
368
586
|
const current = await readText(snapshot.config_path, '');
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
587
|
+
let next = current;
|
|
588
|
+
try {
|
|
589
|
+
if (snapshot.desktop_mode === 'desktop-native-bridge' && snapshot.bridge_base_url) {
|
|
590
|
+
next = upsertCodexLbNativeDesktopConfig(current, {
|
|
591
|
+
bridgeBaseUrl: snapshot.bridge_base_url,
|
|
592
|
+
remoteBaseUrl: snapshot.base_url
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
else if (snapshot.desktop_mode === 'desktop-dual-auth-compat') {
|
|
596
|
+
next = upsertCodexLbCompatDesktopConfig(current, { remoteBaseUrl: snapshot.base_url });
|
|
597
|
+
}
|
|
598
|
+
else if (snapshot.selected !== true) {
|
|
599
|
+
next = upsertCodexLbCliProviderConfig(current, {
|
|
600
|
+
remoteBaseUrl: snapshot.base_url,
|
|
601
|
+
selectGlobally: false
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
configStatus = 'legacy_migration_required';
|
|
606
|
+
}
|
|
374
607
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
await
|
|
388
|
-
|
|
608
|
+
catch (error) {
|
|
609
|
+
return {
|
|
610
|
+
status: 'failed',
|
|
611
|
+
reason: 'managed_routing_restore_conflict',
|
|
612
|
+
config_path: snapshot.config_path,
|
|
613
|
+
auth_path: snapshot.auth_path,
|
|
614
|
+
config_restored: false,
|
|
615
|
+
auth_restored: false,
|
|
616
|
+
error: error instanceof Error ? error.message : String(error)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (configStatus !== 'legacy_migration_required' && ensureTrailingNewline(next) !== ensureTrailingNewline(current)) {
|
|
620
|
+
const safeWrite = await safeWriteCodexConfigToml(snapshot.config_path, current, next, 'codex-lb-safe-routing-restore', { verifyUnchangedBeforeWrite: true });
|
|
621
|
+
configRestored = safeWrite.ok && safeWrite.changed === true;
|
|
622
|
+
configStatus = safeWrite.ok ? (configRestored ? 'restored' : 'present') : safeWrite.status;
|
|
389
623
|
}
|
|
390
624
|
}
|
|
625
|
+
const authAfter = await captureCodexAuthSnapshot({
|
|
626
|
+
home: path.dirname(path.dirname(snapshot.auth_path)),
|
|
627
|
+
authPath: snapshot.auth_path
|
|
628
|
+
});
|
|
629
|
+
const authUnchanged = snapshot.auth_sha256 === authAfter.sha256;
|
|
391
630
|
return {
|
|
392
|
-
status:
|
|
631
|
+
status: configStatus,
|
|
393
632
|
config_path: snapshot.config_path,
|
|
394
633
|
auth_path: snapshot.auth_path,
|
|
395
634
|
config_restored: configRestored,
|
|
396
|
-
auth_restored:
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
async function ensureCodexLbToolCatalogSelection(input, opts = {}) {
|
|
400
|
-
const codexHome = opts.codexHome || path.join(input.home, '.codex');
|
|
401
|
-
const catalogPath = codexLbToolCatalogPath(codexHome);
|
|
402
|
-
const current = await readText(input.configPath, '');
|
|
403
|
-
const selected = hasTopLevelCodexLbSelected(current);
|
|
404
|
-
const canBindCatalog = selected || opts.allowUnselectedCatalogWrite === true;
|
|
405
|
-
const existingCatalogPath = topLevelTomlString(current, 'model_catalog_json');
|
|
406
|
-
const { isSksManagedCatalogPath, resolveCatalogPath } = await import('../core/codex-app/multi-provider-router-support.js');
|
|
407
|
-
const existingCatalogResolved = existingCatalogPath
|
|
408
|
-
? resolveCatalogPath(existingCatalogPath, { home: input.home, configPath: input.configPath })
|
|
409
|
-
: null;
|
|
410
|
-
const existingCatalogReplaceable = !existingCatalogResolved
|
|
411
|
-
|| (isSksManagedCatalogPath(existingCatalogResolved, { home: input.home })
|
|
412
|
-
&& (selected || opts.willSelectProvider === true));
|
|
413
|
-
if (canBindCatalog && existingCatalogResolved && existingCatalogResolved !== path.resolve(catalogPath) && !existingCatalogReplaceable) {
|
|
414
|
-
return {
|
|
415
|
-
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
416
|
-
ok: false,
|
|
417
|
-
required: true,
|
|
418
|
-
status: 'user_catalog_conflict',
|
|
419
|
-
path: catalogPath,
|
|
420
|
-
configured_path: existingCatalogPath,
|
|
421
|
-
config_changed: false,
|
|
422
|
-
selected,
|
|
423
|
-
blockers: ['codex_lb_user_model_catalog_conflict']
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
let hostname = '';
|
|
427
|
-
try {
|
|
428
|
-
hostname = new URL(input.baseUrl).hostname.toLowerCase();
|
|
429
|
-
}
|
|
430
|
-
catch { }
|
|
431
|
-
const reservedFixtureHost = /(?:^|\.)(?:test|invalid|example)$/.test(hostname);
|
|
432
|
-
if (reservedFixtureHost && typeof opts.toolCatalogFetch !== 'function') {
|
|
433
|
-
return {
|
|
434
|
-
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
435
|
-
ok: true,
|
|
436
|
-
required: false,
|
|
437
|
-
status: 'skipped_reserved_host',
|
|
438
|
-
path: catalogPath,
|
|
439
|
-
config_changed: false,
|
|
440
|
-
selected,
|
|
441
|
-
blockers: []
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
const catalog = await ensureCodexLbToolCatalog({
|
|
445
|
-
codexHome,
|
|
446
|
-
baseUrl: input.baseUrl,
|
|
447
|
-
apiKey: input.apiKey,
|
|
448
|
-
...(typeof opts.toolCatalogFetch === 'function' ? { fetchImpl: opts.toolCatalogFetch } : {}),
|
|
449
|
-
timeoutMs: Number(opts.toolCatalogTimeoutMs || 5000),
|
|
450
|
-
force: opts.forceToolCatalog === true
|
|
451
|
-
});
|
|
452
|
-
if (!catalog.ok || !canBindCatalog) {
|
|
453
|
-
return {
|
|
454
|
-
...catalog,
|
|
455
|
-
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
456
|
-
config_changed: false,
|
|
457
|
-
selected
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
const next = ensureTrailingNewline(upsertTopLevelTomlString(current, 'model_catalog_json', catalog.path));
|
|
461
|
-
if (next === ensureTrailingNewline(current)) {
|
|
462
|
-
return { ...catalog, schema: 'sks.codex-lb-tool-catalog-selection.v1', config_changed: false, selected };
|
|
463
|
-
}
|
|
464
|
-
const safeWrite = await safeWriteCodexConfigToml(input.configPath, current, next, 'codex-lb-tool-catalog');
|
|
465
|
-
return {
|
|
466
|
-
...catalog,
|
|
467
|
-
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
468
|
-
ok: catalog.ok && safeWrite.ok,
|
|
469
|
-
status: safeWrite.ok ? catalog.status : safeWrite.status,
|
|
470
|
-
config_changed: safeWrite.ok && safeWrite.changed === true,
|
|
471
|
-
backup_path: safeWrite.backup_path,
|
|
472
|
-
selected,
|
|
473
|
-
blockers: safeWrite.ok ? catalog.blockers : [...new Set([...(catalog.blockers || []), 'codex_lb_tool_catalog_config_write_failed'])]
|
|
635
|
+
auth_restored: false,
|
|
636
|
+
auth_unchanged: authUnchanged
|
|
474
637
|
};
|
|
475
638
|
}
|
|
476
639
|
export async function configureCodexLb(opts = {}) {
|
|
@@ -480,7 +643,12 @@ export async function configureCodexLb(opts = {}) {
|
|
|
480
643
|
const rawHost = String(opts.host || opts.baseUrl || '');
|
|
481
644
|
const baseUrl = normalizeCodexLbBaseUrl(rawHost);
|
|
482
645
|
const apiKey = String(opts.apiKey || '').trim();
|
|
483
|
-
const
|
|
646
|
+
const initialConfig = await readText(configPath, '');
|
|
647
|
+
const preservedDesktopMode = managedCodexLbDesktopMode(initialConfig);
|
|
648
|
+
const preservedLegacySelection = hasTopLevelCodexLbSelected(initialConfig) && !preservedDesktopMode;
|
|
649
|
+
const requestedDesktopMode = parseCodexLbDesktopMode(opts.desktopMode || 'cli-provider');
|
|
650
|
+
const desktopMode = preservedDesktopMode || 'cli-provider';
|
|
651
|
+
const useDefaultProvider = desktopMode === 'desktop-dual-auth-compat' || preservedLegacySelection;
|
|
484
652
|
const writeEnvFile = opts.writeEnvFile !== false;
|
|
485
653
|
const storeKeychain = opts.storeKeychain === true || opts.keychain === true;
|
|
486
654
|
const syncLaunchctl = opts.syncLaunchctl === true || opts.syncLaunchEnv === true;
|
|
@@ -488,7 +656,8 @@ export async function configureCodexLb(opts = {}) {
|
|
|
488
656
|
const setupAnswers = {
|
|
489
657
|
host_or_base_url: rawHost,
|
|
490
658
|
api_key_source: opts.apiKeySource || 'stdin',
|
|
491
|
-
|
|
659
|
+
desktop_mode: 'cli-provider',
|
|
660
|
+
gateway_auth_transport: (opts.gatewayAuthTransport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT),
|
|
492
661
|
write_env_file: writeEnvFile,
|
|
493
662
|
store_keychain: storeKeychain,
|
|
494
663
|
sync_launchctl: syncLaunchctl,
|
|
@@ -535,16 +704,63 @@ export async function configureCodexLb(opts = {}) {
|
|
|
535
704
|
? ['codex-lb base URL uses http outside localhost; prefer https or pass an explicit allow flag in the calling surface.']
|
|
536
705
|
: [];
|
|
537
706
|
const beforeState = await captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile });
|
|
707
|
+
const authBefore = await captureCodexAuthSnapshot({ home, authPath: opts.authPath || codexAuthPath(home) });
|
|
538
708
|
const appliedActions = [];
|
|
539
709
|
await ensureDir(path.dirname(configPath));
|
|
540
710
|
const current = await readText(configPath, '');
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
711
|
+
let providerOnly = current;
|
|
712
|
+
try {
|
|
713
|
+
if (desktopMode === 'desktop-native-bridge') {
|
|
714
|
+
const bridgeBaseUrl = topLevelTomlString(current, 'openai_base_url');
|
|
715
|
+
if (!bridgeBaseUrl) {
|
|
716
|
+
return {
|
|
717
|
+
ok: false,
|
|
718
|
+
status: 'managed_desktop_bridge_missing_base_url',
|
|
719
|
+
mode: desktopMode,
|
|
720
|
+
config_path: configPath,
|
|
721
|
+
env_path: envPath
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
providerOnly = upsertCodexLbNativeDesktopConfig(current, {
|
|
725
|
+
bridgeBaseUrl,
|
|
726
|
+
remoteBaseUrl: baseUrl
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
else if (desktopMode === 'desktop-dual-auth-compat') {
|
|
730
|
+
providerOnly = upsertCodexLbCompatDesktopConfig(current, { remoteBaseUrl: baseUrl });
|
|
731
|
+
}
|
|
732
|
+
else if (!preservedLegacySelection) {
|
|
733
|
+
providerOnly = upsertCodexLbCliProviderConfig(current, {
|
|
734
|
+
remoteBaseUrl: baseUrl,
|
|
735
|
+
selectGlobally: false
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
return {
|
|
741
|
+
ok: false,
|
|
742
|
+
status: 'managed_desktop_routing_conflict',
|
|
743
|
+
mode: desktopMode,
|
|
744
|
+
config_path: configPath,
|
|
745
|
+
env_path: envPath,
|
|
746
|
+
error: error instanceof Error ? error.message : String(error)
|
|
747
|
+
};
|
|
748
|
+
}
|
|
544
749
|
const safeWrite = await safeWriteCodexConfigToml(configPath, current, providerOnly, 'codex-lb');
|
|
545
750
|
if (!safeWrite.ok)
|
|
546
751
|
return { ok: false, status: safeWrite.status, config_path: configPath, env_path: envPath, backup_path: safeWrite.backup_path };
|
|
547
|
-
appliedActions.push({
|
|
752
|
+
appliedActions.push({
|
|
753
|
+
type: preservedLegacySelection
|
|
754
|
+
? 'preserve_legacy_desktop_routing'
|
|
755
|
+
: desktopMode === 'desktop-native-bridge'
|
|
756
|
+
? 'preserve_desktop_native_bridge'
|
|
757
|
+
: desktopMode === 'desktop-dual-auth-compat'
|
|
758
|
+
? 'preserve_desktop_compat_provider'
|
|
759
|
+
: 'write_cli_provider',
|
|
760
|
+
target: configPath,
|
|
761
|
+
ok: true,
|
|
762
|
+
backup_path: safeWrite.backup_path
|
|
763
|
+
});
|
|
548
764
|
if (writeEnvFile) {
|
|
549
765
|
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`, { mode: 0o600 });
|
|
550
766
|
await fsp.chmod(envPath, 0o600).catch(() => { });
|
|
@@ -552,47 +768,20 @@ export async function configureCodexLb(opts = {}) {
|
|
|
552
768
|
}
|
|
553
769
|
process.env.CODEX_LB_BASE_URL = baseUrl;
|
|
554
770
|
process.env.CODEX_LB_API_KEY = apiKey;
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return {
|
|
570
|
-
ok: false,
|
|
571
|
-
status: selectWrite.status || 'provider_selection_failed',
|
|
572
|
-
config_path: configPath,
|
|
573
|
-
env_path: envPath,
|
|
574
|
-
backup_path: selectWrite.backup_path,
|
|
575
|
-
tool_catalog: toolCatalog,
|
|
576
|
-
tool_output_recovery: toolOutputRecovery,
|
|
577
|
-
error: 'codex_lb_provider_selection_write_failed'
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
providerSelected = true;
|
|
581
|
-
appliedActions.push({ type: 'select_default_provider', target: configPath, ok: true, backup_path: selectWrite.backup_path });
|
|
582
|
-
toolCatalog = await ensureCodexLbToolCatalogSelection({ home, configPath, baseUrl, apiKey }, opts);
|
|
583
|
-
if (toolCatalog.config_changed) {
|
|
584
|
-
appliedActions.push({ type: 'write_model_tool_catalog', target: toolCatalog.path, ok: toolCatalog.ok === true, status: toolCatalog.status });
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
else if (useDefaultProvider) {
|
|
588
|
-
appliedActions.push({
|
|
589
|
-
type: 'select_default_provider',
|
|
590
|
-
target: configPath,
|
|
591
|
-
ok: false,
|
|
592
|
-
status: 'deferred_until_tool_catalog_ready',
|
|
593
|
-
blockers: toolCatalog.blockers || ['codex_lb_gpt56_tool_catalog_not_ready']
|
|
594
|
-
});
|
|
595
|
-
}
|
|
771
|
+
const toolCatalog = {
|
|
772
|
+
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
773
|
+
ok: true,
|
|
774
|
+
required: false,
|
|
775
|
+
status: desktopMode === 'desktop-native-bridge'
|
|
776
|
+
? 'not_bound_for_desktop_native'
|
|
777
|
+
: desktopMode === 'desktop-dual-auth-compat'
|
|
778
|
+
? 'not_bound_for_desktop_compat'
|
|
779
|
+
: 'not_bound_for_cli_provider',
|
|
780
|
+
path: codexLbToolCatalogPath(path.join(home, '.codex')),
|
|
781
|
+
config_changed: false,
|
|
782
|
+
selected: desktopMode === 'desktop-dual-auth-compat',
|
|
783
|
+
blockers: []
|
|
784
|
+
};
|
|
596
785
|
const keyFingerprint = await sha256Text(apiKey);
|
|
597
786
|
const metadataPath = opts.metadataPath || codexLbMetadataPath(home);
|
|
598
787
|
await writeTextAtomic(metadataPath, `${JSON.stringify({
|
|
@@ -600,6 +789,9 @@ export async function configureCodexLb(opts = {}) {
|
|
|
600
789
|
base_url: baseUrl,
|
|
601
790
|
updated_at: new Date().toISOString(),
|
|
602
791
|
source: opts.source || 'setup',
|
|
792
|
+
desktop_mode: desktopMode,
|
|
793
|
+
requested_desktop_mode: requestedDesktopMode,
|
|
794
|
+
gateway_auth_transport: opts.gatewayAuthTransport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT,
|
|
603
795
|
api_key: { redacted: true, sha256: keyFingerprint }
|
|
604
796
|
}, null, 2)}\n`);
|
|
605
797
|
await fsp.chmod(metadataPath, 0o600).catch(() => { });
|
|
@@ -607,25 +799,54 @@ export async function configureCodexLb(opts = {}) {
|
|
|
607
799
|
const keychain = storeKeychain ? await writeCodexLbKeychain(apiKey, opts).catch((err) => ({ ok: false, status: 'keychain_store_failed', error: err.message })) : { ok: false, status: 'skipped' };
|
|
608
800
|
if (storeKeychain)
|
|
609
801
|
appliedActions.push({ type: 'store_keychain', target: 'macOS Keychain service sks-codex-lb', ok: keychain.ok === true, status: keychain.status });
|
|
610
|
-
const
|
|
611
|
-
|
|
802
|
+
const { syncDesktopCenterLaunchCredentials } = await import('../core/codex-lb/desktop-center-credentials.js');
|
|
803
|
+
const centerCredentials = await syncDesktopCenterLaunchCredentials({
|
|
804
|
+
mode: desktopMode,
|
|
805
|
+
home,
|
|
806
|
+
loadedEnv: await loadCodexLbEnv({ home, processEnv: {}, envPath, metadataPath })
|
|
807
|
+
});
|
|
808
|
+
appliedActions.push({
|
|
809
|
+
type: 'sync_center_desktop_credentials',
|
|
810
|
+
target: 'launchctl from official sks-codex-lb store',
|
|
811
|
+
ok: centerCredentials.ok === true,
|
|
812
|
+
status: centerCredentials.status
|
|
813
|
+
});
|
|
814
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment({ env_path: envPath, base_url: baseUrl }, {
|
|
815
|
+
...opts,
|
|
816
|
+
home,
|
|
817
|
+
apiKey,
|
|
818
|
+
baseUrl,
|
|
819
|
+
syncLaunchEnv: syncLaunchctl && desktopMode !== 'desktop-dual-auth-compat'
|
|
820
|
+
});
|
|
821
|
+
if (syncLaunchctl && desktopMode !== 'desktop-dual-auth-compat') {
|
|
612
822
|
appliedActions.push({ type: 'sync_launchctl', target: 'macOS launchctl user environment (base URL only; API-key env removed)', ok: codexEnvironment.ok === true, status: codexEnvironment.status });
|
|
823
|
+
}
|
|
613
824
|
const shellProfileResult = await installCodexLbShellProfileSnippet({ home, envPath, shellProfile }).catch((err) => ({ ok: false, status: 'failed', files: [], error: err.message }));
|
|
614
825
|
if (shellProfile !== 'skip')
|
|
615
826
|
appliedActions.push({ type: 'install_shell_profile_snippet', target: shellProfileResult.files?.join(', ') || shellProfile, ok: shellProfileResult.ok === true, status: shellProfileResult.status });
|
|
616
827
|
const codexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
828
|
+
const authAfter = await captureCodexAuthSnapshot({ home, authPath: opts.authPath || codexAuthPath(home) });
|
|
829
|
+
let authInvariantError = null;
|
|
830
|
+
try {
|
|
831
|
+
await assertDesktopAuthUnchangedBySks(authBefore, authAfter);
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
authInvariantError = error instanceof Error ? error.message : String(error);
|
|
835
|
+
}
|
|
836
|
+
const authReconcile = {
|
|
837
|
+
status: authInvariantError ? 'failed' : 'oauth_untouched',
|
|
838
|
+
reason: authInvariantError || 'credential_setup_does_not_mutate_desktop_auth',
|
|
839
|
+
auth_path: authBefore.path,
|
|
840
|
+
...(authInvariantError ? { error: authInvariantError } : {})
|
|
841
|
+
};
|
|
842
|
+
const codexLogin = {
|
|
843
|
+
ok: !authInvariantError,
|
|
844
|
+
status: authInvariantError ? 'desktop_auth_byte_invariant_failed' : 'not_required',
|
|
845
|
+
reason: authInvariantError || 'cli_gateway_key_is_separate_from_chatgpt_oauth',
|
|
846
|
+
error: authInvariantError
|
|
847
|
+
};
|
|
627
848
|
const finalCodexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
|
|
628
|
-
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
849
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok && centerCredentials.ok !== false);
|
|
629
850
|
const afterState = await captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile });
|
|
630
851
|
const drift = [
|
|
631
852
|
...detectCodexLbSetupDrift({
|
|
@@ -642,6 +863,7 @@ export async function configureCodexLb(opts = {}) {
|
|
|
642
863
|
beforeState,
|
|
643
864
|
afterState
|
|
644
865
|
}),
|
|
866
|
+
...(authInvariantError ? ['desktop_auth_byte_invariant_failed'] : []),
|
|
645
867
|
...(toolCatalog.required !== false && toolCatalog.ok !== true ? ['codex_lb_gpt56_tool_catalog_not_ready'] : [])
|
|
646
868
|
];
|
|
647
869
|
const appliedPersistenceModes = appliedCodexLbPersistenceModes({
|
|
@@ -663,20 +885,30 @@ export async function configureCodexLb(opts = {}) {
|
|
|
663
885
|
const warnings = [
|
|
664
886
|
...insecureLocalWarning,
|
|
665
887
|
...persistence.warnings,
|
|
888
|
+
...(preservedLegacySelection ? ['legacy_desktop_auth_routing_requires_explicit_migrate_legacy_desktop'] : []),
|
|
666
889
|
...(toolCatalog.required !== false && toolCatalog.ok !== true ? ['codex_lb_gpt56_tool_catalog_not_ready'] : [])
|
|
667
890
|
];
|
|
668
|
-
const failureStatus =
|
|
669
|
-
? (
|
|
670
|
-
:
|
|
671
|
-
? (
|
|
672
|
-
:
|
|
891
|
+
const failureStatus = centerCredentials.ok !== true
|
|
892
|
+
? (centerCredentials.status || 'center_desktop_credentials_failed')
|
|
893
|
+
: codexEnvironment.ok !== true
|
|
894
|
+
? (codexEnvironment.status || 'environment_failed')
|
|
895
|
+
: codexLogin.ok !== true
|
|
896
|
+
? (codexLogin.status || 'login_failed')
|
|
897
|
+
: null;
|
|
673
898
|
return {
|
|
674
899
|
ok: ok && drift.length === 0,
|
|
675
900
|
status: ok && drift.length === 0 ? 'configured' : failureStatus || (drift.length ? 'setup_choice_drift' : 'configuration_failed'),
|
|
901
|
+
mode: desktopMode,
|
|
902
|
+
identity_plane: 'unchanged',
|
|
903
|
+
routing_plane: codexLbRoutingPlane(desktopMode),
|
|
904
|
+
gateway_auth_transport: opts.gatewayAuthTransport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT,
|
|
905
|
+
oauth_preserved: !authInvariantError,
|
|
906
|
+
auth_mutated: false,
|
|
676
907
|
plan: plan,
|
|
677
908
|
applied_actions: appliedActions,
|
|
678
909
|
drift,
|
|
679
910
|
persistence,
|
|
911
|
+
center_credentials: centerCredentials,
|
|
680
912
|
config_path: configPath,
|
|
681
913
|
env_path: envPath,
|
|
682
914
|
metadata_path: metadataPath,
|
|
@@ -718,19 +950,44 @@ export async function codexLbStatus(opts = {}) {
|
|
|
718
950
|
const providerRequiresOpenAiAuth = codexLbProviderRequiresOpenAiAuth(config);
|
|
719
951
|
const providerOpenAiAuthDisabled = codexLbProviderOpenAiAuthDisabled(config);
|
|
720
952
|
const providerEnvKey = codexLbProviderEnvKey(config);
|
|
953
|
+
const providerHasSeparateGatewayAuth = codexLbProviderHasSeparateGatewayAuth(config);
|
|
954
|
+
const desktopMode = managedCodexLbDesktopMode(config);
|
|
955
|
+
const bridgeBaseUrl = desktopMode === 'desktop-native-bridge'
|
|
956
|
+
? topLevelTomlString(config, 'openai_base_url')
|
|
957
|
+
: '';
|
|
721
958
|
const sharedOpenAiRouting = codexLbSharedOpenAiRoutingState(config, baseUrl);
|
|
722
959
|
const codexLbKeyInSharedAuth = Boolean(authApiKey && envLoad.secret_api_key && authApiKey === envLoad.secret_api_key);
|
|
723
960
|
const sharedOpenAiRoutingSafe = !codexLbKeyInSharedAuth || sharedOpenAiRouting.status === 'matched';
|
|
724
|
-
const
|
|
725
|
-
const providerContractOk = providerConfigured
|
|
961
|
+
const cliProviderContractOk = providerConfigured
|
|
726
962
|
&& providerBaseUrlMatchesCredential
|
|
727
|
-
&& providerName ===
|
|
963
|
+
&& providerName === 'codex-lb'
|
|
728
964
|
&& providerWireApi === 'responses'
|
|
729
965
|
&& providerEnvKey === CODEX_LB_PROVIDER_ENV_KEY
|
|
730
966
|
&& providerSupportsWebsockets === true
|
|
731
|
-
&&
|
|
967
|
+
&& providerOpenAiAuthDisabled;
|
|
968
|
+
const compatProviderContractOk = providerConfigured
|
|
969
|
+
&& providerBaseUrlMatchesCredential
|
|
970
|
+
&& providerName === 'OpenAI'
|
|
971
|
+
&& providerWireApi === 'responses'
|
|
972
|
+
&& providerSupportsWebsockets === true
|
|
973
|
+
&& providerRequiresOpenAiAuth === true
|
|
974
|
+
&& !providerEnvKey
|
|
975
|
+
&& providerHasSeparateGatewayAuth;
|
|
976
|
+
const providerContractOk = desktopMode === 'desktop-dual-auth-compat'
|
|
977
|
+
? compatProviderContractOk
|
|
978
|
+
: cliProviderContractOk;
|
|
732
979
|
const providerUsesCodexLbEnvAuth = providerConfigured && providerEnvKey === CODEX_LB_PROVIDER_ENV_KEY && providerOpenAiAuthDisabled;
|
|
733
|
-
const
|
|
980
|
+
const oauthAvailable = authMode.mode === 'chatgpt_oauth' || authMode.mode === 'browser_marker';
|
|
981
|
+
const authRoutingCoherent = desktopMode === 'desktop-native-bridge'
|
|
982
|
+
? !selected && oauthAvailable && !codexLbKeyInSharedAuth
|
|
983
|
+
: desktopMode === 'desktop-dual-auth-compat'
|
|
984
|
+
? selected && oauthAvailable && !codexLbKeyInSharedAuth
|
|
985
|
+
: !codexLbKeyInSharedAuth && (!selected || providerUsesCodexLbEnvAuth);
|
|
986
|
+
const codexAppUsableWithCodexLb = desktopMode === 'desktop-native-bridge'
|
|
987
|
+
? Boolean(bridgeBaseUrl && oauthAvailable)
|
|
988
|
+
: desktopMode === 'desktop-dual-auth-compat'
|
|
989
|
+
? compatProviderContractOk && oauthAvailable
|
|
990
|
+
: authMode.codex_app_usable;
|
|
734
991
|
const fastMode = codexLbFastModeConfigStatus(config);
|
|
735
992
|
const launchEnvironment = await inspectCodexLbMacLaunchEnvironment(baseUrl, opts).catch((err) => ({
|
|
736
993
|
checked: true,
|
|
@@ -738,7 +995,11 @@ export async function codexLbStatus(opts = {}) {
|
|
|
738
995
|
status: 'inspect_failed',
|
|
739
996
|
error: err.message
|
|
740
997
|
}));
|
|
741
|
-
const providerReady = providerContractOk
|
|
998
|
+
const providerReady = providerContractOk
|
|
999
|
+
&& envLoad.configured
|
|
1000
|
+
&& Boolean(baseUrl)
|
|
1001
|
+
&& authRoutingCoherent
|
|
1002
|
+
&& (desktopMode === 'desktop-native-bridge' ? Boolean(bridgeBaseUrl) : true);
|
|
742
1003
|
const probeToolOutputRecovery = opts.probeToolOutputRecovery === true;
|
|
743
1004
|
const toolOutputRecovery = !selected
|
|
744
1005
|
? codexLbToolOutputRecoveryNotSelected()
|
|
@@ -767,7 +1028,14 @@ export async function codexLbStatus(opts = {}) {
|
|
|
767
1028
|
provider_requires_openai_auth: providerRequiresOpenAiAuth,
|
|
768
1029
|
provider_openai_auth_disabled: providerOpenAiAuthDisabled,
|
|
769
1030
|
provider_env_key: providerEnvKey || null,
|
|
1031
|
+
provider_has_separate_gateway_auth: providerHasSeparateGatewayAuth,
|
|
770
1032
|
provider_uses_codex_lb_env_auth: providerUsesCodexLbEnvAuth,
|
|
1033
|
+
desktop_mode: desktopMode || (selected ? 'legacy-or-manual-selected-provider' : 'cli-provider'),
|
|
1034
|
+
bridge_base_url: bridgeBaseUrl || null,
|
|
1035
|
+
legacy_migration_required: Boolean(!desktopMode
|
|
1036
|
+
&& selected
|
|
1037
|
+
&& authMode.mode === 'apikey'
|
|
1038
|
+
&& sharedOpenAiRouting.managed),
|
|
771
1039
|
selected,
|
|
772
1040
|
env_file: envExists,
|
|
773
1041
|
env_key_configured: envKeyConfigured,
|
|
@@ -793,7 +1061,7 @@ export async function codexLbStatus(opts = {}) {
|
|
|
793
1061
|
managed: sharedOpenAiRouting.managed,
|
|
794
1062
|
configured_base_url: sharedOpenAiRouting.configured_base_url
|
|
795
1063
|
},
|
|
796
|
-
auth_usable_for_codex_app:
|
|
1064
|
+
auth_usable_for_codex_app: codexAppUsableWithCodexLb && authRoutingCoherent,
|
|
797
1065
|
auth_summary: !sharedOpenAiRoutingSafe
|
|
798
1066
|
? 'codex-lb key is active in shared auth without a verified built-in OpenAI routing guard'
|
|
799
1067
|
: !authRoutingCoherent
|
|
@@ -838,38 +1106,36 @@ export function formatCodexLbStatusText(status = {}, opts = {}) {
|
|
|
838
1106
|
if (status.base_url)
|
|
839
1107
|
lines.push(`Base URL: ${status.base_url}`);
|
|
840
1108
|
lines.push(`ChatGPT backup: ${backupPresent ? `yes (${backupPath})` : 'no'}`);
|
|
841
|
-
if (status.shared_openai_routing?.safe === false)
|
|
842
|
-
lines.push('', 'Run: sks codex-lb
|
|
1109
|
+
if (status.legacy_migration_required || status.shared_openai_routing?.safe === false)
|
|
1110
|
+
lines.push('', 'Run: sks codex-lb migrate-legacy-desktop --restart-app. Ordinary repair will not rewrite shared Codex auth.');
|
|
843
1111
|
else if (status.provider_configured && !status.provider_contract_ok)
|
|
844
1112
|
lines.push('', 'Run: sks codex-lb repair to rewrite the provider block to the current codex-lb App contract.');
|
|
845
|
-
else if (status.
|
|
846
|
-
lines.push('', '
|
|
847
|
-
else if (status.ok && !status.
|
|
848
|
-
lines.push('', 'Run
|
|
849
|
-
else if (status.ok &&
|
|
850
|
-
lines.push('', '
|
|
1113
|
+
else if (status.desktop_mode === 'desktop-native-bridge' && !status.auth_usable_for_codex_app)
|
|
1114
|
+
lines.push('', 'Sign in with ChatGPT OAuth, then run: sks codex-lb use-desktop-full.');
|
|
1115
|
+
else if (status.ok && status.desktop_mode === 'cli-provider' && !status.selected)
|
|
1116
|
+
lines.push('', 'CLI provider is stored but unselected. Run `sks codex-lb use-cli` for CLI use or `sks codex-lb use-desktop-full` for managed Desktop routing.');
|
|
1117
|
+
else if (status.ok && status.desktop_mode === 'desktop-dual-auth-compat')
|
|
1118
|
+
lines.push('', 'Compatibility routing is active. Prefer `sks codex-lb use-desktop-full`; use `sks codex-lb disable` to remove managed Desktop routing.');
|
|
851
1119
|
else if (status.ok)
|
|
852
|
-
lines.push('', 'Status:
|
|
1120
|
+
lines.push('', 'Status: configured; no ordinary repair needed.');
|
|
853
1121
|
else if (!status.ok && status.base_url && status.env_key_configured)
|
|
854
|
-
lines.push('', 'Run: sks codex-lb repair
|
|
1122
|
+
lines.push('', 'Run: sks codex-lb repair. To change routing explicitly, use `use-desktop-full`, `use-cli`, or `disable`.');
|
|
855
1123
|
else if (!status.ok)
|
|
856
1124
|
lines.push('', 'Run: sks codex-lb setup --host <domain> --api-key-stdin');
|
|
857
1125
|
if (backupPresent)
|
|
858
|
-
lines.push('
|
|
1126
|
+
lines.push('Legacy OAuth backup detected; use `sks codex-lb migrate-legacy-desktop --restart-app` instead of background auth switching.');
|
|
859
1127
|
return `${lines.join('\n')}\n`;
|
|
860
1128
|
}
|
|
861
1129
|
export function formatCodexLbRepairResultText(result = {}) {
|
|
862
1130
|
const lines = [
|
|
863
|
-
'codex-lb provider
|
|
1131
|
+
'codex-lb credential/provider configuration repaired without changing Codex shared auth.',
|
|
864
1132
|
`Config: ${result.config_path}`,
|
|
865
1133
|
`Key env: ${result.env_path}`
|
|
866
1134
|
];
|
|
867
|
-
if (result.auth_reconcile?.status === '
|
|
868
|
-
lines.push(
|
|
869
|
-
else if (result.auth_reconcile?.status === '
|
|
870
|
-
lines.push('
|
|
871
|
-
else if (['apikey_auth_active', 'apikey_forced'].includes(result.auth_reconcile?.status))
|
|
872
|
-
lines.push('Codex App auth: codex-lb API-key auth.json is active.');
|
|
1135
|
+
if (result.auth_reconcile?.status === 'oauth_untouched')
|
|
1136
|
+
lines.push('Codex App auth: unchanged.');
|
|
1137
|
+
else if (result.auth_reconcile?.status === 'legacy_migration_required')
|
|
1138
|
+
lines.push('Legacy Desktop auth routing: run `sks codex-lb migrate-legacy-desktop --restart-app` explicitly.');
|
|
873
1139
|
return `${lines.join('\n')}\n`;
|
|
874
1140
|
}
|
|
875
1141
|
function codexLbProviderBaseUrl(text = '') {
|
|
@@ -904,6 +1170,10 @@ function codexLbProviderEnvKey(text = '') {
|
|
|
904
1170
|
const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
|
|
905
1171
|
return block.match(/(^|\n)\s*env_key\s*=\s*"([^"]+)"/)?.[2] || '';
|
|
906
1172
|
}
|
|
1173
|
+
function codexLbProviderHasSeparateGatewayAuth(text = '') {
|
|
1174
|
+
const block = String(text || '').match(/(^|\n)\[model_providers\.codex-lb\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
|
|
1175
|
+
return /X-Codex-LB-API-Key/.test(block) && /CODEX_LB_API_KEY/.test(block);
|
|
1176
|
+
}
|
|
907
1177
|
function codexLbFastModeConfigStatus(text = '') {
|
|
908
1178
|
const globalServiceTier = topLevelTomlString(text, 'service_tier');
|
|
909
1179
|
const profileBlock = String(text || '').match(/(^|\n)\[profiles\.sks-fast-high\]([\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/)?.[2] || '';
|
|
@@ -930,111 +1200,214 @@ function tomlTableString(text = '', table, key) {
|
|
|
930
1200
|
return block.match(new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]+)"\\s*(?:#.*)?(?=\\n|$)`))?.[2] || '';
|
|
931
1201
|
}
|
|
932
1202
|
export async function repairCodexLbAuth(opts = {}) {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1203
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
1204
|
+
let status = await codexLbStatus({ ...opts, home });
|
|
1205
|
+
const authPath = opts.authPath || status.auth_path || codexAuthPath(home);
|
|
1206
|
+
const authBefore = await captureCodexAuthSnapshot({ home, authPath });
|
|
1207
|
+
const currentConfig = await readText(status.config_path, '');
|
|
1208
|
+
const legacyEnv = await loadCodexLbEnv({ ...opts, home });
|
|
1209
|
+
const legacyDetection = await detectLegacyCodexLbDesktopState({
|
|
1210
|
+
home,
|
|
1211
|
+
configPath: status.config_path,
|
|
1212
|
+
authPath,
|
|
1213
|
+
...(status.base_url ? { remoteBaseUrl: status.base_url } : {}),
|
|
1214
|
+
...(legacyEnv.secret_api_key
|
|
1215
|
+
? { expectedGatewayApiKey: legacyEnv.secret_api_key }
|
|
1216
|
+
: {})
|
|
937
1217
|
});
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
? await probeCodexLbToolOutputRecovery({
|
|
943
|
-
baseUrl: status.base_url,
|
|
944
|
-
...(typeof opts.toolOutputRecoveryFetch === 'function' ? { fetchImpl: opts.toolOutputRecoveryFetch } : {}),
|
|
945
|
-
timeoutMs: Number(opts.toolOutputRecoveryTimeoutMs || 4_000),
|
|
946
|
-
allowUnverified: opts.allowUnverifiedToolOutputRecovery === true
|
|
947
|
-
|| codexLbToolOutputRecoveryOverrideAcknowledged({ env: opts.env || process.env })
|
|
948
|
-
})
|
|
949
|
-
: status.tool_output_recovery;
|
|
950
|
-
if (status.base_url && toolOutputRecovery?.ok !== true) {
|
|
1218
|
+
const legacySharedAuthSelected = status.selected === true
|
|
1219
|
+
&& status.auth_mode === 'apikey'
|
|
1220
|
+
&& status.shared_openai_routing?.managed === true;
|
|
1221
|
+
if (legacyDetection.legacy_destructive_mode || legacySharedAuthSelected) {
|
|
951
1222
|
return {
|
|
952
1223
|
ok: false,
|
|
953
|
-
status: '
|
|
1224
|
+
status: 'legacy_migration_required',
|
|
1225
|
+
reason: 'legacy_desktop_auth_routing_requires_explicit_migration',
|
|
1226
|
+
config_path: status.config_path,
|
|
1227
|
+
env_path: status.env_path,
|
|
1228
|
+
base_url: status.base_url,
|
|
954
1229
|
codex_lb: status,
|
|
955
|
-
|
|
956
|
-
|
|
1230
|
+
auth_reconcile: {
|
|
1231
|
+
status: 'legacy_migration_required',
|
|
1232
|
+
reason: 'run_sks_codex_lb_migrate_legacy_desktop',
|
|
1233
|
+
auth_path: authPath
|
|
1234
|
+
},
|
|
1235
|
+
codex_login: {
|
|
1236
|
+
ok: true,
|
|
1237
|
+
status: 'not_required',
|
|
1238
|
+
reason: 'ordinary_repair_never_changes_shared_codex_auth'
|
|
1239
|
+
},
|
|
1240
|
+
tool_catalog: {
|
|
1241
|
+
ok: true,
|
|
1242
|
+
required: false,
|
|
1243
|
+
status: 'not_bound_during_repair',
|
|
1244
|
+
config_changed: false
|
|
1245
|
+
}
|
|
957
1246
|
};
|
|
958
1247
|
}
|
|
959
|
-
const currentConfig = await readText(status.config_path, '');
|
|
960
1248
|
if (!status.env_key_configured && status.base_url && (status.provider_configured || status.selected || status.env_base_url_configured)) {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1249
|
+
if (status.auth_mode === 'apikey') {
|
|
1250
|
+
return {
|
|
1251
|
+
ok: false,
|
|
1252
|
+
status: 'legacy_migration_required',
|
|
1253
|
+
reason: 'shared_codex_api_key_cannot_be_assumed_to_be_the_codex_lb_gateway_key',
|
|
1254
|
+
config_path: status.config_path,
|
|
1255
|
+
env_path: status.env_path,
|
|
1256
|
+
base_url: status.base_url,
|
|
1257
|
+
codex_lb: status,
|
|
1258
|
+
auth_reconcile: {
|
|
1259
|
+
status: 'legacy_migration_required',
|
|
1260
|
+
reason: 'run_sks_codex_lb_setup_with_a_separate_gateway_key_or_migrate_legacy_desktop',
|
|
1261
|
+
auth_path: authPath
|
|
1262
|
+
},
|
|
1263
|
+
codex_login: {
|
|
1264
|
+
ok: true,
|
|
1265
|
+
status: 'not_required',
|
|
1266
|
+
reason: 'ordinary_repair_never_reads_gateway_credentials_from_shared_codex_auth'
|
|
1267
|
+
},
|
|
1268
|
+
tool_catalog: {
|
|
1269
|
+
ok: true,
|
|
1270
|
+
required: false,
|
|
1271
|
+
status: 'not_bound_during_repair',
|
|
1272
|
+
config_changed: false
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
966
1275
|
}
|
|
967
1276
|
}
|
|
968
|
-
|
|
969
|
-
const repairPreserveThirdParty = Boolean(repairCurrentProvider)
|
|
970
|
-
&& repairCurrentProvider !== 'codex-lb'
|
|
971
|
-
&& opts.forceCodexLbApiKeyAuth !== true
|
|
972
|
-
&& opts.authMode !== 'codex-lb';
|
|
973
|
-
if (status.env_key_configured && status.base_url && (!status.provider_contract_ok || (!status.selected && !repairPreserveThirdParty) || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig) || (opts.forceCodexLbApiKeyAuth === true && !status.ok))) {
|
|
974
|
-
await ensureDir(path.dirname(status.config_path));
|
|
975
|
-
let next = upsertCodexLbConfig(currentConfig, status.base_url, !repairPreserveThirdParty);
|
|
976
|
-
next = normalizeCodexFastModeUiConfig(next, {
|
|
977
|
-
forceFastMode: opts.forceFastMode === true || opts.forceCodexLbApiKeyAuth === true
|
|
978
|
-
});
|
|
979
|
-
const safeWrite = await safeWriteCodexConfigToml(status.config_path, currentConfig, next, 'codex-lb-repair');
|
|
980
|
-
configRepaired = safeWrite.ok && safeWrite.changed === true;
|
|
981
|
-
status = await codexLbStatus({ ...opts, probeToolOutputRecovery: true });
|
|
982
|
-
}
|
|
983
|
-
const canRepairAuthMode = (opts.forceCodexLbApiKeyAuth === true || status.codex_lb_key_in_shared_auth === true)
|
|
984
|
-
&& status.provider_contract_ok
|
|
985
|
-
&& status.env_key_configured
|
|
986
|
-
&& Boolean(status.base_url);
|
|
987
|
-
if (!status.ok && !canRepairAuthMode) {
|
|
1277
|
+
if (!status.env_key_configured || !status.base_url) {
|
|
988
1278
|
return {
|
|
989
1279
|
ok: false,
|
|
990
|
-
status: !status.env_key_configured ? 'missing_env_key' :
|
|
1280
|
+
status: !status.env_key_configured ? 'missing_env_key' : 'missing_base_url',
|
|
991
1281
|
config_path: status.config_path,
|
|
992
1282
|
env_path: status.env_path,
|
|
993
|
-
codex_lb: status
|
|
1283
|
+
codex_lb: status,
|
|
1284
|
+
auth_reconcile: {
|
|
1285
|
+
status: 'oauth_untouched',
|
|
1286
|
+
reason: 'ordinary_repair_never_changes_shared_codex_auth',
|
|
1287
|
+
auth_path: authPath
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
const configBeforeRepair = await readText(status.config_path, '');
|
|
1292
|
+
const desktopMode = managedCodexLbDesktopMode(configBeforeRepair);
|
|
1293
|
+
let nextConfig = configBeforeRepair;
|
|
1294
|
+
try {
|
|
1295
|
+
if (desktopMode === 'desktop-native-bridge') {
|
|
1296
|
+
const bridgeBaseUrl = topLevelTomlString(configBeforeRepair, 'openai_base_url');
|
|
1297
|
+
if (!bridgeBaseUrl)
|
|
1298
|
+
throw new Error('managed_desktop_bridge_missing_base_url');
|
|
1299
|
+
nextConfig = upsertCodexLbNativeDesktopConfig(configBeforeRepair, {
|
|
1300
|
+
bridgeBaseUrl,
|
|
1301
|
+
remoteBaseUrl: status.base_url
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
else if (desktopMode === 'desktop-dual-auth-compat') {
|
|
1305
|
+
nextConfig = upsertCodexLbCompatDesktopConfig(configBeforeRepair, {
|
|
1306
|
+
remoteBaseUrl: status.base_url
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
else {
|
|
1310
|
+
nextConfig = upsertCodexLbCliProviderConfig(configBeforeRepair, {
|
|
1311
|
+
remoteBaseUrl: status.base_url,
|
|
1312
|
+
selectGlobally: status.selected === true
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
catch (error) {
|
|
1317
|
+
return {
|
|
1318
|
+
ok: false,
|
|
1319
|
+
status: 'managed_desktop_routing_conflict',
|
|
1320
|
+
config_path: status.config_path,
|
|
1321
|
+
env_path: status.env_path,
|
|
1322
|
+
base_url: status.base_url,
|
|
1323
|
+
codex_lb: status,
|
|
1324
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
await ensureDir(path.dirname(status.config_path));
|
|
1328
|
+
const configWrite = await safeWriteCodexConfigToml(status.config_path, configBeforeRepair, nextConfig, 'codex-lb-safe-repair', { verifyUnchangedBeforeWrite: true });
|
|
1329
|
+
if (!configWrite.ok) {
|
|
1330
|
+
return {
|
|
1331
|
+
ok: false,
|
|
1332
|
+
status: configWrite.status,
|
|
1333
|
+
config_path: status.config_path,
|
|
1334
|
+
env_path: status.env_path,
|
|
1335
|
+
base_url: status.base_url,
|
|
1336
|
+
codex_lb: status,
|
|
1337
|
+
error: 'codex_lb_config_repair_failed'
|
|
994
1338
|
};
|
|
995
1339
|
}
|
|
996
|
-
await migrateCodexAuthKeyFormat({ home: opts.home });
|
|
997
1340
|
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
const
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1341
|
+
const authAfter = await captureCodexAuthSnapshot({ home, authPath });
|
|
1342
|
+
let authInvariantError = null;
|
|
1343
|
+
try {
|
|
1344
|
+
await assertDesktopAuthUnchangedBySks(authBefore, authAfter);
|
|
1345
|
+
}
|
|
1346
|
+
catch (error) {
|
|
1347
|
+
authInvariantError = error instanceof Error ? error.message : String(error);
|
|
1348
|
+
}
|
|
1349
|
+
if (authInvariantError) {
|
|
1350
|
+
const writtenConfig = await readText(status.config_path, '');
|
|
1351
|
+
await safeWriteCodexConfigToml(status.config_path, writtenConfig, configBeforeRepair, 'codex-lb-safe-repair-auth-invariant-rollback', { verifyUnchangedBeforeWrite: true });
|
|
1352
|
+
}
|
|
1353
|
+
const finalStatus = await codexLbStatus({ ...opts, home });
|
|
1354
|
+
const toolOutputRecovery = opts.probeToolOutputRecovery === true && status.selected
|
|
1355
|
+
? await probeCodexLbToolOutputRecovery({
|
|
1356
|
+
baseUrl: status.base_url,
|
|
1357
|
+
...(typeof opts.toolOutputRecoveryFetch === 'function' ? { fetchImpl: opts.toolOutputRecoveryFetch } : {}),
|
|
1358
|
+
timeoutMs: Number(opts.toolOutputRecoveryTimeoutMs || 4_000),
|
|
1359
|
+
allowUnverified: opts.allowUnverifiedToolOutputRecovery === true
|
|
1360
|
+
|| codexLbToolOutputRecoveryOverrideAcknowledged({ env: opts.env || process.env })
|
|
1361
|
+
})
|
|
1362
|
+
: codexLbToolOutputRecoveryNotChecked(status.selected === true);
|
|
1363
|
+
const authReconcile = {
|
|
1364
|
+
status: authInvariantError ? 'failed' : 'oauth_untouched',
|
|
1365
|
+
reason: authInvariantError || 'ordinary_repair_never_changes_shared_codex_auth',
|
|
1366
|
+
auth_path: authPath,
|
|
1367
|
+
...(authInvariantError ? { error: authInvariantError } : {})
|
|
1368
|
+
};
|
|
1369
|
+
const codexLogin = {
|
|
1370
|
+
ok: !authInvariantError,
|
|
1371
|
+
status: authInvariantError ? 'desktop_auth_byte_invariant_failed' : 'not_required',
|
|
1372
|
+
reason: authInvariantError || 'gateway_credentials_are_independent_from_codex_login',
|
|
1373
|
+
error: authInvariantError
|
|
1374
|
+
};
|
|
1375
|
+
const toolCatalog = {
|
|
1376
|
+
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
1377
|
+
ok: true,
|
|
1378
|
+
required: false,
|
|
1379
|
+
status: desktopMode === 'desktop-native-bridge'
|
|
1380
|
+
? 'not_bound_for_desktop_native'
|
|
1381
|
+
: 'not_bound_during_repair',
|
|
1382
|
+
config_changed: false,
|
|
1383
|
+
selected: finalStatus.selected,
|
|
1384
|
+
blockers: []
|
|
1385
|
+
};
|
|
1386
|
+
const ok = Boolean(configWrite.ok && codexEnvironment.ok && !authInvariantError);
|
|
1024
1387
|
return {
|
|
1025
1388
|
ok,
|
|
1026
|
-
status: ok
|
|
1389
|
+
status: ok
|
|
1390
|
+
? configWrite.changed
|
|
1391
|
+
? 'repaired'
|
|
1392
|
+
: finalStatus.selected
|
|
1393
|
+
? 'present'
|
|
1394
|
+
: 'present_unselected'
|
|
1395
|
+
: authInvariantError
|
|
1396
|
+
? 'desktop_auth_byte_invariant_failed'
|
|
1397
|
+
: codexEnvironment.status,
|
|
1027
1398
|
config_path: status.config_path,
|
|
1028
1399
|
env_path: status.env_path,
|
|
1029
1400
|
base_url: status.base_url,
|
|
1030
|
-
config_repaired:
|
|
1031
|
-
legacy_auth_migrated:
|
|
1032
|
-
legacy_auth_path:
|
|
1401
|
+
config_repaired: configWrite.changed === true,
|
|
1402
|
+
legacy_auth_migrated: false,
|
|
1403
|
+
legacy_auth_path: null,
|
|
1033
1404
|
auth_reconcile: authReconcile,
|
|
1034
1405
|
codex_lb: finalStatus,
|
|
1035
1406
|
codex_environment: codexEnvironment,
|
|
1036
1407
|
codex_login: codexLogin,
|
|
1037
|
-
tool_catalog: toolCatalog
|
|
1408
|
+
tool_catalog: toolCatalog,
|
|
1409
|
+
tool_output_recovery: toolOutputRecovery,
|
|
1410
|
+
error: authInvariantError || codexEnvironment.error || null
|
|
1038
1411
|
};
|
|
1039
1412
|
}
|
|
1040
1413
|
export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
@@ -1043,87 +1416,10 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
|
1043
1416
|
const status = await codexLbStatus(opts);
|
|
1044
1417
|
if (!status.selected && !status.provider_configured && !status.env_key_configured)
|
|
1045
1418
|
return { status: 'not_configured', codex_lb: status };
|
|
1046
|
-
|
|
1047
|
-
return {
|
|
1048
|
-
ok: true,
|
|
1049
|
-
status: 'present_unselected',
|
|
1050
|
-
reason: 'chatgpt_oauth_active_codex_lb_unselected',
|
|
1051
|
-
config_path: status.config_path,
|
|
1052
|
-
env_path: status.env_path,
|
|
1053
|
-
base_url: status.base_url,
|
|
1054
|
-
codex_lb: status
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
await migrateCodexAuthKeyFormat({ home: opts.home });
|
|
1058
|
-
if (status.ok && (!status.selected || !status.provider_contract_ok))
|
|
1059
|
-
return repairCodexLbAuth(opts);
|
|
1060
|
-
if (!status.ok) {
|
|
1061
|
-
if (status.base_url && (status.env_key_configured || status.provider_configured || status.selected || status.env_base_url_configured))
|
|
1062
|
-
return repairCodexLbAuth(opts);
|
|
1063
|
-
return { status: status.env_key_configured ? 'missing_base_url' : 'missing_env_key', codex_lb: status, config_path: status.config_path, env_path: status.env_path };
|
|
1064
|
-
}
|
|
1065
|
-
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
1066
|
-
const loadedEnv = await loadCodexLbEnv({
|
|
1419
|
+
return repairCodexLbAuth({
|
|
1067
1420
|
...opts,
|
|
1068
|
-
|
|
1069
|
-
envPath: status.env_path
|
|
1421
|
+
probeToolOutputRecovery: false
|
|
1070
1422
|
});
|
|
1071
|
-
const apiKey = String(opts.apiKey || loadedEnv.secret_api_key || '').trim();
|
|
1072
|
-
const toolCatalog = await ensureCodexLbToolCatalogSelection({
|
|
1073
|
-
home: opts.home || process.env.HOME || os.homedir(),
|
|
1074
|
-
configPath: status.config_path,
|
|
1075
|
-
baseUrl: String(status.base_url || ''),
|
|
1076
|
-
apiKey
|
|
1077
|
-
}, opts);
|
|
1078
|
-
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
|
|
1079
|
-
const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, status }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
|
|
1080
|
-
const finalStatus = await codexLbStatus(opts);
|
|
1081
|
-
const finalReady = finalStatus.selected === true
|
|
1082
|
-
&& finalStatus.provider_ready === true
|
|
1083
|
-
&& finalStatus.auth_routing_coherent === true
|
|
1084
|
-
&& finalStatus.shared_openai_routing?.safe === true;
|
|
1085
|
-
const baseOk = Boolean(codexEnvironment.ok
|
|
1086
|
-
&& codexLogin.ok
|
|
1087
|
-
&& (toolCatalog.required === false || toolCatalog.ok === true));
|
|
1088
|
-
const ok = baseOk && finalReady;
|
|
1089
|
-
return {
|
|
1090
|
-
ok,
|
|
1091
|
-
status: ok ? 'present' : baseOk ? 'final_readiness_failed' : (codexEnvironment.status || codexLogin.status),
|
|
1092
|
-
config_path: status.config_path,
|
|
1093
|
-
env_path: status.env_path,
|
|
1094
|
-
base_url: status.base_url,
|
|
1095
|
-
codex_lb: finalStatus,
|
|
1096
|
-
codex_environment: codexEnvironment,
|
|
1097
|
-
codex_login: codexLogin,
|
|
1098
|
-
tool_catalog: toolCatalog,
|
|
1099
|
-
auth_reconcile: authReconcile,
|
|
1100
|
-
error: codexEnvironment.error || codexLogin.error || null
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
async function restoreCodexLbEnvFromSharedLogin(status = {}, opts = {}) {
|
|
1104
|
-
const home = opts.home || process.env.HOME || os.homedir();
|
|
1105
|
-
const authPath = opts.authPath || codexAuthPath(home);
|
|
1106
|
-
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
1107
|
-
const authText = await readText(authPath, '');
|
|
1108
|
-
const apiKey = parseCodexSharedLoginApiKey(authText);
|
|
1109
|
-
if (!apiKey)
|
|
1110
|
-
return { ok: false, status: 'missing_legacy_login_key', auth_path: authPath, env_path: envPath };
|
|
1111
|
-
const baseUrl = status.base_url || parseCodexLbEnvBaseUrl(await readText(envPath, ''));
|
|
1112
|
-
if (!baseUrl)
|
|
1113
|
-
return { ok: false, status: 'missing_base_url', auth_path: authPath, env_path: envPath };
|
|
1114
|
-
await ensureDir(path.dirname(envPath));
|
|
1115
|
-
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(normalizeCodexLbBaseUrl(baseUrl))}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`, { mode: 0o600 });
|
|
1116
|
-
await fsp.chmod(envPath, 0o600).catch(() => { });
|
|
1117
|
-
const metadataPath = opts.metadataPath || codexLbMetadataPath(home);
|
|
1118
|
-
await writeTextAtomic(metadataPath, `${JSON.stringify({
|
|
1119
|
-
schema: 'sks.codex-lb-metadata.v1',
|
|
1120
|
-
base_url: normalizeCodexLbBaseUrl(baseUrl),
|
|
1121
|
-
updated_at: new Date().toISOString(),
|
|
1122
|
-
source: 'legacy-login-migration',
|
|
1123
|
-
api_key: { redacted: true, sha256: await sha256Text(apiKey) }
|
|
1124
|
-
}, null, 2)}\n`, { mode: 0o600 });
|
|
1125
|
-
await fsp.chmod(metadataPath, 0o600).catch(() => { });
|
|
1126
|
-
return { ok: true, status: 'migrated_login_cache', auth_path: authPath, env_path: envPath, base_url: normalizeCodexLbBaseUrl(baseUrl) };
|
|
1127
1423
|
}
|
|
1128
1424
|
function hasChatgptOAuthTokens(text = '') {
|
|
1129
1425
|
try {
|
|
@@ -1173,30 +1469,6 @@ function codexAuthModeSummary(text = '') {
|
|
|
1173
1469
|
catch { }
|
|
1174
1470
|
return { mode: 'unknown', codex_app_usable: false, summary: 'unrecognized auth.json shape' };
|
|
1175
1471
|
}
|
|
1176
|
-
async function migrateCodexAuthKeyFormat(opts = {}) {
|
|
1177
|
-
const home = opts.home || process.env.HOME || os.homedir();
|
|
1178
|
-
const authPath = opts.authPath || codexAuthPath(home);
|
|
1179
|
-
const text = await readText(authPath, '');
|
|
1180
|
-
if (!text.trim())
|
|
1181
|
-
return { status: 'skipped', reason: 'empty' };
|
|
1182
|
-
try {
|
|
1183
|
-
const parsed = JSON.parse(text);
|
|
1184
|
-
if (parsed?.auth_mode !== 'apikey')
|
|
1185
|
-
return { status: 'skipped', reason: 'not_apikey' };
|
|
1186
|
-
if (parsed.OPENAI_API_KEY)
|
|
1187
|
-
return { status: 'skipped', reason: 'already_migrated' };
|
|
1188
|
-
const legacyKey = parsed.key || parsed.api_key || parsed.apiKey || parsed.openai_api_key;
|
|
1189
|
-
if (!legacyKey)
|
|
1190
|
-
return { status: 'skipped', reason: 'no_key_found' };
|
|
1191
|
-
const replacement = `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: legacyKey })}\n`;
|
|
1192
|
-
await writeTextAtomic(authPath, replacement, { mode: 0o600 });
|
|
1193
|
-
await fsp.chmod(authPath, 0o600).catch(() => { });
|
|
1194
|
-
return { status: 'migrated', auth_path: authPath };
|
|
1195
|
-
}
|
|
1196
|
-
catch {
|
|
1197
|
-
return { status: 'skipped', reason: 'parse_error' };
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
1472
|
export async function reconcileCodexLbAuthConflict(opts = {}) {
|
|
1201
1473
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
1202
1474
|
const status = opts.status || await codexLbStatus({ ...opts, home });
|
|
@@ -1403,8 +1675,7 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
|
|
|
1403
1675
|
const unselected = await unselectCodexLbProvider({
|
|
1404
1676
|
...opts,
|
|
1405
1677
|
home,
|
|
1406
|
-
authPath
|
|
1407
|
-
allowActiveSharedAuthTransition: true
|
|
1678
|
+
authPath
|
|
1408
1679
|
});
|
|
1409
1680
|
if (!unselected.ok) {
|
|
1410
1681
|
await writeTextAtomic(authPath, authText, { mode: 0o600 });
|
|
@@ -1471,21 +1742,24 @@ export async function unselectCodexLbProvider(opts = {}) {
|
|
|
1471
1742
|
const envLoad = await loadCodexLbEnv({ ...opts, home, envPath });
|
|
1472
1743
|
const authPath = opts.authPath || codexAuthPath(home);
|
|
1473
1744
|
const authApiKey = parseCodexAuthApiKey(await readText(authPath, ''));
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1745
|
+
const managedCatalogPath = codexLbToolCatalogPath(opts.codexHome || path.join(home, '.codex'));
|
|
1746
|
+
const managedCatalogSelected = topLevelTomlString(current, 'model_catalog_json') === managedCatalogPath;
|
|
1747
|
+
const providerBaseUrl = codexLbProviderBaseUrl(current) || envLoad.base_url || '';
|
|
1748
|
+
const sharedOpenAiRouting = codexLbSharedOpenAiRoutingState(current, providerBaseUrl);
|
|
1749
|
+
const managedSharedOpenAiRouting = sharedOpenAiRouting.status === 'matched' && sharedOpenAiRouting.managed;
|
|
1750
|
+
const codexLbRoutingOwned = hasTopLevelCodexLbSelected(current)
|
|
1751
|
+
|| /\[model_providers\.codex-lb\]/.test(current)
|
|
1752
|
+
|| managedCatalogSelected
|
|
1753
|
+
|| managedSharedOpenAiRouting;
|
|
1754
|
+
if (authApiKey && codexLbRoutingOwned) {
|
|
1476
1755
|
return {
|
|
1477
1756
|
ok: false,
|
|
1478
1757
|
status: 'failed',
|
|
1479
1758
|
reason: 'shared_codex_lb_auth_active',
|
|
1480
|
-
provider_error: 'refusing to unselect codex-lb while
|
|
1759
|
+
provider_error: 'refusing to unselect codex-lb while API-key auth could fall through to built-in OpenAI',
|
|
1481
1760
|
config_path: configPath
|
|
1482
1761
|
};
|
|
1483
1762
|
}
|
|
1484
|
-
const managedCatalogPath = codexLbToolCatalogPath(opts.codexHome || path.join(home, '.codex'));
|
|
1485
|
-
const managedCatalogSelected = topLevelTomlString(current, 'model_catalog_json') === managedCatalogPath;
|
|
1486
|
-
const providerBaseUrl = codexLbProviderBaseUrl(current) || envLoad.base_url || '';
|
|
1487
|
-
const sharedOpenAiRouting = codexLbSharedOpenAiRoutingState(current, providerBaseUrl);
|
|
1488
|
-
const managedSharedOpenAiRouting = sharedOpenAiRouting.status === 'matched' && sharedOpenAiRouting.managed;
|
|
1489
1763
|
if (!hasTopLevelCodexLbSelected(current) && !managedCatalogSelected && !managedSharedOpenAiRouting)
|
|
1490
1764
|
return { ok: true, status: 'not_selected', config_path: configPath };
|
|
1491
1765
|
try {
|
|
@@ -1558,8 +1832,7 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1558
1832
|
...opts,
|
|
1559
1833
|
home,
|
|
1560
1834
|
configPath,
|
|
1561
|
-
authPath
|
|
1562
|
-
allowActiveSharedAuthTransition: true
|
|
1835
|
+
authPath
|
|
1563
1836
|
});
|
|
1564
1837
|
provider = providerDeselectionOutcome(unselected);
|
|
1565
1838
|
}
|
|
@@ -1600,8 +1873,7 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1600
1873
|
...opts,
|
|
1601
1874
|
home,
|
|
1602
1875
|
configPath,
|
|
1603
|
-
authPath
|
|
1604
|
-
allowActiveSharedAuthTransition: true
|
|
1876
|
+
authPath
|
|
1605
1877
|
});
|
|
1606
1878
|
provider = providerDeselectionOutcome(unselected);
|
|
1607
1879
|
}
|
|
@@ -1651,8 +1923,7 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1651
1923
|
...opts,
|
|
1652
1924
|
home,
|
|
1653
1925
|
configPath,
|
|
1654
|
-
authPath
|
|
1655
|
-
allowActiveSharedAuthTransition: true
|
|
1926
|
+
authPath
|
|
1656
1927
|
});
|
|
1657
1928
|
provider = providerDeselectionOutcome(unselected);
|
|
1658
1929
|
}
|
|
@@ -1714,108 +1985,46 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
1714
1985
|
return { status: 'skipped' };
|
|
1715
1986
|
const allowUnverifiedToolOutputRecovery = opts.allowUnverifiedToolOutputRecovery === true
|
|
1716
1987
|
|| codexLbToolOutputRecoveryOverrideAcknowledged({ args, env: opts.env || process.env });
|
|
1717
|
-
let status = await codexLbStatus({
|
|
1718
|
-
|
|
1719
|
-
probeToolOutputRecovery: true,
|
|
1720
|
-
allowUnverifiedToolOutputRecovery
|
|
1721
|
-
});
|
|
1722
|
-
if (status.selected && status.tool_output_recovery?.ok !== true) {
|
|
1988
|
+
let status = await codexLbStatus({ ...opts, probeToolOutputRecovery: false });
|
|
1989
|
+
if (status.legacy_migration_required) {
|
|
1723
1990
|
return {
|
|
1724
|
-
status: '
|
|
1991
|
+
status: 'legacy_migration_required',
|
|
1725
1992
|
ok: false,
|
|
1726
1993
|
codex_lb: status,
|
|
1727
|
-
|
|
1728
|
-
blockers: status.tool_output_recovery.blockers,
|
|
1729
|
-
operator_actions: status.tool_output_recovery.operator_actions,
|
|
1730
|
-
bypass_codex_lb: false
|
|
1994
|
+
blockers: ['run_sks_codex_lb_migrate_legacy_desktop']
|
|
1731
1995
|
};
|
|
1732
1996
|
}
|
|
1733
|
-
if (status.env_key_configured && status.base_url
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
blockers: status.tool_output_recovery.blockers,
|
|
1754
|
-
operator_actions: status.tool_output_recovery.operator_actions,
|
|
1755
|
-
bypass_codex_lb: false
|
|
1756
|
-
};
|
|
1757
|
-
}
|
|
1758
|
-
if (!status.ok)
|
|
1759
|
-
return { status: 'repair_failed', ok: false, repair: repaired, codex_lb: status };
|
|
1760
|
-
if (!repaired.ok && repaired.error && promptedRestore)
|
|
1761
|
-
console.log(`codex-lb provider restored, but launch environment sync reported: ${repaired.error}`);
|
|
1762
|
-
else if (!repaired.ok && promptedRestore)
|
|
1763
|
-
console.log(`codex-lb provider restored, but provider auth sync reported: ${repaired.status}`);
|
|
1764
|
-
else if (repaired.config_repaired && promptedRestore)
|
|
1765
|
-
console.log(`codex-lb provider restored: ${status.base_url}`);
|
|
1766
|
-
}
|
|
1767
|
-
if (status.ok) {
|
|
1768
|
-
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
1769
|
-
if (codexEnvironment.status === 'synced')
|
|
1770
|
-
console.log('codex-lb provider auth synced for this user session.');
|
|
1771
|
-
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
1772
|
-
const toolCatalog = await ensureCodexLbToolCatalogSelection({
|
|
1773
|
-
home: opts.home || process.env.HOME || os.homedir(),
|
|
1774
|
-
configPath: status.config_path,
|
|
1775
|
-
baseUrl: String(status.base_url || ''),
|
|
1776
|
-
apiKey
|
|
1777
|
-
}, opts);
|
|
1778
|
-
if (toolCatalog.required !== false && toolCatalog.ok !== true) {
|
|
1779
|
-
return { status: 'repair_failed', ok: false, codex_lb: status, codex_environment: codexEnvironment, tool_catalog: toolCatalog };
|
|
1780
|
-
}
|
|
1781
|
-
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir() });
|
|
1782
|
-
if (codexLogin.status === 'synced')
|
|
1783
|
-
console.log('codex-lb auth synced with Codex CLI login cache.');
|
|
1784
|
-
const chainHealth = await checkCodexLbResponseChain(status, opts);
|
|
1785
|
-
if (!chainHealth.ok && chainHealth.chain_unhealthy) {
|
|
1786
|
-
if (chainHealth.status === 'previous_response_not_found') {
|
|
1787
|
-
console.log('codex-lb response chain check: previous_response_id not persisted by the load balancer (this is normal for stateless deployments). Keeping codex-lb active.');
|
|
1788
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1789
|
-
}
|
|
1790
|
-
console.log(`codex-lb response chain check failed (${chainHealth.status}${chainHealth.error ? `: ${chainHealth.error}` : ''}).`);
|
|
1791
|
-
if (process.env.SKS_CODEX_LB_AUTOBYPASS === '1') {
|
|
1792
|
-
console.log('SKS_CODEX_LB_AUTOBYPASS=1 set; bypassing codex-lb to ChatGPT OAuth for this launch.');
|
|
1793
|
-
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth, bypass_codex_lb: true };
|
|
1794
|
-
}
|
|
1795
|
-
if (canAskYesNo()) {
|
|
1796
|
-
const answer = (await askPostinstallQuestion('Use codex-lb anyway, or fall back to ChatGPT OAuth? [LB/oauth] ')).trim().toLowerCase();
|
|
1797
|
-
if (/^(oauth|o|chatgpt|fall ?back|n|no|아니|아니요|ㄴ)$/.test(answer)) {
|
|
1798
|
-
console.log('Falling back to ChatGPT OAuth for this launch. Re-enable codex-lb anytime with `sks codex-lb repair`.');
|
|
1799
|
-
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth, bypass_codex_lb: true };
|
|
1800
|
-
}
|
|
1801
|
-
console.log('Keeping codex-lb active. To switch back to ChatGPT OAuth: `sks codex-lb release`.');
|
|
1802
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1803
|
-
}
|
|
1804
|
-
console.log('Non-interactive launch + chain check failure. Keeping codex-lb active. Set SKS_CODEX_LB_AUTOBYPASS=1 to auto-bypass to ChatGPT OAuth.');
|
|
1805
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1806
|
-
}
|
|
1807
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1997
|
+
if (status.env_key_configured && status.base_url) {
|
|
1998
|
+
const repaired = await repairCodexLbAuth({
|
|
1999
|
+
...opts,
|
|
2000
|
+
allowUnverifiedToolOutputRecovery,
|
|
2001
|
+
probeToolOutputRecovery: false
|
|
2002
|
+
});
|
|
2003
|
+
status = await codexLbStatus({ ...opts, probeToolOutputRecovery: false });
|
|
2004
|
+
if (!repaired.ok)
|
|
2005
|
+
return { status: repaired.status, ok: false, repair: repaired, codex_lb: status };
|
|
2006
|
+
const codexEnvironment = repaired.codex_environment || await syncCodexLbProviderEnvironment(status, opts);
|
|
2007
|
+
return {
|
|
2008
|
+
status: status.desktop_mode === 'cli-provider' && !status.selected ? 'continued_to_codex' : 'present',
|
|
2009
|
+
...status,
|
|
2010
|
+
codex_environment: codexEnvironment,
|
|
2011
|
+
...(repaired.codex_login ? { codex_login: repaired.codex_login } : {}),
|
|
2012
|
+
...(repaired.tool_catalog ? { tool_catalog: repaired.tool_catalog } : {}),
|
|
2013
|
+
reason: status.desktop_mode === 'cli-provider' && !status.selected
|
|
2014
|
+
? 'unselected_cli_provider_available_for_explicit_use'
|
|
2015
|
+
: 'explicit_managed_desktop_routing_preserved'
|
|
2016
|
+
};
|
|
1808
2017
|
}
|
|
1809
2018
|
if (!canAskYesNo())
|
|
1810
2019
|
return { status: 'non_interactive', codex_lb: status };
|
|
1811
|
-
const useCodexLb = (await askPostinstallQuestion('\ncodex-lb is not configured
|
|
2020
|
+
const useCodexLb = (await askPostinstallQuestion('\ncodex-lb is not configured. Store credentials and an unselected CLI provider now? [y/N] ')).trim();
|
|
1812
2021
|
if (!/^(y|yes|예|네|응)$/i.test(useCodexLb))
|
|
1813
2022
|
return { status: 'continued_to_codex' };
|
|
1814
2023
|
const host = (await askPostinstallQuestion('codex-lb host domain [http://127.0.0.1:2455]: ')).trim() || 'http://127.0.0.1:2455';
|
|
1815
2024
|
const apiKey = (await askPostinstallQuestion('codex-lb API key: ')).trim();
|
|
1816
2025
|
const configured = await configureCodexLb({ ...opts, host, apiKey, allowUnverifiedToolOutputRecovery });
|
|
1817
2026
|
if (configured.ok)
|
|
1818
|
-
console.log(`codex-lb
|
|
2027
|
+
console.log(`codex-lb credentials stored: ${configured.base_url}. Use \`sks codex-lb use-cli\`, \`use-desktop-full\`, or \`disable\` explicitly.`);
|
|
1819
2028
|
else
|
|
1820
2029
|
console.log('codex-lb setup skipped: API key was empty.');
|
|
1821
2030
|
return configured;
|
|
@@ -1922,39 +2131,9 @@ async function inspectCodexLbMacLaunchEnvironment(baseUrl = '', opts = {}) {
|
|
|
1922
2131
|
: []
|
|
1923
2132
|
};
|
|
1924
2133
|
}
|
|
1925
|
-
async function maybeSyncCodexLbSharedLogin(apiKey, opts = {}) {
|
|
1926
|
-
if (!apiKey)
|
|
1927
|
-
return { ok: false, status: 'missing_env_key' };
|
|
1928
|
-
if (!shouldSyncCodexLbSharedLogin(opts)) {
|
|
1929
|
-
return { ok: true, status: 'skipped', reason: 'codex-lb repair preserved the current Codex App auth; run `sks codex-lb use-codex-lb` to switch the app to codex-lb API-key auth.' };
|
|
1930
|
-
}
|
|
1931
|
-
return syncCodexApiKeyLogin(apiKey, opts);
|
|
1932
|
-
}
|
|
1933
|
-
function shouldSyncCodexLbSharedLogin(opts = {}) {
|
|
1934
|
-
return opts.syncCodexLogin === true || process.env.SKS_CODEX_LB_SYNC_CODEX_LOGIN === '1';
|
|
1935
|
-
}
|
|
1936
|
-
async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
1937
|
-
const home = opts.home || process.env.HOME || os.homedir();
|
|
1938
|
-
const codexHome = opts.codexHome || path.join(home, '.codex');
|
|
1939
|
-
const codexBin = opts.codexBin || (await getCodexInfo().catch(() => EMPTY_CODEX_INFO)).bin || await which('codex').catch(() => null);
|
|
1940
|
-
if (!codexBin)
|
|
1941
|
-
return { ok: false, status: 'codex_missing' };
|
|
1942
|
-
await ensureDir(codexHome);
|
|
1943
|
-
const env = { HOME: home, CODEX_HOME: codexHome, CODEX_LB_API_KEY: apiKey };
|
|
1944
|
-
if (!opts.force) {
|
|
1945
|
-
const current = await runProcess(codexBin, ['login', 'status'], { env, timeoutMs: 10000, maxOutputBytes: 8192 });
|
|
1946
|
-
if (current.code === 0 && !/not logged in/i.test(`${current.stdout}\n${current.stderr}`))
|
|
1947
|
-
return { ok: true, status: 'present' };
|
|
1948
|
-
}
|
|
1949
|
-
const login = await runProcess(codexBin, ['login', '--with-api-key'], { input: `${apiKey}\n`, env, timeoutMs: 15000, maxOutputBytes: 8192 });
|
|
1950
|
-
if (login.code === 0)
|
|
1951
|
-
return { ok: true, status: 'synced' };
|
|
1952
|
-
return { ok: false, status: 'login_failed', error: redactSecretText(login.stderr || login.stdout || 'codex login failed', [apiKey]).trim() };
|
|
1953
|
-
}
|
|
1954
2134
|
export { checkContext7, ensureCodexCliTool, ensureMadLaunchDependencies, ensureRelatedCliTools, ensureZellijCliTool, formatMadLaunchDependencyAction, maybePromptCodexUpdateForLaunch, maybePromptSksUpdateForLaunch, shouldAutoApproveInstall } from './install-tool-helpers.js';
|
|
1955
2135
|
export { codexFastModeDesktopStatus, ensureGlobalCodexFastModeDuringInstall, normalizeCodexFastModeUiConfig, safeWriteCodexConfigToml } from '../core/codex-runtime/codex-desktop-config-policy.js';
|
|
1956
2136
|
export { checkRequiredSkills, context7GlobalMcpStatus, ensureCodexImagegenDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureSksCommandDuringInstall, globalCodexSkillsRoot, selftestSksShimRepair } from './install-helpers-install-support.js';
|
|
1957
2137
|
export { askPostinstallQuestion, codexLbConfigPath, codexLbEnvPath, normalizeCodexLbBaseUrl } from './install-helpers-codex-lb-shared.js';
|
|
1958
2138
|
export { ensureGlobalCodexAppGlmProfile, ensureStoredOpenRouterProviderDuringInstall, upsertCodexAppGlmConfig, upsertCodexLbConfig } from './install-helpers-codex-lb-config.js';
|
|
1959
2139
|
export { checkCodexLbResponseChain } from './install-helpers-codex-lb-chain.js';
|
|
1960
|
-
export { selftestCodexLb } from './install-helpers-codex-lb-selftest.js';
|