sneakoscope 6.0.3 → 6.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -12
- package/config/codex-releases/{rust-v0.142.0.json → rust-v0.144.1.json} +6 -6
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +18 -14
- package/dist/bin/sks-dispatch.js +2 -4
- package/dist/cli/args.js +2 -0
- package/dist/cli/command-manifest-lite.js +2 -3
- package/dist/cli/command-registry.js +3 -4
- package/dist/cli/help-fast.js +1 -1
- package/dist/cli/install-helpers.js +348 -763
- package/dist/cli/install-tool-helpers.js +287 -0
- package/dist/cli/router.js +91 -6
- package/dist/commands/codex-lb.js +188 -53
- package/dist/commands/codex.js +44 -5
- package/dist/commands/doctor.js +302 -46
- package/dist/commands/tmux.js +5 -1
- package/dist/commands/versioning.js +7 -0
- package/dist/commands/zellij-monitor-pane.js +2 -0
- package/dist/commands/zellij-viewport-pane.js +3 -1
- package/dist/commands/zellij.js +1 -1
- package/dist/config/skills-manifest.json +59 -59
- package/dist/core/agent-bridge/agent-manifest.js +48 -0
- package/dist/core/agents/agent-central-ledger.js +9 -0
- package/dist/core/agents/agent-cleanup-executor.js +334 -41
- package/dist/core/agents/agent-cleanup.js +20 -1
- package/dist/core/agents/agent-command-surface.js +3 -1
- package/dist/core/agents/agent-effort-policy.js +30 -38
- package/dist/core/agents/agent-janitor.js +9 -10
- package/dist/core/agents/agent-namespace-safety.js +56 -0
- package/dist/core/agents/agent-orchestrator.js +25 -3
- package/dist/core/agents/agent-output-validator.js +1 -1
- package/dist/core/agents/agent-plan.js +100 -21
- package/dist/core/agents/agent-role-config.js +43 -49
- package/dist/core/agents/agent-runner-codex-exec.js +18 -3
- package/dist/core/agents/agent-runner-process.js +1 -0
- package/dist/core/agents/agent-schema.js +3 -2
- package/dist/core/agents/fast-mode-policy.js +3 -5
- package/dist/core/agents/native-cli-session-swarm.js +1 -1
- package/dist/core/agents/native-worker-backend-router.js +2 -1
- package/dist/core/agents/work-partition/repo-inventory.js +68 -7
- package/dist/core/agents/zellij-right-lane-cockpit.js +2 -2
- package/dist/core/code-structure.js +5 -4
- package/dist/core/codex/agent-config-file-repair.js +94 -193
- package/dist/core/codex/codex-cli-update.js +723 -0
- package/dist/core/codex/codex-config-guard.js +38 -11
- package/dist/core/codex/codex-project-config-policy.js +3 -6
- package/dist/core/codex/codex-startup-config-postcheck.js +52 -42
- package/dist/core/codex-adapter.js +35 -22
- package/dist/core/codex-app/codex-agent-role-sync.js +13 -54
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +51 -20
- package/dist/core/codex-app/codex-app-launcher.js +48 -17
- package/dist/core/codex-app/codex-app-restart.js +23 -2
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +11 -3
- package/dist/core/codex-app/sks-menubar.js +136 -5
- package/dist/core/codex-app.js +35 -14
- package/dist/core/codex-compat/codex-hook-warning-detector.js +10 -1
- package/dist/core/codex-compat/codex-release-manifest.js +6 -6
- package/dist/core/codex-control/codex-0139-doctor-real-probe.js +7 -1
- package/dist/core/codex-control/codex-0139-image-path-real-probe.js +29 -12
- package/dist/core/codex-control/codex-0139-probe-runner.js +103 -28
- package/dist/core/codex-control/codex-0139-real-probe-summary.js +7 -3
- package/dist/core/codex-control/codex-0139-real-probes.js +21 -6
- package/dist/core/codex-control/codex-0139-sandbox-real-probe.js +39 -13
- package/dist/core/codex-control/codex-0139-web-search-probe.js +28 -14
- package/dist/core/codex-control/{codex-0142-capability.js → codex-0144-capability.js} +22 -22
- package/dist/core/codex-control/codex-0144-collab-agent-real-probe.js +63 -0
- package/dist/core/codex-control/codex-lb-launch-recovery.js +278 -0
- package/dist/core/codex-control/codex-multi-agent-event-normalizer.js +5 -1
- package/dist/core/codex-control/codex-reliability-shield.js +93 -30
- package/dist/core/codex-control/codex-sdk-adapter.js +96 -13
- package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
- package/dist/core/codex-control/codex-sdk-sandbox-policy.js +6 -3
- package/dist/core/codex-control/codex-task-runner.js +62 -9
- package/dist/core/codex-control/python-codex-sdk-adapter.js +30 -3
- package/dist/core/codex-exec-output-schema.js +35 -6
- package/dist/core/codex-lb/codex-lb-env.js +30 -1
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +478 -0
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +291 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +31 -38
- package/dist/core/codex-native/codex-native-feature-matrix.js +2 -2
- package/dist/core/codex-native/codex-native-repair-transaction.js +1 -1
- package/dist/core/codex-native/core-skill-manifest.js +8 -4
- package/dist/core/codex-native/native-capability-postcheck.js +16 -8
- package/dist/core/codex-native/native-capability-repair-matrix.js +76 -27
- package/dist/core/codex-plugins/codex-plugin-json.js +67 -21
- package/dist/core/codex-plugins/codex-plugin-repair.js +118 -0
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +305 -0
- package/dist/core/commands/agent-bridge-command.js +23 -2
- package/dist/core/commands/agent-command.js +38 -5
- package/dist/core/commands/basic-cli.js +77 -30
- package/dist/core/commands/command-utils.js +1 -1
- package/dist/core/commands/gc-command.js +27 -4
- package/dist/core/commands/image-ux-review-command.js +55 -24
- package/dist/core/commands/mad-sks-command.js +182 -137
- package/dist/core/commands/naruto-command.js +643 -1153
- package/dist/core/commands/pipeline-command.js +1 -0
- package/dist/core/commands/research-command.js +293 -231
- package/dist/core/commands/run-command.js +100 -23
- package/dist/core/commands/team-command.js +2 -3
- package/dist/core/commands/team-legacy-observe-command.js +94 -359
- package/dist/core/commands/wiki-command.js +179 -83
- package/dist/core/computer-use-live-evidence.js +166 -3
- package/dist/core/computer-use-status.js +5 -2
- package/dist/core/daemon/sksd-hook-daemon-entrypoint.js +2 -2
- package/dist/core/daemon/sksd-hook-daemon.js +171 -28
- package/dist/core/daemon/sksd-hook-dispatch.js +2 -2
- package/dist/core/db-safety.js +2 -2
- package/dist/core/decision-lattice.js +6 -6
- package/dist/core/dfix/verification-selector.js +6 -4
- package/dist/core/doctor/browser-use-repair.js +54 -13
- package/dist/core/doctor/codex-0139-doctor.js +3 -2
- package/dist/core/doctor/codex-startup-config-repair.js +8 -3
- package/dist/core/doctor/computer-use-repair.js +28 -39
- package/dist/core/doctor/doctor-codex-startup-repair.js +24 -149
- package/dist/core/doctor/doctor-context7-repair.js +1 -1
- package/dist/core/doctor/doctor-dirty-planner.js +1 -1
- package/dist/core/doctor/doctor-native-capability-repair.js +74 -3
- package/dist/core/doctor/imagegen-repair.js +47 -12
- package/dist/core/doctor/legacy-global-hook-cleanup.js +179 -0
- package/dist/core/feature-fixture-executor.js +71 -7
- package/dist/core/feature-fixture-runner.js +53 -12
- package/dist/core/feature-fixtures.js +47 -14
- package/dist/core/feature-registry.js +53 -58
- package/dist/core/fsx.js +54 -6
- package/dist/core/harness-conflicts.js +19 -10
- package/dist/core/hooks-runtime/code-pack-freshness-preflight.js +5 -4
- package/dist/core/hooks-runtime/hook-invocation-dedupe.js +147 -0
- package/dist/core/hooks-runtime/hook-io.js +14 -4
- package/dist/core/hooks-runtime/light-turn.js +70 -0
- package/dist/core/hooks-runtime/payload-signals.js +270 -0
- package/dist/core/hooks-runtime/team-digest.js +0 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +93 -0
- package/dist/core/hooks-runtime.js +418 -250
- package/dist/core/image/image-artifact-path-contract.js +8 -6
- package/dist/core/image/image-artifact-registry.js +5 -1
- package/dist/core/image-ux-review/imagegen-adapter.js +5 -2
- package/dist/core/imagegen/imagegen-capability.js +10 -13
- package/dist/core/imagegen/require-imagegen.js +37 -11
- package/dist/core/init/skills.js +16 -30
- package/dist/core/init.js +144 -36
- package/dist/core/managed-assets/managed-assets-manifest.js +417 -14
- package/dist/core/mission.js +24 -1
- package/dist/core/naruto/naruto-work-graph.js +70 -4
- package/dist/core/ops/reporting.js +3 -0
- package/dist/core/pipeline-internals/runtime-core.js +578 -180
- package/dist/core/pipeline-internals/runtime-gates.js +191 -49
- package/dist/core/ppt/style-tokens.js +1 -1
- package/dist/core/ppt-review/index.js +1 -1
- package/dist/core/ppt-review/slide-exporter.js +23 -17
- package/dist/core/ppt-review/slide-imagegen-review.js +1 -1
- package/dist/core/ppt-review/slide-issue-extraction.js +4 -2
- package/dist/core/preflight/parallel-preflight-engine.js +50 -1
- package/dist/core/product-design-plugin.js +2 -2
- package/dist/core/proof/route-finalizer.js +1 -1
- package/dist/core/proof/route-proof-gate.js +7 -2
- package/dist/core/proof-field.js +3 -3
- package/dist/core/provider/model-router.js +38 -32
- package/dist/core/recallpulse/policy.js +12 -28
- package/dist/core/recallpulse.js +11 -6
- package/dist/core/release/gate-manifest.js +18 -12
- package/dist/core/release/gate-pack-manifest.js +1 -1
- package/dist/core/release/npm-pack-proof.js +247 -0
- package/dist/core/release/package-dist-snapshot.js +151 -0
- package/dist/core/release/package-size-budget.js +5 -0
- package/dist/core/release/release-authorization-snapshot.js +115 -0
- package/dist/core/release/release-gate-affected-selector.js +15 -3
- package/dist/core/release/release-gate-cache-v2.js +30 -0
- package/dist/core/release/release-gate-contract.js +161 -0
- package/dist/core/release/release-gate-dag.js +44 -3
- package/dist/core/release/release-gate-hermetic-env.js +28 -17
- package/dist/core/release/release-real-contract.js +496 -0
- package/dist/core/release-parallel-full-coverage.js +31 -143
- package/dist/core/research/claim-evidence-matrix.js +41 -6
- package/dist/core/research/experiment-plan.js +14 -10
- package/dist/core/research/falsification.js +9 -2
- package/dist/core/research/implementation-blueprint-densifier.js +82 -60
- package/dist/core/research/implementation-blueprint.js +32 -26
- package/dist/core/research/mock-result.js +122 -11
- package/dist/core/research/replication-pack.js +15 -8
- package/dist/core/research/research-adversarial-review.js +1068 -0
- package/dist/core/research/research-claim-builder.js +69 -17
- package/dist/core/research/research-claim-synthesizer.js +343 -0
- package/dist/core/research/research-cycle-runner.js +53 -3
- package/dist/core/research/research-falsification-runner.js +176 -0
- package/dist/core/research/research-final-reviewer.js +44 -125
- package/dist/core/research/research-realistic-report.js +14 -6
- package/dist/core/research/research-review-artifact-digest.js +66 -0
- package/dist/core/research/research-source-evidence.js +144 -0
- package/dist/core/research/research-source-layer-catalog.js +68 -0
- package/dist/core/research/research-source-ledger-merge.js +250 -12
- package/dist/core/research/research-source-shards.js +26 -70
- package/dist/core/research/research-stage-runner.js +237 -247
- package/dist/core/research/research-super-search.js +184 -0
- package/dist/core/research/research-synthesis-prompt.js +20 -1
- package/dist/core/research/research-synthesis-writer.js +86 -5
- package/dist/core/research/research-work-graph.js +25 -18
- package/dist/core/research/source-quality-report.js +21 -0
- package/dist/core/research.js +429 -280
- package/dist/core/retention/retention-budget.js +25 -6
- package/dist/core/retention.js +980 -101
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/dollar-manifest-lite.js +11 -11
- package/dist/core/routes/evidence.js +4 -4
- package/dist/core/routes/ppt-policy.js +1 -1
- package/dist/core/routes.js +92 -64
- package/dist/core/runtime/task-profile.js +66 -0
- package/dist/core/runtime/verification-budget.js +12 -0
- package/dist/core/secret-redaction.js +4 -0
- package/dist/core/security/high-risk-contracts.js +6 -6
- package/dist/core/stop-gate/stop-gate-check.js +49 -12
- package/dist/core/subagents/agent-catalog.js +215 -0
- package/dist/core/subagents/model-policy.js +118 -0
- package/dist/core/subagents/naruto-help-contract.js +40 -0
- package/dist/core/subagents/official-subagent-config.js +488 -0
- package/dist/core/subagents/official-subagent-preparation.js +251 -0
- package/dist/core/subagents/official-subagent-prompt.js +137 -0
- package/dist/core/subagents/official-subagent-runner.js +129 -0
- package/dist/core/subagents/subagent-evidence.js +663 -0
- package/dist/core/subagents/terminology.js +16 -0
- package/dist/core/subagents/thread-budget.js +21 -0
- package/dist/core/subagents/triwiki-attention.js +65 -0
- package/dist/core/super-search/runtime-helpers.js +173 -19
- package/dist/core/super-search/runtime.js +140 -15
- package/dist/core/super-search/source-records.js +49 -8
- package/dist/core/team-review-policy.js +15 -0
- package/dist/core/triwiki/code-pack.js +8 -2
- package/dist/core/triwiki/triwiki-cache-key.js +108 -29
- package/dist/core/triwiki/triwiki-module-card.js +1 -1
- package/dist/core/triwiki-attention.js +13 -6
- package/dist/core/triwiki-provenance.js +274 -0
- package/dist/core/triwiki-runtime.js +21 -7
- package/dist/core/triwiki-wrongness/wrongness-ledger.js +187 -61
- package/dist/core/triwiki-wrongness/wrongness-retrieval.js +11 -2
- package/dist/core/triwiki-wrongness/wrongness-trust-policy.js +2 -0
- package/dist/core/update-check.js +23 -15
- package/dist/core/version-manager.js +51 -1
- package/dist/core/version.js +1 -1
- package/dist/core/wiki-coordinate.js +128 -12
- package/dist/core/work-order-ledger.js +208 -10
- package/dist/core/zellij/zellij-command.js +12 -1
- package/dist/core/zellij/zellij-dashboard-pane.js +3 -3
- package/dist/core/zellij/zellij-fake-adapter.js +3 -1
- package/dist/core/zellij/zellij-launcher.js +51 -21
- package/dist/core/zellij/zellij-layout-builder.js +14 -5
- package/dist/core/zellij/zellij-official-subagent-activity.js +477 -0
- package/dist/core/zellij/zellij-official-subagent-telemetry.js +264 -0
- package/dist/core/zellij/zellij-pane-proof.js +32 -6
- package/dist/core/zellij/zellij-slot-pane-renderer.js +17 -5
- package/dist/core/zellij/zellij-slot-telemetry.js +15 -3
- package/dist/core/zellij/zellij-ui-mode.js +1 -1
- package/dist/core/zellij/zellij-viewport-binder.js +3 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +5 -3
- package/dist/scripts/agent-fast-mode-default-check.js +48 -29
- package/dist/scripts/agent-role-config-repair-check.js +34 -15
- package/dist/scripts/agent-wiki-context-proof-check.js +16 -2
- package/dist/scripts/all-feature-deep-completion-check.js +14 -4
- package/dist/scripts/architecture-guard-check.js +8 -1
- package/dist/scripts/build-dist.js +17 -5
- package/dist/scripts/canonical-test-runner.js +188 -0
- package/dist/scripts/check-command-module-budget.js +38 -16
- package/dist/scripts/check-legacy-free.js +1 -1
- package/dist/scripts/check-route-modularity.js +0 -1
- package/dist/scripts/check-ts-contracts.js +1 -1
- package/dist/scripts/codex-0139-interrupt-agent-check.js +5 -1
- package/dist/scripts/{codex-0142-app-server-v2-check.js → codex-0144-app-server-v2-check.js} +11 -10
- package/dist/scripts/{codex-0142-binary-identity-check.js → codex-0144-binary-identity-check.js} +4 -4
- package/dist/scripts/codex-0144-capability-check.js +21 -0
- package/dist/scripts/{codex-0139-real-probes-check.js → codex-0144-core-real-probes-check.js} +19 -5
- package/dist/scripts/{codex-0142-doctor-wiring-check.js → codex-0144-doctor-wiring-check.js} +6 -6
- package/dist/scripts/{codex-0142-manifest-check.js → codex-0144-manifest-check.js} +11 -3
- package/dist/scripts/{codex-0142-policy-check.js → codex-0144-policy-check.js} +2 -2
- package/dist/scripts/{codex-0142-thread-store-check.js → codex-0144-thread-store-check.js} +3 -3
- package/dist/scripts/codex-app-provider-model-ui-check.js +53 -19
- package/dist/scripts/codex-app-ui-preservation-check.js +35 -4
- package/dist/scripts/codex-control-all-pipelines-check.js +7 -3
- package/dist/scripts/codex-control-side-effect-scope-check.js +2 -2
- package/dist/scripts/codex-control-tool-output-continuity-audit-check.js +27 -0
- package/dist/scripts/codex-effort-auto-discovery-check.js +1 -1
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +1 -0
- package/dist/scripts/{codex-lb-gpt55-fast-profile-check.js → codex-lb-gpt56-fast-profile-check.js} +56 -5
- package/dist/scripts/codex-model-metadata-check.js +1 -1
- package/dist/scripts/codex-native-agent-role-content-check.js +15 -7
- package/dist/scripts/codex-native-repair-transaction-check.js +3 -1
- package/dist/scripts/codex-sdk-all-pipelines-check.js +7 -4
- package/dist/scripts/codex-sdk-research-pipeline-check.js +30 -8
- package/dist/scripts/codex-sdk-sandbox-policy-check.js +5 -1
- package/dist/scripts/codex-sdk-version-compat-check.js +2 -1
- package/dist/scripts/db-route-materialization-check.js +33 -0
- package/dist/scripts/dfix-fixture-check.js +1 -1
- package/dist/scripts/docs-truthfulness-check.js +4 -2
- package/dist/scripts/doctor-codex-startup-repair-check.js +47 -25
- package/dist/scripts/doctor-context7-repair-check.js +2 -1
- package/dist/scripts/doctor-fix-production-blackbox.js +12 -2
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +34 -7
- package/dist/scripts/doctor-imagegen-repair-check.js +14 -4
- package/dist/scripts/doctor-startup-config-repair-blackbox.js +9 -3
- package/dist/scripts/doctor-startup-config-repair-check.js +13 -3
- package/dist/scripts/install-update-preserves-config-check.js +7 -2
- package/dist/scripts/legacy-gate-inventory-check.js +7 -5
- package/dist/scripts/legacy-strong-inventory-check.js +7 -5
- package/dist/scripts/legacy-upgrade-matrix-check.js +4 -4
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +173 -13
- package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +165 -6
- package/dist/scripts/managed-role-manifest-parity-check.js +5 -4
- package/dist/scripts/mutation-callsite-coverage-check.js +0 -11
- package/dist/scripts/naruto-codex-e2e-check.js +14 -7
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +4 -201
- package/dist/scripts/npm-publish-performance-check.js +20 -12
- package/dist/scripts/official-subagent-workflow-check.js +145 -0
- package/dist/scripts/package-published-contract-check.js +6 -20
- package/dist/scripts/packlist-performance-check.js +17 -3
- package/dist/scripts/parallel-verification-engine-check.js +2 -2
- package/dist/scripts/postinstall-safe-side-effects-check.js +8 -3
- package/dist/scripts/ppt-no-mock-as-real-check.js +3 -1
- package/dist/scripts/prepublish-release-check-or-fast.js +23 -86
- package/dist/scripts/product-design-plugin-routing-check.js +1 -1
- package/dist/scripts/release-affected-selector-check.js +74 -1
- package/dist/scripts/release-check-stamp.js +324 -239
- package/dist/scripts/release-dag-full-coverage-check.js +7 -15
- package/dist/scripts/release-dynamic-presets-check.js +10 -1
- package/dist/scripts/release-gate-dag-runner-check.js +3 -3
- package/dist/scripts/release-gate-dag-runner.js +35 -11
- package/dist/scripts/release-gate-existence-audit.js +1 -2
- package/dist/scripts/release-gate-script-parity-check.js +1 -1
- package/dist/scripts/release-parallel-full-coverage-check.js +12 -6
- package/dist/scripts/release-parallel-speed-budget-check.js +18 -2
- package/dist/scripts/release-real-check.js +390 -157
- package/dist/scripts/release-registry-check.js +61 -16
- package/dist/scripts/release-runtime-truth-matrix-check.js +22 -0
- package/dist/scripts/research-blueprint-densifier-check.js +2 -2
- package/dist/scripts/research-real-cycle-no-legacy-final-md-check.js +11 -7
- package/dist/scripts/route-proof-artifact-structure-check.js +1 -0
- package/dist/scripts/search-visibility-gate-lib.js +1 -1
- package/dist/scripts/seo-geo-route-identity-check.js +30 -4
- package/dist/scripts/sizecheck.js +4 -4
- package/dist/scripts/sks-1-11-gate-lib.js +53 -6
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +8 -2
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +3 -2
- package/dist/scripts/sks-3-1-6-directive-check-lib.js +5 -3
- package/dist/scripts/sks-menubar-install-check.js +8 -0
- package/dist/scripts/sks-uninstall-regression-check.js +1 -1
- package/dist/scripts/sksd-daemon-check.js +16 -6
- package/dist/scripts/trust-fixture-check.js +32 -10
- package/dist/scripts/write-build-manifest.js +2 -1
- package/dist/scripts/wrongness-fixture-check.js +1 -1
- package/dist/scripts/zellij-layout-valid-check.js +36 -11
- package/dist/scripts/zellij-pane-proof-check.js +33 -2
- package/dist/scripts/zellij-real-session-cleanup-check.js +122 -2
- package/dist/scripts/zellij-real-session-launch-check.js +76 -5
- package/dist/scripts/zellij-slot-pane-renderer-check.js +4 -4
- package/dist/scripts/zellij-spawn-on-demand-layout-check.js +3 -3
- package/docs/demo.tape +1 -1
- package/infra-harness-gates.json +1486 -0
- package/package.json +32 -19
- package/release-gates.v2.json +4060 -0
- package/runtime-required-scripts.json +9 -0
- package/schemas/codex/{app-server-0.142 → app-server-0.144}/codex_app_server_protocol.v2.schemas.json +1146 -781
- package/schemas/codex/codex-0139-real-probe-result.schema.json +39 -5
- package/schemas/codex/ppt-slide-issue-ledger.schema.json +2 -1
- package/dist/commands/db.js +0 -6
- package/dist/core/codex-control/codex-0139-multi-agent-real-probe.js +0 -107
- package/dist/core/commands/db-command.js +0 -146
- package/dist/core/research/prompt.js +0 -15
- package/dist/scripts/agent-native-release-gate.js +0 -274
- package/dist/scripts/codex-0142-capability-check.js +0 -21
- package/dist/scripts/codex-control-tool-call-sequence-repair-check.js +0 -14
- package/dist/scripts/command-performance-scorecard-check.js +0 -204
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +0 -212
- package/dist/scripts/dollar-performance-scorecard-check.js +0 -257
- package/dist/scripts/gate-timing-check.js +0 -16
- package/dist/scripts/ops-maturity-scorecard-check.js +0 -12
- package/dist/scripts/performance-baseline-check.js +0 -12
- package/dist/scripts/performance-improvement-report-check.js +0 -58
- package/dist/scripts/release-parallel-check.js +0 -389
- package/dist/scripts/release-runner-efficiency-check.js +0 -15
- package/dist/scripts/retention-long-run-smoke-check.js +0 -299
- package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
|
@@ -16,25 +16,21 @@ import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
|
|
|
16
16
|
import { cleanupMacLaunchSecretEnvironment } from '../core/codex-app/sks-menubar.js';
|
|
17
17
|
import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
|
|
18
18
|
import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
|
|
19
|
+
import { codexLbToolCatalogPath, ensureCodexLbToolCatalog } from '../core/codex-lb/codex-lb-tool-catalog.js';
|
|
20
|
+
import { codexLbToolOutputRecoveryNotChecked, codexLbToolOutputRecoveryNotSelected, codexLbToolOutputRecoveryOverrideAcknowledged, probeCodexLbToolOutputRecovery } from '../core/codex-lb/codex-lb-tool-output-recovery.js';
|
|
19
21
|
import { GLM_CODEX_CONFIG_PROFILE_ID, GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from '../core/providers/glm/glm-52-profile.js';
|
|
20
22
|
import { GLM_52_OPENROUTER_MODEL } from '../core/providers/glm/glm-52-settings.js';
|
|
21
23
|
import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
|
|
22
24
|
import { extractTomlTable, writeCodexConfigGuarded } from '../core/codex/codex-config-guard.js';
|
|
23
|
-
import {
|
|
25
|
+
import { ensureGlobalCodexFastModeDuringInstall, ensureTrailingNewline, normalizeCodexFastModeUiConfig, removeTopLevelTomlKeyIfValue, safeWriteCodexConfigToml, upsertTopLevelTomlString, upsertTomlTable } from '../core/codex-runtime/codex-desktop-config-policy.js';
|
|
24
26
|
import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
|
|
25
27
|
import { repairCodexImagegen } from '../core/doctor/imagegen-repair.js';
|
|
28
|
+
import { canAskYesNo, compareVersions, hasCodexUnstableFeatureWarningSuppression, hasDeprecatedCodexHooksFeatureFlag, hasTopLevelCodexModeLock, isProjectSetupCandidate } from './install-tool-helpers.js';
|
|
29
|
+
export { checkContext7, ensureCodexCliTool, ensureMadLaunchDependencies, ensureRelatedCliTools, ensureZellijCliTool, formatMadLaunchDependencyAction, maybePromptCodexUpdateForLaunch, maybePromptSksUpdateForLaunch, shouldAutoApproveInstall } from './install-tool-helpers.js';
|
|
30
|
+
export { codexFastModeDesktopStatus, ensureGlobalCodexFastModeDuringInstall, normalizeCodexFastModeUiConfig, safeWriteCodexConfigToml } from '../core/codex-runtime/codex-desktop-config-policy.js';
|
|
26
31
|
const CODEX_LB_PROVIDER_NAME = 'openai';
|
|
27
32
|
const CODEX_LB_PROVIDER_ENV_KEY = 'CODEX_LB_API_KEY';
|
|
28
33
|
const CODEX_LB_CANONICAL_FAST_SERVICE_TIER = 'priority';
|
|
29
|
-
const DEFAULT_CODEX_APP_PLUGINS = [
|
|
30
|
-
['browser', 'openai-bundled'],
|
|
31
|
-
['chrome', 'openai-bundled'],
|
|
32
|
-
['computer-use', 'openai-bundled'],
|
|
33
|
-
['latex', 'openai-bundled'],
|
|
34
|
-
['documents', 'openai-primary-runtime'],
|
|
35
|
-
['presentations', 'openai-primary-runtime'],
|
|
36
|
-
['spreadsheets', 'openai-primary-runtime']
|
|
37
|
-
];
|
|
38
34
|
function packagedSksEntrypoint() {
|
|
39
35
|
return path.join(packageRoot(), 'dist', 'bin', 'sks.js');
|
|
40
36
|
}
|
|
@@ -112,7 +108,7 @@ export async function postinstall({ bootstrap, args = [] }) {
|
|
|
112
108
|
if (imagegenRepair.status === 'ready')
|
|
113
109
|
console.log('Codex App Image Gen: ready ($imagegen/gpt-image-2 detected).');
|
|
114
110
|
else if (imagegenRepair.status === 'recovered')
|
|
115
|
-
console.log('Codex App Image Gen: recovered and re-detected.');
|
|
111
|
+
console.log('Codex App Image Gen: recovered and re-detected. Start a new Codex/Work task; restart the desktop app only if the new task still lacks $imagegen.');
|
|
116
112
|
else if (imagegenRepair.status === 'blocked')
|
|
117
113
|
console.log(`Codex App Image Gen: blocked; run \`sks doctor --fix\`. ${(imagegenRepair.blockers || []).join(', ')}`.trim());
|
|
118
114
|
else if (imagegenRepair.status === 'skipped')
|
|
@@ -395,7 +391,7 @@ async function restorePostinstallCodexLbConfigSnapshot(snapshot) {
|
|
|
395
391
|
const currentAuthText = currentAuthExists ? await readText(snapshot.auth_path, '') : '';
|
|
396
392
|
if (!currentAuthExists || !currentAuthText.trim()) {
|
|
397
393
|
await ensureDir(path.dirname(snapshot.auth_path));
|
|
398
|
-
await writeTextAtomic(snapshot.auth_path, snapshot.auth_text);
|
|
394
|
+
await writeTextAtomic(snapshot.auth_path, snapshot.auth_text, { mode: 0o600 });
|
|
399
395
|
await fsp.chmod(snapshot.auth_path, 0o600).catch(() => { });
|
|
400
396
|
authRestored = true;
|
|
401
397
|
}
|
|
@@ -417,6 +413,73 @@ export function normalizeCodexLbBaseUrl(input = '') {
|
|
|
417
413
|
host = host.replace(/\/+$/, '');
|
|
418
414
|
return /\/backend-api\/codex$/i.test(host) ? host : `${host}/backend-api/codex`;
|
|
419
415
|
}
|
|
416
|
+
async function ensureCodexLbToolCatalogSelection(input, opts = {}) {
|
|
417
|
+
const codexHome = opts.codexHome || path.join(input.home, '.codex');
|
|
418
|
+
const catalogPath = codexLbToolCatalogPath(codexHome);
|
|
419
|
+
const current = await readText(input.configPath, '');
|
|
420
|
+
const selected = hasTopLevelCodexLbSelected(current);
|
|
421
|
+
const existingCatalogPath = topLevelTomlString(current, 'model_catalog_json');
|
|
422
|
+
if (selected && existingCatalogPath && path.resolve(existingCatalogPath) !== path.resolve(catalogPath)) {
|
|
423
|
+
return {
|
|
424
|
+
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
425
|
+
ok: false,
|
|
426
|
+
required: true,
|
|
427
|
+
status: 'user_catalog_conflict',
|
|
428
|
+
path: catalogPath,
|
|
429
|
+
configured_path: existingCatalogPath,
|
|
430
|
+
config_changed: false,
|
|
431
|
+
blockers: ['codex_lb_user_model_catalog_conflict']
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
let hostname = '';
|
|
435
|
+
try {
|
|
436
|
+
hostname = new URL(input.baseUrl).hostname.toLowerCase();
|
|
437
|
+
}
|
|
438
|
+
catch { }
|
|
439
|
+
const reservedFixtureHost = /(?:^|\.)(?:test|invalid|example)$/.test(hostname);
|
|
440
|
+
if (reservedFixtureHost && typeof opts.toolCatalogFetch !== 'function') {
|
|
441
|
+
return {
|
|
442
|
+
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
443
|
+
ok: true,
|
|
444
|
+
required: false,
|
|
445
|
+
status: 'skipped_reserved_host',
|
|
446
|
+
path: catalogPath,
|
|
447
|
+
config_changed: false,
|
|
448
|
+
blockers: []
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const catalog = await ensureCodexLbToolCatalog({
|
|
452
|
+
codexHome,
|
|
453
|
+
baseUrl: input.baseUrl,
|
|
454
|
+
apiKey: input.apiKey,
|
|
455
|
+
...(typeof opts.toolCatalogFetch === 'function' ? { fetchImpl: opts.toolCatalogFetch } : {}),
|
|
456
|
+
timeoutMs: Number(opts.toolCatalogTimeoutMs || 5000),
|
|
457
|
+
force: opts.forceToolCatalog === true
|
|
458
|
+
});
|
|
459
|
+
if (!catalog.ok || !selected) {
|
|
460
|
+
return {
|
|
461
|
+
...catalog,
|
|
462
|
+
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
463
|
+
config_changed: false,
|
|
464
|
+
selected
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const next = ensureTrailingNewline(upsertTopLevelTomlString(current, 'model_catalog_json', catalog.path));
|
|
468
|
+
if (next === ensureTrailingNewline(current)) {
|
|
469
|
+
return { ...catalog, schema: 'sks.codex-lb-tool-catalog-selection.v1', config_changed: false, selected: true };
|
|
470
|
+
}
|
|
471
|
+
const safeWrite = await safeWriteCodexConfigToml(input.configPath, current, next, 'codex-lb-tool-catalog');
|
|
472
|
+
return {
|
|
473
|
+
...catalog,
|
|
474
|
+
schema: 'sks.codex-lb-tool-catalog-selection.v1',
|
|
475
|
+
ok: catalog.ok && safeWrite.ok,
|
|
476
|
+
status: safeWrite.ok ? catalog.status : safeWrite.status,
|
|
477
|
+
config_changed: safeWrite.ok && safeWrite.changed === true,
|
|
478
|
+
backup_path: safeWrite.backup_path,
|
|
479
|
+
selected: true,
|
|
480
|
+
blockers: safeWrite.ok ? catalog.blockers : [...new Set([...(catalog.blockers || []), 'codex_lb_tool_catalog_config_write_failed'])]
|
|
481
|
+
};
|
|
482
|
+
}
|
|
420
483
|
export async function configureCodexLb(opts = {}) {
|
|
421
484
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
422
485
|
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
@@ -455,6 +518,26 @@ export async function configureCodexLb(opts = {}) {
|
|
|
455
518
|
return { ok: false, status: 'invalid_host_or_base_url', config_path: configPath, env_path: envPath, error: 'host_or_base_url_contains_whitespace_or_control_character' };
|
|
456
519
|
if (!apiKey)
|
|
457
520
|
return { ok: false, status: 'missing_api_key', config_path: configPath, env_path: envPath };
|
|
521
|
+
const toolOutputRecovery = await probeCodexLbToolOutputRecovery({
|
|
522
|
+
baseUrl,
|
|
523
|
+
...(typeof opts.toolOutputRecoveryFetch === 'function' ? { fetchImpl: opts.toolOutputRecoveryFetch } : {}),
|
|
524
|
+
timeoutMs: Number(opts.toolOutputRecoveryTimeoutMs || 4_000),
|
|
525
|
+
allowUnverified: opts.allowUnverifiedToolOutputRecovery === true
|
|
526
|
+
|| codexLbToolOutputRecoveryOverrideAcknowledged({ env: opts.env || process.env })
|
|
527
|
+
});
|
|
528
|
+
if (!toolOutputRecovery.ok) {
|
|
529
|
+
return {
|
|
530
|
+
ok: false,
|
|
531
|
+
status: 'tool_output_recovery_blocked',
|
|
532
|
+
plan: plan,
|
|
533
|
+
config_path: configPath,
|
|
534
|
+
env_path: envPath,
|
|
535
|
+
base_url: baseUrl,
|
|
536
|
+
tool_output_recovery: toolOutputRecovery,
|
|
537
|
+
drift: toolOutputRecovery.blockers,
|
|
538
|
+
warnings: toolOutputRecovery.warnings
|
|
539
|
+
};
|
|
540
|
+
}
|
|
458
541
|
const insecureLocalWarning = /^http:\/\//i.test(baseUrl) && !/^http:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/i.test(baseUrl) && !opts.allowInsecureHttp
|
|
459
542
|
? ['codex-lb base URL uses http outside localhost; prefer https or pass an explicit allow flag in the calling surface.']
|
|
460
543
|
: [];
|
|
@@ -472,12 +555,16 @@ export async function configureCodexLb(opts = {}) {
|
|
|
472
555
|
if (useDefaultProvider)
|
|
473
556
|
appliedActions.push({ type: 'select_default_provider', target: configPath, ok: true });
|
|
474
557
|
if (writeEnvFile) {
|
|
475
|
-
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n
|
|
558
|
+
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`, { mode: 0o600 });
|
|
476
559
|
await fsp.chmod(envPath, 0o600).catch(() => { });
|
|
477
560
|
appliedActions.push({ type: 'write_env_file', target: envPath, ok: true });
|
|
478
561
|
}
|
|
479
562
|
process.env.CODEX_LB_BASE_URL = baseUrl;
|
|
480
563
|
process.env.CODEX_LB_API_KEY = apiKey;
|
|
564
|
+
const toolCatalog = await ensureCodexLbToolCatalogSelection({ home, configPath, baseUrl, apiKey }, opts);
|
|
565
|
+
if (toolCatalog.config_changed || toolCatalog.status === 'repaired' || toolCatalog.status === 'cached_compatible') {
|
|
566
|
+
appliedActions.push({ type: 'write_model_tool_catalog', target: toolCatalog.path, ok: toolCatalog.ok === true, status: toolCatalog.status });
|
|
567
|
+
}
|
|
481
568
|
const keyFingerprint = await sha256Text(apiKey);
|
|
482
569
|
const metadataPath = opts.metadataPath || codexLbMetadataPath(home);
|
|
483
570
|
await writeTextAtomic(metadataPath, `${JSON.stringify({
|
|
@@ -507,20 +594,23 @@ export async function configureCodexLb(opts = {}) {
|
|
|
507
594
|
const finalCodexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
|
|
508
595
|
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
509
596
|
const afterState = await captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile });
|
|
510
|
-
const drift =
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
597
|
+
const drift = [
|
|
598
|
+
...detectCodexLbSetupDrift({
|
|
599
|
+
useDefaultProvider,
|
|
600
|
+
writeEnvFile,
|
|
601
|
+
storeKeychain,
|
|
602
|
+
syncLaunchctl,
|
|
603
|
+
shellProfile,
|
|
604
|
+
selected: finalCodexLb.selected,
|
|
605
|
+
envFile: finalCodexLb.env_file,
|
|
606
|
+
keychain,
|
|
607
|
+
codexEnvironment,
|
|
608
|
+
shellProfileResult,
|
|
609
|
+
beforeState,
|
|
610
|
+
afterState
|
|
611
|
+
}),
|
|
612
|
+
...(toolCatalog.required !== false && toolCatalog.ok !== true ? ['codex_lb_gpt56_tool_catalog_not_ready'] : [])
|
|
613
|
+
];
|
|
524
614
|
const appliedPersistenceModes = appliedCodexLbPersistenceModes({
|
|
525
615
|
writeEnvFile,
|
|
526
616
|
storeKeychain,
|
|
@@ -537,7 +627,11 @@ export async function configureCodexLb(opts = {}) {
|
|
|
537
627
|
appliedModes: appliedPersistenceModes,
|
|
538
628
|
processOnly: appliedPersistenceModes.includes('process_only_ephemeral')
|
|
539
629
|
});
|
|
540
|
-
const warnings = [
|
|
630
|
+
const warnings = [
|
|
631
|
+
...insecureLocalWarning,
|
|
632
|
+
...persistence.warnings,
|
|
633
|
+
...(toolCatalog.required !== false && toolCatalog.ok !== true ? ['codex_lb_gpt56_tool_catalog_not_ready'] : [])
|
|
634
|
+
];
|
|
541
635
|
return {
|
|
542
636
|
ok: ok && drift.length === 0,
|
|
543
637
|
status: ok && drift.length === 0 ? 'configured' : drift.length ? 'setup_choice_drift' : (codexEnvironment.status || codexLogin.status),
|
|
@@ -556,6 +650,8 @@ export async function configureCodexLb(opts = {}) {
|
|
|
556
650
|
codex_lb: finalCodexLb,
|
|
557
651
|
codex_environment: codexEnvironment,
|
|
558
652
|
codex_login: codexLogin,
|
|
653
|
+
tool_catalog: toolCatalog,
|
|
654
|
+
tool_output_recovery: toolOutputRecovery,
|
|
559
655
|
error: authReconcile.error || codexEnvironment.error || codexLogin.error || null
|
|
560
656
|
};
|
|
561
657
|
}
|
|
@@ -595,8 +691,22 @@ export async function codexLbStatus(opts = {}) {
|
|
|
595
691
|
status: 'inspect_failed',
|
|
596
692
|
error: err.message
|
|
597
693
|
}));
|
|
694
|
+
const providerReady = providerContractOk && envKeyConfigured && Boolean(baseUrl) && authMode.codex_app_usable;
|
|
695
|
+
const probeToolOutputRecovery = opts.probeToolOutputRecovery === true;
|
|
696
|
+
const toolOutputRecovery = !selected
|
|
697
|
+
? codexLbToolOutputRecoveryNotSelected()
|
|
698
|
+
: !probeToolOutputRecovery
|
|
699
|
+
? codexLbToolOutputRecoveryNotChecked(true)
|
|
700
|
+
: await probeCodexLbToolOutputRecovery({
|
|
701
|
+
baseUrl,
|
|
702
|
+
...(typeof opts.toolOutputRecoveryFetch === 'function' ? { fetchImpl: opts.toolOutputRecoveryFetch } : {}),
|
|
703
|
+
timeoutMs: Number(opts.toolOutputRecoveryTimeoutMs || 4_000),
|
|
704
|
+
allowUnverified: opts.allowUnverifiedToolOutputRecovery === true
|
|
705
|
+
|| codexLbToolOutputRecoveryOverrideAcknowledged({ env: opts.env || process.env })
|
|
706
|
+
});
|
|
598
707
|
return {
|
|
599
|
-
ok:
|
|
708
|
+
ok: providerReady && (!selected || !probeToolOutputRecovery || toolOutputRecovery.ok),
|
|
709
|
+
provider_ready: providerReady,
|
|
600
710
|
config_path: configPath,
|
|
601
711
|
env_path: envPath,
|
|
602
712
|
provider_configured: providerConfigured,
|
|
@@ -627,7 +737,8 @@ export async function codexLbStatus(opts = {}) {
|
|
|
627
737
|
auth_usable_for_codex_app: authMode.codex_app_usable || codexAppUsableWithCodexLb,
|
|
628
738
|
auth_summary: codexAppUsableWithCodexLb ? `codex-lb provider uses ${authMode.mode} OpenAI-style auth through Codex App` : authMode.summary,
|
|
629
739
|
fast_mode: fastMode,
|
|
630
|
-
launch_environment: launchEnvironment
|
|
740
|
+
launch_environment: launchEnvironment,
|
|
741
|
+
tool_output_recovery: toolOutputRecovery
|
|
631
742
|
};
|
|
632
743
|
}
|
|
633
744
|
export function formatCodexLbStatusText(status = {}, opts = {}) {
|
|
@@ -642,6 +753,14 @@ export function formatCodexLbStatusText(status = {}, opts = {}) {
|
|
|
642
753
|
`Provider OpenAI Auth: ${status.provider_requires_openai_auth ? 'required' : 'not required/drifted'} (${status.provider_name || 'missing'})`,
|
|
643
754
|
`Codex App auth: ${status.auth_usable_for_codex_app ? 'ok' : 'needs sign-in/repair'} (${status.auth_mode || 'unknown'})`
|
|
644
755
|
];
|
|
756
|
+
if (status.tool_output_recovery?.status && status.tool_output_recovery.status !== 'not_selected') {
|
|
757
|
+
const recovery = status.tool_output_recovery;
|
|
758
|
+
lines.push(`Interrupted tool-output recovery: ${recovery.ok ? 'ready' : 'blocked'} (${recovery.observed_version || recovery.status}; minimum ${recovery.minimum_version})`);
|
|
759
|
+
if (!recovery.ok) {
|
|
760
|
+
for (const action of recovery.operator_actions || [])
|
|
761
|
+
lines.push(` action: ${action}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
645
764
|
if (status.auth_summary)
|
|
646
765
|
lines.push(`Auth detail: ${status.auth_summary}`);
|
|
647
766
|
if (status.fast_mode) {
|
|
@@ -1016,17 +1135,39 @@ function tomlTableString(text = '', table, key) {
|
|
|
1016
1135
|
return block.match(new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]+)"\\s*(?:#.*)?(?=\\n|$)`))?.[2] || '';
|
|
1017
1136
|
}
|
|
1018
1137
|
export async function repairCodexLbAuth(opts = {}) {
|
|
1019
|
-
let status = await codexLbStatus(
|
|
1138
|
+
let status = await codexLbStatus({
|
|
1139
|
+
...opts,
|
|
1140
|
+
probeToolOutputRecovery: true,
|
|
1141
|
+
allowUnverifiedToolOutputRecovery: opts.allowUnverifiedToolOutputRecovery === true
|
|
1142
|
+
});
|
|
1020
1143
|
let configRepaired = false;
|
|
1021
1144
|
let legacyAuthMigrated = false;
|
|
1022
1145
|
let legacyAuthPath = null;
|
|
1146
|
+
const toolOutputRecovery = status.base_url
|
|
1147
|
+
? await probeCodexLbToolOutputRecovery({
|
|
1148
|
+
baseUrl: status.base_url,
|
|
1149
|
+
...(typeof opts.toolOutputRecoveryFetch === 'function' ? { fetchImpl: opts.toolOutputRecoveryFetch } : {}),
|
|
1150
|
+
timeoutMs: Number(opts.toolOutputRecoveryTimeoutMs || 4_000),
|
|
1151
|
+
allowUnverified: opts.allowUnverifiedToolOutputRecovery === true
|
|
1152
|
+
|| codexLbToolOutputRecoveryOverrideAcknowledged({ env: opts.env || process.env })
|
|
1153
|
+
})
|
|
1154
|
+
: status.tool_output_recovery;
|
|
1155
|
+
if (status.base_url && toolOutputRecovery?.ok !== true) {
|
|
1156
|
+
return {
|
|
1157
|
+
ok: false,
|
|
1158
|
+
status: 'tool_output_recovery_blocked',
|
|
1159
|
+
codex_lb: status,
|
|
1160
|
+
tool_output_recovery: toolOutputRecovery,
|
|
1161
|
+
error: toolOutputRecovery?.blockers?.join(', ') || 'codex-lb interrupted tool-output recovery is unverified'
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1023
1164
|
const currentConfig = await readText(status.config_path, '');
|
|
1024
1165
|
if (!status.env_key_configured && status.base_url && (status.provider_configured || status.selected || status.env_base_url_configured)) {
|
|
1025
1166
|
const legacyAuth = await restoreCodexLbEnvFromSharedLogin(status, opts);
|
|
1026
1167
|
if (legacyAuth.ok) {
|
|
1027
1168
|
legacyAuthMigrated = true;
|
|
1028
1169
|
legacyAuthPath = legacyAuth.auth_path;
|
|
1029
|
-
status = await codexLbStatus(opts);
|
|
1170
|
+
status = await codexLbStatus({ ...opts, probeToolOutputRecovery: true });
|
|
1030
1171
|
}
|
|
1031
1172
|
}
|
|
1032
1173
|
if (status.env_key_configured && status.base_url && (!status.provider_contract_ok || !status.selected || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig) || (opts.forceCodexLbApiKeyAuth === true && !status.ok))) {
|
|
@@ -1037,7 +1178,7 @@ export async function repairCodexLbAuth(opts = {}) {
|
|
|
1037
1178
|
});
|
|
1038
1179
|
const safeWrite = await safeWriteCodexConfigToml(status.config_path, currentConfig, next, 'codex-lb-repair');
|
|
1039
1180
|
configRepaired = safeWrite.ok && safeWrite.changed === true;
|
|
1040
|
-
status = await codexLbStatus(opts);
|
|
1181
|
+
status = await codexLbStatus({ ...opts, probeToolOutputRecovery: true });
|
|
1041
1182
|
}
|
|
1042
1183
|
const canRepairAuthMode = opts.forceCodexLbApiKeyAuth === true && status.provider_contract_ok && status.env_key_configured && Boolean(status.base_url);
|
|
1043
1184
|
if (!status.ok && !canRepairAuthMode) {
|
|
@@ -1052,13 +1193,19 @@ export async function repairCodexLbAuth(opts = {}) {
|
|
|
1052
1193
|
await migrateCodexAuthKeyFormat({ home: opts.home });
|
|
1053
1194
|
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
1054
1195
|
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
1196
|
+
const toolCatalog = await ensureCodexLbToolCatalogSelection({
|
|
1197
|
+
home: opts.home || process.env.HOME || os.homedir(),
|
|
1198
|
+
configPath: status.config_path,
|
|
1199
|
+
baseUrl: String(status.base_url || ''),
|
|
1200
|
+
apiKey
|
|
1201
|
+
}, opts);
|
|
1055
1202
|
const forceCodexLbApiKeyAuth = opts.forceCodexLbApiKeyAuth === true || opts.authMode === 'codex-lb';
|
|
1056
1203
|
const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, status, forceCodexLbApiKeyAuth }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
|
|
1057
1204
|
const codexLogin = forceCodexLbApiKeyAuth
|
|
1058
1205
|
? { ok: ['apikey_forced', 'apikey_auth_active'].includes(authReconcile.status), status: authReconcile.status, ...(authReconcile.reason ? { reason: authReconcile.reason } : {}), error: authReconcile.error || null }
|
|
1059
1206
|
: await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
|
|
1060
|
-
const finalStatus = await codexLbStatus(opts);
|
|
1061
|
-
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
1207
|
+
const finalStatus = await codexLbStatus({ ...opts, probeToolOutputRecovery: true });
|
|
1208
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok && (toolCatalog.required === false || toolCatalog.ok === true));
|
|
1062
1209
|
return {
|
|
1063
1210
|
ok,
|
|
1064
1211
|
status: ok ? 'repaired' : (codexEnvironment.status || codexLogin.status),
|
|
@@ -1071,7 +1218,8 @@ export async function repairCodexLbAuth(opts = {}) {
|
|
|
1071
1218
|
auth_reconcile: authReconcile,
|
|
1072
1219
|
codex_lb: finalStatus,
|
|
1073
1220
|
codex_environment: codexEnvironment,
|
|
1074
|
-
codex_login: codexLogin
|
|
1221
|
+
codex_login: codexLogin,
|
|
1222
|
+
tool_catalog: toolCatalog
|
|
1075
1223
|
};
|
|
1076
1224
|
}
|
|
1077
1225
|
export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
@@ -1101,10 +1249,16 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
|
1101
1249
|
}
|
|
1102
1250
|
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
1103
1251
|
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
1252
|
+
const toolCatalog = await ensureCodexLbToolCatalogSelection({
|
|
1253
|
+
home: opts.home || process.env.HOME || os.homedir(),
|
|
1254
|
+
configPath: status.config_path,
|
|
1255
|
+
baseUrl: String(status.base_url || ''),
|
|
1256
|
+
apiKey
|
|
1257
|
+
}, opts);
|
|
1104
1258
|
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
|
|
1105
1259
|
const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, status }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
|
|
1106
1260
|
const finalStatus = await codexLbStatus(opts);
|
|
1107
|
-
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
1261
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok && (toolCatalog.required === false || toolCatalog.ok === true));
|
|
1108
1262
|
return {
|
|
1109
1263
|
ok,
|
|
1110
1264
|
status: ok ? 'present' : (codexEnvironment.status || codexLogin.status),
|
|
@@ -1114,6 +1268,7 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
|
1114
1268
|
codex_lb: finalStatus,
|
|
1115
1269
|
codex_environment: codexEnvironment,
|
|
1116
1270
|
codex_login: codexLogin,
|
|
1271
|
+
tool_catalog: toolCatalog,
|
|
1117
1272
|
auth_reconcile: authReconcile,
|
|
1118
1273
|
error: codexEnvironment.error || codexLogin.error || null
|
|
1119
1274
|
};
|
|
@@ -1130,7 +1285,7 @@ async function restoreCodexLbEnvFromSharedLogin(status = {}, opts = {}) {
|
|
|
1130
1285
|
if (!baseUrl)
|
|
1131
1286
|
return { ok: false, status: 'missing_base_url', auth_path: authPath, env_path: envPath };
|
|
1132
1287
|
await ensureDir(path.dirname(envPath));
|
|
1133
|
-
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(normalizeCodexLbBaseUrl(baseUrl))}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n
|
|
1288
|
+
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(normalizeCodexLbBaseUrl(baseUrl))}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`, { mode: 0o600 });
|
|
1134
1289
|
await fsp.chmod(envPath, 0o600).catch(() => { });
|
|
1135
1290
|
return { ok: true, status: 'migrated_login_cache', auth_path: authPath, env_path: envPath, base_url: normalizeCodexLbBaseUrl(baseUrl) };
|
|
1136
1291
|
}
|
|
@@ -1203,7 +1358,7 @@ async function migrateCodexAuthKeyFormat(opts = {}) {
|
|
|
1203
1358
|
if (!legacyKey)
|
|
1204
1359
|
return { status: 'skipped', reason: 'no_key_found' };
|
|
1205
1360
|
const replacement = `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: legacyKey })}\n`;
|
|
1206
|
-
await writeTextAtomic(authPath, replacement);
|
|
1361
|
+
await writeTextAtomic(authPath, replacement, { mode: 0o600 });
|
|
1207
1362
|
await fsp.chmod(authPath, 0o600).catch(() => { });
|
|
1208
1363
|
return { status: 'migrated', auth_path: authPath };
|
|
1209
1364
|
}
|
|
@@ -1234,7 +1389,7 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
|
|
|
1234
1389
|
const forceCodexLbApiKeyAuth = opts.forceCodexLbApiKeyAuth === true;
|
|
1235
1390
|
const writeApiKeyAuth = async (reason, backupPathForResult = null) => {
|
|
1236
1391
|
try {
|
|
1237
|
-
await writeTextAtomic(authPath, `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: apiKey }, null, 2)}\n
|
|
1392
|
+
await writeTextAtomic(authPath, `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: apiKey }, null, 2)}\n`, { mode: 0o600 });
|
|
1238
1393
|
await fsp.chmod(authPath, 0o600).catch(() => { });
|
|
1239
1394
|
return {
|
|
1240
1395
|
status: 'apikey_forced',
|
|
@@ -1260,7 +1415,7 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
|
|
|
1260
1415
|
if (hasChatgptOAuthTokens(authText)) {
|
|
1261
1416
|
try {
|
|
1262
1417
|
await ensureDir(path.dirname(backupPath));
|
|
1263
|
-
await writeTextAtomic(backupPath, authText);
|
|
1418
|
+
await writeTextAtomic(backupPath, authText, { mode: 0o600 });
|
|
1264
1419
|
await fsp.chmod(backupPath, 0o600).catch(() => { });
|
|
1265
1420
|
}
|
|
1266
1421
|
catch (err) {
|
|
@@ -1286,7 +1441,7 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
|
|
|
1286
1441
|
}
|
|
1287
1442
|
try {
|
|
1288
1443
|
const replacement = `${JSON.stringify({ auth_mode: 'apikey', OPENAI_API_KEY: apiKey }, null, 2)}\n`;
|
|
1289
|
-
await writeTextAtomic(authPath, replacement);
|
|
1444
|
+
await writeTextAtomic(authPath, replacement, { mode: 0o600 });
|
|
1290
1445
|
await fsp.chmod(authPath, 0o600).catch(() => { });
|
|
1291
1446
|
}
|
|
1292
1447
|
catch (err) {
|
|
@@ -1316,7 +1471,7 @@ export async function reconcileCodexLbAuthConflict(opts = {}) {
|
|
|
1316
1471
|
if (hasChatgptOAuthTokens(backupText) && process.env.SKS_CODEX_LB_KEEP_APIKEY_AUTH !== '1') {
|
|
1317
1472
|
try {
|
|
1318
1473
|
const restored = backupText.endsWith('\n') ? backupText : `${backupText}\n`;
|
|
1319
|
-
await writeTextAtomic(authPath, restored);
|
|
1474
|
+
await writeTextAtomic(authPath, restored, { mode: 0o600 });
|
|
1320
1475
|
await fsp.chmod(authPath, 0o600).catch(() => { });
|
|
1321
1476
|
return {
|
|
1322
1477
|
status: 'oauth_restored',
|
|
@@ -1369,18 +1524,46 @@ export async function unselectCodexLbProvider(opts = {}) {
|
|
|
1369
1524
|
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
1370
1525
|
const current = await readText(configPath, '');
|
|
1371
1526
|
if (!current.trim())
|
|
1372
|
-
return { status: 'not_selected', reason: 'no_config', config_path: configPath };
|
|
1373
|
-
|
|
1374
|
-
|
|
1527
|
+
return { ok: true, status: 'not_selected', reason: 'no_config', config_path: configPath };
|
|
1528
|
+
const managedCatalogPath = codexLbToolCatalogPath(opts.codexHome || path.join(home, '.codex'));
|
|
1529
|
+
const managedCatalogSelected = topLevelTomlString(current, 'model_catalog_json') === managedCatalogPath;
|
|
1530
|
+
if (!hasTopLevelCodexLbSelected(current) && !managedCatalogSelected)
|
|
1531
|
+
return { ok: true, status: 'not_selected', config_path: configPath };
|
|
1375
1532
|
try {
|
|
1376
|
-
|
|
1533
|
+
let next = removeTopLevelTomlString(current, 'model_provider');
|
|
1534
|
+
next = removeTopLevelTomlKeyIfValue(next, 'model_catalog_json', managedCatalogPath);
|
|
1535
|
+
next = ensureTrailingNewline(next);
|
|
1377
1536
|
const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'codex-lb-unselect');
|
|
1378
|
-
|
|
1537
|
+
const after = safeWrite.ok ? await readText(configPath, '') : current;
|
|
1538
|
+
const selectionRemoved = !hasTopLevelCodexLbSelected(after)
|
|
1539
|
+
&& topLevelTomlString(after, 'model_catalog_json') !== managedCatalogPath;
|
|
1540
|
+
if (safeWrite.ok && selectionRemoved)
|
|
1541
|
+
return { ok: true, status: 'unselected', config_path: configPath, backup_path: safeWrite.backup_path };
|
|
1542
|
+
const providerError = safeWrite.ok ? 'provider_selection_remains_after_write' : safeWrite.status || 'provider_config_write_blocked';
|
|
1543
|
+
return {
|
|
1544
|
+
ok: false,
|
|
1545
|
+
status: 'failed',
|
|
1546
|
+
reason: 'provider_config_write_blocked',
|
|
1547
|
+
provider_error: providerError,
|
|
1548
|
+
write_status: safeWrite.status || 'failed',
|
|
1549
|
+
config_path: configPath,
|
|
1550
|
+
backup_path: safeWrite.backup_path,
|
|
1551
|
+
config_preserved: safeWrite.changed !== true
|
|
1552
|
+
};
|
|
1379
1553
|
}
|
|
1380
1554
|
catch (err) {
|
|
1381
|
-
return { status: 'failed', reason: 'write_failed', config_path: configPath, error: err.message };
|
|
1555
|
+
return { ok: false, status: 'failed', reason: 'write_failed', provider_error: err.message || 'write_failed', config_path: configPath, error: err.message };
|
|
1382
1556
|
}
|
|
1383
1557
|
}
|
|
1558
|
+
function providerDeselectionOutcome(result) {
|
|
1559
|
+
const ok = result?.status === 'unselected' || result?.status === 'not_selected';
|
|
1560
|
+
return {
|
|
1561
|
+
ok,
|
|
1562
|
+
provider_unselected: ok,
|
|
1563
|
+
provider_status: result?.status || 'failed',
|
|
1564
|
+
provider_error: ok ? null : String(result?.provider_error || result?.error || result?.reason || result?.status || 'unselect_failed')
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1384
1567
|
// Reverse of reconcileCodexLbAuthConflict: restore the ChatGPT OAuth blob from the backup file
|
|
1385
1568
|
// so the user can return to the official ChatGPT account login. Also deselects codex-lb at the
|
|
1386
1569
|
// model_provider level by default so the restored OAuth blob actually wins; pass keepProvider
|
|
@@ -1397,6 +1580,7 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1397
1580
|
const authPath = opts.authPath || codexAuthPath(home);
|
|
1398
1581
|
const backupPath = opts.backupPath || codexAuthChatgptBackupPath(home);
|
|
1399
1582
|
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
1583
|
+
const authExisted = await exists(authPath);
|
|
1400
1584
|
const currentAuthText = await readText(authPath, '');
|
|
1401
1585
|
const trimmedCurrent = currentAuthText.trim();
|
|
1402
1586
|
// Repeated "Use ChatGPT OAuth" is idempotent. If OAuth/browser auth is
|
|
@@ -1404,21 +1588,20 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1404
1588
|
// codex-lb provider is no longer selected.
|
|
1405
1589
|
const currentAuthMode = codexAuthModeSummary(currentAuthText);
|
|
1406
1590
|
if (!opts.force && (currentAuthMode.mode === 'chatgpt_oauth' || currentAuthMode.mode === 'browser_marker')) {
|
|
1407
|
-
let
|
|
1408
|
-
let providerError = null;
|
|
1591
|
+
let provider = { ok: true, provider_unselected: false, provider_status: 'kept', provider_error: null };
|
|
1409
1592
|
if (!opts.keepProvider) {
|
|
1410
1593
|
const unselected = await unselectCodexLbProvider({ ...opts, home, configPath });
|
|
1411
|
-
|
|
1412
|
-
providerUnselected = true;
|
|
1413
|
-
else if (unselected.status === 'failed')
|
|
1414
|
-
providerError = unselected.error || unselected.reason || 'unselect_failed';
|
|
1594
|
+
provider = providerDeselectionOutcome(unselected);
|
|
1415
1595
|
}
|
|
1416
1596
|
return {
|
|
1417
|
-
|
|
1597
|
+
ok: provider.ok,
|
|
1598
|
+
status: provider.ok ? 'already_chatgpt' : 'failed',
|
|
1599
|
+
...(provider.ok ? {} : { reason: 'provider_unselect_failed' }),
|
|
1418
1600
|
auth_path: authPath,
|
|
1419
1601
|
backup_path: backupPath,
|
|
1420
|
-
provider_unselected:
|
|
1421
|
-
|
|
1602
|
+
provider_unselected: provider.provider_unselected,
|
|
1603
|
+
provider_status: provider.provider_status,
|
|
1604
|
+
provider_error: provider.provider_error
|
|
1422
1605
|
};
|
|
1423
1606
|
}
|
|
1424
1607
|
const backupExists = await exists(backupPath);
|
|
@@ -1443,21 +1626,20 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1443
1626
|
// If auth.json already looks like ChatGPT OAuth (user re-logged in some other way), don't
|
|
1444
1627
|
// clobber it — but still honor the deselect request so the OAuth blob takes effect.
|
|
1445
1628
|
if (trimmedCurrent && hasChatgptOAuthTokens(currentAuthText) && !opts.force) {
|
|
1446
|
-
let
|
|
1447
|
-
let providerError = null;
|
|
1629
|
+
let provider = { ok: true, provider_unselected: false, provider_status: 'kept', provider_error: null };
|
|
1448
1630
|
if (!opts.keepProvider) {
|
|
1449
1631
|
const unselected = await unselectCodexLbProvider({ ...opts, home, configPath });
|
|
1450
|
-
|
|
1451
|
-
providerUnselected = true;
|
|
1452
|
-
else if (unselected.status === 'failed')
|
|
1453
|
-
providerError = unselected.error || unselected.reason || 'unselect_failed';
|
|
1632
|
+
provider = providerDeselectionOutcome(unselected);
|
|
1454
1633
|
}
|
|
1455
1634
|
return {
|
|
1456
|
-
|
|
1635
|
+
ok: provider.ok,
|
|
1636
|
+
status: provider.ok ? 'already_chatgpt' : 'failed',
|
|
1637
|
+
...(provider.ok ? {} : { reason: 'provider_unselect_failed' }),
|
|
1457
1638
|
auth_path: authPath,
|
|
1458
1639
|
backup_path: backupPath,
|
|
1459
|
-
provider_unselected:
|
|
1460
|
-
|
|
1640
|
+
provider_unselected: provider.provider_unselected,
|
|
1641
|
+
provider_status: provider.provider_status,
|
|
1642
|
+
provider_error: provider.provider_error
|
|
1461
1643
|
};
|
|
1462
1644
|
}
|
|
1463
1645
|
// Refuse to clobber unfamiliar auth.json shapes unless forced. We expect either an empty file,
|
|
@@ -1478,7 +1660,7 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1478
1660
|
try {
|
|
1479
1661
|
await ensureDir(path.dirname(authPath));
|
|
1480
1662
|
const restored = backupText.endsWith('\n') ? backupText : `${backupText}\n`;
|
|
1481
|
-
await writeTextAtomic(authPath, restored);
|
|
1663
|
+
await writeTextAtomic(authPath, restored, { mode: 0o600 });
|
|
1482
1664
|
await fsp.chmod(authPath, 0o600).catch(() => { });
|
|
1483
1665
|
}
|
|
1484
1666
|
catch (err) {
|
|
@@ -1491,6 +1673,28 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1491
1673
|
provider_unselected: false
|
|
1492
1674
|
};
|
|
1493
1675
|
}
|
|
1676
|
+
let provider = { ok: true, provider_unselected: false, provider_status: 'kept', provider_error: null };
|
|
1677
|
+
if (!opts.keepProvider) {
|
|
1678
|
+
const unselected = await unselectCodexLbProvider({ ...opts, home, configPath });
|
|
1679
|
+
provider = providerDeselectionOutcome(unselected);
|
|
1680
|
+
}
|
|
1681
|
+
if (!provider.ok) {
|
|
1682
|
+
const rollback = await rollbackCodexAuthRestore({ authPath, authExisted, currentAuthText });
|
|
1683
|
+
return {
|
|
1684
|
+
ok: false,
|
|
1685
|
+
status: 'failed',
|
|
1686
|
+
reason: 'provider_unselect_failed',
|
|
1687
|
+
auth_path: authPath,
|
|
1688
|
+
backup_path: backupPath,
|
|
1689
|
+
backup_removed: false,
|
|
1690
|
+
auth_restored: rollback.ok !== true,
|
|
1691
|
+
auth_rollback: rollback,
|
|
1692
|
+
rollback_safe: rollback.ok === true,
|
|
1693
|
+
provider_unselected: false,
|
|
1694
|
+
provider_status: provider.provider_status,
|
|
1695
|
+
provider_error: provider.provider_error
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1494
1698
|
let backupRemoved = false;
|
|
1495
1699
|
if (opts.deleteBackup) {
|
|
1496
1700
|
try {
|
|
@@ -1498,31 +1702,57 @@ export async function releaseCodexLbAuthHold(opts = {}) {
|
|
|
1498
1702
|
backupRemoved = true;
|
|
1499
1703
|
}
|
|
1500
1704
|
catch {
|
|
1501
|
-
// Non-fatal: the restore already landed.
|
|
1705
|
+
// Non-fatal: the restore and provider deselection already landed.
|
|
1502
1706
|
}
|
|
1503
1707
|
}
|
|
1504
|
-
let providerUnselected = false;
|
|
1505
|
-
let providerError = null;
|
|
1506
|
-
if (!opts.keepProvider) {
|
|
1507
|
-
const unselected = await unselectCodexLbProvider({ ...opts, home, configPath });
|
|
1508
|
-
if (unselected.status === 'unselected')
|
|
1509
|
-
providerUnselected = true;
|
|
1510
|
-
else if (unselected.status === 'failed')
|
|
1511
|
-
providerError = unselected.error || unselected.reason || 'unselect_failed';
|
|
1512
|
-
}
|
|
1513
1708
|
return {
|
|
1709
|
+
ok: true,
|
|
1514
1710
|
status: 'released',
|
|
1515
1711
|
auth_path: authPath,
|
|
1516
1712
|
backup_path: backupPath,
|
|
1517
1713
|
backup_removed: backupRemoved,
|
|
1518
|
-
|
|
1519
|
-
|
|
1714
|
+
auth_restored: true,
|
|
1715
|
+
provider_unselected: provider.provider_unselected,
|
|
1716
|
+
provider_status: provider.provider_status,
|
|
1717
|
+
provider_error: provider.provider_error
|
|
1520
1718
|
};
|
|
1521
1719
|
}
|
|
1720
|
+
async function rollbackCodexAuthRestore(input) {
|
|
1721
|
+
try {
|
|
1722
|
+
if (input.authExisted) {
|
|
1723
|
+
await writeTextAtomic(input.authPath, input.currentAuthText, { mode: 0o600 });
|
|
1724
|
+
await fsp.chmod(input.authPath, 0o600).catch(() => { });
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
await fsp.rm(input.authPath, { force: true });
|
|
1728
|
+
}
|
|
1729
|
+
return { ok: true, status: 'restored_previous_auth' };
|
|
1730
|
+
}
|
|
1731
|
+
catch (err) {
|
|
1732
|
+
return { ok: false, status: 'rollback_failed', error: err.message };
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1522
1735
|
export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
1523
1736
|
if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1')
|
|
1524
1737
|
return { status: 'skipped' };
|
|
1525
|
-
|
|
1738
|
+
const allowUnverifiedToolOutputRecovery = opts.allowUnverifiedToolOutputRecovery === true
|
|
1739
|
+
|| codexLbToolOutputRecoveryOverrideAcknowledged({ args, env: opts.env || process.env });
|
|
1740
|
+
let status = await codexLbStatus({
|
|
1741
|
+
...opts,
|
|
1742
|
+
probeToolOutputRecovery: true,
|
|
1743
|
+
allowUnverifiedToolOutputRecovery
|
|
1744
|
+
});
|
|
1745
|
+
if (status.selected && status.tool_output_recovery?.ok !== true) {
|
|
1746
|
+
return {
|
|
1747
|
+
status: 'tool_output_recovery_blocked',
|
|
1748
|
+
ok: false,
|
|
1749
|
+
codex_lb: status,
|
|
1750
|
+
tool_output_recovery: status.tool_output_recovery,
|
|
1751
|
+
blockers: status.tool_output_recovery.blockers,
|
|
1752
|
+
operator_actions: status.tool_output_recovery.operator_actions,
|
|
1753
|
+
bypass_codex_lb: false
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1526
1756
|
if (status.env_key_configured && status.base_url && !status.selected && status.auth_mode === 'chatgpt_oauth') {
|
|
1527
1757
|
return { status: 'continued_to_codex', ok: false, chain_health: null, codex_lb: status, reason: 'chatgpt_oauth_active_codex_lb_unselected' };
|
|
1528
1758
|
}
|
|
@@ -1534,8 +1764,20 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
1534
1764
|
if (/^(n|no|아니|아니요|ㄴ)$/i.test(restore))
|
|
1535
1765
|
return { status: 'continued_to_codex', codex_lb: status };
|
|
1536
1766
|
}
|
|
1537
|
-
const repaired = await repairCodexLbAuth(opts);
|
|
1538
|
-
status = await codexLbStatus(opts);
|
|
1767
|
+
const repaired = await repairCodexLbAuth({ ...opts, allowUnverifiedToolOutputRecovery });
|
|
1768
|
+
status = await codexLbStatus({ ...opts, probeToolOutputRecovery: true, allowUnverifiedToolOutputRecovery });
|
|
1769
|
+
if (status.selected && status.tool_output_recovery?.ok !== true) {
|
|
1770
|
+
return {
|
|
1771
|
+
status: 'tool_output_recovery_blocked',
|
|
1772
|
+
ok: false,
|
|
1773
|
+
repair: repaired,
|
|
1774
|
+
codex_lb: status,
|
|
1775
|
+
tool_output_recovery: status.tool_output_recovery,
|
|
1776
|
+
blockers: status.tool_output_recovery.blockers,
|
|
1777
|
+
operator_actions: status.tool_output_recovery.operator_actions,
|
|
1778
|
+
bypass_codex_lb: false
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1539
1781
|
if (!status.ok)
|
|
1540
1782
|
return { status: 'repair_failed', ok: false, repair: repaired, codex_lb: status };
|
|
1541
1783
|
if (!repaired.ok && repaired.error && promptedRestore)
|
|
@@ -1550,6 +1792,15 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
1550
1792
|
if (codexEnvironment.status === 'synced')
|
|
1551
1793
|
console.log('codex-lb provider auth synced for this user session.');
|
|
1552
1794
|
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
1795
|
+
const toolCatalog = await ensureCodexLbToolCatalogSelection({
|
|
1796
|
+
home: opts.home || process.env.HOME || os.homedir(),
|
|
1797
|
+
configPath: status.config_path,
|
|
1798
|
+
baseUrl: String(status.base_url || ''),
|
|
1799
|
+
apiKey
|
|
1800
|
+
}, opts);
|
|
1801
|
+
if (toolCatalog.required !== false && toolCatalog.ok !== true) {
|
|
1802
|
+
return { status: 'repair_failed', ok: false, codex_lb: status, codex_environment: codexEnvironment, tool_catalog: toolCatalog };
|
|
1803
|
+
}
|
|
1553
1804
|
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir() });
|
|
1554
1805
|
if (codexLogin.status === 'synced')
|
|
1555
1806
|
console.log('codex-lb auth synced with Codex CLI login cache.');
|
|
@@ -1560,7 +1811,7 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
1560
1811
|
// health probe fails. Keep codex-lb active and just warn.
|
|
1561
1812
|
if (chainHealth.status === 'previous_response_not_found') {
|
|
1562
1813
|
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.');
|
|
1563
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth };
|
|
1814
|
+
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1564
1815
|
}
|
|
1565
1816
|
// Hard chain failure (auth rejected, timeout, missing base URL, etc.). Don't silently
|
|
1566
1817
|
// demote a configured codex-lb to ChatGPT OAuth — surface the failure and let the user
|
|
@@ -1568,23 +1819,23 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
1568
1819
|
console.log(`codex-lb response chain check failed (${chainHealth.status}${chainHealth.error ? `: ${chainHealth.error}` : ''}).`);
|
|
1569
1820
|
if (process.env.SKS_CODEX_LB_AUTOBYPASS === '1') {
|
|
1570
1821
|
console.log('SKS_CODEX_LB_AUTOBYPASS=1 set; bypassing codex-lb to ChatGPT OAuth for this launch.');
|
|
1571
|
-
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth, bypass_codex_lb: true };
|
|
1822
|
+
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth, bypass_codex_lb: true };
|
|
1572
1823
|
}
|
|
1573
1824
|
if (canAskYesNo()) {
|
|
1574
1825
|
const answer = (await askPostinstallQuestion('Use codex-lb anyway, or fall back to ChatGPT OAuth? [LB/oauth] ')).trim().toLowerCase();
|
|
1575
1826
|
if (/^(oauth|o|chatgpt|fall ?back|n|no|아니|아니요|ㄴ)$/.test(answer)) {
|
|
1576
1827
|
console.log('Falling back to ChatGPT OAuth for this launch. Re-enable codex-lb anytime with `sks codex-lb repair`.');
|
|
1577
|
-
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth, bypass_codex_lb: true };
|
|
1828
|
+
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth, bypass_codex_lb: true };
|
|
1578
1829
|
}
|
|
1579
1830
|
console.log('Keeping codex-lb active. To switch back to ChatGPT OAuth: `sks codex-lb release`.');
|
|
1580
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth };
|
|
1831
|
+
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1581
1832
|
}
|
|
1582
1833
|
// Non-interactive context with no opt-out env var. The user explicitly configured codex-lb,
|
|
1583
1834
|
// so default to keeping it active rather than silently swapping providers.
|
|
1584
1835
|
console.log('Non-interactive launch + chain check failure. Keeping codex-lb active. Set SKS_CODEX_LB_AUTOBYPASS=1 to auto-bypass to ChatGPT OAuth.');
|
|
1585
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth };
|
|
1836
|
+
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1586
1837
|
}
|
|
1587
|
-
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth };
|
|
1838
|
+
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, tool_catalog: toolCatalog, chain_health: chainHealth };
|
|
1588
1839
|
}
|
|
1589
1840
|
if (!canAskYesNo())
|
|
1590
1841
|
return { status: 'non_interactive', codex_lb: status };
|
|
@@ -1593,7 +1844,7 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
1593
1844
|
return { status: 'continued_to_codex' };
|
|
1594
1845
|
const host = (await askPostinstallQuestion('codex-lb host domain [http://127.0.0.1:2455]: ')).trim() || 'http://127.0.0.1:2455';
|
|
1595
1846
|
const apiKey = (await askPostinstallQuestion('codex-lb API key: ')).trim();
|
|
1596
|
-
const configured = await configureCodexLb({ ...opts, host, apiKey });
|
|
1847
|
+
const configured = await configureCodexLb({ ...opts, host, apiKey, allowUnverifiedToolOutputRecovery });
|
|
1597
1848
|
if (configured.ok)
|
|
1598
1849
|
console.log(`codex-lb configured: ${configured.base_url}`);
|
|
1599
1850
|
else
|
|
@@ -1870,379 +2121,6 @@ function appliedCodexLbPersistenceModes(state = {}) {
|
|
|
1870
2121
|
modes.push('none');
|
|
1871
2122
|
return modes;
|
|
1872
2123
|
}
|
|
1873
|
-
export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
|
|
1874
|
-
if (process.env.SKS_SKIP_CODEX_FAST_MODE_REPAIR === '1')
|
|
1875
|
-
return { status: 'skipped', reason: 'SKS_SKIP_CODEX_FAST_MODE_REPAIR=1' };
|
|
1876
|
-
const home = opts.home || process.env.HOME || os.homedir();
|
|
1877
|
-
const configPath = opts.configPath || codexLbConfigPath(home);
|
|
1878
|
-
try {
|
|
1879
|
-
await ensureDir(path.dirname(configPath));
|
|
1880
|
-
const current = await readText(configPath, '');
|
|
1881
|
-
// Safety gate 1: never blind-overwrite an unparseable user config — that would
|
|
1882
|
-
// entrench corruption on the file Codex actually loads. Back it up and bail.
|
|
1883
|
-
if (current.trim()) {
|
|
1884
|
-
const currentSmoke = codexConfigParseSmoke(current);
|
|
1885
|
-
if (!currentSmoke.ok) {
|
|
1886
|
-
const backupPath = await backupCodexConfig(configPath, current, 'unparseable');
|
|
1887
|
-
return { status: 'unparseable_config_preserved', config_path: configPath, backup_path: backupPath, parse_smoke: currentSmoke };
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
const next = normalizeCodexFastModeUiConfig(current, {
|
|
1891
|
-
forceFastMode: opts.forceFastMode === true,
|
|
1892
|
-
forceFastModeOff: opts.forceFastModeOff === true
|
|
1893
|
-
});
|
|
1894
|
-
if (next === ensureTrailingNewline(current))
|
|
1895
|
-
return { status: 'present', config_path: configPath };
|
|
1896
|
-
const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'codex-fast-mode-install', {
|
|
1897
|
-
preserveFastUiKeys: opts.forceFastModeOff !== true
|
|
1898
|
-
});
|
|
1899
|
-
return {
|
|
1900
|
-
status: safeWrite.status === 'written' ? 'updated' : safeWrite.status,
|
|
1901
|
-
config_path: configPath,
|
|
1902
|
-
backup_path: safeWrite.backup_path,
|
|
1903
|
-
parse_smoke: safeWrite.ok ? undefined : safeWrite
|
|
1904
|
-
};
|
|
1905
|
-
}
|
|
1906
|
-
catch (err) {
|
|
1907
|
-
return { status: 'failed', config_path: configPath, error: err.message };
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
export function normalizeCodexFastModeUiConfig(text = '', opts = {}) {
|
|
1911
|
-
// Run to a fixed point so a second install is a true no-op (idempotent). The per-pass
|
|
1912
|
-
// table/whitespace normalization converges within one extra pass.
|
|
1913
|
-
return normalizeCodexFastModeUiConfigOnce(normalizeCodexFastModeUiConfigOnce(text, opts), opts);
|
|
1914
|
-
}
|
|
1915
|
-
function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
|
|
1916
|
-
// 2026-07 Codex App renewal (ChatGPT desktop merge): the config schema removed
|
|
1917
|
-
// `default_profile`, `[profiles.<name>]` tables, `[user.fast_mode]`, and
|
|
1918
|
-
// `notice.fast_default_opt_out`. The only documented fast-default mechanism is
|
|
1919
|
-
// the plain top-level `service_tier = "fast"`. This pass strips every legacy
|
|
1920
|
-
// schema stamps SKS wrote and keeps user-authored top-level choices intact.
|
|
1921
|
-
let next = String(text || '');
|
|
1922
|
-
next = removeLegacyTopLevelCodexModeLocks(next);
|
|
1923
|
-
next = removeTopLevelTomlKeyIfValue(next, 'default_profile', 'sks-fast-high');
|
|
1924
|
-
next = removeTomlTable(next, 'user.fast_mode');
|
|
1925
|
-
next = removeTomlTable(next, 'profiles.sks-fast-high');
|
|
1926
|
-
next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
|
|
1927
|
-
// Feature flags SKS wrote in earlier versions that are not in the current
|
|
1928
|
-
// [features] reference (renewal removed/renamed them). They only trigger the
|
|
1929
|
-
// under-development warning now — strip our own stamps.
|
|
1930
|
-
for (const legacyFlag of [
|
|
1931
|
-
'codex_hooks', 'remote_control', 'fast_mode_ui', 'codex_git_commit', 'computer_use',
|
|
1932
|
-
'browser_use', 'browser_use_external', 'image_generation', 'in_app_browser',
|
|
1933
|
-
'guardian_approval', 'tool_suggest', 'plugins'
|
|
1934
|
-
]) {
|
|
1935
|
-
// Earlier SKS releases wrote these flags as `true`. Remove only that
|
|
1936
|
-
// managed legacy value; an explicit user `false` remains authoritative.
|
|
1937
|
-
next = removeTomlTableKey(next, 'features', legacyFlag, 'true');
|
|
1938
|
-
}
|
|
1939
|
-
if (opts.forceFastMode === true) {
|
|
1940
|
-
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
1941
|
-
}
|
|
1942
|
-
else if (opts.forceFastModeOff === true) {
|
|
1943
|
-
// "off" = remove the tier and let Codex's own default apply. The old code
|
|
1944
|
-
// wrote service_tier = "default", which is not a documented tier value.
|
|
1945
|
-
next = removeTopLevelTomlKey(next, 'service_tier');
|
|
1946
|
-
}
|
|
1947
|
-
// Documented, currently-valid feature flags are SET-IF-ABSENT: a fresh config
|
|
1948
|
-
// gets SKS's defaults, but SKS never re-enables a feature the user disabled.
|
|
1949
|
-
next = upsertTopLevelTomlBooleanIfAbsent(next, 'suppress_unstable_features_warning', true);
|
|
1950
|
-
for (const featureLine of ['hooks = true', 'multi_agent = true', 'fast_mode = true', 'apps = true']) {
|
|
1951
|
-
next = upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
|
|
1952
|
-
}
|
|
1953
|
-
if (process.env.SKS_ALLOW_HIGH_AGENT_CONCURRENCY !== '1') {
|
|
1954
|
-
next = upsertTomlTableKey(next, 'agents', 'max_threads = 4');
|
|
1955
|
-
}
|
|
1956
|
-
next = removeTomlTable(next, 'features.multi_agent_v2');
|
|
1957
|
-
// Plugin auto-enable is OPT-IN only. Force-writing `[plugins."name@marketplace"] enabled =
|
|
1958
|
-
// true` for marketplace plugins the App may not have installed (different build/channel)
|
|
1959
|
-
// makes the App reference plugins it cannot load -> broken/blocked plugin UI. It also
|
|
1960
|
-
// replaced the user's whole plugin table, reverting any `enabled = false` they set. By
|
|
1961
|
-
// default SKS leaves the user's [plugins] alone; opt in with SKS_MANAGE_CODEX_APP_PLUGINS=1.
|
|
1962
|
-
if (process.env.SKS_MANAGE_CODEX_APP_PLUGINS === '1') {
|
|
1963
|
-
for (const [name, marketplace] of DEFAULT_CODEX_APP_PLUGINS) {
|
|
1964
|
-
const table = `plugins."${name}@${marketplace}"`;
|
|
1965
|
-
if (!hasTomlTable(next, table))
|
|
1966
|
-
next = upsertTomlTable(next, table, `[${table}]\nenabled = true`);
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
return ensureTrailingNewline(next);
|
|
1970
|
-
}
|
|
1971
|
-
function removeTopLevelTomlKey(text = '', key = '') {
|
|
1972
|
-
const lines = String(text || '').split('\n');
|
|
1973
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
1974
|
-
const end = firstTable === -1 ? lines.length : firstTable;
|
|
1975
|
-
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
1976
|
-
return lines.filter((line, index) => index >= end || !keyPattern.test(line)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
1977
|
-
}
|
|
1978
|
-
function removeTomlTable(text, table) {
|
|
1979
|
-
const lines = String(text || '').trimEnd().split('\n');
|
|
1980
|
-
const header = `[${table}]`;
|
|
1981
|
-
const start = lines.findIndex((x) => x.trim() === header);
|
|
1982
|
-
if (start === -1)
|
|
1983
|
-
return String(text || '');
|
|
1984
|
-
let end = lines.length;
|
|
1985
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
1986
|
-
const ln = lines[i];
|
|
1987
|
-
if (ln !== undefined && /^\s*\[.+\]\s*$/.test(ln)) {
|
|
1988
|
-
end = i;
|
|
1989
|
-
break;
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
return lines.filter((_, index) => index < start || index >= end).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
1993
|
-
}
|
|
1994
|
-
function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
1995
|
-
const lines = String(text || '').split('\n');
|
|
1996
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
1997
|
-
const end = firstTable === -1 ? lines.length : firstTable;
|
|
1998
|
-
return lines.filter((line, index) => {
|
|
1999
|
-
if (index >= end)
|
|
2000
|
-
return true;
|
|
2001
|
-
if (!/^\s*(?:model|model_reasoning_effort)\s*=/.test(line))
|
|
2002
|
-
return true;
|
|
2003
|
-
return ![line, lines[index - 1] || ''].some((candidate) => {
|
|
2004
|
-
const trimmed = String(candidate || '').trim();
|
|
2005
|
-
const comment = trimmed.startsWith('#') ? trimmed : trimmed.includes('#') ? trimmed.slice(trimmed.indexOf('#')) : '';
|
|
2006
|
-
return /(?:SKS|Sneakoscope|codex-lb|sks fast)/i.test(comment);
|
|
2007
|
-
});
|
|
2008
|
-
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
2009
|
-
}
|
|
2010
|
-
function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
|
|
2011
|
-
const lines = String(text || '').split('\n');
|
|
2012
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
2013
|
-
const end = firstTable === -1 ? lines.length : firstTable;
|
|
2014
|
-
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?$`);
|
|
2015
|
-
return lines.filter((line, index) => index >= end || !keyPattern.test(line)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
2016
|
-
}
|
|
2017
|
-
function removeTomlTableKey(text, table, key, expectedValue = null) {
|
|
2018
|
-
const lines = String(text || '').trimEnd().split('\n');
|
|
2019
|
-
if (lines.length === 1 && lines[0] === '')
|
|
2020
|
-
return '';
|
|
2021
|
-
const header = `[${table}]`;
|
|
2022
|
-
const start = lines.findIndex((x) => x.trim() === header);
|
|
2023
|
-
if (start === -1)
|
|
2024
|
-
return String(text || '');
|
|
2025
|
-
let end = lines.length;
|
|
2026
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
2027
|
-
const ln = lines[i];
|
|
2028
|
-
if (ln === undefined)
|
|
2029
|
-
continue;
|
|
2030
|
-
if (/^\s*\[.+\]\s*$/.test(ln)) {
|
|
2031
|
-
end = i;
|
|
2032
|
-
break;
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
const valuePattern = expectedValue === null
|
|
2036
|
-
? ''
|
|
2037
|
-
: `\\s*${escapeRegExp(String(expectedValue))}\\s*(?:#.*)?$`;
|
|
2038
|
-
const keyPattern = new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=${valuePattern}`);
|
|
2039
|
-
return lines.filter((line, index) => index <= start || index >= end || !keyPattern.test(line)).join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2040
|
-
}
|
|
2041
|
-
function upsertTomlTableKey(text, table, line) {
|
|
2042
|
-
const key = String(line).split('=')[0]?.trim() ?? '';
|
|
2043
|
-
const lines = String(text || '').trimEnd().split('\n');
|
|
2044
|
-
if (lines.length === 1 && lines[0] === '')
|
|
2045
|
-
lines.length = 0;
|
|
2046
|
-
const header = `[${table}]`;
|
|
2047
|
-
const start = lines.findIndex((x) => x.trim() === header);
|
|
2048
|
-
if (start === -1)
|
|
2049
|
-
return [...lines, ...(lines.length ? [''] : []), header, line].join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2050
|
-
let end = lines.length;
|
|
2051
|
-
for (let i = start + 1; i < lines.length; i++) {
|
|
2052
|
-
const ln = lines[i];
|
|
2053
|
-
if (ln === undefined)
|
|
2054
|
-
continue;
|
|
2055
|
-
if (/^\s*\[.+\]\s*$/.test(ln)) {
|
|
2056
|
-
end = i;
|
|
2057
|
-
break;
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
const keyRe = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
2061
|
-
for (let i = start + 1; i < end; i++) {
|
|
2062
|
-
const ln = lines[i];
|
|
2063
|
-
if (ln === undefined)
|
|
2064
|
-
continue;
|
|
2065
|
-
if (keyRe.test(ln)) {
|
|
2066
|
-
lines[i] = line;
|
|
2067
|
-
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
if (hasTomlTableKey(lines.join('\n'), table, key))
|
|
2071
|
-
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2072
|
-
lines.splice(end, 0, line);
|
|
2073
|
-
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2074
|
-
}
|
|
2075
|
-
// True if [table] already declares `key` (so we never override a user's explicit value).
|
|
2076
|
-
function hasTomlTableKey(text, table, key) {
|
|
2077
|
-
const lines = String(text || '').split('\n');
|
|
2078
|
-
const header = `[${table}]`;
|
|
2079
|
-
const start = lines.findIndex((x) => x.trim() === header);
|
|
2080
|
-
if (start === -1)
|
|
2081
|
-
return false;
|
|
2082
|
-
const keyRe = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
2083
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
2084
|
-
const ln = lines[i];
|
|
2085
|
-
if (ln === undefined)
|
|
2086
|
-
continue;
|
|
2087
|
-
if (/^\s*\[.+\]\s*$/.test(ln))
|
|
2088
|
-
break;
|
|
2089
|
-
if (keyRe.test(ln))
|
|
2090
|
-
return true;
|
|
2091
|
-
}
|
|
2092
|
-
return false;
|
|
2093
|
-
}
|
|
2094
|
-
// Set a [table] key only when absent — preserves a Codex App feature the user toggled off
|
|
2095
|
-
// (so SKS never re-enables / re-surfaces UI the user hid). On a fresh config the key/table
|
|
2096
|
-
// is still created, preserving fresh-install enablement.
|
|
2097
|
-
function upsertTomlTableKeyIfAbsent(text, table, line) {
|
|
2098
|
-
const key = String(line).split('=')[0]?.trim() ?? '';
|
|
2099
|
-
return hasTomlTableKey(text, table, key) ? String(text || '') : upsertTomlTableKey(text, table, line);
|
|
2100
|
-
}
|
|
2101
|
-
function upsertTopLevelTomlBooleanIfAbsent(text, key, value) {
|
|
2102
|
-
return hasTopLevelTomlKey(text, key) ? String(text || '') : upsertTopLevelTomlBoolean(text, key, value);
|
|
2103
|
-
}
|
|
2104
|
-
function ensureTrailingNewline(text = '') {
|
|
2105
|
-
const value = String(text || '').trimEnd();
|
|
2106
|
-
return value ? `${value}\n` : '';
|
|
2107
|
-
}
|
|
2108
|
-
function upsertTopLevelTomlString(text, key, value) {
|
|
2109
|
-
const line = `${key} = "${value}"`;
|
|
2110
|
-
const lines = String(text || '').split('\n');
|
|
2111
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
2112
|
-
const end = firstTable === -1 ? lines.length : firstTable;
|
|
2113
|
-
for (let i = 0; i < end; i++) {
|
|
2114
|
-
const ln = lines[i];
|
|
2115
|
-
if (ln === undefined)
|
|
2116
|
-
continue;
|
|
2117
|
-
if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(ln)) {
|
|
2118
|
-
lines[i] = line;
|
|
2119
|
-
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
lines.splice(end, 0, line);
|
|
2123
|
-
return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
2124
|
-
}
|
|
2125
|
-
function hasTopLevelTomlKey(text, key) {
|
|
2126
|
-
const lines = String(text || '').split('\n');
|
|
2127
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
2128
|
-
const end = firstTable === -1 ? lines.length : firstTable;
|
|
2129
|
-
const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
|
|
2130
|
-
for (let i = 0; i < end; i += 1) {
|
|
2131
|
-
if (typeof lines[i] === 'string' && pattern.test(lines[i]))
|
|
2132
|
-
return true;
|
|
2133
|
-
}
|
|
2134
|
-
return false;
|
|
2135
|
-
}
|
|
2136
|
-
// Preserve a user's deliberate top-level scalar (model/service_tier/reasoning); only set
|
|
2137
|
-
// the SKS default when the key is ABSENT. This is what stops `npm i -g` from clobbering
|
|
2138
|
-
// a user's global Codex config on every update.
|
|
2139
|
-
function upsertTopLevelTomlStringIfAbsent(text, key, value) {
|
|
2140
|
-
return hasTopLevelTomlKey(text, key) ? String(text || '') : upsertTopLevelTomlString(text, key, value);
|
|
2141
|
-
}
|
|
2142
|
-
// Lightweight safety gate: detect clearly-broken TOML so we never overwrite (or produce)
|
|
2143
|
-
// an unparseable config that Codex itself would reject. Mirrors the project-config smoke.
|
|
2144
|
-
function codexConfigParseSmoke(text = '') {
|
|
2145
|
-
const str = String(text || '');
|
|
2146
|
-
const tripleTokens = (str.match(/"""|'''/g) || []).length;
|
|
2147
|
-
const unterminatedTriple = tripleTokens % 2 !== 0;
|
|
2148
|
-
const invalidHeader = str.split('\n').find((line) => /^\s*\[/.test(line) && !/^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/.test(line)) || null;
|
|
2149
|
-
return { ok: !unterminatedTriple && !invalidHeader, unterminated_multiline_string: unterminatedTriple, invalid_table_header: invalidHeader };
|
|
2150
|
-
}
|
|
2151
|
-
async function backupCodexConfig(configPath, text, tag) {
|
|
2152
|
-
try {
|
|
2153
|
-
const stamp = `${PACKAGE_VERSION}-${Date.now().toString(36)}`;
|
|
2154
|
-
const backupPath = `${configPath}.sks-${tag}-${stamp}.bak`;
|
|
2155
|
-
await writeTextAtomic(backupPath, text);
|
|
2156
|
-
await cleanupCodexConfigBackups(configPath, { keepPerTag: 3, maxAgeMs: 30 * 24 * 60 * 60 * 1000 }).catch(() => undefined);
|
|
2157
|
-
return backupPath;
|
|
2158
|
-
}
|
|
2159
|
-
catch {
|
|
2160
|
-
return null;
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
// Single TOML-safe gate for every codex-lb config write. Mirrors the fast-mode safety so the
|
|
2164
|
-
// codex-lb path can NEVER corrupt ~/.codex/config.toml on install (esp. a fresh/initial one):
|
|
2165
|
-
// - refuse to overwrite an existing config that is already unparseable (back it up, bail),
|
|
2166
|
-
// - refuse to WRITE a result that would not parse (e.g. a regex helper mangled a multiline
|
|
2167
|
-
// string), leaving the existing config untouched,
|
|
2168
|
-
// - otherwise back up the prior config before mutating.
|
|
2169
|
-
export async function safeWriteCodexConfigToml(configPath, current, next, tag = 'codex-lb', opts = {}) {
|
|
2170
|
-
return writeCodexConfigGuarded({
|
|
2171
|
-
configPath,
|
|
2172
|
-
before: String(current || ''),
|
|
2173
|
-
cause: tag,
|
|
2174
|
-
removeTopLevelModeLocks: true,
|
|
2175
|
-
...(opts.preserveFastUiKeys === undefined ? {} : { preserveFastUiKeys: opts.preserveFastUiKeys }),
|
|
2176
|
-
mutate: () => String(next || '')
|
|
2177
|
-
});
|
|
2178
|
-
}
|
|
2179
|
-
export function codexFastModeDesktopStatus(text = '') {
|
|
2180
|
-
const validation = validateCodexConfigRoundTrip(String(text || ''));
|
|
2181
|
-
// Post-renewal contract: the fast default is the plain top-level
|
|
2182
|
-
// service_tier = "fast". default_profile/[profiles.*]/[user.fast_mode] are
|
|
2183
|
-
// gone from the Codex config schema and are only reported as legacy_keys.
|
|
2184
|
-
const globalOn = validation.ok && validation.service_tier === 'fast';
|
|
2185
|
-
return {
|
|
2186
|
-
schema: 'sks.codex-fast-mode-desktop-status.v2',
|
|
2187
|
-
ok: validation.ok,
|
|
2188
|
-
on: Boolean(globalOn),
|
|
2189
|
-
service_tier: validation.service_tier ?? null,
|
|
2190
|
-
model: validation.model ?? null,
|
|
2191
|
-
legacy_keys: validation.legacy_keys,
|
|
2192
|
-
validation: {
|
|
2193
|
-
ok: validation.ok,
|
|
2194
|
-
blockers: validation.blockers,
|
|
2195
|
-
parse_error: validation.parse_error || null,
|
|
2196
|
-
service_tier: validation.service_tier ?? null,
|
|
2197
|
-
model: validation.model ?? null,
|
|
2198
|
-
model_reasoning_effort: validation.model_reasoning_effort ?? null,
|
|
2199
|
-
legacy_keys: validation.legacy_keys
|
|
2200
|
-
}
|
|
2201
|
-
};
|
|
2202
|
-
}
|
|
2203
|
-
function upsertTopLevelTomlBoolean(text, key, value) {
|
|
2204
|
-
const line = `${key} = ${value ? 'true' : 'false'}`;
|
|
2205
|
-
const lines = String(text || '').split('\n');
|
|
2206
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
2207
|
-
const end = firstTable === -1 ? lines.length : firstTable;
|
|
2208
|
-
for (let i = 0; i < end; i += 1) {
|
|
2209
|
-
const ln = lines[i];
|
|
2210
|
-
if (ln === undefined)
|
|
2211
|
-
continue;
|
|
2212
|
-
if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(ln)) {
|
|
2213
|
-
lines[i] = line;
|
|
2214
|
-
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
lines.splice(end, 0, line);
|
|
2218
|
-
return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
2219
|
-
}
|
|
2220
|
-
function hasTomlTable(text, table) {
|
|
2221
|
-
const header = `[${table}]`;
|
|
2222
|
-
return String(text || '').split('\n').some((line) => String(line).trim() === header);
|
|
2223
|
-
}
|
|
2224
|
-
function upsertTomlTable(text, table, block) {
|
|
2225
|
-
let lines = String(text || '').trimEnd().split('\n');
|
|
2226
|
-
if (lines.length === 1 && lines[0] === '')
|
|
2227
|
-
lines = [];
|
|
2228
|
-
const header = `[${table}]`;
|
|
2229
|
-
const start = lines.findIndex((x) => x.trim() === header);
|
|
2230
|
-
const blockLines = String(block || '').trim().split('\n');
|
|
2231
|
-
if (start === -1)
|
|
2232
|
-
return [...lines, ...(lines.length ? [''] : []), ...blockLines].join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2233
|
-
let end = lines.length;
|
|
2234
|
-
for (let i = start + 1; i < lines.length; i++) {
|
|
2235
|
-
const ln = lines[i];
|
|
2236
|
-
if (ln === undefined)
|
|
2237
|
-
continue;
|
|
2238
|
-
if (/^\s*\[.+\]\s*$/.test(ln)) {
|
|
2239
|
-
end = i;
|
|
2240
|
-
break;
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
lines.splice(start, end - start, ...blockLines);
|
|
2244
|
-
return lines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
2245
|
-
}
|
|
2246
2124
|
function shellSingleQuote(value) {
|
|
2247
2125
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
2248
2126
|
}
|
|
@@ -2562,273 +2440,6 @@ export async function ensureCodexImagegenDuringInstall(opts = {}) {
|
|
|
2562
2440
|
return { status: 'recovered', report };
|
|
2563
2441
|
return { status: 'blocked', blockers: report.blockers || ['codex_imagegen_unavailable'], report };
|
|
2564
2442
|
}
|
|
2565
|
-
export async function ensureRelatedCliTools(args = []) {
|
|
2566
|
-
const skip = args.includes('--skip-cli-tools') || process.env.SKS_SKIP_CLI_TOOLS === '1';
|
|
2567
|
-
const codex = await ensureCodexCliTool({ skip, args });
|
|
2568
|
-
const zellijRepair = skip ? { status: 'skipped', reason: 'SKS_SKIP_CLI_TOOLS=1 or --skip-cli-tools' } : await ensureZellijCliTool(args);
|
|
2569
|
-
const zellij = await checkZellijCapability({ require: false, writeReport: false });
|
|
2570
|
-
return {
|
|
2571
|
-
codex,
|
|
2572
|
-
zellij: {
|
|
2573
|
-
ok: zellij.status === 'ok',
|
|
2574
|
-
bin: zellij.bin,
|
|
2575
|
-
version: zellij.version,
|
|
2576
|
-
min_version: zellij.min_version,
|
|
2577
|
-
current_session: false,
|
|
2578
|
-
repair: zellijRepair,
|
|
2579
|
-
install_hint: zellij.status === 'ok' ? null : zellijInstallHint(),
|
|
2580
|
-
error: zellijRepair.error || zellij.blockers[0] || zellij.warnings[0] || null
|
|
2581
|
-
}
|
|
2582
|
-
};
|
|
2583
|
-
}
|
|
2584
|
-
export async function ensureMadLaunchDependencies(args = []) {
|
|
2585
|
-
const skip = args.includes('--skip-cli-tools') || process.env.SKS_SKIP_CLI_TOOLS === '1';
|
|
2586
|
-
const zellijRepair = skip ? { target: 'zellij', status: 'skipped', reason: 'SKS_SKIP_CLI_TOOLS=1 or --skip-cli-tools' } : await ensureZellijCliTool(args);
|
|
2587
|
-
const zellij = await checkZellijCapability({ require: false, writeReport: false });
|
|
2588
|
-
const ready = zellij.status === 'ok';
|
|
2589
|
-
return {
|
|
2590
|
-
ready,
|
|
2591
|
-
actions: ready ? [] : [{
|
|
2592
|
-
target: 'zellij',
|
|
2593
|
-
status: zellijRepair.status,
|
|
2594
|
-
command: zellijRepair.command || zellijInstallHint(),
|
|
2595
|
-
error: zellijRepair.error || zellij.blockers[0] || zellij.warnings[0] || null,
|
|
2596
|
-
repair: zellijRepair
|
|
2597
|
-
}],
|
|
2598
|
-
status: {
|
|
2599
|
-
zellij: {
|
|
2600
|
-
ok: ready,
|
|
2601
|
-
status: zellij.status,
|
|
2602
|
-
version: zellij.version,
|
|
2603
|
-
min_version: zellij.min_version,
|
|
2604
|
-
repair: zellijRepair,
|
|
2605
|
-
install_hint: ready ? null : zellijInstallHint()
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
};
|
|
2609
|
-
}
|
|
2610
|
-
export function formatMadLaunchDependencyAction(action = {}) {
|
|
2611
|
-
const command = action.command ? ` Run: ${action.command}.` : '';
|
|
2612
|
-
const error = action.error ? ` ${action.error}` : '';
|
|
2613
|
-
return `${action.target || 'dependency'} ${action.status || 'blocked'}.${command}${error}`.trim();
|
|
2614
|
-
}
|
|
2615
|
-
export async function ensureCodexCliTool({ skip = false, args = [] } = {}) {
|
|
2616
|
-
if (skip)
|
|
2617
|
-
return { status: 'skipped', reason: 'SKS_SKIP_CLI_TOOLS=1 or --skip-cli-tools' };
|
|
2618
|
-
const before = await getCodexInfo().catch(() => EMPTY_CODEX_INFO);
|
|
2619
|
-
if (before.bin)
|
|
2620
|
-
return { status: 'present', bin: before.bin, version: before.version || null };
|
|
2621
|
-
const npmBin = await which('npm');
|
|
2622
|
-
if (!npmBin)
|
|
2623
|
-
return { status: 'failed', error: 'npm not found on PATH; install Codex CLI manually with npm i -g @openai/codex@latest.' };
|
|
2624
|
-
const command = 'npm i -g @openai/codex@latest';
|
|
2625
|
-
if (args.includes('--dry-run'))
|
|
2626
|
-
return { status: 'dry_run', command, error: 'Codex CLI not found on PATH.' };
|
|
2627
|
-
if (!await confirmInstallYesDefault(`Codex CLI is missing. Install latest Codex CLI with ${command}?`, args)) {
|
|
2628
|
-
return { status: 'needs_approval', command, error: 'Codex CLI not found on PATH.' };
|
|
2629
|
-
}
|
|
2630
|
-
// Global package install is a confirmation-required mutation: route it through
|
|
2631
|
-
// the mutation guard so it is scope-checked and recorded in the ledger. The
|
|
2632
|
-
// user already approved via confirmInstallYesDefault above (confirmed:true).
|
|
2633
|
-
const installRoot = globalSksRoot();
|
|
2634
|
-
const installContract = createRequestedScopeContract({
|
|
2635
|
-
route: 'install', userRequest: command, projectRoot: installRoot, overrides: { package_install: true }
|
|
2636
|
-
});
|
|
2637
|
-
const install = await guardedPackageInstall(guardContextForRoute(installRoot, installContract, command), '@openai/codex@latest', { confirmed: true, command: npmBin, args: ['i', '-g', '@openai/codex@latest'], timeoutMs: 120000 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
2638
|
-
if (install.code !== 0) {
|
|
2639
|
-
return { status: 'failed', error: `${install.stderr || install.stdout || 'npm i -g @openai/codex@latest failed'}`.trim() };
|
|
2640
|
-
}
|
|
2641
|
-
const after = await getCodexInfo().catch(() => EMPTY_CODEX_INFO);
|
|
2642
|
-
return {
|
|
2643
|
-
status: after.bin ? 'installed' : 'installed_not_on_path',
|
|
2644
|
-
bin: after.bin || null,
|
|
2645
|
-
version: after.version || null,
|
|
2646
|
-
hint: after.bin ? null : 'npm completed, but codex is not on PATH. Restart the shell or set SKS_CODEX_BIN.'
|
|
2647
|
-
};
|
|
2648
|
-
}
|
|
2649
|
-
export async function ensureZellijCliTool(args = [], opts = {}) {
|
|
2650
|
-
const before = await checkZellijCapability({ require: false, writeReport: false });
|
|
2651
|
-
if (before.status === 'ok')
|
|
2652
|
-
return { target: 'zellij', status: 'present', bin: before.bin, version: before.version || null };
|
|
2653
|
-
const command = zellijInstallHint();
|
|
2654
|
-
if (process.platform !== 'darwin')
|
|
2655
|
-
return { target: 'zellij', status: 'manual_required', command, error: before.blockers[0] || before.warnings[0] || 'zellij not found' };
|
|
2656
|
-
const brew = await which('brew').catch(() => null);
|
|
2657
|
-
if (!brew)
|
|
2658
|
-
return { target: 'zellij', status: 'manual_required', command: 'Install Homebrew, then run: brew install zellij', error: before.blockers[0] || before.warnings[0] || 'zellij not found' };
|
|
2659
|
-
const repairCommand = command;
|
|
2660
|
-
if (args.includes('--dry-run') || opts.dryRun)
|
|
2661
|
-
return { target: 'zellij', status: 'dry_run', command: repairCommand, error: before.blockers[0] || before.warnings[0] || null };
|
|
2662
|
-
const hasInstalledZellij = Boolean(before.version);
|
|
2663
|
-
const question = hasInstalledZellij
|
|
2664
|
-
? `Homebrew Zellij ${before.version || 'unknown'} is not ready. Upgrade to latest Zellij with ${repairCommand}?`
|
|
2665
|
-
: `Zellij is missing. Install latest Zellij with ${repairCommand}?`;
|
|
2666
|
-
if (!await confirmInstallYesDefault(question, args))
|
|
2667
|
-
return { target: 'zellij', status: 'needs_approval', command: repairCommand, error: before.blockers[0] || before.warnings[0] || null };
|
|
2668
|
-
const brewArgs = hasInstalledZellij ? ['upgrade', 'zellij'] : ['install', 'zellij'];
|
|
2669
|
-
const zellijRoot = globalSksRoot();
|
|
2670
|
-
const zellijContract = createRequestedScopeContract({
|
|
2671
|
-
route: 'install', userRequest: repairCommand, projectRoot: zellijRoot, overrides: { package_install: true, zellij_install: true }
|
|
2672
|
-
});
|
|
2673
|
-
const install = await guardedPackageInstall(guardContextForRoute(zellijRoot, zellijContract, repairCommand), 'zellij', { confirmed: true, command: brew, args: brewArgs, timeoutMs: 180000 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
2674
|
-
if (install.code !== 0)
|
|
2675
|
-
return { target: 'zellij', status: 'failed', command: repairCommand, error: `${install.stderr || install.stdout || repairCommand + ' failed'}`.trim() };
|
|
2676
|
-
const after = await checkZellijCapability({ require: false, writeReport: false });
|
|
2677
|
-
if (after.status !== 'ok')
|
|
2678
|
-
return { target: 'zellij', status: 'installed_not_ready', command: repairCommand, error: after.blockers[0] || after.warnings[0] || 'zellij installed but not ready' };
|
|
2679
|
-
return { target: 'zellij', status: hasInstalledZellij ? 'upgraded' : 'installed', command: repairCommand, bin: after.bin, version: after.version || null };
|
|
2680
|
-
}
|
|
2681
|
-
function zellijInstallHint() {
|
|
2682
|
-
return process.platform === 'darwin' ? 'brew install zellij' : 'Install Zellij from https://zellij.dev/documentation/installation.html';
|
|
2683
|
-
}
|
|
2684
|
-
async function confirmInstallYesDefault(question, args = []) {
|
|
2685
|
-
if (hasFlag(args, '--from-postinstall') && process.env.SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS !== '1')
|
|
2686
|
-
return false;
|
|
2687
|
-
if (shouldAutoApproveInstall(args))
|
|
2688
|
-
return true;
|
|
2689
|
-
if (!canAskYesNo())
|
|
2690
|
-
return false;
|
|
2691
|
-
const answer = (await askPostinstallQuestion(`${question} [Y/n] `)).trim();
|
|
2692
|
-
return answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
|
|
2693
|
-
}
|
|
2694
|
-
export async function maybePromptCodexUpdateForLaunch(args = [], opts = {}) {
|
|
2695
|
-
if (hasFlag(args, '--json') || hasFlag(args, '--skip-cli-tools') || hasFlag(args, '--skip-codex-update') || process.env.SKS_SKIP_CODEX_UPDATE === '1')
|
|
2696
|
-
return { status: 'skipped' };
|
|
2697
|
-
const latest = await npmPackageVersion('@openai/codex');
|
|
2698
|
-
const codex = await getCodexInfo().catch(() => EMPTY_CODEX_INFO);
|
|
2699
|
-
const current = codexCliVersionNumber(codex.version);
|
|
2700
|
-
const command = 'npm i -g @openai/codex@latest';
|
|
2701
|
-
const label = opts.label || 'Zellij launch';
|
|
2702
|
-
const missing = !codex.bin;
|
|
2703
|
-
const updateAvailable = Boolean(latest.version && current && compareVersions(latest.version, current) > 0);
|
|
2704
|
-
if (!missing && !updateAvailable)
|
|
2705
|
-
return { status: 'current', latest: latest.version || null, current, bin: codex.bin || null, error: latest.error || null };
|
|
2706
|
-
const prompt = missing
|
|
2707
|
-
? `Codex CLI missing. Install @openai/codex${latest.version ? ` ${latest.version}` : '@latest'} before ${label}? [Y/n] `
|
|
2708
|
-
: `Codex CLI ${current} -> ${latest.version} update before ${label}? [Y/n] `;
|
|
2709
|
-
if (shouldAutoApproveInstall(args))
|
|
2710
|
-
return installCodexLatest(command, latest.version, current);
|
|
2711
|
-
if (!canAskYesNo()) {
|
|
2712
|
-
const reason = missing ? 'Codex CLI missing' : `Codex CLI update available: ${current} -> ${latest.version}`;
|
|
2713
|
-
console.log(`${reason}. Run: ${command}`);
|
|
2714
|
-
return { status: missing ? 'missing' : 'available', latest: latest.version || null, current, command, bin: codex.bin || null };
|
|
2715
|
-
}
|
|
2716
|
-
const answer = (await askPostinstallQuestion(prompt)).trim();
|
|
2717
|
-
const yes = answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
|
|
2718
|
-
if (!yes)
|
|
2719
|
-
return { status: 'skipped_by_user', latest: latest.version || null, current, command, bin: codex.bin || null };
|
|
2720
|
-
return installCodexLatest(command, latest.version, current);
|
|
2721
|
-
}
|
|
2722
|
-
export async function maybePromptSksUpdateForLaunch(args = [], opts = {}) {
|
|
2723
|
-
void args;
|
|
2724
|
-
void opts;
|
|
2725
|
-
return {
|
|
2726
|
-
status: 'skipped',
|
|
2727
|
-
reason: 'manual_update_commands_only',
|
|
2728
|
-
current: PACKAGE_VERSION,
|
|
2729
|
-
latest: null,
|
|
2730
|
-
command: null
|
|
2731
|
-
};
|
|
2732
|
-
}
|
|
2733
|
-
export function shouldAutoApproveInstall(args = [], env = process.env) {
|
|
2734
|
-
if (hasFlag(args, '--from-postinstall') && env.SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS !== '1')
|
|
2735
|
-
return false;
|
|
2736
|
-
if (hasFlag(args, '--from-postinstall') && env.SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS === '1')
|
|
2737
|
-
return true;
|
|
2738
|
-
return hasFlag(args, '--yes') || hasFlag(args, '-y') || isAgentRuntime(env);
|
|
2739
|
-
}
|
|
2740
|
-
function canAskYesNo() {
|
|
2741
|
-
return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true');
|
|
2742
|
-
}
|
|
2743
|
-
function hasFlag(args = [], name) {
|
|
2744
|
-
return args.includes(name);
|
|
2745
|
-
}
|
|
2746
|
-
function isAgentRuntime(env = process.env) {
|
|
2747
|
-
return [
|
|
2748
|
-
'SKS_OPENCLAW',
|
|
2749
|
-
'OPENCLAW',
|
|
2750
|
-
'OPENCLAW_AGENT',
|
|
2751
|
-
'OPENCLAW_RUN_ID',
|
|
2752
|
-
'OPENCLAW_SESSION_ID',
|
|
2753
|
-
'SKS_HERMES',
|
|
2754
|
-
'HERMES_AGENT',
|
|
2755
|
-
'HERMES_RUN_ID',
|
|
2756
|
-
'HERMES_SESSION_ID'
|
|
2757
|
-
]
|
|
2758
|
-
.some((key) => /^(1|true|yes|y)$/i.test(String(env[key] || '').trim()));
|
|
2759
|
-
}
|
|
2760
|
-
async function installCodexLatest(command, latestVersion, previousVersion = null) {
|
|
2761
|
-
const npm = await which('npm').catch(() => null);
|
|
2762
|
-
if (!npm)
|
|
2763
|
-
return { status: 'failed', latest: latestVersion || null, previous: previousVersion || null, command, error: 'npm not found on PATH' };
|
|
2764
|
-
const install = await runProcess(npm, ['i', '-g', '@openai/codex@latest'], { timeoutMs: 180000, maxOutputBytes: 128 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
2765
|
-
if (install.code !== 0)
|
|
2766
|
-
return { status: 'failed', latest: latestVersion || null, previous: previousVersion || null, command, error: `${install.stderr || install.stdout || command + ' failed'}`.trim() };
|
|
2767
|
-
const after = await getCodexInfo().catch(() => EMPTY_CODEX_INFO);
|
|
2768
|
-
const afterVersion = codexCliVersionNumber(after.version);
|
|
2769
|
-
if (!after.bin)
|
|
2770
|
-
return { status: 'updated_not_reflected', latest: latestVersion || null, previous: previousVersion || null, version: afterVersion || null, command, error: 'npm completed, but codex is not on PATH. Restart the shell or set SKS_CODEX_BIN.' };
|
|
2771
|
-
if (latestVersion && afterVersion && compareVersions(afterVersion, latestVersion) < 0) {
|
|
2772
|
-
return { status: 'updated_not_reflected', latest: latestVersion, previous: previousVersion || null, version: afterVersion, bin: after.bin, command, error: `npm completed, but PATH still resolves Codex CLI ${afterVersion}; expected ${latestVersion}.` };
|
|
2773
|
-
}
|
|
2774
|
-
console.log(`Codex CLI ready: ${previousVersion || 'missing'} -> ${after.version || after.bin}`);
|
|
2775
|
-
return { status: previousVersion ? 'updated' : 'installed', latest: latestVersion || null, previous: previousVersion || null, version: afterVersion || null, raw_version: after.version || null, bin: after.bin || null, command };
|
|
2776
|
-
}
|
|
2777
|
-
function codexCliVersionNumber(versionText = '') {
|
|
2778
|
-
const match = String(versionText || '').match(/(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
|
|
2779
|
-
return match ? match[1] : null;
|
|
2780
|
-
}
|
|
2781
|
-
async function npmPackageVersion(name) {
|
|
2782
|
-
const envName = `SKS_NPM_VIEW_${String(name || '').replace(/[^A-Za-z0-9]+/g, '_').toUpperCase()}_VERSION`;
|
|
2783
|
-
if (process.env[envName])
|
|
2784
|
-
return { version: process.env[envName] };
|
|
2785
|
-
const npm = await which('npm').catch(() => null);
|
|
2786
|
-
if (!npm)
|
|
2787
|
-
return { error: 'npm not found' };
|
|
2788
|
-
const result = await runProcess(npm, ['view', name, 'version'], { timeoutMs: 5000, maxOutputBytes: 4096 });
|
|
2789
|
-
if (result.code !== 0)
|
|
2790
|
-
return { error: `${result.stderr || result.stdout || 'npm view failed'}`.trim() };
|
|
2791
|
-
return { version: result.stdout.trim().split(/\s+/).pop() };
|
|
2792
|
-
}
|
|
2793
|
-
function compareVersions(a, b) {
|
|
2794
|
-
const pa = String(a || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
2795
|
-
const pb = String(b || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
2796
|
-
for (let i = 0; i < Math.max(pa.length, pb.length, 3); i++) {
|
|
2797
|
-
if ((pa[i] || 0) > (pb[i] || 0))
|
|
2798
|
-
return 1;
|
|
2799
|
-
if ((pa[i] || 0) < (pb[i] || 0))
|
|
2800
|
-
return -1;
|
|
2801
|
-
}
|
|
2802
|
-
return 0;
|
|
2803
|
-
}
|
|
2804
|
-
async function isProjectSetupCandidate(root) {
|
|
2805
|
-
const markers = ['package.json', '.git', 'AGENTS.md', '.codex', '.sneakoscope'];
|
|
2806
|
-
for (const marker of markers) {
|
|
2807
|
-
if (await exists(path.join(root, marker)))
|
|
2808
|
-
return true;
|
|
2809
|
-
}
|
|
2810
|
-
return false;
|
|
2811
|
-
}
|
|
2812
|
-
export async function checkContext7(root) {
|
|
2813
|
-
const projectPath = path.join(root, '.codex', 'config.toml');
|
|
2814
|
-
const globalPath = path.join(process.env.HOME || '', '.codex', 'config.toml');
|
|
2815
|
-
const projectText = await safeReadText(projectPath);
|
|
2816
|
-
const globalText = await safeReadText(globalPath);
|
|
2817
|
-
const codex = await getCodexInfo().catch(() => EMPTY_CODEX_INFO);
|
|
2818
|
-
let list = { checked: false, ok: false, stdout: '', stderr: '' };
|
|
2819
|
-
if (codex.bin) {
|
|
2820
|
-
const out = await runProcess(codex.bin, ['mcp', 'list'], { timeoutMs: 8000, maxOutputBytes: 32 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
|
|
2821
|
-
list = { checked: true, ok: out.code === 0 && /context7/i.test(`${out.stdout}\n${out.stderr}`), stdout: out.stdout || '', stderr: out.stderr || '' };
|
|
2822
|
-
}
|
|
2823
|
-
const result = {
|
|
2824
|
-
ok: false,
|
|
2825
|
-
project: { path: projectPath, ok: hasContext7ConfigText(projectText) },
|
|
2826
|
-
global: { path: globalPath, ok: hasContext7ConfigText(globalText) },
|
|
2827
|
-
codex_mcp_list: list
|
|
2828
|
-
};
|
|
2829
|
-
result.ok = result.project.ok || result.codex_mcp_list.ok || (result.global.ok && !list.checked);
|
|
2830
|
-
return result;
|
|
2831
|
-
}
|
|
2832
2443
|
export async function ensureProjectContext7Config(root, transport = 'local') {
|
|
2833
2444
|
const configPath = path.join(root, '.codex', 'config.toml');
|
|
2834
2445
|
await ensureDir(path.dirname(configPath));
|
|
@@ -3435,14 +3046,14 @@ export async function selftestCodexLb(tmp) {
|
|
|
3435
3046
|
});
|
|
3436
3047
|
if (brokenChain.ok || brokenChain.status !== 'previous_response_not_found' || brokenChain.chain_unhealthy !== true)
|
|
3437
3048
|
throw new Error('selftest: codex-lb response chain health check did not detect previous_response_not_found');
|
|
3438
|
-
//
|
|
3439
|
-
//
|
|
3440
|
-
|
|
3441
|
-
// are preserved untouched even though the schema dropped profile tables.
|
|
3442
|
-
const legacyStamps = ['remote_control = true', 'fast_mode_ui = true', 'codex_git_commit = true', 'computer_use = true', 'browser_use = true', 'browser_use_external = true', 'guardian_approval = true', 'tool_suggest = true', 'plugins = true', '[user.fast_mode]', '[profiles.sks-fast-high]', 'fast_default_opt_out = true'];
|
|
3049
|
+
// 0.144 contract: removed feature stamps and legacy fast profile tables are
|
|
3050
|
+
// stripped, while stable Computer Use/Browser/ImageGen/plugin flags remain.
|
|
3051
|
+
const legacyStamps = ['remote_control = true', 'fast_mode_ui = true', 'codex_git_commit = true', '[user.fast_mode]', '[profiles.sks-fast-high]', 'fast_default_opt_out = true'];
|
|
3443
3052
|
const survivingLegacy = legacyStamps.filter((stamp) => codexLbConfig.includes(stamp));
|
|
3444
|
-
|
|
3445
|
-
|
|
3053
|
+
const stableCapabilityStamps = ['computer_use = true', 'browser_use = true', 'browser_use_external = true', 'image_generation = true', 'in_app_browser = true', 'guardian_approval = true', 'tool_suggest = true', 'plugins = true'];
|
|
3054
|
+
const missingStableCapabilities = stableCapabilityStamps.filter((stamp) => !codexLbConfig.includes(stamp));
|
|
3055
|
+
if (!codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('apps = true') || survivingLegacy.length || missingStableCapabilities.length || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || hasTopLevelCodexModeLock(codexLbConfig))
|
|
3056
|
+
throw new Error(`selftest: codex-lb setup did not enforce the current feature-flag contract${survivingLegacy.length ? ` — surviving legacy stamps: ${survivingLegacy.join(', ')}` : ''}${missingStableCapabilities.length ? ` — missing stable capabilities: ${missingStableCapabilities.join(', ')}` : ''}`);
|
|
3446
3057
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig))
|
|
3447
3058
|
throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
3448
3059
|
const codexLbLaunch = `source ${path.join(tmp, '.codex', 'sks-codex-lb.env')} && codex`;
|
|
@@ -3454,30 +3065,4 @@ export async function selftestCodexLb(tmp) {
|
|
|
3454
3065
|
if (!madLaunchSource.includes('const lb = await deps.maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb') || !madLaunchSource.includes('bypass_codex_lb') || !madLaunchSource.includes('model_provider="openai"') || !madLaunchSource.includes('codexLbFreshSession: true'))
|
|
3455
3066
|
throw new Error('selftest: MAD launch does not sync codex-lb auth and fresh-session launch options');
|
|
3456
3067
|
}
|
|
3457
|
-
function hasTopLevelCodexModeLock(text = '') {
|
|
3458
|
-
const lines = String(text || '').split('\n');
|
|
3459
|
-
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
3460
|
-
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
3461
|
-
return /(^|\n)\s*model_reasoning_effort\s*=/.test(top);
|
|
3462
|
-
}
|
|
3463
|
-
function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
3464
|
-
const lines = String(text || '').split('\n');
|
|
3465
|
-
const start = lines.findIndex((line) => line.trim() === '[features]');
|
|
3466
|
-
if (start === -1)
|
|
3467
|
-
return false;
|
|
3468
|
-
let end = lines.length;
|
|
3469
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
3470
|
-
const line = lines[i];
|
|
3471
|
-
if (line === undefined)
|
|
3472
|
-
continue;
|
|
3473
|
-
if (/^\s*\[.+\]\s*$/.test(line)) {
|
|
3474
|
-
end = i;
|
|
3475
|
-
break;
|
|
3476
|
-
}
|
|
3477
|
-
}
|
|
3478
|
-
return lines.slice(start + 1, end).some((line) => /^\s*codex_hooks\s*=/.test(line));
|
|
3479
|
-
}
|
|
3480
|
-
function hasCodexUnstableFeatureWarningSuppression(text = '') {
|
|
3481
|
-
return /(^|\n)\s*suppress_unstable_features_warning\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(String(text || ''));
|
|
3482
|
-
}
|
|
3483
3068
|
//# sourceMappingURL=install-helpers.js.map
|