sneakoscope 6.0.1 → 6.0.3

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 (113) hide show
  1. package/README.md +4 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/bin/fast-inline.js +0 -55
  5. package/dist/bin/sks-dispatch.js +0 -22
  6. package/dist/cli/codex-app-command.js +1 -2
  7. package/dist/cli/help-fast.js +2 -2
  8. package/dist/cli/install-helpers.js +66 -37
  9. package/dist/commands/codex-app.js +8 -3
  10. package/dist/commands/codex-lb.js +37 -12
  11. package/dist/config/skills-manifest.json +57 -57
  12. package/dist/core/agents/agent-effort-policy.js +61 -67
  13. package/dist/core/agents/agent-orchestrator.js +3 -3
  14. package/dist/core/agents/agent-proof-evidence.js +16 -2
  15. package/dist/core/agents/agent-role-config.js +17 -10
  16. package/dist/core/agents/agent-roster.js +21 -41
  17. package/dist/core/agents/agent-scheduler.js +6 -5
  18. package/dist/core/agents/agent-schema.js +4 -5
  19. package/dist/core/agents/agent-task-graph.js +1 -1
  20. package/dist/core/agents/agent-work-partition.js +2 -1
  21. package/dist/core/agents/native-cli-session-swarm.js +18 -1
  22. package/dist/core/agents/native-worker-backend-router.js +179 -108
  23. package/dist/core/agents/parallel-write-fixture.js +15 -10
  24. package/dist/core/agents/route-collaboration-ledger.js +2 -2
  25. package/dist/core/agents/runtime-proof-summary.js +21 -3
  26. package/dist/core/auto-review.js +1 -4
  27. package/dist/core/codex/codex-config-guard.js +9 -13
  28. package/dist/core/codex-adapter.js +2 -2
  29. package/dist/core/codex-app/codex-agent-role-sync.js +0 -1
  30. package/dist/core/codex-app/codex-app-fast-ui-repair.js +6 -40
  31. package/dist/core/codex-app/codex-app-restart.js +38 -8
  32. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +1 -7
  33. package/dist/core/codex-app/sks-menubar.js +92 -37
  34. package/dist/core/codex-app.js +26 -4
  35. package/dist/core/codex-control/codex-model-capabilities.js +1 -2
  36. package/dist/core/codex-control/codex-model-metadata.js +18 -7
  37. package/dist/core/codex-control/codex-sdk-config-policy.js +3 -3
  38. package/dist/core/codex-control/codex-task-runner.js +2 -3
  39. package/dist/core/codex-lb/codex-lb-env.js +78 -0
  40. package/dist/core/codex-model-guard.js +24 -87
  41. package/dist/core/commands/basic-cli.js +21 -0
  42. package/dist/core/commands/loop-command.js +2 -2
  43. package/dist/core/commands/naruto-command.js +48 -20
  44. package/dist/core/commands/profile-command.js +14 -5
  45. package/dist/core/commands/qa-loop-command.js +2 -2
  46. package/dist/core/commands/research-command.js +2 -2
  47. package/dist/core/doctor/doctor-codex-startup-repair.js +0 -3
  48. package/dist/core/evaluation.js +1 -1
  49. package/dist/core/feature-fixtures.js +2 -2
  50. package/dist/core/feature-registry.js +1 -1
  51. package/dist/core/hooks-runtime.js +7 -45
  52. package/dist/core/image-ux-review/imagegen-adapter.js +17 -2
  53. package/dist/core/init/skills.js +6 -7
  54. package/dist/core/init.js +6 -20
  55. package/dist/core/loops/loop-concurrency-budget.js +6 -4
  56. package/dist/core/loops/loop-planner.js +1 -1
  57. package/dist/core/loops/loop-runtime.js +2 -2
  58. package/dist/core/loops/loop-scheduler.js +7 -7
  59. package/dist/core/managed-assets/managed-assets-manifest.js +1 -4
  60. package/dist/core/naruto/naruto-concurrency-governor.js +20 -17
  61. package/dist/core/naruto/naruto-loop-mesh.js +3 -3
  62. package/dist/core/naruto/naruto-write-e2e.js +3 -4
  63. package/dist/core/naruto/resource-pressure-monitor.js +5 -2
  64. package/dist/core/provider/model-router.js +77 -36
  65. package/dist/core/release/package-size-budget.js +2 -0
  66. package/dist/core/release/release-gate-batch-runner.js +2 -1
  67. package/dist/core/release/release-gate-resource-governor.js +12 -12
  68. package/dist/core/release/resource-class-budget.js +5 -5
  69. package/dist/core/routes/constants.js +4 -5
  70. package/dist/core/routes/model-mode-router.js +3 -0
  71. package/dist/core/routes.js +8 -10
  72. package/dist/core/structured-output-adapter.js +15 -1
  73. package/dist/core/update/update-migration-state.js +4 -33
  74. package/dist/core/update/update-notice.js +23 -0
  75. package/dist/core/update-check.js +46 -10
  76. package/dist/core/version.js +1 -1
  77. package/dist/scripts/agent-backfill-replenishment-check.js +1 -1
  78. package/dist/scripts/agent-dynamic-pool-check.js +3 -3
  79. package/dist/scripts/agent-dynamic-pool-fixture.js +1 -1
  80. package/dist/scripts/agent-goal-mode-propagation-check.js +1 -1
  81. package/dist/scripts/agent-patch-swarm-gate-lib.js +2 -2
  82. package/dist/scripts/agent-role-config-repair-check.js +9 -5
  83. package/dist/scripts/agent-scheduler-proof-check.js +1 -1
  84. package/dist/scripts/agent-scheduler-proof-hardening-check.js +1 -1
  85. package/dist/scripts/agent-source-intelligence-propagation-check.js +1 -1
  86. package/dist/scripts/agent-strategy-to-lease-wiring-check.js +2 -2
  87. package/dist/scripts/agent-zellij-dynamic-backfill-panes-check.js +1 -1
  88. package/dist/scripts/codex-app-fast-ui-preservation-check.js +2 -2
  89. package/dist/scripts/codex-app-provider-model-ui-check.js +17 -2
  90. package/dist/scripts/codex-app-ui-preservation-check.js +6 -5
  91. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +3 -3
  92. package/dist/scripts/codex-lb-gpt55-fast-profile-check.js +2 -3
  93. package/dist/scripts/doctor-fix-proves-codex-read-check.js +1 -1
  94. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +7 -7
  95. package/dist/scripts/gate-policy-audit-check.js +2 -2
  96. package/dist/scripts/install-update-preserves-config-check.js +4 -4
  97. package/dist/scripts/legacy-upgrade-matrix-check.js +1 -1
  98. package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +2 -1
  99. package/dist/scripts/loop-hardening-check-lib.js +1 -1
  100. package/dist/scripts/naruto-parallelism-mode-check.js +2 -1
  101. package/dist/scripts/naruto-shadow-clone-swarm-check.js +103 -74
  102. package/dist/scripts/npm-publish-performance-check.js +2 -1
  103. package/dist/scripts/packlist-performance-check.js +5 -2
  104. package/dist/scripts/release-aggressive-resource-governor-check.js +2 -2
  105. package/dist/scripts/release-dag-full-coverage-check.js +7 -7
  106. package/dist/scripts/release-gate-existence-audit.js +8 -9
  107. package/dist/scripts/release-parallel-check.js +1 -2
  108. package/dist/scripts/release-real-check.js +1 -1
  109. package/dist/scripts/scheduler-batch-dispatch-check.js +1 -1
  110. package/dist/scripts/sizecheck.js +9 -4
  111. package/dist/scripts/sks-menubar-install-check.js +7 -0
  112. package/dist/scripts/update-default-command-check.js +4 -2
  113. package/package.json +5 -17
package/README.md CHANGED
@@ -25,7 +25,7 @@ sneakoscope is a proof-first Codex trust layer for bounded agent workflows, sear
25
25
 
26
26
  Sneakoscope Codex (`sks`) is a Codex CLI and Codex App harness for people who want parallel AI coding without losing proof. It gives Codex a simple front door, a dynamic worker swarm, a local dashboard, TriWiki project memory, and release gates that separate machine evidence from LLM opinion.
27
27
 
28
- Current release: SKS **6.0.1**. New in this release: GPT-5.6 Codex App model selection repair, Sol/Terra/Luna compatibility, removal of legacy `multi_agent_v2`/Fast-mode config stamps, renewed Codex App feature flags, safer menu bar and doctor repair paths, and release metadata prepared for `npm publish --ignore-scripts`. See [CHANGELOG.md](CHANGELOG.md).
28
+ Current release: SKS **6.0.3**. Normal Codex sessions continue to inherit the complete Codex catalog without silent replacement. Naruto workers now use a scoped, live-catalog-verified GPT-5.6 policy: Terra `xhigh/max` for coding, Sol `max` for refactoring/planning/strategy/integration, and Luna `xhigh/max` for E2E/browser/Computer Use/GUI verification. This release also fixes menu-bar auth restarts, cross-version menu updates, status checkmarks, dashboard readiness, and stale update badges. See [CHANGELOG.md](CHANGELOG.md).
29
29
 
30
30
  ## Install
31
31
 
@@ -96,6 +96,7 @@ It shows the v5 flow: one-line install, `$Plan`, `$Work`/`$Swarm`, `sks review`,
96
96
  - Native capability repair: `sks doctor --fix` (imagegen/Computer Use/Browser Use), `.sneakoscope/reports/native-capability-readiness.json`
97
97
  - Agent bridge for any agent system: `sks mcp-server`, `sks agent-bridge setup`, `SKS_AGENT_MODE=1` — see [docs/AGENT-BRIDGE.md](docs/AGENT-BRIDGE.md)
98
98
  - Release gates: `node ./dist/scripts/release-gate-dag-runner.js --preset release --full`
99
+ - Lifecycle-disabled npm release: run `npm run publish:verify-ignore-scripts` first, then `npm publish --ignore-scripts`; the raw publish command intentionally cannot run build or validation hooks.
99
100
  - Release readiness notes: [docs/release-readiness.md](docs/release-readiness.md) and [CHANGELOG.md](CHANGELOG.md)
100
101
  - Image generation review routes require Codex App `$imagegen`/`gpt-image-2` evidence with recorded output hashes; direct API fallback and mock fixtures do not satisfy full route gates.
101
102
 
@@ -106,6 +107,8 @@ It shows the v5 flow: one-line install, `$Plan`, `$Work`/`$Swarm`, `sks review`,
106
107
  - macOS optional: menu bar integration and `/usr/bin/open`
107
108
  - The menubar icon shows and hides itself automatically as the Codex desktop app launches/quits; set `quit_with_codex: true` in `~/.codex/sks-menubar/config.json` to have the menubar fully quit with Codex instead of just hiding (default `false`).
108
109
  - Native input dialogs (API keys, codex-lb setup) pass secrets to `sks` via `--api-key-stdin` instead of a visible Terminal window or process arguments.
110
+ - Auth/provider changes wait for the ChatGPT/Codex app to exit and reopen by bundle id; a failed restart is reported as a failed menu action.
111
+ - Update installs always rebuild the companion with the newly installed SKS package, preventing a previous-version updater from restoring a stale menu binary.
109
112
  - The menubar dropdown's `View Last Log` item opens the most recent background action's log file, so you don't need to keep a Terminal window open to see command output.
110
113
  - `sks menubar status --json` reports a `codex_sync` object with `bundle_id`, `codex_running`, and `icon_visible_expected` to show Codex-lifecycle detection state.
111
114
  - Zellij optional but recommended for terminal worker panes
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "6.0.1"
79
+ version = "6.0.3"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "6.0.1"
3
+ version = "6.0.3"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -156,58 +156,3 @@ exports.rootJsonFastInline = rootJsonFastInline;
156
156
  exports.doctorJsonFastInline = doctorJsonFastInline;
157
157
  exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
158
158
  exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
159
-
160
- exports.rootJsonFastInline = rootJsonFastInline;
161
- exports.doctorJsonFastInline = doctorJsonFastInline;
162
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
163
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
164
-
165
- exports.rootJsonFastInline = rootJsonFastInline;
166
- exports.doctorJsonFastInline = doctorJsonFastInline;
167
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
168
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
169
-
170
- exports.rootJsonFastInline = rootJsonFastInline;
171
- exports.doctorJsonFastInline = doctorJsonFastInline;
172
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
173
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
174
-
175
- exports.rootJsonFastInline = rootJsonFastInline;
176
- exports.doctorJsonFastInline = doctorJsonFastInline;
177
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
178
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
179
-
180
- exports.rootJsonFastInline = rootJsonFastInline;
181
- exports.doctorJsonFastInline = doctorJsonFastInline;
182
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
183
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
184
-
185
- exports.rootJsonFastInline = rootJsonFastInline;
186
- exports.doctorJsonFastInline = doctorJsonFastInline;
187
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
188
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
189
-
190
- exports.rootJsonFastInline = rootJsonFastInline;
191
- exports.doctorJsonFastInline = doctorJsonFastInline;
192
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
193
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
194
-
195
- exports.rootJsonFastInline = rootJsonFastInline;
196
- exports.doctorJsonFastInline = doctorJsonFastInline;
197
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
198
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
199
-
200
- exports.rootJsonFastInline = rootJsonFastInline;
201
- exports.doctorJsonFastInline = doctorJsonFastInline;
202
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
203
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
204
-
205
- exports.rootJsonFastInline = rootJsonFastInline;
206
- exports.doctorJsonFastInline = doctorJsonFastInline;
207
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
208
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
209
-
210
- exports.rootJsonFastInline = rootJsonFastInline;
211
- exports.doctorJsonFastInline = doctorJsonFastInline;
212
- exports.narutoHelpJsonFastInline = narutoHelpJsonFastInline;
213
- exports.hookUserPromptSubmitPerfInline = hookUserPromptSubmitPerfInline;
@@ -62,25 +62,3 @@ async function runSks(args) {
62
62
  exports.runSks = runSks;
63
63
 
64
64
  exports.runSks = runSks;
65
-
66
- exports.runSks = runSks;
67
-
68
- exports.runSks = runSks;
69
-
70
- exports.runSks = runSks;
71
-
72
- exports.runSks = runSks;
73
-
74
- exports.runSks = runSks;
75
-
76
- exports.runSks = runSks;
77
-
78
- exports.runSks = runSks;
79
-
80
- exports.runSks = runSks;
81
-
82
- exports.runSks = runSks;
83
-
84
- exports.runSks = runSks;
85
-
86
- exports.runSks = runSks;
@@ -1,6 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { codexRemoteControlStatus, formatCodexRemoteControlStatus } from '../core/codex-app.js';
3
- import { forceRequiredCodexModelConfigArgs } from '../core/codex-model-guard.js';
4
3
  export async function codexAppRemoteControlCommand(args = [], opts = {}) {
5
4
  const controlArgs = argsBeforeSeparator(args);
6
5
  if (controlArgs.includes('--help') || controlArgs.includes('-h')) {
@@ -25,7 +24,7 @@ export async function codexAppRemoteControlCommand(args = [], opts = {}) {
25
24
  process.exitCode = 1;
26
25
  return;
27
26
  }
28
- const passthrough = forceRequiredCodexModelConfigArgs(stripSeparator(args));
27
+ const passthrough = stripSeparator(args).map((arg) => String(arg));
29
28
  const spawnFn = opts.spawn || spawn;
30
29
  const code = await spawnInherited(spawnFn, status.codex_cli.bin, ['remote-control', ...passthrough], {
31
30
  cwd: process.cwd(),
@@ -1,8 +1,8 @@
1
1
  import { COMMAND_MANIFEST_LITE } from './command-manifest-lite.js';
2
- const FAST_PACKAGE_VERSION = '5.10.0';
2
+ import { PACKAGE_VERSION } from '../core/version.js';
3
3
  export function helpFast() {
4
4
  console.log(`SKS
5
- SNEAKOSCOPE CODEX v${FAST_PACKAGE_VERSION}
5
+ SNEAKOSCOPE CODEX v${PACKAGE_VERSION}
6
6
 
7
7
  3-pillar frontdoor
8
8
 
@@ -23,7 +23,6 @@ import { extractTomlTable, writeCodexConfigGuarded } from '../core/codex/codex-c
23
23
  import { cleanupCodexConfigBackups, validateCodexConfigRoundTrip } from '../core/codex/codex-config-toml.js';
24
24
  import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
25
25
  import { repairCodexImagegen } from '../core/doctor/imagegen-repair.js';
26
- import { GPT55_CODEX_MODEL, GPT56_CODEX_MODELS, REQUIRED_CODEX_MODEL } from '../core/codex-model-guard.js';
27
26
  const CODEX_LB_PROVIDER_NAME = 'openai';
28
27
  const CODEX_LB_PROVIDER_ENV_KEY = 'CODEX_LB_API_KEY';
29
28
  const CODEX_LB_CANONICAL_FAST_SERVICE_TIER = 'priority';
@@ -276,7 +275,7 @@ async function postinstallHarnessConflictNotice(conflictScan) {
276
275
  console.log(formatHarnessConflictReport(conflictScan, { includePrompt: false }));
277
276
  console.log('\nWhat this means: npm can finish installing the package, but `sks setup` and `sks doctor --fix` will refuse to activate SKS until the conflicting harness is removed with human approval.');
278
277
  console.log('No files were removed by postinstall.');
279
- console.log(`Cleanup requires a human-approved Codex App session. Recommended model: ${REQUIRED_CODEX_MODEL}, reasoning: high.`);
278
+ console.log('Cleanup requires a human-approved Codex App session. Keep the model selected in Codex and use high reasoning effort.');
280
279
  if (shouldAskPostinstallQuestion()) {
281
280
  const answer = await askPostinstallQuestion('Show the cleanup prompt now? [y/N] ');
282
281
  if (/^(y|yes|예|네|응)$/i.test(answer.trim())) {
@@ -902,7 +901,9 @@ export async function checkCodexLbResponseChain(status = {}, opts = {}) {
902
901
  const fetchImpl = opts.fetch || globalThis.fetch;
903
902
  if (typeof fetchImpl !== 'function')
904
903
  return { ok: true, status: 'skipped', skipped: true, reason: 'fetch unavailable' };
905
- const model = opts.model || env.SKS_CODEX_MODEL || REQUIRED_CODEX_MODEL;
904
+ const model = String(opts.model || env.SKS_CODEX_MODEL || env.CODEX_MODEL || '').trim();
905
+ if (!model)
906
+ return { ok: true, status: 'skipped', skipped: true, reason: 'model_unselected_use_explicit_model_or_codex_catalog' };
906
907
  const timeoutMs = Number(opts.timeoutMs || env.SKS_CODEX_LB_CHAIN_CHECK_TIMEOUT_MS || 8000);
907
908
  const serviceTier = opts.fastMode === true || opts.serviceTier === 'fast' || opts.serviceTier === CODEX_LB_CANONICAL_FAST_SERVICE_TIER
908
909
  ? CODEX_LB_CANONICAL_FAST_SERVICE_TIER
@@ -1396,6 +1397,30 @@ export async function releaseCodexLbAuthHold(opts = {}) {
1396
1397
  const authPath = opts.authPath || codexAuthPath(home);
1397
1398
  const backupPath = opts.backupPath || codexAuthChatgptBackupPath(home);
1398
1399
  const configPath = opts.configPath || codexLbConfigPath(home);
1400
+ const currentAuthText = await readText(authPath, '');
1401
+ const trimmedCurrent = currentAuthText.trim();
1402
+ // Repeated "Use ChatGPT OAuth" is idempotent. If OAuth/browser auth is
1403
+ // already active, a historical backup is unnecessary; only ensure that the
1404
+ // codex-lb provider is no longer selected.
1405
+ const currentAuthMode = codexAuthModeSummary(currentAuthText);
1406
+ if (!opts.force && (currentAuthMode.mode === 'chatgpt_oauth' || currentAuthMode.mode === 'browser_marker')) {
1407
+ let providerUnselected = false;
1408
+ let providerError = null;
1409
+ if (!opts.keepProvider) {
1410
+ 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';
1415
+ }
1416
+ return {
1417
+ status: providerError ? 'failed' : 'already_chatgpt',
1418
+ auth_path: authPath,
1419
+ backup_path: backupPath,
1420
+ provider_unselected: providerUnselected,
1421
+ provider_error: providerError
1422
+ };
1423
+ }
1399
1424
  const backupExists = await exists(backupPath);
1400
1425
  const backupText = backupExists ? await readText(backupPath, '') : '';
1401
1426
  if (!backupExists || !backupText.trim()) {
@@ -1415,8 +1440,6 @@ export async function releaseCodexLbAuthHold(opts = {}) {
1415
1440
  provider_unselected: false
1416
1441
  };
1417
1442
  }
1418
- const currentAuthText = await readText(authPath, '');
1419
- const trimmedCurrent = currentAuthText.trim();
1420
1443
  // If auth.json already looks like ChatGPT OAuth (user re-logged in some other way), don't
1421
1444
  // clobber it — but still honor the deselect request so the OAuth blob takes effect.
1422
1445
  if (trimmedCurrent && hasChatgptOAuthTokens(currentAuthText) && !opts.force) {
@@ -1894,10 +1917,9 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1894
1917
  // `default_profile`, `[profiles.<name>]` tables, `[user.fast_mode]`, and
1895
1918
  // `notice.fast_default_opt_out`. The only documented fast-default mechanism is
1896
1919
  // the plain top-level `service_tier = "fast"`. This pass strips every legacy
1897
- // stamp SKS ever wrote and keeps user-authored top-level choices intact.
1920
+ // schema stamps SKS wrote and keeps user-authored top-level choices intact.
1898
1921
  let next = String(text || '');
1899
1922
  next = removeLegacyTopLevelCodexModeLocks(next);
1900
- next = normalizeCodexAppDefaultModel(next);
1901
1923
  next = removeTopLevelTomlKeyIfValue(next, 'default_profile', 'sks-fast-high');
1902
1924
  next = removeTomlTable(next, 'user.fast_mode');
1903
1925
  next = removeTomlTable(next, 'profiles.sks-fast-high');
@@ -1910,7 +1932,9 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1910
1932
  'browser_use', 'browser_use_external', 'image_generation', 'in_app_browser',
1911
1933
  'guardian_approval', 'tool_suggest', 'plugins'
1912
1934
  ]) {
1913
- next = removeTomlTableKey(next, 'features', legacyFlag);
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');
1914
1938
  }
1915
1939
  if (opts.forceFastMode === true) {
1916
1940
  next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
@@ -1926,6 +1950,10 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1926
1950
  for (const featureLine of ['hooks = true', 'multi_agent = true', 'fast_mode = true', 'apps = true']) {
1927
1951
  next = upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
1928
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');
1929
1957
  // Plugin auto-enable is OPT-IN only. Force-writing `[plugins."name@marketplace"] enabled =
1930
1958
  // true` for marketplace plugins the App may not have installed (different build/channel)
1931
1959
  // makes the App reference plugins it cannot load -> broken/blocked plugin UI. It also
@@ -1967,28 +1995,18 @@ function removeLegacyTopLevelCodexModeLocks(text = '') {
1967
1995
  const lines = String(text || '').split('\n');
1968
1996
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
1969
1997
  const end = firstTable === -1 ? lines.length : firstTable;
1970
- const topLevelModel = topLevelTomlString(text, 'model');
1971
- const removeSksOwnedModeLock = isLegacySksCodexModelStamp(topLevelModel);
1972
1998
  return lines.filter((line, index) => {
1973
1999
  if (index >= end)
1974
2000
  return true;
1975
- if (!removeSksOwnedModeLock)
2001
+ if (!/^\s*(?:model|model_reasoning_effort)\s*=/.test(line))
1976
2002
  return true;
1977
- return !/^\s*model_reasoning_effort\s*=/.test(line);
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
+ });
1978
2008
  }).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
1979
2009
  }
1980
- function normalizeCodexAppDefaultModel(text = '') {
1981
- const currentModel = topLevelTomlString(text, 'model');
1982
- if (!currentModel)
1983
- return upsertTopLevelTomlString(text, 'model', REQUIRED_CODEX_MODEL);
1984
- if (isLegacySksCodexModelStamp(currentModel))
1985
- return upsertTopLevelTomlString(text, 'model', REQUIRED_CODEX_MODEL);
1986
- return String(text || '');
1987
- }
1988
- function isLegacySksCodexModelStamp(value = '') {
1989
- const model = String(value || '').trim().toLowerCase();
1990
- return model === REQUIRED_CODEX_MODEL || model === GPT55_CODEX_MODEL || GPT56_CODEX_MODELS.includes(model);
1991
- }
1992
2010
  function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
1993
2011
  const lines = String(text || '').split('\n');
1994
2012
  const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
@@ -1996,7 +2014,7 @@ function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
1996
2014
  const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?$`);
1997
2015
  return lines.filter((line, index) => index >= end || !keyPattern.test(line)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
1998
2016
  }
1999
- function removeTomlTableKey(text, table, key) {
2017
+ function removeTomlTableKey(text, table, key, expectedValue = null) {
2000
2018
  const lines = String(text || '').trimEnd().split('\n');
2001
2019
  if (lines.length === 1 && lines[0] === '')
2002
2020
  return '';
@@ -2014,7 +2032,10 @@ function removeTomlTableKey(text, table, key) {
2014
2032
  break;
2015
2033
  }
2016
2034
  }
2017
- const keyPattern = new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=`);
2035
+ const valuePattern = expectedValue === null
2036
+ ? ''
2037
+ : `\\s*${escapeRegExp(String(expectedValue))}\\s*(?:#.*)?$`;
2038
+ const keyPattern = new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=${valuePattern}`);
2018
2039
  return lines.filter((line, index) => index <= start || index >= end || !keyPattern.test(line)).join('\n').replace(/\n{3,}/g, '\n\n');
2019
2040
  }
2020
2041
  function upsertTomlTableKey(text, table, line) {
@@ -2168,7 +2189,15 @@ export function codexFastModeDesktopStatus(text = '') {
2168
2189
  service_tier: validation.service_tier ?? null,
2169
2190
  model: validation.model ?? null,
2170
2191
  legacy_keys: validation.legacy_keys,
2171
- validation
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
+ }
2172
2201
  };
2173
2202
  }
2174
2203
  function upsertTopLevelTomlBoolean(text, key, value) {
@@ -2877,8 +2906,8 @@ export async function selftestCodexLb(tmp) {
2877
2906
  // escape that dash emits literally and bash collapses to `"`).
2878
2907
  await writeTextAtomic(codexLbFakeCodex, "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo \"codex-cli 99.0.0\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"status\" ]; then echo \"logged in with browser auth\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"--with-api-key\" ]; then read key; mkdir -p \"$HOME/.codex\"; printf '{\"auth_mode\":\"apikey\",\"OPENAI_API_KEY\":\"%s\"}\\n' \"$key\" > \"$HOME/.codex/auth.json\"; printf '%s\\n' \"$key\" >> \"$HOME/.codex/login-calls.log\"; exit 0; fi\necho \"fake codex unsupported\" >&2\nexit 1\n");
2879
2908
  await fsp.chmod(codexLbFakeCodex, 0o755);
2880
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `model = "${REQUIRED_CODEX_MODEL}"\nmodel_reasoning_effort = "low"\nservice_tier = "fast"\nsuppress_unstable_features_warning = true\n\n[profiles.custom]\nmodel_reasoning_effort = "low"\n\n[notice]\nfast_default_opt_out = true\n\n[features]\nhooks = true\n`);
2881
- const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}`, SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1' };
2909
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `model_reasoning_effort = "low"\nservice_tier = "fast"\nsuppress_unstable_features_warning = true\n\n[profiles.custom]\nmodel_reasoning_effort = "low"\n\n[notice]\nfast_default_opt_out = true\n\n[features]\nhooks = true\n`);
2910
+ const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}`, SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1', SKS_CODEX_MODEL: 'selftest-codex-model' };
2882
2911
  const codexLbSetup = await runProcess(process.execPath, [packagedSksEntrypoint(), 'codex-lb', 'setup', '--host', 'lb.example.test', '--api-key', 'sk-test', '--json'], {
2883
2912
  cwd: tmp,
2884
2913
  env: codexLbEnvForSelftest,
@@ -2982,7 +3011,7 @@ export async function selftestCodexLb(tmp) {
2982
3011
  await postinstall({
2983
3012
  bootstrap: async () => {
2984
3013
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
2985
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `model = "${REQUIRED_CODEX_MODEL}"\nservice_tier = "fast"\nsuppress_unstable_features_warning = true\n\n[features]\nhooks = true\n`);
3014
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `service_tier = "fast"\nsuppress_unstable_features_warning = true\n\n[features]\nhooks = true\n`);
2986
3015
  }
2987
3016
  });
2988
3017
  }
@@ -3008,7 +3037,7 @@ export async function selftestCodexLb(tmp) {
3008
3037
  await writeTextAtomic(path.join(doctorProject, 'package.json'), '{"name":"codex-lb-doctor-project","version":"0.0.0"}\n');
3009
3038
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-test'\n");
3010
3039
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
3011
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `model = "${REQUIRED_CODEX_MODEL}"\nservice_tier = "fast"\nsuppress_unstable_features_warning = true\n\n[features]\nhooks = true\n`);
3040
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `service_tier = "fast"\nsuppress_unstable_features_warning = true\n\n[features]\nhooks = true\n`);
3012
3041
  const codexLbDoctorRepair = await runProcess(process.execPath, [packagedSksEntrypoint(), 'doctor', '--fix', '--json'], {
3013
3042
  cwd: doctorProject,
3014
3043
  env: { ...codexLbEnvForSelftest, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-doctor-global') },
@@ -3201,7 +3230,7 @@ export async function selftestCodexLb(tmp) {
3201
3230
  if (!codexLbLegacyDoctorJson.repair?.codex_lb?.ok || !codexLbLegacyDoctorJson.repair.codex_lb.legacy_auth_migrated || !codexLbLegacyDoctorEnv.includes("CODEX_LB_API_KEY='sk-legacy-doctor'") || !codexLbLegacyDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyDoctorAuth.includes('sk-legacy-doctor') || !hasTopLevelCodexLbSelected(codexLbLegacyDoctorConfig) || !codexLbLegacyDoctorConfig.includes('env_key = "CODEX_LB_API_KEY"'))
3202
3231
  throw new Error('selftest: legacy doctor codex-lb restore');
3203
3232
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\n");
3204
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `model = "${REQUIRED_CODEX_MODEL}"\nservice_tier = "fast"\n`);
3233
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'service_tier = "fast"\n');
3205
3234
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-env-only"}\n');
3206
3235
  const codexLbLoginCallsBeforeEnvOnlyPostinstall = await codexLbLoginCallCount(codexLbHome);
3207
3236
  const codexLbEnvOnlyPostinstall = await runProcess(process.execPath, [packagedSksEntrypoint(), 'postinstall'], {
@@ -3219,7 +3248,7 @@ export async function selftestCodexLb(tmp) {
3219
3248
  if (!String(codexLbEnvOnlyPostinstall.stdout || '').includes('codex-lb auth: restored from existing Codex login cache') || !codexLbEnvOnlyPostinstallEnv.includes("CODEX_LB_API_KEY='sk-env-only'") || !codexLbEnvOnlyPostinstallConfig.includes('env_key = "CODEX_LB_API_KEY"') || !hasTopLevelCodexLbSelected(codexLbEnvOnlyPostinstallConfig) || !codexLbEnvOnlyPostinstallAuth.includes('sk-env-only') || codexLbLoginCallsAfterEnvOnlyPostinstall !== codexLbLoginCallsBeforeEnvOnlyPostinstall)
3220
3249
  throw new Error('selftest: env-only codex-lb postinstall restore');
3221
3250
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\n");
3222
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `model = "${REQUIRED_CODEX_MODEL}"\nservice_tier = "fast"\n`);
3251
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'service_tier = "fast"\n');
3223
3252
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-env-only-doctor"}\n');
3224
3253
  const codexLbEnvOnlyDoctorProject = tmpdir();
3225
3254
  await ensureDir(path.join(codexLbEnvOnlyDoctorProject, '.git'));
@@ -3349,7 +3378,7 @@ export async function selftestCodexLb(tmp) {
3349
3378
  }
3350
3379
  if (autobypassLaunch.status !== 'chain_unhealthy' || autobypassLaunch.bypass_codex_lb !== true || autobypassLaunch.chain_health?.status !== 'second_request_failed')
3351
3380
  throw new Error('selftest: SKS_CODEX_LB_AUTOBYPASS=1 should bypass codex-lb on hard chain failure');
3352
- await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `model = "${REQUIRED_CODEX_MODEL}"\nservice_tier = "fast"\n`);
3381
+ await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'service_tier = "fast"\n');
3353
3382
  await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\nexport CODEX_LB_API_KEY='sk-test'\n");
3354
3383
  const missingProviderLaunchCalls = [];
3355
3384
  const missingProviderLaunch = await maybePromptCodexLbSetupForLaunch([], {
@@ -3416,11 +3445,11 @@ export async function selftestCodexLb(tmp) {
3416
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(', ')}` : ''}`);
3417
3446
  if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig))
3418
3447
  throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
3419
- const codexLbLaunch = `source ${path.join(tmp, '.codex', 'sks-codex-lb.env')} && codex '--model' '${REQUIRED_CODEX_MODEL}'`;
3448
+ const codexLbLaunch = `source ${path.join(tmp, '.codex', 'sks-codex-lb.env')} && codex`;
3420
3449
  if (!codexLbLaunch.includes('sks-codex-lb.env'))
3421
3450
  throw new Error('selftest: Zellij launch command does not source codex-lb env file');
3422
- if (!codexLbLaunch.includes(`'--model' '${REQUIRED_CODEX_MODEL}'`))
3423
- throw new Error(`selftest: Zellij launch command without args did not force ${REQUIRED_CODEX_MODEL}`);
3451
+ if (codexLbLaunch.includes('--model'))
3452
+ throw new Error('selftest: Zellij launch command without an explicit model must inherit the Codex selection');
3424
3453
  const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'core', 'commands', 'mad-sks-command.js'));
3425
3454
  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'))
3426
3455
  throw new Error('selftest: MAD launch does not sync codex-lb auth and fresh-session launch options');
@@ -12,8 +12,11 @@ import { resolveCodexAppExecutionProfile } from '../core/codex-app/codex-app-exe
12
12
  import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
13
13
  import { doctorCodexAppGlmProfile, installCodexAppGlmProfile } from '../core/codex-app/glm-profile-installer.js';
14
14
  import { promptForOpenRouterKeyHidden, writeStoredOpenRouterKey } from '../core/providers/openrouter/openrouter-secret-store.js';
15
+ import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
15
16
  export async function run(_command, args = []) {
16
17
  const action = args[0] || 'check';
18
+ if (action === 'restart')
19
+ return printCodexAppResult(args, await restartCodexApp());
17
20
  if (action === 'remote-control' || action === 'remote')
18
21
  return codexAppRemoteControlCommand(args.slice(1));
19
22
  if (action === 'harness-matrix') {
@@ -47,15 +50,17 @@ export async function run(_command, args = []) {
47
50
  }
48
51
  const record = await writeStoredOpenRouterKey(key);
49
52
  const profile = await installCodexAppGlmProfile({ root, apply: true });
53
+ const restart = await restartCodexApp({ enabled: flag(args, '--restart-app') || flag(args, '--restart') });
50
54
  const result = {
51
55
  schema: 'sks.codex-app-openrouter-key.v1',
52
- ok: profile.ok,
53
- status: profile.ok ? 'stored' : 'stored_profile_blocked',
56
+ ok: Boolean(profile.ok && restart.ok),
57
+ status: !profile.ok ? 'stored_profile_blocked' : restart.ok ? 'stored' : 'stored_restart_blocked',
54
58
  key_preview: record.key_preview,
55
59
  raw_key_recorded: false,
56
60
  secret_store: 'sks-openrouter-secret-store',
57
61
  glm_profile: profile,
58
- blockers: profile.blockers || [],
62
+ restart_app: restart,
63
+ blockers: [...(profile.blockers || []), ...(restart.blockers || [])],
59
64
  warnings: profile.warnings || []
60
65
  };
61
66
  return printCodexAppResult(args, result);
@@ -126,8 +126,10 @@ export async function run(command, args = []) {
126
126
  const host = status.base_url;
127
127
  if (!host) {
128
128
  const result = { schema: 'sks.codex-lb-set-key.v1', ok: false, status: 'not_configured' };
129
- if (flag(args, '--json'))
129
+ if (flag(args, '--json')) {
130
+ process.exitCode = 1;
130
131
  return printJson(result);
132
+ }
131
133
  console.error('codex-lb is not configured yet. Run: sks codex-lb setup --host <domain> --api-key-stdin');
132
134
  process.exitCode = 1;
133
135
  return;
@@ -135,21 +137,26 @@ export async function run(command, args = []) {
135
137
  const newKey = await resolveNewApiKey(args);
136
138
  if (!newKey) {
137
139
  const result = { schema: 'sks.codex-lb-set-key.v1', ok: false, status: 'missing_api_key' };
138
- if (flag(args, '--json'))
140
+ if (flag(args, '--json')) {
141
+ process.exitCode = 1;
139
142
  return printJson(result);
143
+ }
140
144
  console.error('No new API key provided. Run: sks codex-lb set-key --api-key-stdin (or --api-key <key>)');
141
145
  process.exitCode = 1;
142
146
  return;
143
147
  }
144
148
  const result = await configureCodexLb({ host, apiKey: newKey, authMode: flag(args, '--preserve-auth') ? 'preserve' : 'codex-lb', forceCodexLbApiKeyAuth: !flag(args, '--preserve-auth') });
145
149
  const restart = await maybeRestartCodexAppForAuthSwitch(args, Boolean(result.ok));
146
- const output = { ...result, action: 'set-key', restart_app: restart };
150
+ const ok = Boolean(result.ok && restart?.ok !== false);
151
+ const output = { ...result, ok, action: 'set-key', restart_app: restart };
152
+ if (!ok)
153
+ process.exitCode = 1;
147
154
  if (flag(args, '--json'))
148
155
  return printJson(output);
149
156
  console.log(result.ok ? `codex-lb API key updated (${result.base_url || host}).` : `codex-lb key update failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
150
157
  if (restart?.status === 'restarted')
151
158
  console.log('Codex App restarted for the new codex-lb auth mode.');
152
- if (!result.ok)
159
+ if (!ok)
153
160
  process.exitCode = 1;
154
161
  return;
155
162
  }
@@ -157,21 +164,30 @@ export async function run(command, args = []) {
157
164
  // Switch auth mode -> codex-lb (API key). Re-selects the provider and re-syncs auth.
158
165
  const result = await repairCodexLbAuth({ forceCodexLbApiKeyAuth: true, authMode: 'codex-lb', forceFastMode: !flag(args, '--no-fast') });
159
166
  const restart = await maybeRestartCodexAppForAuthSwitch(args, Boolean(result.ok));
167
+ const ok = Boolean(result.ok && restart?.ok !== false);
168
+ const shaped = { ...result, ok, mode: 'codex-lb', restart_app: restart };
169
+ if (!ok)
170
+ process.exitCode = 1;
160
171
  if (flag(args, '--json'))
161
- return printJson({ ...result, mode: 'codex-lb', restart_app: restart });
172
+ return printJson(shaped);
162
173
  console.log(result.ok ? 'Auth mode: codex-lb selected (API key).' : `Switch to codex-lb failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
163
174
  if (restart?.status === 'restarted')
164
175
  console.log('Codex App restarted for codex-lb auth.');
165
- if (!result.ok)
176
+ if (!ok)
166
177
  process.exitCode = 1;
167
178
  return;
168
179
  }
169
180
  if (action === 'use-oauth' || action === 'use-chatgpt') {
170
181
  // Switch auth mode -> ChatGPT OAuth. Restores the saved OAuth login if present.
171
182
  const result = await releaseCodexLbAuthHold({ force: flag(args, '--force') });
172
- const restart = await maybeRestartCodexAppForAuthSwitch(args, !['no_backup', 'auth_in_use', 'failed'].includes(result.status));
183
+ const authOk = !['no_backup', 'auth_in_use', 'failed'].includes(result.status);
184
+ const restart = await maybeRestartCodexAppForAuthSwitch(args, authOk);
185
+ const ok = Boolean(authOk && restart?.ok !== false);
186
+ const shaped = { ...result, ok, mode: 'oauth', restart_app: restart };
187
+ if (!ok)
188
+ process.exitCode = 1;
173
189
  if (flag(args, '--json'))
174
- return printJson({ ...result, mode: 'oauth', restart_app: restart });
190
+ return printJson(shaped);
175
191
  if (result.status === 'no_backup') {
176
192
  console.log('No saved ChatGPT OAuth credentials to restore. Switch to OAuth by logging in:');
177
193
  console.log(' codex login');
@@ -210,8 +226,10 @@ export async function run(command, args = []) {
210
226
  'Or: sks codex-lb setup --host <domain> --api-key-stdin --yes'
211
227
  ]
212
228
  };
213
- if (flag(args, '--json'))
229
+ if (flag(args, '--json')) {
230
+ process.exitCode = 1;
214
231
  return printJson(result);
232
+ }
215
233
  console.error('codex-lb API key is not configured.');
216
234
  console.error('Run:');
217
235
  console.error(' sks codex-lb setup');
@@ -222,6 +240,8 @@ export async function run(command, args = []) {
222
240
  }
223
241
  if (flag(args, '--plan')) {
224
242
  const result = { schema: 'sks.codex-lb-setup-plan-result.v1', ok: plan.blockers.length === 0, plan, writes: false, expected_actions: plan.expected_actions, persistence: plan.persistence };
243
+ if (!result.ok)
244
+ process.exitCode = 1;
225
245
  if (flag(args, '--json'))
226
246
  return printJson(result);
227
247
  process.stdout.write(renderCodexLbSetupPlan(plan));
@@ -235,8 +255,10 @@ export async function run(command, args = []) {
235
255
  const confirm = (await ask('Apply this codex-lb setup plan? [y/N] ')).trim();
236
256
  if (!/^(y|yes|예|네|응)$/i.test(confirm)) {
237
257
  const result = { schema: 'sks.codex-lb-setup.v1', ok: false, status: 'cancelled', plan, applied_actions: [] };
238
- if (flag(args, '--json'))
258
+ if (flag(args, '--json')) {
259
+ process.exitCode = 1;
239
260
  return printJson(result);
261
+ }
240
262
  console.log('codex-lb setup cancelled.');
241
263
  process.exitCode = 1;
242
264
  return;
@@ -298,12 +320,15 @@ export async function run(command, args = []) {
298
320
  shaped.applied_actions = [...(shaped.applied_actions || []), { type: 'run_health_check', target: 'codex-lb response chain', ok: true }];
299
321
  if (options.health)
300
322
  shaped.chain_health = result.ok ? await checkCodexLbResponseChain(result, { force: true, root }) : null;
323
+ shaped.ok = Boolean(result.ok && restart?.ok !== false);
324
+ if (!shaped.ok)
325
+ process.exitCode = 1;
301
326
  if (flag(args, '--json'))
302
327
  return printJson(shaped);
303
328
  console.log(`codex-lb configured: ${result.base_url || result.status}`);
304
329
  if (shaped.persistence?.warning)
305
330
  console.log(`warning: ${shaped.persistence.warning}`);
306
- if (!result.ok)
331
+ if (!shaped.ok)
307
332
  process.exitCode = 1;
308
333
  return;
309
334
  }
@@ -345,7 +370,7 @@ export async function run(command, args = []) {
345
370
  }
346
371
  async function maybeRestartCodexAppForAuthSwitch(args = [], enabled) {
347
372
  if (!enabled)
348
- return { schema: 'sks.codex-app-restart.v1', ok: true, status: 'skipped', skipped: true, reason: 'previous_step_failed', app_name: 'Codex', blockers: [] };
373
+ return { schema: 'sks.codex-app-restart.v1', ok: true, status: 'skipped', skipped: true, reason: 'previous_step_failed', app_name: 'ChatGPT', bundle_id: 'com.openai.codex', blockers: [] };
349
374
  const requested = flag(args, '--restart-app') || flag(args, '--restart');
350
375
  const shouldRestart = requested || (!flag(args, '--json') && !flag(args, '--no-restart-app') && !flag(args, '--no-restart'));
351
376
  return restartCodexApp({ enabled: shouldRestart });