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.
Files changed (378) hide show
  1. package/README.md +29 -12
  2. package/config/codex-releases/{rust-v0.142.0.json → rust-v0.144.1.json} +6 -6
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/bin/fast-inline.js +18 -14
  6. package/dist/bin/sks-dispatch.js +2 -4
  7. package/dist/cli/args.js +2 -0
  8. package/dist/cli/command-manifest-lite.js +2 -3
  9. package/dist/cli/command-registry.js +3 -4
  10. package/dist/cli/help-fast.js +1 -1
  11. package/dist/cli/install-helpers.js +348 -763
  12. package/dist/cli/install-tool-helpers.js +287 -0
  13. package/dist/cli/router.js +91 -6
  14. package/dist/commands/codex-lb.js +188 -53
  15. package/dist/commands/codex.js +44 -5
  16. package/dist/commands/doctor.js +302 -46
  17. package/dist/commands/tmux.js +5 -1
  18. package/dist/commands/versioning.js +7 -0
  19. package/dist/commands/zellij-monitor-pane.js +2 -0
  20. package/dist/commands/zellij-viewport-pane.js +3 -1
  21. package/dist/commands/zellij.js +1 -1
  22. package/dist/config/skills-manifest.json +59 -59
  23. package/dist/core/agent-bridge/agent-manifest.js +48 -0
  24. package/dist/core/agents/agent-central-ledger.js +9 -0
  25. package/dist/core/agents/agent-cleanup-executor.js +334 -41
  26. package/dist/core/agents/agent-cleanup.js +20 -1
  27. package/dist/core/agents/agent-command-surface.js +3 -1
  28. package/dist/core/agents/agent-effort-policy.js +30 -38
  29. package/dist/core/agents/agent-janitor.js +9 -10
  30. package/dist/core/agents/agent-namespace-safety.js +56 -0
  31. package/dist/core/agents/agent-orchestrator.js +25 -3
  32. package/dist/core/agents/agent-output-validator.js +1 -1
  33. package/dist/core/agents/agent-plan.js +100 -21
  34. package/dist/core/agents/agent-role-config.js +43 -49
  35. package/dist/core/agents/agent-runner-codex-exec.js +18 -3
  36. package/dist/core/agents/agent-runner-process.js +1 -0
  37. package/dist/core/agents/agent-schema.js +3 -2
  38. package/dist/core/agents/fast-mode-policy.js +3 -5
  39. package/dist/core/agents/native-cli-session-swarm.js +1 -1
  40. package/dist/core/agents/native-worker-backend-router.js +2 -1
  41. package/dist/core/agents/work-partition/repo-inventory.js +68 -7
  42. package/dist/core/agents/zellij-right-lane-cockpit.js +2 -2
  43. package/dist/core/code-structure.js +5 -4
  44. package/dist/core/codex/agent-config-file-repair.js +94 -193
  45. package/dist/core/codex/codex-cli-update.js +723 -0
  46. package/dist/core/codex/codex-config-guard.js +38 -11
  47. package/dist/core/codex/codex-project-config-policy.js +3 -6
  48. package/dist/core/codex/codex-startup-config-postcheck.js +52 -42
  49. package/dist/core/codex-adapter.js +35 -22
  50. package/dist/core/codex-app/codex-agent-role-sync.js +13 -54
  51. package/dist/core/codex-app/codex-app-fast-ui-repair.js +51 -20
  52. package/dist/core/codex-app/codex-app-launcher.js +48 -17
  53. package/dist/core/codex-app/codex-app-restart.js +23 -2
  54. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +11 -3
  55. package/dist/core/codex-app/sks-menubar.js +136 -5
  56. package/dist/core/codex-app.js +35 -14
  57. package/dist/core/codex-compat/codex-hook-warning-detector.js +10 -1
  58. package/dist/core/codex-compat/codex-release-manifest.js +6 -6
  59. package/dist/core/codex-control/codex-0139-doctor-real-probe.js +7 -1
  60. package/dist/core/codex-control/codex-0139-image-path-real-probe.js +29 -12
  61. package/dist/core/codex-control/codex-0139-probe-runner.js +103 -28
  62. package/dist/core/codex-control/codex-0139-real-probe-summary.js +7 -3
  63. package/dist/core/codex-control/codex-0139-real-probes.js +21 -6
  64. package/dist/core/codex-control/codex-0139-sandbox-real-probe.js +39 -13
  65. package/dist/core/codex-control/codex-0139-web-search-probe.js +28 -14
  66. package/dist/core/codex-control/{codex-0142-capability.js → codex-0144-capability.js} +22 -22
  67. package/dist/core/codex-control/codex-0144-collab-agent-real-probe.js +63 -0
  68. package/dist/core/codex-control/codex-lb-launch-recovery.js +278 -0
  69. package/dist/core/codex-control/codex-multi-agent-event-normalizer.js +5 -1
  70. package/dist/core/codex-control/codex-reliability-shield.js +93 -30
  71. package/dist/core/codex-control/codex-sdk-adapter.js +96 -13
  72. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  73. package/dist/core/codex-control/codex-sdk-sandbox-policy.js +6 -3
  74. package/dist/core/codex-control/codex-task-runner.js +62 -9
  75. package/dist/core/codex-control/python-codex-sdk-adapter.js +30 -3
  76. package/dist/core/codex-exec-output-schema.js +35 -6
  77. package/dist/core/codex-lb/codex-lb-env.js +30 -1
  78. package/dist/core/codex-lb/codex-lb-tool-catalog.js +478 -0
  79. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +291 -0
  80. package/dist/core/codex-native/codex-native-feature-broker.js +31 -38
  81. package/dist/core/codex-native/codex-native-feature-matrix.js +2 -2
  82. package/dist/core/codex-native/codex-native-repair-transaction.js +1 -1
  83. package/dist/core/codex-native/core-skill-manifest.js +8 -4
  84. package/dist/core/codex-native/native-capability-postcheck.js +16 -8
  85. package/dist/core/codex-native/native-capability-repair-matrix.js +76 -27
  86. package/dist/core/codex-plugins/codex-plugin-json.js +67 -21
  87. package/dist/core/codex-plugins/codex-plugin-repair.js +118 -0
  88. package/dist/core/codex-runtime/codex-desktop-config-policy.js +305 -0
  89. package/dist/core/commands/agent-bridge-command.js +23 -2
  90. package/dist/core/commands/agent-command.js +38 -5
  91. package/dist/core/commands/basic-cli.js +77 -30
  92. package/dist/core/commands/command-utils.js +1 -1
  93. package/dist/core/commands/gc-command.js +27 -4
  94. package/dist/core/commands/image-ux-review-command.js +55 -24
  95. package/dist/core/commands/mad-sks-command.js +182 -137
  96. package/dist/core/commands/naruto-command.js +643 -1153
  97. package/dist/core/commands/pipeline-command.js +1 -0
  98. package/dist/core/commands/research-command.js +293 -231
  99. package/dist/core/commands/run-command.js +100 -23
  100. package/dist/core/commands/team-command.js +2 -3
  101. package/dist/core/commands/team-legacy-observe-command.js +94 -359
  102. package/dist/core/commands/wiki-command.js +179 -83
  103. package/dist/core/computer-use-live-evidence.js +166 -3
  104. package/dist/core/computer-use-status.js +5 -2
  105. package/dist/core/daemon/sksd-hook-daemon-entrypoint.js +2 -2
  106. package/dist/core/daemon/sksd-hook-daemon.js +171 -28
  107. package/dist/core/daemon/sksd-hook-dispatch.js +2 -2
  108. package/dist/core/db-safety.js +2 -2
  109. package/dist/core/decision-lattice.js +6 -6
  110. package/dist/core/dfix/verification-selector.js +6 -4
  111. package/dist/core/doctor/browser-use-repair.js +54 -13
  112. package/dist/core/doctor/codex-0139-doctor.js +3 -2
  113. package/dist/core/doctor/codex-startup-config-repair.js +8 -3
  114. package/dist/core/doctor/computer-use-repair.js +28 -39
  115. package/dist/core/doctor/doctor-codex-startup-repair.js +24 -149
  116. package/dist/core/doctor/doctor-context7-repair.js +1 -1
  117. package/dist/core/doctor/doctor-dirty-planner.js +1 -1
  118. package/dist/core/doctor/doctor-native-capability-repair.js +74 -3
  119. package/dist/core/doctor/imagegen-repair.js +47 -12
  120. package/dist/core/doctor/legacy-global-hook-cleanup.js +179 -0
  121. package/dist/core/feature-fixture-executor.js +71 -7
  122. package/dist/core/feature-fixture-runner.js +53 -12
  123. package/dist/core/feature-fixtures.js +47 -14
  124. package/dist/core/feature-registry.js +53 -58
  125. package/dist/core/fsx.js +54 -6
  126. package/dist/core/harness-conflicts.js +19 -10
  127. package/dist/core/hooks-runtime/code-pack-freshness-preflight.js +5 -4
  128. package/dist/core/hooks-runtime/hook-invocation-dedupe.js +147 -0
  129. package/dist/core/hooks-runtime/hook-io.js +14 -4
  130. package/dist/core/hooks-runtime/light-turn.js +70 -0
  131. package/dist/core/hooks-runtime/payload-signals.js +270 -0
  132. package/dist/core/hooks-runtime/team-digest.js +0 -1
  133. package/dist/core/hooks-runtime/tool-output-quarantine.js +93 -0
  134. package/dist/core/hooks-runtime.js +418 -250
  135. package/dist/core/image/image-artifact-path-contract.js +8 -6
  136. package/dist/core/image/image-artifact-registry.js +5 -1
  137. package/dist/core/image-ux-review/imagegen-adapter.js +5 -2
  138. package/dist/core/imagegen/imagegen-capability.js +10 -13
  139. package/dist/core/imagegen/require-imagegen.js +37 -11
  140. package/dist/core/init/skills.js +16 -30
  141. package/dist/core/init.js +144 -36
  142. package/dist/core/managed-assets/managed-assets-manifest.js +417 -14
  143. package/dist/core/mission.js +24 -1
  144. package/dist/core/naruto/naruto-work-graph.js +70 -4
  145. package/dist/core/ops/reporting.js +3 -0
  146. package/dist/core/pipeline-internals/runtime-core.js +578 -180
  147. package/dist/core/pipeline-internals/runtime-gates.js +191 -49
  148. package/dist/core/ppt/style-tokens.js +1 -1
  149. package/dist/core/ppt-review/index.js +1 -1
  150. package/dist/core/ppt-review/slide-exporter.js +23 -17
  151. package/dist/core/ppt-review/slide-imagegen-review.js +1 -1
  152. package/dist/core/ppt-review/slide-issue-extraction.js +4 -2
  153. package/dist/core/preflight/parallel-preflight-engine.js +50 -1
  154. package/dist/core/product-design-plugin.js +2 -2
  155. package/dist/core/proof/route-finalizer.js +1 -1
  156. package/dist/core/proof/route-proof-gate.js +7 -2
  157. package/dist/core/proof-field.js +3 -3
  158. package/dist/core/provider/model-router.js +38 -32
  159. package/dist/core/recallpulse/policy.js +12 -28
  160. package/dist/core/recallpulse.js +11 -6
  161. package/dist/core/release/gate-manifest.js +18 -12
  162. package/dist/core/release/gate-pack-manifest.js +1 -1
  163. package/dist/core/release/npm-pack-proof.js +247 -0
  164. package/dist/core/release/package-dist-snapshot.js +151 -0
  165. package/dist/core/release/package-size-budget.js +5 -0
  166. package/dist/core/release/release-authorization-snapshot.js +115 -0
  167. package/dist/core/release/release-gate-affected-selector.js +15 -3
  168. package/dist/core/release/release-gate-cache-v2.js +30 -0
  169. package/dist/core/release/release-gate-contract.js +161 -0
  170. package/dist/core/release/release-gate-dag.js +44 -3
  171. package/dist/core/release/release-gate-hermetic-env.js +28 -17
  172. package/dist/core/release/release-real-contract.js +496 -0
  173. package/dist/core/release-parallel-full-coverage.js +31 -143
  174. package/dist/core/research/claim-evidence-matrix.js +41 -6
  175. package/dist/core/research/experiment-plan.js +14 -10
  176. package/dist/core/research/falsification.js +9 -2
  177. package/dist/core/research/implementation-blueprint-densifier.js +82 -60
  178. package/dist/core/research/implementation-blueprint.js +32 -26
  179. package/dist/core/research/mock-result.js +122 -11
  180. package/dist/core/research/replication-pack.js +15 -8
  181. package/dist/core/research/research-adversarial-review.js +1068 -0
  182. package/dist/core/research/research-claim-builder.js +69 -17
  183. package/dist/core/research/research-claim-synthesizer.js +343 -0
  184. package/dist/core/research/research-cycle-runner.js +53 -3
  185. package/dist/core/research/research-falsification-runner.js +176 -0
  186. package/dist/core/research/research-final-reviewer.js +44 -125
  187. package/dist/core/research/research-realistic-report.js +14 -6
  188. package/dist/core/research/research-review-artifact-digest.js +66 -0
  189. package/dist/core/research/research-source-evidence.js +144 -0
  190. package/dist/core/research/research-source-layer-catalog.js +68 -0
  191. package/dist/core/research/research-source-ledger-merge.js +250 -12
  192. package/dist/core/research/research-source-shards.js +26 -70
  193. package/dist/core/research/research-stage-runner.js +237 -247
  194. package/dist/core/research/research-super-search.js +184 -0
  195. package/dist/core/research/research-synthesis-prompt.js +20 -1
  196. package/dist/core/research/research-synthesis-writer.js +86 -5
  197. package/dist/core/research/research-work-graph.js +25 -18
  198. package/dist/core/research/source-quality-report.js +21 -0
  199. package/dist/core/research.js +429 -280
  200. package/dist/core/retention/retention-budget.js +25 -6
  201. package/dist/core/retention.js +980 -101
  202. package/dist/core/routes/constants.js +1 -1
  203. package/dist/core/routes/dollar-manifest-lite.js +11 -11
  204. package/dist/core/routes/evidence.js +4 -4
  205. package/dist/core/routes/ppt-policy.js +1 -1
  206. package/dist/core/routes.js +92 -64
  207. package/dist/core/runtime/task-profile.js +66 -0
  208. package/dist/core/runtime/verification-budget.js +12 -0
  209. package/dist/core/secret-redaction.js +4 -0
  210. package/dist/core/security/high-risk-contracts.js +6 -6
  211. package/dist/core/stop-gate/stop-gate-check.js +49 -12
  212. package/dist/core/subagents/agent-catalog.js +215 -0
  213. package/dist/core/subagents/model-policy.js +118 -0
  214. package/dist/core/subagents/naruto-help-contract.js +40 -0
  215. package/dist/core/subagents/official-subagent-config.js +488 -0
  216. package/dist/core/subagents/official-subagent-preparation.js +251 -0
  217. package/dist/core/subagents/official-subagent-prompt.js +137 -0
  218. package/dist/core/subagents/official-subagent-runner.js +129 -0
  219. package/dist/core/subagents/subagent-evidence.js +663 -0
  220. package/dist/core/subagents/terminology.js +16 -0
  221. package/dist/core/subagents/thread-budget.js +21 -0
  222. package/dist/core/subagents/triwiki-attention.js +65 -0
  223. package/dist/core/super-search/runtime-helpers.js +173 -19
  224. package/dist/core/super-search/runtime.js +140 -15
  225. package/dist/core/super-search/source-records.js +49 -8
  226. package/dist/core/team-review-policy.js +15 -0
  227. package/dist/core/triwiki/code-pack.js +8 -2
  228. package/dist/core/triwiki/triwiki-cache-key.js +108 -29
  229. package/dist/core/triwiki/triwiki-module-card.js +1 -1
  230. package/dist/core/triwiki-attention.js +13 -6
  231. package/dist/core/triwiki-provenance.js +274 -0
  232. package/dist/core/triwiki-runtime.js +21 -7
  233. package/dist/core/triwiki-wrongness/wrongness-ledger.js +187 -61
  234. package/dist/core/triwiki-wrongness/wrongness-retrieval.js +11 -2
  235. package/dist/core/triwiki-wrongness/wrongness-trust-policy.js +2 -0
  236. package/dist/core/update-check.js +23 -15
  237. package/dist/core/version-manager.js +51 -1
  238. package/dist/core/version.js +1 -1
  239. package/dist/core/wiki-coordinate.js +128 -12
  240. package/dist/core/work-order-ledger.js +208 -10
  241. package/dist/core/zellij/zellij-command.js +12 -1
  242. package/dist/core/zellij/zellij-dashboard-pane.js +3 -3
  243. package/dist/core/zellij/zellij-fake-adapter.js +3 -1
  244. package/dist/core/zellij/zellij-launcher.js +51 -21
  245. package/dist/core/zellij/zellij-layout-builder.js +14 -5
  246. package/dist/core/zellij/zellij-official-subagent-activity.js +477 -0
  247. package/dist/core/zellij/zellij-official-subagent-telemetry.js +264 -0
  248. package/dist/core/zellij/zellij-pane-proof.js +32 -6
  249. package/dist/core/zellij/zellij-slot-pane-renderer.js +17 -5
  250. package/dist/core/zellij/zellij-slot-telemetry.js +15 -3
  251. package/dist/core/zellij/zellij-ui-mode.js +1 -1
  252. package/dist/core/zellij/zellij-viewport-binder.js +3 -0
  253. package/dist/core/zellij/zellij-worker-pane-manager.js +5 -3
  254. package/dist/scripts/agent-fast-mode-default-check.js +48 -29
  255. package/dist/scripts/agent-role-config-repair-check.js +34 -15
  256. package/dist/scripts/agent-wiki-context-proof-check.js +16 -2
  257. package/dist/scripts/all-feature-deep-completion-check.js +14 -4
  258. package/dist/scripts/architecture-guard-check.js +8 -1
  259. package/dist/scripts/build-dist.js +17 -5
  260. package/dist/scripts/canonical-test-runner.js +188 -0
  261. package/dist/scripts/check-command-module-budget.js +38 -16
  262. package/dist/scripts/check-legacy-free.js +1 -1
  263. package/dist/scripts/check-route-modularity.js +0 -1
  264. package/dist/scripts/check-ts-contracts.js +1 -1
  265. package/dist/scripts/codex-0139-interrupt-agent-check.js +5 -1
  266. package/dist/scripts/{codex-0142-app-server-v2-check.js → codex-0144-app-server-v2-check.js} +11 -10
  267. package/dist/scripts/{codex-0142-binary-identity-check.js → codex-0144-binary-identity-check.js} +4 -4
  268. package/dist/scripts/codex-0144-capability-check.js +21 -0
  269. package/dist/scripts/{codex-0139-real-probes-check.js → codex-0144-core-real-probes-check.js} +19 -5
  270. package/dist/scripts/{codex-0142-doctor-wiring-check.js → codex-0144-doctor-wiring-check.js} +6 -6
  271. package/dist/scripts/{codex-0142-manifest-check.js → codex-0144-manifest-check.js} +11 -3
  272. package/dist/scripts/{codex-0142-policy-check.js → codex-0144-policy-check.js} +2 -2
  273. package/dist/scripts/{codex-0142-thread-store-check.js → codex-0144-thread-store-check.js} +3 -3
  274. package/dist/scripts/codex-app-provider-model-ui-check.js +53 -19
  275. package/dist/scripts/codex-app-ui-preservation-check.js +35 -4
  276. package/dist/scripts/codex-control-all-pipelines-check.js +7 -3
  277. package/dist/scripts/codex-control-side-effect-scope-check.js +2 -2
  278. package/dist/scripts/codex-control-tool-output-continuity-audit-check.js +27 -0
  279. package/dist/scripts/codex-effort-auto-discovery-check.js +1 -1
  280. package/dist/scripts/codex-lb-fast-mode-truth-check.js +1 -0
  281. package/dist/scripts/{codex-lb-gpt55-fast-profile-check.js → codex-lb-gpt56-fast-profile-check.js} +56 -5
  282. package/dist/scripts/codex-model-metadata-check.js +1 -1
  283. package/dist/scripts/codex-native-agent-role-content-check.js +15 -7
  284. package/dist/scripts/codex-native-repair-transaction-check.js +3 -1
  285. package/dist/scripts/codex-sdk-all-pipelines-check.js +7 -4
  286. package/dist/scripts/codex-sdk-research-pipeline-check.js +30 -8
  287. package/dist/scripts/codex-sdk-sandbox-policy-check.js +5 -1
  288. package/dist/scripts/codex-sdk-version-compat-check.js +2 -1
  289. package/dist/scripts/db-route-materialization-check.js +33 -0
  290. package/dist/scripts/dfix-fixture-check.js +1 -1
  291. package/dist/scripts/docs-truthfulness-check.js +4 -2
  292. package/dist/scripts/doctor-codex-startup-repair-check.js +47 -25
  293. package/dist/scripts/doctor-context7-repair-check.js +2 -1
  294. package/dist/scripts/doctor-fix-production-blackbox.js +12 -2
  295. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +34 -7
  296. package/dist/scripts/doctor-imagegen-repair-check.js +14 -4
  297. package/dist/scripts/doctor-startup-config-repair-blackbox.js +9 -3
  298. package/dist/scripts/doctor-startup-config-repair-check.js +13 -3
  299. package/dist/scripts/install-update-preserves-config-check.js +7 -2
  300. package/dist/scripts/legacy-gate-inventory-check.js +7 -5
  301. package/dist/scripts/legacy-strong-inventory-check.js +7 -5
  302. package/dist/scripts/legacy-upgrade-matrix-check.js +4 -4
  303. package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +173 -13
  304. package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +165 -6
  305. package/dist/scripts/managed-role-manifest-parity-check.js +5 -4
  306. package/dist/scripts/mutation-callsite-coverage-check.js +0 -11
  307. package/dist/scripts/naruto-codex-e2e-check.js +14 -7
  308. package/dist/scripts/naruto-shadow-clone-swarm-check.js +4 -201
  309. package/dist/scripts/npm-publish-performance-check.js +20 -12
  310. package/dist/scripts/official-subagent-workflow-check.js +145 -0
  311. package/dist/scripts/package-published-contract-check.js +6 -20
  312. package/dist/scripts/packlist-performance-check.js +17 -3
  313. package/dist/scripts/parallel-verification-engine-check.js +2 -2
  314. package/dist/scripts/postinstall-safe-side-effects-check.js +8 -3
  315. package/dist/scripts/ppt-no-mock-as-real-check.js +3 -1
  316. package/dist/scripts/prepublish-release-check-or-fast.js +23 -86
  317. package/dist/scripts/product-design-plugin-routing-check.js +1 -1
  318. package/dist/scripts/release-affected-selector-check.js +74 -1
  319. package/dist/scripts/release-check-stamp.js +324 -239
  320. package/dist/scripts/release-dag-full-coverage-check.js +7 -15
  321. package/dist/scripts/release-dynamic-presets-check.js +10 -1
  322. package/dist/scripts/release-gate-dag-runner-check.js +3 -3
  323. package/dist/scripts/release-gate-dag-runner.js +35 -11
  324. package/dist/scripts/release-gate-existence-audit.js +1 -2
  325. package/dist/scripts/release-gate-script-parity-check.js +1 -1
  326. package/dist/scripts/release-parallel-full-coverage-check.js +12 -6
  327. package/dist/scripts/release-parallel-speed-budget-check.js +18 -2
  328. package/dist/scripts/release-real-check.js +390 -157
  329. package/dist/scripts/release-registry-check.js +61 -16
  330. package/dist/scripts/release-runtime-truth-matrix-check.js +22 -0
  331. package/dist/scripts/research-blueprint-densifier-check.js +2 -2
  332. package/dist/scripts/research-real-cycle-no-legacy-final-md-check.js +11 -7
  333. package/dist/scripts/route-proof-artifact-structure-check.js +1 -0
  334. package/dist/scripts/search-visibility-gate-lib.js +1 -1
  335. package/dist/scripts/seo-geo-route-identity-check.js +30 -4
  336. package/dist/scripts/sizecheck.js +4 -4
  337. package/dist/scripts/sks-1-11-gate-lib.js +53 -6
  338. package/dist/scripts/sks-3-1-4-directive-check-lib.js +8 -2
  339. package/dist/scripts/sks-3-1-5-directive-check-lib.js +3 -2
  340. package/dist/scripts/sks-3-1-6-directive-check-lib.js +5 -3
  341. package/dist/scripts/sks-menubar-install-check.js +8 -0
  342. package/dist/scripts/sks-uninstall-regression-check.js +1 -1
  343. package/dist/scripts/sksd-daemon-check.js +16 -6
  344. package/dist/scripts/trust-fixture-check.js +32 -10
  345. package/dist/scripts/write-build-manifest.js +2 -1
  346. package/dist/scripts/wrongness-fixture-check.js +1 -1
  347. package/dist/scripts/zellij-layout-valid-check.js +36 -11
  348. package/dist/scripts/zellij-pane-proof-check.js +33 -2
  349. package/dist/scripts/zellij-real-session-cleanup-check.js +122 -2
  350. package/dist/scripts/zellij-real-session-launch-check.js +76 -5
  351. package/dist/scripts/zellij-slot-pane-renderer-check.js +4 -4
  352. package/dist/scripts/zellij-spawn-on-demand-layout-check.js +3 -3
  353. package/docs/demo.tape +1 -1
  354. package/infra-harness-gates.json +1486 -0
  355. package/package.json +32 -19
  356. package/release-gates.v2.json +4060 -0
  357. package/runtime-required-scripts.json +9 -0
  358. package/schemas/codex/{app-server-0.142 → app-server-0.144}/codex_app_server_protocol.v2.schemas.json +1146 -781
  359. package/schemas/codex/codex-0139-real-probe-result.schema.json +39 -5
  360. package/schemas/codex/ppt-slide-issue-ledger.schema.json +2 -1
  361. package/dist/commands/db.js +0 -6
  362. package/dist/core/codex-control/codex-0139-multi-agent-real-probe.js +0 -107
  363. package/dist/core/commands/db-command.js +0 -146
  364. package/dist/core/research/prompt.js +0 -15
  365. package/dist/scripts/agent-native-release-gate.js +0 -274
  366. package/dist/scripts/codex-0142-capability-check.js +0 -21
  367. package/dist/scripts/codex-control-tool-call-sequence-repair-check.js +0 -14
  368. package/dist/scripts/command-performance-scorecard-check.js +0 -204
  369. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +0 -212
  370. package/dist/scripts/dollar-performance-scorecard-check.js +0 -257
  371. package/dist/scripts/gate-timing-check.js +0 -16
  372. package/dist/scripts/ops-maturity-scorecard-check.js +0 -12
  373. package/dist/scripts/performance-baseline-check.js +0 -12
  374. package/dist/scripts/performance-improvement-report-check.js +0 -58
  375. package/dist/scripts/release-parallel-check.js +0 -389
  376. package/dist/scripts/release-runner-efficiency-check.js +0 -15
  377. package/dist/scripts/retention-long-run-smoke-check.js +0 -299
  378. 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 { cleanupCodexConfigBackups, validateCodexConfigRoundTrip } from '../core/codex/codex-config-toml.js';
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 = detectCodexLbSetupDrift({
511
- useDefaultProvider,
512
- writeEnvFile,
513
- storeKeychain,
514
- syncLaunchctl,
515
- shellProfile,
516
- selected: finalCodexLb.selected,
517
- envFile: finalCodexLb.env_file,
518
- keychain,
519
- codexEnvironment,
520
- shellProfileResult,
521
- beforeState,
522
- afterState
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 = [...insecureLocalWarning, ...persistence.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: providerContractOk && envKeyConfigured && Boolean(baseUrl) && authMode.codex_app_usable,
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(opts);
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
- if (!hasTopLevelCodexLbSelected(current))
1374
- return { status: 'not_selected', config_path: configPath };
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
- const next = ensureTrailingNewline(removeTopLevelTomlString(current, 'model_provider'));
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
- return { status: safeWrite.ok ? 'unselected' : safeWrite.status, config_path: configPath, backup_path: safeWrite.backup_path };
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 providerUnselected = false;
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
- if (unselected.status === 'unselected' || unselected.status === 'not_selected')
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
- status: providerError ? 'failed' : 'already_chatgpt',
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: providerUnselected,
1421
- provider_error: providerError
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 providerUnselected = false;
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
- if (unselected.status === 'unselected')
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
- status: 'already_chatgpt',
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: providerUnselected,
1460
- provider_error: providerError
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
- provider_unselected: providerUnselected,
1519
- provider_error: providerError
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
- let status = await codexLbStatus(opts);
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
- // 2026-07 renewal contract: only documented [features] flags remain; the flags,
3439
- // [user.fast_mode], [profiles.sks-fast-high], and notice.fast_default_opt_out SKS
3440
- // used to write must be STRIPPED by setup. User-authored tables ([profiles.custom])
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
- if (!codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('apps = true') || survivingLegacy.length || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || hasTopLevelCodexModeLock(codexLbConfig))
3445
- throw new Error(`selftest: codex-lb setup did not enforce the renewed feature-flag contract (documented flags present, legacy stamps stripped, user profile tables preserved)${survivingLegacy.length ? ` — surviving legacy stamps: ${survivingLegacy.join(', ')}` : ''}`);
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