sneakoscope 8.0.0 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/config/skills-hash-ledger.v1.json +63 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +14 -3
- package/dist/bin/sks-dispatch.js +1 -1
- package/dist/cli/codex-lb-setup-warning-output.js +17 -0
- package/dist/cli/command-manifest-lite.js +2 -13
- package/dist/cli/install-helpers-codex-lb-config.js +475 -8
- package/dist/cli/install-helpers-install-support.js +1 -1
- package/dist/cli/install-helpers.js +764 -68
- package/dist/cli/router.js +5 -2
- package/dist/commands/codex-lb.js +77 -47
- package/dist/commands/doctor-profile.js +1 -0
- package/dist/commands/doctor.js +165 -58
- package/dist/config/skills-manifest.json +193 -97
- package/dist/core/codex/codex-config-guard.js +70 -16
- package/dist/core/codex/oauth-callback-port-diagnostic.js +121 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -2
- package/dist/core/codex-app/menubar/constants.js +3 -0
- package/dist/core/codex-app/menubar/installer.js +32 -9
- package/dist/core/codex-app/menubar/launch-agent.js +58 -6
- package/dist/core/codex-app/menubar/migration.js +17 -6
- package/dist/core/codex-app/menubar/status.js +15 -3
- package/dist/core/codex-app.js +15 -20
- package/dist/core/codex-control/codex-lb-launch-recovery.js +26 -0
- package/dist/core/codex-lb/capability-runner.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +52 -41
- package/dist/core/codex-lb/codex-lb-setup.js +85 -8
- package/dist/core/codex-lb/desktop-center-credentials.js +474 -39
- package/dist/core/codex-lb/desktop-controller.js +118 -4
- package/dist/core/codex-lb/legacy-migration.js +6 -3
- package/dist/core/codex-native/codex-native-feature-broker.js +5 -1
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/codex-native/managed-skill-generation-lock.js +13 -0
- package/dist/core/codex-native/sks-skill-paths.js +97 -10
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +1 -0
- package/dist/core/commands/basic-cli.js +54 -4
- package/dist/core/commands/mad-sks-command.js +1 -1
- package/dist/core/commands/menubar-command.js +24 -8
- package/dist/core/commands/naruto-command.js +42 -217
- package/dist/core/doctor/codex-config-syntax-repair.js +368 -0
- package/dist/core/doctor/doctor-native-capability-repair.js +12 -5
- package/dist/core/doctor/doctor-readiness-matrix.js +51 -4
- package/dist/core/doctor/skill-legacy-surface.js +0 -1
- package/dist/core/doctor/sks-menubar-doctor.js +21 -0
- package/dist/core/fsx.js +7 -14
- package/dist/core/hooks-runtime/hook-context.js +30 -16
- package/dist/core/hooks-runtime/managed-skill-admission.js +17 -0
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +891 -30
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +99 -6
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +3 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-evidence.js +1 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-guards.js +4 -4
- package/dist/core/hooks-runtime/subagent-skill-availability.js +9 -7
- package/dist/core/hooks-runtime/subagent-stop-hook.js +19 -0
- package/dist/core/hooks-runtime.js +41 -14
- package/dist/core/init/skills.js +270 -11
- package/dist/core/mission.js +20 -0
- package/dist/core/naruto/hardware-capacity-probe.js +3 -1
- package/dist/core/naruto/naruto-concurrency-governor.js +9 -3
- package/dist/core/pipeline-internals/runtime-core.js +68 -9
- package/dist/core/pipeline-internals/runtime-gates.js +10 -2
- package/dist/core/preflight/parallel-preflight-engine.js +18 -4
- package/dist/core/release/package-dist-snapshot.js +50 -8
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/command-contract/contracts.js +10 -13
- package/dist/core/safety/command-contract/json-schema.js +38 -1
- package/dist/core/subagents/agent-catalog.js +37 -13
- package/dist/core/subagents/model-policy.js +14 -9
- package/dist/core/subagents/naruto-command-args.js +237 -0
- package/dist/core/subagents/naruto-command-input-contract.js +123 -0
- package/dist/core/subagents/naruto-help-contract.js +35 -2
- package/dist/core/subagents/naruto-launch-diagnostics.js +35 -0
- package/dist/core/subagents/official-subagent-config.js +30 -3
- package/dist/core/subagents/official-subagent-preparation.js +30 -5
- package/dist/core/subagents/official-subagent-prompt.js +4 -3
- package/dist/core/subagents/official-subagent-runner.js +12 -0
- package/dist/core/subagents/subagent-evidence.js +10 -0
- package/dist/core/subagents/thread-budget.js +1 -1
- package/dist/core/subagents/wave-lifecycle.js +41 -11
- package/dist/core/subagents/wave-parent-guidance.js +10 -3
- package/dist/core/update/update-migration-state.js +694 -63
- package/dist/core/update/update-operation.js +498 -32
- package/dist/core/update-check.js +990 -509
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +1 -0
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -1
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +1 -1
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +127 -12
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +7 -2
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +287 -84
- package/dist/native/sks-menubar/Sources/ProcessExecutionState.swift +45 -0
- package/dist/native/sks-menubar/Sources/ProcessIdentityGuard.swift +196 -0
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +169 -0
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +23 -5
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +113 -24
- package/dist/native/sks-menubar/Sources/main.swift +12 -0
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +14 -0
- package/dist/native/sks-menubar/Tests/OperationCoordinatorTests.swift +174 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +4 -0
- package/dist/scripts/current-surface-update-e2e-check.js +24 -5
- package/dist/scripts/lib/ensure-dist-fresh.js +12 -8
- package/dist/scripts/mutation-callsite-analysis.js +381 -0
- package/dist/scripts/mutation-callsite-coverage-check.js +127 -62
- package/dist/scripts/official-subagent-workflow-check.js +1 -1
- package/dist/scripts/skills-manifest-continuity-check.js +19 -1
- package/package.json +5 -3
- package/runtime-required-scripts.json +5 -0
- package/scripts/build-clean-atomic.mjs +323 -0
|
@@ -14,7 +14,7 @@ import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
|
|
|
14
14
|
import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
|
|
15
15
|
import { cleanupMacLaunchSecretEnvironment } from '../core/codex-app/menubar/index.js';
|
|
16
16
|
import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
|
|
17
|
-
import {
|
|
17
|
+
import { CODEX_LB_SECURE_KEYCHAIN_SERVICE, loadCodexLbEnv, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
|
|
18
18
|
import { codexLbToolCatalogPath } from '../core/codex-lb/codex-lb-tool-catalog.js';
|
|
19
19
|
import { codexLbToolOutputRecoveryNotChecked, codexLbToolOutputRecoveryNotSelected, codexLbToolOutputRecoveryOverrideAcknowledged, probeCodexLbToolOutputRecovery } from '../core/codex-lb/codex-lb-tool-output-recovery.js';
|
|
20
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';
|
|
@@ -26,9 +26,10 @@ import { ensureGlobalCodexFastModeDuringInstall, ensureTrailingNewline, normaliz
|
|
|
26
26
|
import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
|
|
27
27
|
import { repairCodexImagegen } from '../core/doctor/imagegen-repair.js';
|
|
28
28
|
import { canAskYesNo, compareVersions, hasCodexUnstableFeatureWarningSuppression, hasDeprecatedCodexHooksFeatureFlag, isProjectSetupCandidate } from './install-tool-helpers.js';
|
|
29
|
+
import { printCodexLbSetupWarnings } from './codex-lb-setup-warning-output.js';
|
|
29
30
|
import { checkCodexLbResponseChain } from './install-helpers-codex-lb-chain.js';
|
|
30
31
|
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 { CODEX_LB_DESKTOP_BRIDGE_MARKER, CODEX_LB_DESKTOP_COMPAT_MARKER, appliedCodexLbPersistenceModes, captureCodexLbSetupWriteState, codexLbSharedOpenAiRoutingState, detectCodexLbSetupDrift, ensureGlobalCodexAppGlmProfile, ensureStoredOpenRouterProviderDuringInstall, hardenCodexLbSetupRecoveryPath, parseCodexLbEnvBaseUrl, removeCodexLbSetupRecoveryPath, removeCodexLbSharedOpenAiRouting, removeCodexLbManagedDesktopConfig, restoreCodexLbSetupWriteState, sha256Text, shellSingleQuote, upsertCodexAppGlmConfig, upsertCodexLbConfig, upsertCodexLbCliProviderConfig, upsertCodexLbCompatDesktopConfig, upsertCodexLbNativeDesktopConfig, upsertCodexLbSharedOpenAiRouting, writeCodexLbSetupFileIfUnchanged } from './install-helpers-codex-lb-config.js';
|
|
32
33
|
import { detectLegacyCodexLbDesktopState } from '../core/codex-lb/legacy-migration.js';
|
|
33
34
|
import { ensureCodexImagegenDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureGlobalContext7DuringInstall, ensureGlobalGetdesignSkillDuringInstall, ensureSksCommandDuringInstall } from './install-helpers-install-support.js';
|
|
34
35
|
import { escapeRegExp } from '../core/text/regex.js';
|
|
@@ -261,6 +262,7 @@ async function reportPostinstallCodexLbAuth(snapshot = null) {
|
|
|
261
262
|
console.log(`codex-lb key updated: ${result.base_url}`);
|
|
262
263
|
else
|
|
263
264
|
console.log(`codex-lb key update failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
|
|
265
|
+
printCodexLbSetupWarnings(result);
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
}
|
|
@@ -336,6 +338,17 @@ export async function configureCodexLbDesktopRouting(opts) {
|
|
|
336
338
|
auth_path: authPath,
|
|
337
339
|
auth_before: authBefore
|
|
338
340
|
};
|
|
341
|
+
if (desktopModeRequiresGlobalSecretEnvironment(mode)) {
|
|
342
|
+
return {
|
|
343
|
+
...base,
|
|
344
|
+
ok: false,
|
|
345
|
+
status: 'desktop_dual_auth_compat_unavailable',
|
|
346
|
+
routing_plane: 'unchanged',
|
|
347
|
+
oauth_preserved: true,
|
|
348
|
+
auth_after: authBefore,
|
|
349
|
+
blockers: ['desktop_dual_auth_compat_requires_global_secret_environment']
|
|
350
|
+
};
|
|
351
|
+
}
|
|
339
352
|
if (mode === 'cli-provider') {
|
|
340
353
|
return {
|
|
341
354
|
...base,
|
|
@@ -640,19 +653,93 @@ export async function configureCodexLb(opts = {}) {
|
|
|
640
653
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
641
654
|
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
642
655
|
const envPath = opts.envPath || codexLbEnvPath(home);
|
|
656
|
+
const metadataPath = opts.metadataPath || codexLbMetadataPath(home);
|
|
643
657
|
const rawHost = String(opts.host || opts.baseUrl || '');
|
|
644
658
|
const baseUrl = normalizeCodexLbBaseUrl(rawHost);
|
|
645
659
|
const apiKey = String(opts.apiKey || '').trim();
|
|
646
|
-
const initialConfig = await readText(configPath, '');
|
|
647
|
-
const preservedDesktopMode = managedCodexLbDesktopMode(initialConfig);
|
|
648
|
-
const preservedLegacySelection = hasTopLevelCodexLbSelected(initialConfig) && !preservedDesktopMode;
|
|
649
660
|
const requestedDesktopMode = parseCodexLbDesktopMode(opts.desktopMode || 'cli-provider');
|
|
650
|
-
const desktopMode = preservedDesktopMode || 'cli-provider';
|
|
651
|
-
const useDefaultProvider = desktopMode === 'desktop-dual-auth-compat' || preservedLegacySelection;
|
|
652
661
|
const writeEnvFile = opts.writeEnvFile !== false;
|
|
653
662
|
const storeKeychain = opts.storeKeychain === true || opts.keychain === true;
|
|
663
|
+
const keychainStoreImpl = typeof opts.keychainStoreImpl === 'function'
|
|
664
|
+
? opts.keychainStoreImpl
|
|
665
|
+
: null;
|
|
654
666
|
const syncLaunchctl = opts.syncLaunchctl === true || opts.syncLaunchEnv === true;
|
|
655
667
|
const shellProfile = opts.shellProfile || 'skip';
|
|
668
|
+
if (storeKeychain && !keychainStoreImpl) {
|
|
669
|
+
return {
|
|
670
|
+
ok: false,
|
|
671
|
+
status: 'keychain_acl_helper_unavailable',
|
|
672
|
+
config_path: configPath,
|
|
673
|
+
env_path: envPath,
|
|
674
|
+
metadata_path: metadataPath,
|
|
675
|
+
keychain: {
|
|
676
|
+
ok: false,
|
|
677
|
+
status: 'keychain_acl_helper_unavailable',
|
|
678
|
+
keychain_state_verified: true,
|
|
679
|
+
keychain_state_status: 'unchanged'
|
|
680
|
+
},
|
|
681
|
+
error: 'Dedicated signed Keychain helper unavailable; use the owner-only env file.'
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const beforeState = await captureCodexLbSetupWriteState({
|
|
685
|
+
home,
|
|
686
|
+
configPath,
|
|
687
|
+
envPath,
|
|
688
|
+
metadataPath,
|
|
689
|
+
shellProfile
|
|
690
|
+
});
|
|
691
|
+
await opts.testHooks?.afterBeforeStateCapture?.({ configPath });
|
|
692
|
+
const configBeforeEntry = beforeState.files.find((entry) => entry?.path === configPath);
|
|
693
|
+
if (!configBeforeEntry) {
|
|
694
|
+
return {
|
|
695
|
+
ok: false,
|
|
696
|
+
status: 'setup_snapshot_failed',
|
|
697
|
+
config_path: configPath,
|
|
698
|
+
env_path: envPath,
|
|
699
|
+
metadata_path: metadataPath
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const setupMutationPaths = new Set([
|
|
703
|
+
configPath,
|
|
704
|
+
metadataPath,
|
|
705
|
+
...(writeEnvFile ? [envPath] : []),
|
|
706
|
+
...(shellProfile === 'skip'
|
|
707
|
+
? []
|
|
708
|
+
: beforeState.files
|
|
709
|
+
.map((entry) => String(entry?.path || ''))
|
|
710
|
+
.filter((file) => file && file !== configPath && file !== envPath && file !== metadataPath))
|
|
711
|
+
]);
|
|
712
|
+
const unsafeSetupTargets = beforeState.files
|
|
713
|
+
.filter((entry) => setupMutationPaths.has(String(entry?.path || ''))
|
|
714
|
+
&& entry?.existed === true
|
|
715
|
+
&& entry?.kind !== 'regular')
|
|
716
|
+
.map((entry) => `unsafe_setup_write_target:${entry.path}:${entry.kind}`);
|
|
717
|
+
if (unsafeSetupTargets.length > 0) {
|
|
718
|
+
return {
|
|
719
|
+
ok: false,
|
|
720
|
+
status: 'unsafe_setup_write_target',
|
|
721
|
+
config_path: configPath,
|
|
722
|
+
env_path: envPath,
|
|
723
|
+
metadata_path: metadataPath,
|
|
724
|
+
drift: unsafeSetupTargets
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
const initialConfig = configBeforeEntry.existed === true
|
|
728
|
+
? Buffer.from(String(configBeforeEntry.bytes_base64 || ''), 'base64').toString('utf8')
|
|
729
|
+
: '';
|
|
730
|
+
const preservedDesktopMode = managedCodexLbDesktopMode(initialConfig);
|
|
731
|
+
const preservedLegacySelection = hasTopLevelCodexLbSelected(initialConfig) && !preservedDesktopMode;
|
|
732
|
+
const desktopMode = preservedDesktopMode || 'cli-provider';
|
|
733
|
+
if (desktopModeRequiresGlobalSecretEnvironment(desktopMode)) {
|
|
734
|
+
return {
|
|
735
|
+
ok: false,
|
|
736
|
+
status: 'desktop_dual_auth_compat_unavailable',
|
|
737
|
+
config_path: configPath,
|
|
738
|
+
env_path: envPath,
|
|
739
|
+
error: 'desktop_dual_auth_compat_requires_global_secret_environment'
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
const useDefaultProvider = desktopMode === 'desktop-dual-auth-compat' || preservedLegacySelection;
|
|
656
743
|
const setupAnswers = {
|
|
657
744
|
host_or_base_url: rawHost,
|
|
658
745
|
api_key_source: opts.apiKeySource || 'stdin',
|
|
@@ -660,6 +747,7 @@ export async function configureCodexLb(opts = {}) {
|
|
|
660
747
|
gateway_auth_transport: (opts.gatewayAuthTransport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT),
|
|
661
748
|
write_env_file: writeEnvFile,
|
|
662
749
|
store_keychain: storeKeychain,
|
|
750
|
+
keychain_helper_verified: Boolean(keychainStoreImpl),
|
|
663
751
|
sync_launchctl: syncLaunchctl,
|
|
664
752
|
install_shell_profile: shellProfile,
|
|
665
753
|
run_health_check: opts.runHealth === true,
|
|
@@ -703,11 +791,14 @@ export async function configureCodexLb(opts = {}) {
|
|
|
703
791
|
const insecureLocalWarning = /^http:\/\//i.test(baseUrl) && !/^http:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/i.test(baseUrl) && !opts.allowInsecureHttp
|
|
704
792
|
? ['codex-lb base URL uses http outside localhost; prefer https or pass an explicit allow flag in the calling surface.']
|
|
705
793
|
: [];
|
|
706
|
-
const
|
|
794
|
+
const processEnvBefore = {
|
|
795
|
+
baseUrl: process.env.CODEX_LB_BASE_URL,
|
|
796
|
+
apiKey: process.env.CODEX_LB_API_KEY
|
|
797
|
+
};
|
|
707
798
|
const authBefore = await captureCodexAuthSnapshot({ home, authPath: opts.authPath || codexAuthPath(home) });
|
|
708
799
|
const appliedActions = [];
|
|
709
800
|
await ensureDir(path.dirname(configPath));
|
|
710
|
-
const current =
|
|
801
|
+
const current = initialConfig;
|
|
711
802
|
let providerOnly = current;
|
|
712
803
|
try {
|
|
713
804
|
if (desktopMode === 'desktop-native-bridge') {
|
|
@@ -746,7 +837,14 @@ export async function configureCodexLb(opts = {}) {
|
|
|
746
837
|
error: error instanceof Error ? error.message : String(error)
|
|
747
838
|
};
|
|
748
839
|
}
|
|
749
|
-
|
|
840
|
+
await opts.testHooks?.beforeInitialConfigWrite?.({ configPath });
|
|
841
|
+
const safeWrite = await safeWriteCodexConfigToml(configPath, current, providerOnly, 'codex-lb', {
|
|
842
|
+
verifyUnchangedBeforeWrite: true,
|
|
843
|
+
expectedBeforeExists: configBeforeEntry.existed === true,
|
|
844
|
+
...(configBeforeEntry.existed === true
|
|
845
|
+
? { expectedBeforeMode: Number(configBeforeEntry.mode) }
|
|
846
|
+
: {})
|
|
847
|
+
});
|
|
750
848
|
if (!safeWrite.ok)
|
|
751
849
|
return { ok: false, status: safeWrite.status, config_path: configPath, env_path: envPath, backup_path: safeWrite.backup_path };
|
|
752
850
|
appliedActions.push({
|
|
@@ -761,13 +859,205 @@ export async function configureCodexLb(opts = {}) {
|
|
|
761
859
|
ok: true,
|
|
762
860
|
backup_path: safeWrite.backup_path
|
|
763
861
|
});
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
862
|
+
const configExpectedAfter = safeWrite.expected_after;
|
|
863
|
+
if (!configExpectedAfter || typeof configExpectedAfter.exists !== 'boolean') {
|
|
864
|
+
return {
|
|
865
|
+
ok: false,
|
|
866
|
+
status: 'partial_configuration_phase_snapshot_failed',
|
|
867
|
+
mode: desktopMode,
|
|
868
|
+
identity_plane: 'unchanged',
|
|
869
|
+
routing_plane: codexLbRoutingPlane(desktopMode),
|
|
870
|
+
oauth_preserved: true,
|
|
871
|
+
auth_mutated: false,
|
|
872
|
+
plan: plan,
|
|
873
|
+
applied_actions: appliedActions,
|
|
874
|
+
drift: ['setup_phase_snapshot_failed:after_config_write'],
|
|
875
|
+
config_path: configPath,
|
|
876
|
+
env_path: envPath,
|
|
877
|
+
metadata_path: metadataPath,
|
|
878
|
+
backup_path: safeWrite.backup_path,
|
|
879
|
+
error: 'guarded config writer did not return an owned post-write snapshot'
|
|
880
|
+
};
|
|
768
881
|
}
|
|
769
|
-
|
|
770
|
-
|
|
882
|
+
let setupExpectedState = {
|
|
883
|
+
...beforeState,
|
|
884
|
+
files: beforeState.files.map((entry) => entry.path === configPath
|
|
885
|
+
? configExpectedAfter.exists
|
|
886
|
+
? {
|
|
887
|
+
path: configPath,
|
|
888
|
+
existed: true,
|
|
889
|
+
kind: 'regular',
|
|
890
|
+
bytes_base64: Buffer.from(String(configExpectedAfter.text || '')).toString('base64'),
|
|
891
|
+
mode: Number(configExpectedAfter.mode)
|
|
892
|
+
}
|
|
893
|
+
: { path: configPath, existed: false, kind: 'missing', bytes_base64: '', mode: null }
|
|
894
|
+
: entry)
|
|
895
|
+
};
|
|
896
|
+
const recordExpectedRegularFile = (file, text, mode) => {
|
|
897
|
+
setupExpectedState = {
|
|
898
|
+
...setupExpectedState,
|
|
899
|
+
files: setupExpectedState.files.map((entry) => entry.path === file
|
|
900
|
+
? {
|
|
901
|
+
path: file,
|
|
902
|
+
existed: true,
|
|
903
|
+
kind: 'regular',
|
|
904
|
+
bytes_base64: Buffer.from(text).toString('base64'),
|
|
905
|
+
mode: mode & 0o777
|
|
906
|
+
}
|
|
907
|
+
: entry)
|
|
908
|
+
};
|
|
909
|
+
};
|
|
910
|
+
const setupWriteRecoveryPaths = [];
|
|
911
|
+
const writeSetupFile = async (input) => {
|
|
912
|
+
const expected = beforeState.files.find((entry) => path.resolve(String(entry?.path || '')) === path.resolve(input.file));
|
|
913
|
+
if (!expected) {
|
|
914
|
+
throw new Error(`setup_snapshot_missing:${input.file}`);
|
|
915
|
+
}
|
|
916
|
+
const result = await writeCodexLbSetupFileIfUnchanged({
|
|
917
|
+
file: input.file,
|
|
918
|
+
expected,
|
|
919
|
+
text: input.text,
|
|
920
|
+
mode: input.mode,
|
|
921
|
+
...(input.beforeReplacement ? { beforeReplacement: input.beforeReplacement } : {})
|
|
922
|
+
});
|
|
923
|
+
if (result.recovery_path)
|
|
924
|
+
setupWriteRecoveryPaths.push(result.recovery_path);
|
|
925
|
+
if (result.ok || result.installed) {
|
|
926
|
+
recordExpectedRegularFile(input.file, input.text, input.mode);
|
|
927
|
+
}
|
|
928
|
+
if (!result.ok) {
|
|
929
|
+
throw new Error(`${result.status}:${input.file}${result.error ? `:${result.error}` : ''}`);
|
|
930
|
+
}
|
|
931
|
+
return result;
|
|
932
|
+
};
|
|
933
|
+
const rollbackSetupAfterFailure = async (input) => {
|
|
934
|
+
const rollback = await restoreCodexLbSetupWriteState(beforeState, input.expectedCurrentState, {
|
|
935
|
+
...(typeof opts.testHooks?.beforeRollbackFileReplacement === 'function'
|
|
936
|
+
? { beforeReplacement: opts.testHooks.beforeRollbackFileReplacement }
|
|
937
|
+
: {})
|
|
938
|
+
});
|
|
939
|
+
const rollbackBlockers = [...rollback.blockers];
|
|
940
|
+
const processRollbackBlockers = [];
|
|
941
|
+
if (input.restoreProcessEnvironment) {
|
|
942
|
+
if (process.env.CODEX_LB_BASE_URL === processEnvBefore.baseUrl) {
|
|
943
|
+
}
|
|
944
|
+
else if (process.env.CODEX_LB_BASE_URL === baseUrl) {
|
|
945
|
+
if (processEnvBefore.baseUrl === undefined)
|
|
946
|
+
delete process.env.CODEX_LB_BASE_URL;
|
|
947
|
+
else
|
|
948
|
+
process.env.CODEX_LB_BASE_URL = processEnvBefore.baseUrl;
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
processRollbackBlockers.push('setup_rollback_conflict:process.env.CODEX_LB_BASE_URL');
|
|
952
|
+
}
|
|
953
|
+
if (process.env.CODEX_LB_API_KEY === processEnvBefore.apiKey) {
|
|
954
|
+
}
|
|
955
|
+
else if (process.env.CODEX_LB_API_KEY === apiKey) {
|
|
956
|
+
if (processEnvBefore.apiKey === undefined)
|
|
957
|
+
delete process.env.CODEX_LB_API_KEY;
|
|
958
|
+
else
|
|
959
|
+
process.env.CODEX_LB_API_KEY = processEnvBefore.apiKey;
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
processRollbackBlockers.push('setup_rollback_conflict:process.env.CODEX_LB_API_KEY');
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
rollbackBlockers.push(...processRollbackBlockers);
|
|
966
|
+
const afterRollback = await captureCodexLbSetupWriteState({
|
|
967
|
+
home,
|
|
968
|
+
configPath,
|
|
969
|
+
envPath,
|
|
970
|
+
metadataPath,
|
|
971
|
+
shellProfile
|
|
972
|
+
});
|
|
973
|
+
if (beforeState.stateHash !== afterRollback.stateHash) {
|
|
974
|
+
rollbackBlockers.push('setup_rollback_state_verification_failed');
|
|
975
|
+
}
|
|
976
|
+
const recoveryPaths = [
|
|
977
|
+
...(safeWrite.backup_path ? [safeWrite.backup_path] : []),
|
|
978
|
+
...setupWriteRecoveryPaths,
|
|
979
|
+
...rollback.recovery
|
|
980
|
+
];
|
|
981
|
+
const secretRecoveryPaths = [...new Set(recoveryPaths)]
|
|
982
|
+
.filter((recoveryPath) => isRecoveryPathForFile(recoveryPath, envPath));
|
|
983
|
+
for (const recoveryPath of secretRecoveryPaths) {
|
|
984
|
+
if (!await hardenCodexLbSetupRecoveryPath(recoveryPath)) {
|
|
985
|
+
rollbackBlockers.push(`setup_secret_recovery_mode_unverified:${recoveryPath}`);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (secretRecoveryPaths.length > 0) {
|
|
989
|
+
rollbackBlockers.push('setup_secret_recovery_retained');
|
|
990
|
+
}
|
|
991
|
+
const filesystemRollbackOk = rollbackBlockers.length === 0;
|
|
992
|
+
const keychain = input.keychain || { ok: false, status: 'not_written' };
|
|
993
|
+
return {
|
|
994
|
+
ok: false,
|
|
995
|
+
status: input.keychainRetained
|
|
996
|
+
? 'partial_configuration_keychain_retained'
|
|
997
|
+
: input.externalStateMayBeMutated
|
|
998
|
+
? 'partial_configuration_external_state_unknown'
|
|
999
|
+
: filesystemRollbackOk
|
|
1000
|
+
? 'setup_failed_rolled_back'
|
|
1001
|
+
: 'setup_failed_rollback_incomplete',
|
|
1002
|
+
mode: desktopMode,
|
|
1003
|
+
identity_plane: 'unchanged',
|
|
1004
|
+
routing_plane: codexLbRoutingPlane(desktopMode),
|
|
1005
|
+
oauth_preserved: true,
|
|
1006
|
+
auth_mutated: false,
|
|
1007
|
+
plan: plan,
|
|
1008
|
+
applied_actions: appliedActions,
|
|
1009
|
+
drift: [
|
|
1010
|
+
`setup_stage_failed:${input.stage}`,
|
|
1011
|
+
...(input.keychainRetained ? ['codex_lb_keychain_replacement_retained'] : []),
|
|
1012
|
+
...(input.externalStateMayBeMutated ? ['codex_lb_external_environment_requires_inspection'] : []),
|
|
1013
|
+
...rollbackBlockers
|
|
1014
|
+
],
|
|
1015
|
+
rollback: {
|
|
1016
|
+
...rollback,
|
|
1017
|
+
ok: filesystemRollbackOk,
|
|
1018
|
+
blockers: rollbackBlockers,
|
|
1019
|
+
byte_and_mode_verified: filesystemRollbackOk,
|
|
1020
|
+
keychain_retained: input.keychainRetained,
|
|
1021
|
+
secret_recovery_paths: secretRecoveryPaths,
|
|
1022
|
+
config_backup_path: safeWrite.backup_path || null,
|
|
1023
|
+
config_backup_status: safeWrite.backup_path ? 'retained_for_recovery' : 'not_created',
|
|
1024
|
+
recovery_paths: recoveryPaths
|
|
1025
|
+
},
|
|
1026
|
+
partial_configuration: {
|
|
1027
|
+
schema: 'sks.codex-lb-partial-configuration.v1',
|
|
1028
|
+
failure_stage: input.stage,
|
|
1029
|
+
filesystem_state: filesystemRollbackOk ? 'restored' : 'indeterminate',
|
|
1030
|
+
process_environment_state: input.restoreProcessEnvironment
|
|
1031
|
+
? (processRollbackBlockers.length === 0 ? 'restored' : 'indeterminate')
|
|
1032
|
+
: 'unchanged',
|
|
1033
|
+
keychain_state: input.keychainRetained ? 'replacement_retained' : 'unchanged',
|
|
1034
|
+
external_environment_state: input.externalStateMayBeMutated ? 'inspect_with_status' : 'unchanged',
|
|
1035
|
+
durable_applied_state: [
|
|
1036
|
+
...(input.keychainRetained
|
|
1037
|
+
? [`macOS Keychain service ${CODEX_LB_SECURE_KEYCHAIN_SERVICE}`]
|
|
1038
|
+
: []),
|
|
1039
|
+
...(input.externalStateMayBeMutated ? ['Center/launch environment may retain a partial update'] : [])
|
|
1040
|
+
],
|
|
1041
|
+
recovery_actions: [
|
|
1042
|
+
'Run: sks codex-lb status --json',
|
|
1043
|
+
'Rerun setup with --api-key-stdin after resolving the failed stage.',
|
|
1044
|
+
`Inspect ${CODEX_LB_SECURE_KEYCHAIN_SERVICE} before removing any retained Keychain credential.`,
|
|
1045
|
+
...(secretRecoveryPaths.length > 0
|
|
1046
|
+
? ['Review and securely remove secret_recovery_paths after preserving any concurrent user edits.']
|
|
1047
|
+
: [])
|
|
1048
|
+
],
|
|
1049
|
+
recovery_paths: recoveryPaths,
|
|
1050
|
+
secret_recovery_paths: secretRecoveryPaths
|
|
1051
|
+
},
|
|
1052
|
+
config_path: configPath,
|
|
1053
|
+
env_path: envPath,
|
|
1054
|
+
metadata_path: metadataPath,
|
|
1055
|
+
keychain,
|
|
1056
|
+
...(input.centerCredentials ? { center_credentials: input.centerCredentials } : {}),
|
|
1057
|
+
...(input.codexEnvironment ? { codex_environment: input.codexEnvironment } : {}),
|
|
1058
|
+
error: redactSecretText(input.error instanceof Error ? input.error.message : String(input.error || input.stage), [apiKey])
|
|
1059
|
+
};
|
|
1060
|
+
};
|
|
771
1061
|
const toolCatalog = {
|
|
772
1062
|
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
773
1063
|
ok: true,
|
|
@@ -783,8 +1073,7 @@ export async function configureCodexLb(opts = {}) {
|
|
|
783
1073
|
blockers: []
|
|
784
1074
|
};
|
|
785
1075
|
const keyFingerprint = await sha256Text(apiKey);
|
|
786
|
-
const
|
|
787
|
-
await writeTextAtomic(metadataPath, `${JSON.stringify({
|
|
1076
|
+
const metadataText = `${JSON.stringify({
|
|
788
1077
|
schema: 'sks.codex-lb-metadata.v1',
|
|
789
1078
|
base_url: baseUrl,
|
|
790
1079
|
updated_at: new Date().toISOString(),
|
|
@@ -793,38 +1082,302 @@ export async function configureCodexLb(opts = {}) {
|
|
|
793
1082
|
requested_desktop_mode: requestedDesktopMode,
|
|
794
1083
|
gateway_auth_transport: opts.gatewayAuthTransport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT,
|
|
795
1084
|
api_key: { redacted: true, sha256: keyFingerprint }
|
|
796
|
-
}, null, 2)}\n
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1085
|
+
}, null, 2)}\n`;
|
|
1086
|
+
try {
|
|
1087
|
+
await opts.testHooks?.beforeMetadataWrite?.({ metadataPath });
|
|
1088
|
+
const metadataWrite = await writeSetupFile({
|
|
1089
|
+
file: metadataPath,
|
|
1090
|
+
text: metadataText,
|
|
1091
|
+
mode: 0o600,
|
|
1092
|
+
...(typeof opts.testHooks?.beforeSetupFileReplacement === 'function'
|
|
1093
|
+
? { beforeReplacement: opts.testHooks.beforeSetupFileReplacement }
|
|
1094
|
+
: {})
|
|
1095
|
+
});
|
|
1096
|
+
appliedActions.push({
|
|
1097
|
+
type: 'write_metadata',
|
|
1098
|
+
target: metadataPath,
|
|
1099
|
+
ok: true,
|
|
1100
|
+
status: metadataWrite.status,
|
|
1101
|
+
recovery_path: metadataWrite.recovery_path || null
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
catch (error) {
|
|
1105
|
+
return rollbackSetupAfterFailure({
|
|
1106
|
+
stage: 'write_metadata',
|
|
1107
|
+
error,
|
|
1108
|
+
keychainRetained: false,
|
|
1109
|
+
restoreProcessEnvironment: false,
|
|
1110
|
+
expectedCurrentState: setupExpectedState
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
const keychainWriteState = setupExpectedState;
|
|
1114
|
+
const keychain = storeKeychain
|
|
1115
|
+
? await keychainStoreImpl(apiKey, opts).catch((err) => ({
|
|
1116
|
+
ok: false,
|
|
1117
|
+
status: 'keychain_store_failed',
|
|
1118
|
+
error: err.message
|
|
1119
|
+
}))
|
|
1120
|
+
: { ok: false, status: 'skipped' };
|
|
800
1121
|
if (storeKeychain)
|
|
801
|
-
appliedActions.push({ type: 'store_keychain', target:
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1122
|
+
appliedActions.push({ type: 'store_keychain', target: `macOS Keychain service ${CODEX_LB_SECURE_KEYCHAIN_SERVICE}`, ok: keychain.ok === true, status: keychain.status });
|
|
1123
|
+
if (storeKeychain && keychain.ok !== true) {
|
|
1124
|
+
await opts.testHooks?.beforeRollbackStart?.({ configPath });
|
|
1125
|
+
const rollback = await restoreCodexLbSetupWriteState(beforeState, keychainWriteState, {
|
|
1126
|
+
...(typeof opts.testHooks?.beforeRollbackFileReplacement === 'function'
|
|
1127
|
+
? { beforeReplacement: opts.testHooks.beforeRollbackFileReplacement }
|
|
1128
|
+
: {})
|
|
1129
|
+
});
|
|
1130
|
+
const rollbackBlockers = [...rollback.blockers];
|
|
1131
|
+
if (keychain.keychain_state_verified !== true) {
|
|
1132
|
+
rollbackBlockers.push('setup_rollback_keychain_state_indeterminate');
|
|
1133
|
+
}
|
|
1134
|
+
const backupStatus = safeWrite.backup_path ? 'retained_for_recovery' : 'not_created';
|
|
1135
|
+
const afterRollback = await captureCodexLbSetupWriteState({ home, configPath, envPath, metadataPath, shellProfile });
|
|
1136
|
+
if (beforeState.stateHash !== afterRollback.stateHash) {
|
|
1137
|
+
rollbackBlockers.push('setup_rollback_state_verification_failed');
|
|
1138
|
+
}
|
|
1139
|
+
const recoveryPaths = [
|
|
1140
|
+
...(safeWrite.backup_path ? [safeWrite.backup_path] : []),
|
|
1141
|
+
...setupWriteRecoveryPaths,
|
|
1142
|
+
...rollback.recovery
|
|
1143
|
+
];
|
|
1144
|
+
const secretRecoveryPaths = [...new Set(recoveryPaths)]
|
|
1145
|
+
.filter((recoveryPath) => isRecoveryPathForFile(recoveryPath, envPath));
|
|
1146
|
+
for (const recoveryPath of secretRecoveryPaths) {
|
|
1147
|
+
if (!await hardenCodexLbSetupRecoveryPath(recoveryPath)) {
|
|
1148
|
+
rollbackBlockers.push(`setup_secret_recovery_mode_unverified:${recoveryPath}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (secretRecoveryPaths.length > 0) {
|
|
1152
|
+
rollbackBlockers.push('setup_secret_recovery_retained');
|
|
1153
|
+
}
|
|
1154
|
+
const finalRollbackOk = rollbackBlockers.length === 0;
|
|
1155
|
+
return {
|
|
1156
|
+
ok: false,
|
|
1157
|
+
status: finalRollbackOk
|
|
1158
|
+
? 'keychain_store_failed_rolled_back'
|
|
1159
|
+
: keychain.keychain_state_verified !== true
|
|
1160
|
+
? 'keychain_state_indeterminate'
|
|
1161
|
+
: 'keychain_store_failed_rollback_incomplete',
|
|
1162
|
+
mode: desktopMode,
|
|
1163
|
+
identity_plane: 'unchanged',
|
|
1164
|
+
routing_plane: codexLbRoutingPlane(desktopMode),
|
|
1165
|
+
oauth_preserved: true,
|
|
1166
|
+
auth_mutated: false,
|
|
1167
|
+
plan: plan,
|
|
1168
|
+
applied_actions: appliedActions,
|
|
1169
|
+
drift: ['codex_lb_keychain_store_failed', ...rollbackBlockers],
|
|
1170
|
+
rollback: {
|
|
1171
|
+
...rollback,
|
|
1172
|
+
ok: finalRollbackOk,
|
|
1173
|
+
blockers: rollbackBlockers,
|
|
1174
|
+
byte_and_mode_verified: finalRollbackOk,
|
|
1175
|
+
config_backup_path: safeWrite.backup_path || null,
|
|
1176
|
+
config_backup_status: backupStatus,
|
|
1177
|
+
recovery_paths: recoveryPaths,
|
|
1178
|
+
secret_recovery_paths: secretRecoveryPaths
|
|
1179
|
+
},
|
|
1180
|
+
config_path: configPath,
|
|
1181
|
+
env_path: envPath,
|
|
1182
|
+
metadata_path: metadataPath,
|
|
1183
|
+
keychain,
|
|
1184
|
+
error: keychain.error || keychain.status
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
const writeSetupEnvFile = async () => {
|
|
1188
|
+
await opts.testHooks?.beforeEnvWrite?.({ envPath });
|
|
1189
|
+
const envText = `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`;
|
|
1190
|
+
const envWrite = await writeSetupFile({
|
|
1191
|
+
file: envPath,
|
|
1192
|
+
text: envText,
|
|
1193
|
+
mode: 0o600,
|
|
1194
|
+
...(typeof opts.testHooks?.beforeSetupFileReplacement === 'function'
|
|
1195
|
+
? { beforeReplacement: opts.testHooks.beforeSetupFileReplacement }
|
|
1196
|
+
: {})
|
|
1197
|
+
});
|
|
1198
|
+
appliedActions.push({
|
|
1199
|
+
type: 'write_env_file',
|
|
1200
|
+
target: envPath,
|
|
1201
|
+
ok: true,
|
|
1202
|
+
status: envWrite.status,
|
|
1203
|
+
recovery_path: envWrite.recovery_path || null
|
|
1204
|
+
});
|
|
1205
|
+
await opts.testHooks?.afterEnvWrite?.({ envPath });
|
|
1206
|
+
};
|
|
1207
|
+
if (writeEnvFile && !storeKeychain) {
|
|
1208
|
+
try {
|
|
1209
|
+
await writeSetupEnvFile();
|
|
1210
|
+
}
|
|
1211
|
+
catch (error) {
|
|
1212
|
+
return rollbackSetupAfterFailure({
|
|
1213
|
+
stage: 'write_env_file',
|
|
1214
|
+
error,
|
|
1215
|
+
keychain,
|
|
1216
|
+
keychainRetained: false,
|
|
1217
|
+
restoreProcessEnvironment: false,
|
|
1218
|
+
expectedCurrentState: setupExpectedState
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
let shellProfileResult = { ok: true, status: 'skipped', files: [] };
|
|
1223
|
+
try {
|
|
1224
|
+
await opts.testHooks?.beforeCenterSync?.();
|
|
1225
|
+
}
|
|
1226
|
+
catch (error) {
|
|
1227
|
+
return rollbackSetupAfterFailure({
|
|
1228
|
+
stage: 'sync_center_desktop_credentials',
|
|
1229
|
+
error,
|
|
1230
|
+
keychain,
|
|
1231
|
+
keychainRetained: storeKeychain && keychain.ok === true,
|
|
1232
|
+
restoreProcessEnvironment: false,
|
|
1233
|
+
expectedCurrentState: setupExpectedState
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
const skipLaunchEnvironment = opts.syncLaunchEnv === false
|
|
1237
|
+
|| process.env.SKS_SKIP_CODEX_LB_LAUNCH_ENV === '1';
|
|
1238
|
+
const { purgeStaleCodexLbCredentialTwins, syncDesktopCenterLaunchCredentials, verifyCodexLbLegacyKeychainReplacementStore } = await import('../core/codex-lb/desktop-center-credentials.js');
|
|
1239
|
+
const centerCredentials = skipLaunchEnvironment
|
|
1240
|
+
? {
|
|
1241
|
+
schema: 'sks.codex-lb-desktop-center-credentials.v1',
|
|
1242
|
+
ok: true,
|
|
1243
|
+
status: 'skipped',
|
|
1244
|
+
mode: desktopMode,
|
|
1245
|
+
api_key_fingerprint: keyFingerprint,
|
|
1246
|
+
base_url_present: true,
|
|
1247
|
+
launch_env: { api_key: 'skipped', base_url: 'skipped' },
|
|
1248
|
+
stale_twins_removed: [],
|
|
1249
|
+
stale_twins_quarantined: [],
|
|
1250
|
+
stale_keychain_cleared: [],
|
|
1251
|
+
blockers: []
|
|
1252
|
+
}
|
|
1253
|
+
: await syncDesktopCenterLaunchCredentials({
|
|
1254
|
+
mode: desktopMode,
|
|
1255
|
+
home,
|
|
1256
|
+
...(opts.platform ? { platform: opts.platform } : {}),
|
|
1257
|
+
...(opts.launchctlBin ? { launchctlBin: opts.launchctlBin } : {}),
|
|
1258
|
+
...(opts.securityBin ? { securityBin: opts.securityBin } : {}),
|
|
1259
|
+
...(opts.runProcessImpl ? { runProcessImpl: opts.runProcessImpl } : {}),
|
|
1260
|
+
deferLegacyKeychainCleanup: writeEnvFile && !storeKeychain,
|
|
1261
|
+
loadedEnv: await loadCodexLbEnv({ ...opts, home, processEnv: {}, envPath, metadataPath })
|
|
1262
|
+
}).catch((error) => ({
|
|
1263
|
+
ok: false,
|
|
1264
|
+
status: 'center_desktop_credentials_failed',
|
|
1265
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1266
|
+
}));
|
|
808
1267
|
appliedActions.push({
|
|
809
1268
|
type: 'sync_center_desktop_credentials',
|
|
810
1269
|
target: 'launchctl from official sks-codex-lb store',
|
|
811
1270
|
ok: centerCredentials.ok === true,
|
|
812
1271
|
status: centerCredentials.status
|
|
813
1272
|
});
|
|
1273
|
+
if (centerCredentials.ok !== true) {
|
|
1274
|
+
return rollbackSetupAfterFailure({
|
|
1275
|
+
stage: 'sync_center_desktop_credentials',
|
|
1276
|
+
error: centerCredentials.error || centerCredentials.status,
|
|
1277
|
+
keychain,
|
|
1278
|
+
keychainRetained: storeKeychain && keychain.ok === true,
|
|
1279
|
+
restoreProcessEnvironment: false,
|
|
1280
|
+
externalStateMayBeMutated: true,
|
|
1281
|
+
expectedCurrentState: setupExpectedState,
|
|
1282
|
+
centerCredentials
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
814
1285
|
const codexEnvironment = await syncCodexLbProviderEnvironment({ env_path: envPath, base_url: baseUrl }, {
|
|
815
1286
|
...opts,
|
|
816
1287
|
home,
|
|
817
1288
|
apiKey,
|
|
818
1289
|
baseUrl,
|
|
819
1290
|
syncLaunchEnv: syncLaunchctl && desktopMode !== 'desktop-dual-auth-compat'
|
|
820
|
-
})
|
|
1291
|
+
}).catch((error) => ({
|
|
1292
|
+
ok: false,
|
|
1293
|
+
status: 'environment_failed',
|
|
1294
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1295
|
+
}));
|
|
821
1296
|
if (syncLaunchctl && desktopMode !== 'desktop-dual-auth-compat') {
|
|
822
1297
|
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
1298
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1299
|
+
if (codexEnvironment.ok !== true) {
|
|
1300
|
+
return rollbackSetupAfterFailure({
|
|
1301
|
+
stage: 'sync_codex_environment',
|
|
1302
|
+
error: codexEnvironment.error || codexEnvironment.status,
|
|
1303
|
+
keychain,
|
|
1304
|
+
keychainRetained: storeKeychain && keychain.ok === true,
|
|
1305
|
+
restoreProcessEnvironment: true,
|
|
1306
|
+
externalStateMayBeMutated: true,
|
|
1307
|
+
expectedCurrentState: setupExpectedState,
|
|
1308
|
+
centerCredentials,
|
|
1309
|
+
codexEnvironment
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
if (writeEnvFile && storeKeychain) {
|
|
1313
|
+
try {
|
|
1314
|
+
await writeSetupEnvFile();
|
|
1315
|
+
}
|
|
1316
|
+
catch (error) {
|
|
1317
|
+
return rollbackSetupAfterFailure({
|
|
1318
|
+
stage: 'write_env_file',
|
|
1319
|
+
error,
|
|
1320
|
+
keychain,
|
|
1321
|
+
keychainRetained: true,
|
|
1322
|
+
restoreProcessEnvironment: true,
|
|
1323
|
+
externalStateMayBeMutated: true,
|
|
1324
|
+
expectedCurrentState: setupExpectedState,
|
|
1325
|
+
centerCredentials,
|
|
1326
|
+
codexEnvironment
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
process.env.CODEX_LB_BASE_URL = baseUrl;
|
|
1331
|
+
process.env.CODEX_LB_API_KEY = apiKey;
|
|
1332
|
+
shellProfileResult = await installCodexLbShellProfileSnippet({
|
|
1333
|
+
home,
|
|
1334
|
+
envPath,
|
|
1335
|
+
shellProfile,
|
|
1336
|
+
expectedFiles: beforeState.files,
|
|
1337
|
+
writeFileIfUnchanged: async ({ file, text, mode }) => {
|
|
1338
|
+
try {
|
|
1339
|
+
await opts.testHooks?.beforeShellProfileWrite?.({ file });
|
|
1340
|
+
return await writeSetupFile({
|
|
1341
|
+
file,
|
|
1342
|
+
text,
|
|
1343
|
+
mode,
|
|
1344
|
+
...(typeof opts.testHooks?.beforeSetupFileReplacement === 'function'
|
|
1345
|
+
? { beforeReplacement: opts.testHooks.beforeSetupFileReplacement }
|
|
1346
|
+
: {})
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
catch (error) {
|
|
1350
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1351
|
+
return {
|
|
1352
|
+
ok: false,
|
|
1353
|
+
status: message.split(':', 1)[0] || 'write_failed',
|
|
1354
|
+
error: message
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
})
|
|
1359
|
+
.catch((err) => ({ ok: false, status: 'failed', files: [], error: err.message }));
|
|
1360
|
+
if (shellProfile !== 'skip') {
|
|
1361
|
+
appliedActions.push({
|
|
1362
|
+
type: 'install_shell_profile_snippet',
|
|
1363
|
+
target: shellProfileResult.files?.join(', ') || shellProfile,
|
|
1364
|
+
ok: shellProfileResult.ok === true,
|
|
1365
|
+
status: shellProfileResult.status
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
if (shellProfileResult.ok !== true) {
|
|
1369
|
+
return rollbackSetupAfterFailure({
|
|
1370
|
+
stage: 'install_shell_profile_snippet',
|
|
1371
|
+
error: shellProfileResult.error || shellProfileResult.status,
|
|
1372
|
+
keychain,
|
|
1373
|
+
keychainRetained: storeKeychain && keychain.ok === true,
|
|
1374
|
+
restoreProcessEnvironment: true,
|
|
1375
|
+
externalStateMayBeMutated: true,
|
|
1376
|
+
expectedCurrentState: setupExpectedState,
|
|
1377
|
+
centerCredentials,
|
|
1378
|
+
codexEnvironment
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
828
1381
|
const authAfter = await captureCodexAuthSnapshot({ home, authPath: opts.authPath || codexAuthPath(home) });
|
|
829
1382
|
let authInvariantError = null;
|
|
830
1383
|
try {
|
|
@@ -845,9 +1398,23 @@ export async function configureCodexLb(opts = {}) {
|
|
|
845
1398
|
reason: authInvariantError || 'cli_gateway_key_is_separate_from_chatgpt_oauth',
|
|
846
1399
|
error: authInvariantError
|
|
847
1400
|
};
|
|
1401
|
+
if (authInvariantError) {
|
|
1402
|
+
return rollbackSetupAfterFailure({
|
|
1403
|
+
stage: 'verify_desktop_auth_invariant',
|
|
1404
|
+
error: authInvariantError,
|
|
1405
|
+
keychain,
|
|
1406
|
+
keychainRetained: storeKeychain && keychain.ok === true,
|
|
1407
|
+
restoreProcessEnvironment: true,
|
|
1408
|
+
externalStateMayBeMutated: true,
|
|
1409
|
+
expectedCurrentState: setupExpectedState,
|
|
1410
|
+
centerCredentials,
|
|
1411
|
+
codexEnvironment
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
848
1414
|
const finalCodexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
|
|
849
|
-
const
|
|
850
|
-
const
|
|
1415
|
+
const keychainOk = !storeKeychain || keychain.ok === true;
|
|
1416
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok && keychainOk);
|
|
1417
|
+
const afterState = await captureCodexLbSetupWriteState({ home, configPath, envPath, metadataPath, shellProfile });
|
|
851
1418
|
const drift = [
|
|
852
1419
|
...detectCodexLbSetupDrift({
|
|
853
1420
|
useDefaultProvider,
|
|
@@ -864,6 +1431,7 @@ export async function configureCodexLb(opts = {}) {
|
|
|
864
1431
|
afterState
|
|
865
1432
|
}),
|
|
866
1433
|
...(authInvariantError ? ['desktop_auth_byte_invariant_failed'] : []),
|
|
1434
|
+
...(!keychainOk ? ['codex_lb_keychain_store_failed'] : []),
|
|
867
1435
|
...(toolCatalog.required !== false && toolCatalog.ok !== true ? ['codex_lb_gpt56_tool_catalog_not_ready'] : [])
|
|
868
1436
|
];
|
|
869
1437
|
const appliedPersistenceModes = appliedCodexLbPersistenceModes({
|
|
@@ -888,16 +1456,103 @@ export async function configureCodexLb(opts = {}) {
|
|
|
888
1456
|
...(preservedLegacySelection ? ['legacy_desktop_auth_routing_requires_explicit_migrate_legacy_desktop'] : []),
|
|
889
1457
|
...(toolCatalog.required !== false && toolCatalog.ok !== true ? ['codex_lb_gpt56_tool_catalog_not_ready'] : [])
|
|
890
1458
|
];
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1459
|
+
if (!ok || drift.length > 0) {
|
|
1460
|
+
return rollbackSetupAfterFailure({
|
|
1461
|
+
stage: 'verify_final_setup_state',
|
|
1462
|
+
error: drift.join(',') || 'configuration_failed',
|
|
1463
|
+
keychain,
|
|
1464
|
+
keychainRetained: storeKeychain && keychain.ok === true,
|
|
1465
|
+
restoreProcessEnvironment: true,
|
|
1466
|
+
externalStateMayBeMutated: true,
|
|
1467
|
+
expectedCurrentState: setupExpectedState,
|
|
1468
|
+
centerCredentials,
|
|
1469
|
+
codexEnvironment
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
const retainedSetupRecoveryPaths = [];
|
|
1473
|
+
for (const recoveryPath of [...new Set(setupWriteRecoveryPaths)]) {
|
|
1474
|
+
if (await removeCodexLbSetupRecoveryPath(recoveryPath)) {
|
|
1475
|
+
for (const action of appliedActions) {
|
|
1476
|
+
if (action.recovery_path === recoveryPath) {
|
|
1477
|
+
action.recovery_path = null;
|
|
1478
|
+
action.recovery_claim_cleanup = 'removed_after_final_verification';
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
retainedSetupRecoveryPaths.push(recoveryPath);
|
|
1484
|
+
await hardenCodexLbSetupRecoveryPath(recoveryPath);
|
|
1485
|
+
}
|
|
1486
|
+
const retainedSecretRecoveryPaths = retainedSetupRecoveryPaths
|
|
1487
|
+
.filter((recoveryPath) => isRecoveryPathForFile(recoveryPath, envPath));
|
|
1488
|
+
let legacyKeychainCleanup = {
|
|
1489
|
+
ok: true,
|
|
1490
|
+
status: 'not_applicable',
|
|
1491
|
+
keychain_cleared: [],
|
|
1492
|
+
blockers: []
|
|
1493
|
+
};
|
|
1494
|
+
if ((opts.platform || process.platform) === 'darwin' && writeEnvFile && !storeKeychain) {
|
|
1495
|
+
await opts.testHooks?.beforeLegacyKeychainCleanup?.({ envPath, metadataPath });
|
|
1496
|
+
const replacement = await verifyCodexLbLegacyKeychainReplacementStore({
|
|
1497
|
+
home,
|
|
1498
|
+
envPath,
|
|
1499
|
+
metadataPath,
|
|
1500
|
+
account: opts.account || process.env.USER || 'sks',
|
|
1501
|
+
expectedApiKeySha256: keyFingerprint
|
|
1502
|
+
});
|
|
1503
|
+
const cleanup = await purgeStaleCodexLbCredentialTwins({
|
|
1504
|
+
home,
|
|
1505
|
+
account: opts.account || process.env.USER || 'sks',
|
|
1506
|
+
platform: 'darwin',
|
|
1507
|
+
skipStaleEnvTwins: true,
|
|
1508
|
+
...(opts.securityBin ? { securityBin: opts.securityBin } : {}),
|
|
1509
|
+
...(opts.runProcessImpl ? { runProcessImpl: opts.runProcessImpl } : {}),
|
|
1510
|
+
...(replacement.receipt
|
|
1511
|
+
? { legacyKeychainMigrationReceipt: replacement.receipt }
|
|
1512
|
+
: {})
|
|
1513
|
+
});
|
|
1514
|
+
const cleanupBlockers = [
|
|
1515
|
+
...(!replacement.ok && replacement.blockers.length === 0
|
|
1516
|
+
? ['legacy_keychain_replacement_store_unverified']
|
|
1517
|
+
: replacement.blockers),
|
|
1518
|
+
...cleanup.blockers
|
|
1519
|
+
];
|
|
1520
|
+
const cleanupOk = replacement.ok && cleanupBlockers.length === 0;
|
|
1521
|
+
legacyKeychainCleanup = {
|
|
1522
|
+
ok: cleanupOk,
|
|
1523
|
+
status: !replacement.ok
|
|
1524
|
+
? 'legacy_keychain_cleanup_blocked_replacement_store_unverified'
|
|
1525
|
+
: cleanupBlockers.length === 0
|
|
1526
|
+
? cleanup.keychain_cleared.length > 0
|
|
1527
|
+
? 'legacy_keychain_removed'
|
|
1528
|
+
: 'legacy_keychain_absent'
|
|
1529
|
+
: 'legacy_keychain_cleanup_failed_secure_store_retained',
|
|
1530
|
+
replacement_store_verified: replacement.ok,
|
|
1531
|
+
keychain_cleared: cleanup.keychain_cleared,
|
|
1532
|
+
blockers: [...new Set(cleanupBlockers)]
|
|
1533
|
+
};
|
|
1534
|
+
appliedActions.push({
|
|
1535
|
+
type: 'migrate_legacy_keychain',
|
|
1536
|
+
target: 'macOS Keychain service sks-codex-lb',
|
|
1537
|
+
ok: legacyKeychainCleanup.ok,
|
|
1538
|
+
status: legacyKeychainCleanup.status
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
const finalWarnings = [
|
|
1542
|
+
...warnings,
|
|
1543
|
+
...(retainedSetupRecoveryPaths.length > 0 ? ['setup_recovery_claim_cleanup_required'] : []),
|
|
1544
|
+
...(retainedSecretRecoveryPaths.length > 0 ? ['setup_secret_recovery_retained'] : []),
|
|
1545
|
+
...(!legacyKeychainCleanup.ok
|
|
1546
|
+
? ['legacy_keychain_cleanup_indeterminate_rotate_provider_key']
|
|
1547
|
+
: legacyKeychainCleanup.keychain_cleared.length > 0
|
|
1548
|
+
? ['legacy_keychain_removed_rotate_provider_key_if_not_already_rotated']
|
|
1549
|
+
: [])
|
|
1550
|
+
];
|
|
898
1551
|
return {
|
|
899
|
-
ok: ok
|
|
900
|
-
status: ok
|
|
1552
|
+
ok: legacyKeychainCleanup.ok,
|
|
1553
|
+
status: legacyKeychainCleanup.ok
|
|
1554
|
+
? 'configured'
|
|
1555
|
+
: 'legacy_keychain_cleanup_failed_secure_store_retained',
|
|
901
1556
|
mode: desktopMode,
|
|
902
1557
|
identity_plane: 'unchanged',
|
|
903
1558
|
routing_plane: codexLbRoutingPlane(desktopMode),
|
|
@@ -906,16 +1561,25 @@ export async function configureCodexLb(opts = {}) {
|
|
|
906
1561
|
auth_mutated: false,
|
|
907
1562
|
plan: plan,
|
|
908
1563
|
applied_actions: appliedActions,
|
|
909
|
-
drift,
|
|
1564
|
+
drift: [...drift, ...legacyKeychainCleanup.blockers],
|
|
910
1565
|
persistence,
|
|
911
1566
|
center_credentials: centerCredentials,
|
|
912
1567
|
config_path: configPath,
|
|
913
1568
|
env_path: envPath,
|
|
914
1569
|
metadata_path: metadataPath,
|
|
1570
|
+
backup_path: safeWrite.backup_path || null,
|
|
1571
|
+
recovery_paths: [
|
|
1572
|
+
...new Set([
|
|
1573
|
+
...(safeWrite.backup_path ? [safeWrite.backup_path] : []),
|
|
1574
|
+
...retainedSetupRecoveryPaths
|
|
1575
|
+
])
|
|
1576
|
+
],
|
|
1577
|
+
secret_recovery_paths: retainedSecretRecoveryPaths,
|
|
915
1578
|
base_url: baseUrl,
|
|
916
1579
|
env_key: 'CODEX_LB_API_KEY',
|
|
917
1580
|
keychain,
|
|
918
|
-
|
|
1581
|
+
legacy_keychain_cleanup: legacyKeychainCleanup,
|
|
1582
|
+
warnings: finalWarnings,
|
|
919
1583
|
auth_reconcile: authReconcile,
|
|
920
1584
|
codex_lb: finalCodexLb,
|
|
921
1585
|
codex_environment: codexEnvironment,
|
|
@@ -925,6 +1589,14 @@ export async function configureCodexLb(opts = {}) {
|
|
|
925
1589
|
error: authReconcile.error || codexEnvironment.error || codexLogin.error || null
|
|
926
1590
|
};
|
|
927
1591
|
}
|
|
1592
|
+
function isRecoveryPathForFile(recoveryPath, file) {
|
|
1593
|
+
const target = path.resolve(file);
|
|
1594
|
+
const candidate = path.resolve(String(recoveryPath || ''));
|
|
1595
|
+
return candidate.startsWith(`${target}.sks-`);
|
|
1596
|
+
}
|
|
1597
|
+
function desktopModeRequiresGlobalSecretEnvironment(mode) {
|
|
1598
|
+
return mode === 'desktop-dual-auth-compat';
|
|
1599
|
+
}
|
|
928
1600
|
export async function codexLbStatus(opts = {}) {
|
|
929
1601
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
930
1602
|
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
@@ -965,17 +1637,8 @@ export async function codexLbStatus(opts = {}) {
|
|
|
965
1637
|
&& providerEnvKey === CODEX_LB_PROVIDER_ENV_KEY
|
|
966
1638
|
&& providerSupportsWebsockets === true
|
|
967
1639
|
&& providerOpenAiAuthDisabled;
|
|
968
|
-
const
|
|
969
|
-
|
|
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;
|
|
1640
|
+
const retiredCompatConfigured = desktopMode === 'desktop-dual-auth-compat';
|
|
1641
|
+
const providerContractOk = !retiredCompatConfigured && cliProviderContractOk;
|
|
979
1642
|
const providerUsesCodexLbEnvAuth = providerConfigured && providerEnvKey === CODEX_LB_PROVIDER_ENV_KEY && providerOpenAiAuthDisabled;
|
|
980
1643
|
const oauthAvailable = authMode.mode === 'chatgpt_oauth' || authMode.mode === 'browser_marker';
|
|
981
1644
|
const authRoutingCoherent = desktopMode === 'desktop-native-bridge'
|
|
@@ -985,8 +1648,8 @@ export async function codexLbStatus(opts = {}) {
|
|
|
985
1648
|
: !codexLbKeyInSharedAuth && (!selected || providerUsesCodexLbEnvAuth);
|
|
986
1649
|
const codexAppUsableWithCodexLb = desktopMode === 'desktop-native-bridge'
|
|
987
1650
|
? Boolean(bridgeBaseUrl && oauthAvailable)
|
|
988
|
-
:
|
|
989
|
-
?
|
|
1651
|
+
: retiredCompatConfigured
|
|
1652
|
+
? false
|
|
990
1653
|
: authMode.codex_app_usable;
|
|
991
1654
|
const fastMode = codexLbFastModeConfigStatus(config);
|
|
992
1655
|
const launchEnvironment = await inspectCodexLbMacLaunchEnvironment(baseUrl, opts).catch((err) => ({
|
|
@@ -999,7 +1662,14 @@ export async function codexLbStatus(opts = {}) {
|
|
|
999
1662
|
&& envLoad.configured
|
|
1000
1663
|
&& Boolean(baseUrl)
|
|
1001
1664
|
&& authRoutingCoherent
|
|
1665
|
+
&& !retiredCompatConfigured
|
|
1002
1666
|
&& (desktopMode === 'desktop-native-bridge' ? Boolean(bridgeBaseUrl) : true);
|
|
1667
|
+
const blockers = [
|
|
1668
|
+
...(retiredCompatConfigured ? ['desktop_dual_auth_compat_unavailable'] : []),
|
|
1669
|
+
...(!retiredCompatConfigured && providerConfigured && !providerContractOk
|
|
1670
|
+
? ['codex_lb_provider_contract_drift']
|
|
1671
|
+
: [])
|
|
1672
|
+
];
|
|
1003
1673
|
const probeToolOutputRecovery = opts.probeToolOutputRecovery === true;
|
|
1004
1674
|
const toolOutputRecovery = !selected
|
|
1005
1675
|
? codexLbToolOutputRecoveryNotSelected()
|
|
@@ -1014,6 +1684,7 @@ export async function codexLbStatus(opts = {}) {
|
|
|
1014
1684
|
});
|
|
1015
1685
|
return {
|
|
1016
1686
|
ok: providerReady && (!selected || !probeToolOutputRecovery || toolOutputRecovery.ok),
|
|
1687
|
+
blockers,
|
|
1017
1688
|
provider_ready: providerReady,
|
|
1018
1689
|
config_path: configPath,
|
|
1019
1690
|
env_path: envPath,
|
|
@@ -1114,8 +1785,8 @@ export function formatCodexLbStatusText(status = {}, opts = {}) {
|
|
|
1114
1785
|
lines.push('', 'Sign in with ChatGPT OAuth, then run: sks codex-lb use-desktop-full.');
|
|
1115
1786
|
else if (status.ok && status.desktop_mode === 'cli-provider' && !status.selected)
|
|
1116
1787
|
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.
|
|
1118
|
-
lines.push('', '
|
|
1788
|
+
else if (status.desktop_mode === 'desktop-dual-auth-compat')
|
|
1789
|
+
lines.push('', 'The retired compatibility route is blocked. Run `sks codex-lb use-desktop-full` or `sks codex-lb disable`.');
|
|
1119
1790
|
else if (status.ok)
|
|
1120
1791
|
lines.push('', 'Status: configured; no ordinary repair needed.');
|
|
1121
1792
|
else if (!status.ok && status.base_url && status.env_key_configured)
|
|
@@ -2027,8 +2698,15 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
2027
2698
|
console.log(`codex-lb credentials stored: ${configured.base_url}. Use \`sks codex-lb use-cli\`, \`use-desktop-full\`, or \`disable\` explicitly.`);
|
|
2028
2699
|
else
|
|
2029
2700
|
console.log('codex-lb setup skipped: API key was empty.');
|
|
2701
|
+
printCodexLbSetupWarnings(configured);
|
|
2030
2702
|
return configured;
|
|
2031
2703
|
}
|
|
2704
|
+
function scrubCodexLbToolEnvironment(env = process.env) {
|
|
2705
|
+
const sanitized = { ...env };
|
|
2706
|
+
delete sanitized.CODEX_LB_API_KEY;
|
|
2707
|
+
delete sanitized.OPENROUTER_API_KEY;
|
|
2708
|
+
return sanitized;
|
|
2709
|
+
}
|
|
2032
2710
|
async function syncCodexLbProviderEnvironment(status = {}, opts = {}) {
|
|
2033
2711
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
2034
2712
|
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
@@ -2056,10 +2734,16 @@ async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
|
|
|
2056
2734
|
return { ok: true, status: 'skipped', skipped: true, reason: 'SKS_SKIP_CODEX_LB_LAUNCH_ENV=1' };
|
|
2057
2735
|
if (process.platform !== 'darwin' && !opts.forceLaunchEnv)
|
|
2058
2736
|
return { ok: true, status: 'not_macos', skipped: true };
|
|
2059
|
-
const launchctl = opts.launchctlBin
|
|
2737
|
+
const launchctl = opts.launchctlBin
|
|
2738
|
+
|| await exists('/bin/launchctl').then((ok) => ok ? '/bin/launchctl' : null).catch(() => null);
|
|
2060
2739
|
if (!launchctl)
|
|
2061
|
-
return { ok: false, status: 'launchctl_missing', error: 'launchctl not found
|
|
2062
|
-
const
|
|
2740
|
+
return { ok: false, status: 'launchctl_missing', error: '/bin/launchctl not found' };
|
|
2741
|
+
const childEnv = scrubCodexLbToolEnvironment();
|
|
2742
|
+
const secretCleanup = await cleanupMacLaunchSecretEnvironment({
|
|
2743
|
+
force: opts.forceLaunchEnv === true,
|
|
2744
|
+
launchctlBin: launchctl,
|
|
2745
|
+
env: childEnv
|
|
2746
|
+
}).catch((err) => ({
|
|
2063
2747
|
ok: false,
|
|
2064
2748
|
status: 'partial',
|
|
2065
2749
|
variables: ['CODEX_LB_API_KEY', 'OPENROUTER_API_KEY'],
|
|
@@ -2070,7 +2754,12 @@ async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
|
|
|
2070
2754
|
const variables = Object.entries(values).filter(([key, value]) => value && !['CODEX_LB_API_KEY', 'OPENROUTER_API_KEY'].includes(String(key)));
|
|
2071
2755
|
const results = [];
|
|
2072
2756
|
for (const [key, value] of variables) {
|
|
2073
|
-
const result = await runProcess(launchctl, ['setenv', key, String(value)], {
|
|
2757
|
+
const result = await runProcess(launchctl, ['setenv', key, String(value)], {
|
|
2758
|
+
timeoutMs: 5000,
|
|
2759
|
+
maxOutputBytes: 8192,
|
|
2760
|
+
env: childEnv,
|
|
2761
|
+
envMode: 'replace'
|
|
2762
|
+
});
|
|
2074
2763
|
results.push({
|
|
2075
2764
|
key,
|
|
2076
2765
|
ok: result.code === 0,
|
|
@@ -2091,11 +2780,18 @@ async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
|
|
|
2091
2780
|
async function inspectCodexLbMacLaunchEnvironment(baseUrl = '', opts = {}) {
|
|
2092
2781
|
if (process.platform !== 'darwin' && !opts.forceLaunchEnv)
|
|
2093
2782
|
return { checked: false, status: 'not_macos', skipped: true };
|
|
2094
|
-
const launchctl = opts.launchctlBin
|
|
2783
|
+
const launchctl = opts.launchctlBin
|
|
2784
|
+
|| await exists('/bin/launchctl').then((ok) => ok ? '/bin/launchctl' : null).catch(() => null);
|
|
2095
2785
|
if (!launchctl)
|
|
2096
2786
|
return { checked: true, available: false, status: 'launchctl_missing' };
|
|
2787
|
+
const childEnv = scrubCodexLbToolEnvironment();
|
|
2097
2788
|
const readVar = async (key) => {
|
|
2098
|
-
const result = await runProcess(launchctl, ['getenv', key], {
|
|
2789
|
+
const result = await runProcess(launchctl, ['getenv', key], {
|
|
2790
|
+
timeoutMs: 3000,
|
|
2791
|
+
maxOutputBytes: 8192,
|
|
2792
|
+
env: childEnv,
|
|
2793
|
+
envMode: 'replace'
|
|
2794
|
+
});
|
|
2099
2795
|
return result.code === 0 ? String(result.stdout || '').trim() : '';
|
|
2100
2796
|
};
|
|
2101
2797
|
const [currentBaseUrl, currentApiKey, currentOpenRouterKey] = await Promise.all([
|