sneakoscope 7.3.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -1,20 +1,18 @@
1
1
  import Cocoa
2
-
3
2
  final class ProvidersViewController: NSViewController, ControlCenterPage, NSTextFieldDelegate {
4
- private struct CodexLbReadiness {
5
- let selected: Bool
6
- let providerReady: Bool
7
- let authRoutingCoherent: Bool
8
- let sharedOpenAiRoutingSafe: Bool
9
-
10
- var ready: Bool {
11
- selected && providerReady && authRoutingCoherent && sharedOpenAiRoutingSafe
12
- }
13
- }
14
-
3
+ private let capabilityDefinitions: [(label: String, keys: [String])] = [
4
+ ("OAuth Identity", ["oauth_identity", "provider_identity"]), ("Built-in Provider", ["built_in_provider", "provider_identity"]),
5
+ ("Bridge", ["bridge"]), ("Models", ["models", "model_picker", "catalog"]), ("Fast", ["fast", "fast_mode"]),
6
+ ("Image", ["image", "image_generation"]), ("Computer", ["computer", "computer_use"]), ("Browser", ["browser", "browser_use"]),
7
+ ("Voice", ["voice", "voice_mode"]), ("Plugins", ["plugins"])
8
+ ]
9
+ private let capabilityStates = Set(["verified", "available_unverified", "blocked", "unsupported", "skipped"])
10
+ private let cliLaunchCommand = "codex --config model_provider='\"codex-lb\"'"
15
11
  let processClient: ProcessClient
16
12
  let operations: OperationCoordinator
17
- let providerStatus = NativeView.detail("Provider status unchecked.")
13
+ let providerStatus = NativeView.detail("Desktop routing status unchecked.")
14
+ let cliProviderStatus = NativeView.detail("CLI provider status unchecked.")
15
+ let capabilityStatus = NativeView.detail("Capabilities have not been verified.")
18
16
  let openRouterCredentialStatus = NativeView.detail("Credential: checking…")
19
17
  let openRouterActiveStatus = NativeView.detail("Active provider: checking…")
20
18
  let openRouterCatalogStatus = NativeView.detail("Model catalog has not loaded yet.")
@@ -30,9 +28,12 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
30
28
  let multiProvider = MultiProviderRouterControls()
31
29
  let roleStatus = NativeView.detail("Role model settings are loading…")
32
30
  let globalSpinner = NativeView.spinner(label: "Provider operation in progress")
33
- let fastStatus = NativeView.detail("Fast Mode: checking current desktop setting…")
31
+ let fastStatus = NativeView.detail("Codex Fast: checking the official service-tier setting…")
34
32
  let activeProviderBadge = ControlKit.badge("Checking the active Codex provider…", tone: .busy)
33
+ var capabilityRows: [String: NSTextField] = [:]
34
+ var desktopFullRoutingNow = false
35
35
  var codexLbSelectedNow = false
36
+ var chatgptOauthPresentNow = false
36
37
  var openRouterSelectedNow = false
37
38
  var openRouterActiveModel = ""
38
39
  var routerSelectedNow = false
@@ -51,47 +52,59 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
51
52
  var openRouterModelSelectionPending = false
52
53
  var openRouterActionRan = false
53
54
  var busy = false
54
-
55
55
  init(processClient: ProcessClient, operations: OperationCoordinator) {
56
56
  self.processClient = processClient
57
57
  self.operations = operations
58
58
  super.init(nibName: nil, bundle: nil)
59
59
  }
60
60
  required init?(coder: NSCoder) { nil }
61
-
62
61
  override func loadView() {
63
- let setDomain = NativeView.button("Set Domain and Key…", target: self, action: #selector(setDomainAndKey))
64
- let replace = NativeView.button("Replace Key…", target: self, action: #selector(replaceKey))
65
- let testConnection = NativeView.button("Test Connection", target: self, action: #selector(testConnection))
66
- let useOAuth = NativeView.button("Restore Chat / Pro (OAuth)", target: self, action: #selector(useOAuth))
67
- let useLb = NativeView.button("Use codex-lb", target: self, action: #selector(useCodexLb))
68
- let fastOn = NativeView.button("Fast Mode On", target: self, action: #selector(fastOn))
69
- let fastOff = NativeView.button("Fast Mode Off", target: self, action: #selector(fastOff))
70
- useLb.setAccessibilityLabel("Activate codex-lb and restart Codex App")
71
- useOAuth.setAccessibilityLabel("Restore ChatGPT OAuth and restart Codex App")
72
- actionButtons = [setDomain, replace, testConnection, useOAuth, useLb, fastOn, fastOff]
73
-
74
- let codexLb = NativeView.card(
75
- title: "codex-lb",
76
- subtitle: "Credentials and activation are separate. Test the saved endpoint before making codex-lb the active Codex provider.",
77
- views: [providerStatus, NativeView.row([setDomain, replace, testConnection]), NativeView.row([useLb, useOAuth])]
62
+ let enableDesktop = ControlKit.primaryButton("Use Codex LB", target: self, action: #selector(enableDesktopFull))
63
+ let verifyDesktop = NativeView.button("Verify Capabilities", target: self, action: #selector(verifyDesktopCapabilities))
64
+ let disableDesktop = NativeView.button("Use ChatGPT OAuth Only", target: self, action: #selector(disableDesktopRouting))
65
+ enableDesktop.setAccessibilityLabel("Use Codex LB Desktop Full Capability routing while keeping ChatGPT OAuth, then restart Codex App")
66
+ verifyDesktop.setAccessibilityLabel("Verify Desktop transport capabilities")
67
+ disableDesktop.setAccessibilityLabel("Disable Codex LB Desktop routing and keep ordinary ChatGPT OAuth, then restart Codex App")
68
+ // Keep the mode CTAs discoverable: never hide Use Codex LB / Use ChatGPT OAuth Only based on status parsing.
69
+ let configureCli = NativeView.button("Configure / Update…", target: self, action: #selector(setDomainAndKey))
70
+ let useCli = NativeView.button("Use Saved CLI Provider", target: self, action: #selector(useCliProvider))
71
+ let testCli = NativeView.button("Test", target: self, action: #selector(testConnection))
72
+ let copyCli = NativeView.button("Copy CLI Command", target: self, action: #selector(copyCliCommand))
73
+ let fastOn = NativeView.button("Codex Fast On", target: self, action: #selector(fastOn))
74
+ let fastOff = NativeView.button("Codex Fast Off", target: self, action: #selector(fastOff))
75
+ actionButtons = [enableDesktop, verifyDesktop, disableDesktop, configureCli, useCli, testCli, copyCli, fastOn, fastOff]
76
+ let desktop = NativeView.card(
77
+ title: "Codex LB · Desktop Full Capability (Recommended)",
78
+ subtitle: "Selectable Codex LB mode: keeps ChatGPT OAuth identity and the built-in OpenAI provider while routing model traffic through the local codex-lb bridge. Use ChatGPT OAuth Only removes LB routing without touching login.",
79
+ views: [providerStatus, ControlKit.actionRow([enableDesktop, verifyDesktop], trailing: [disableDesktop])]
80
+ )
81
+ let cli = NativeView.card(
82
+ title: "Codex LB · Credentials & CLI Provider",
83
+ subtitle: "Saves the official Center Codex LB host and API key (sks-codex-lb store). Desktop routing buttons above consume that same store automatically — no shell source or twin env files.",
84
+ views: [
85
+ cliProviderStatus,
86
+ NativeView.detail("One-off command: \(cliLaunchCommand)"),
87
+ ControlKit.actionRow([configureCli, useCli]),
88
+ ControlKit.actionRow([testCli, copyCli])
89
+ ]
78
90
  )
79
91
  let fast = NativeView.card(
80
- title: "Fast Mode",
81
- subtitle: "Controls the desktop service tier independently from the provider and model selection.",
82
- views: [fastStatus, NativeView.row([fastOn, fastOff])]
92
+ title: "Codex Fast",
93
+ subtitle: "Official Codex speed option: 1.5× faster on supported models. It changes the service tier, not the selected model, Codex-Spark, or reasoning effort.",
94
+ views: [
95
+ fastStatus,
96
+ NativeView.detail("ChatGPT sign-in: GPT-5.6 and GPT-5.5 use credits at 2.5× Standard; GPT-5.4 uses 2× Standard. API-key Codex uses API token pricing instead, and API Priority processing is a separate billing path."),
97
+ NativeView.row([fastOn, fastOff])
98
+ ]
83
99
  )
84
100
  view = NativeView.page([
85
101
  NativeView.row([NativeView.title("Providers & Models"), globalSpinner]),
86
- NativeView.detail("Save credentials, verify connectivity, then explicitly activate a provider. Secrets are sent through stdin and redacted from logs."),
87
- makeActiveProviderCard(),
88
- makeOpenRouterCard(), makeMultiProviderRouterCard(), codexLb, makeRoleModelsCard(), fast
102
+ NativeView.detail("Codex LB and ChatGPT OAuth are explicit Desktop modes below. CLI automation and Fast stay independent. Secrets are sent through stdin and redacted from logs."),
103
+ makeActiveProviderCard(), desktop, cli, makeCapabilityMatrixCard(), makeOpenRouterCard(),
104
+ makeMultiProviderRouterCard(), makeRoleModelsCard(), fast
89
105
  ])
90
- refresh()
91
106
  }
92
-
93
107
  func refreshOnAppear() { refresh() }
94
-
95
108
  func setBusy(_ value: Bool) {
96
109
  busy = value
97
110
  for button in actionButtons { button.isEnabled = !value }
@@ -105,47 +118,41 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
105
118
  if value { globalSpinner.startAnimation(nil) }
106
119
  else { globalSpinner.stopAnimation(nil) }
107
120
  }
108
-
109
121
  private func run(_ args: [String], title: String, kind: String, group: String?, timeout: TimeInterval = NativeView.mutationTimeout, completion: (() -> Void)? = nil) {
110
- guard !busy else {
111
- providerStatus.stringValue = "Another provider action is already running."
112
- return
113
- }
122
+ guard !busy else { fastStatus.stringValue = "Another provider action is already running."; return }
114
123
  guard let snapshot = operations.begin(kind: kind, mutationGroup: group, summary: title) else {
115
- providerStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
124
+ fastStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
116
125
  return
117
126
  }
118
- setBusy(true)
119
- providerStatus.stringValue = "\(title)…"
127
+ setBusy(true); fastStatus.stringValue = "\(title)…"
120
128
  _ = operations.update(snapshot, state: .running, stage: "running", progress: nil, summary: title)
121
129
  processClient.run(args, timeout: timeout) { [weak self] result in
122
130
  guard let self = self else { return }
123
131
  self.setBusy(false)
124
- _ = self.operations.update(
125
- snapshot,
126
- state: result.code == 0 ? .succeeded : .failed,
127
- stage: "complete",
128
- progress: 1,
129
- summary: result.code == 0 ? "\(title) completed" : "\(title) failed"
130
- )
131
- if result.code != 0 {
132
- self.providerStatus.stringValue = "\(title) failed · \(NativeView.redactPreview(result.output))"
133
- }
134
- self.refresh()
132
+ _ = self.operations.update(snapshot, state: result.code == 0 ? .succeeded : .failed, stage: "complete", progress: 1, summary: result.code == 0 ? "\(title) completed" : "\(title) failed")
133
+ if result.code != 0 { self.fastStatus.stringValue = "\(title) failed · \(NativeView.redactPreview(result.output))" }
135
134
  completion?()
136
135
  }
137
136
  }
138
-
139
137
  func refresh() {
140
138
  processClient.run(["codex-lb", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in
141
139
  guard let self = self else { return }
142
- guard result.code == 0, let json = self.json(result.output) else {
143
- if !self.busy { self.providerStatus.stringValue = "Provider status unavailable. Retry or open Diagnostics." }
140
+ guard let json = self.json(result.output) else {
141
+ if !self.busy {
142
+ self.providerStatus.stringValue = "Desktop routing status unavailable. No routing mode was assumed."
143
+ self.cliProviderStatus.stringValue = "CLI provider status unavailable. No configuration was assumed."
144
+ }
144
145
  return
145
146
  }
146
- self.codexLbSelectedNow = self.codexLbReadiness(self.codexLbPayload(json)).selected
147
+ let snapshot = ProviderRoutingTruth.snapshot(from: json)
148
+ self.desktopFullRoutingNow = snapshot.desktopFullRouting
149
+ self.codexLbSelectedNow = snapshot.legacyCodexLbSelected
150
+ self.chatgptOauthPresentNow = snapshot.chatgptOauthPresent
147
151
  self.renderActiveProviderSummary()
148
- if !self.busy { self.providerStatus.stringValue = self.describeProviderStatus(json) }
152
+ if !self.busy {
153
+ self.providerStatus.stringValue = self.describeDesktopStatus(snapshot)
154
+ self.cliProviderStatus.stringValue = self.describeCliStatus(snapshot)
155
+ }
149
156
  }
150
157
  refreshOpenRouterStatus()
151
158
  refreshMultiProviderRouterStatus()
@@ -153,223 +160,130 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
153
160
  refreshRoleModels()
154
161
  refreshFastStatus()
155
162
  }
156
-
157
- private func describeProviderStatus(_ json: [String: Any]) -> String {
158
- let codexLb = codexLbPayload(json)
159
- let configured = codexLb["provider_configured"] as? Bool == true
160
- let readiness = codexLbReadiness(codexLb)
161
- let ok = codexLb["ok"] as? Bool == true
162
- let authMode = codexLb["auth_mode"] as? String ?? "unknown"
163
- let keyInShared = codexLb["codex_lb_key_in_shared_auth"] as? Bool == true
164
-
165
- if !configured {
166
- return "Codex LB: not configured. Use Set Domain and Key, then Use codex-lb."
163
+ private func makeCapabilityMatrixCard() -> NSBox {
164
+ var views: [NSView] = [capabilityStatus]
165
+ for definition in capabilityDefinitions {
166
+ let value = NativeView.detail("skipped")
167
+ value.setAccessibilityLabel("\(definition.label) capability state")
168
+ capabilityRows[definition.label] = value
169
+ views.append(ControlKit.keyValueRow(definition.label, value))
167
170
  }
168
- if !readiness.sharedOpenAiRoutingSafe || (keyInShared && !readiness.authRoutingCoherent) {
169
- return "Codex LB: routing unsafe — shared key can hit api.openai.com. Click Use codex-lb to repair."
170
- }
171
- if readiness.ready && ok {
172
- return "Codex LB: active (auth=\(authMode), routing guarded). Restore Chat / Pro switches to ChatGPT OAuth."
171
+ return NativeView.card(title: "Capability Matrix", subtitle: "States come from capability evidence, not configuration alone: verified, available_unverified, blocked, unsupported, or skipped.", views: views)
172
+ }
173
+ private func describeDesktopStatus(_ snapshot: ProviderRoutingTruth.Snapshot) -> String {
174
+ if snapshot.legacyDestructive {
175
+ return "Legacy destructive provider/auth state detected · choose Use Codex LB for migration guidance, or Use ChatGPT OAuth Only to remove SKS-owned routing."
173
176
  }
174
- if readiness.selected && (!readiness.providerReady || !ok) {
175
- return "Codex LB: selected but not ready. Click Use codex-lb or Test Connection."
177
+ if snapshot.mode == "desktop-native-bridge" {
178
+ let oauth = snapshot.chatgptOauthPresent
179
+ ? "ChatGPT OAuth present"
180
+ : "ChatGPT OAuth missing — sign in with ChatGPT, then Use Codex LB"
181
+ return "Codex LB mode: enabled · \(oauth) · built-in OpenAI via bridge. Run Verify Capabilities for evidence."
176
182
  }
177
- if keyInShared {
178
- return "Codex LB: credentials in shared auth but provider not selected. Click Use ChatGPT OAuth or Use codex-lb."
183
+ if snapshot.mode == "disabled" {
184
+ return snapshot.chatgptOauthPresent
185
+ ? "ChatGPT OAuth mode: active · Codex LB Desktop routing disabled. Choose Use Codex LB to switch."
186
+ : "ChatGPT OAuth mode: selected, but ChatGPT sign-in is missing · run codex login."
179
187
  }
180
- return "Codex LB: credentials stored, not selected (auth=\(authMode)). Click Use codex-lb to activate."
181
- }
182
-
183
- private func codexLbPayload(_ json: [String: Any]) -> [String: Any] {
184
- if let codexLb = json["codex_lb"] as? [String: Any] { return codexLb }
185
- return json
186
- }
187
-
188
- private func codexLbReadiness(_ codexLb: [String: Any]) -> CodexLbReadiness {
189
- let sharedOpenAiRouting = codexLb["shared_openai_routing"] as? [String: Any]
190
- return CodexLbReadiness(
191
- selected: codexLb["selected"] as? Bool == true,
192
- providerReady: codexLb["provider_ready"] as? Bool == true,
193
- authRoutingCoherent: codexLb["auth_routing_coherent"] as? Bool == true,
194
- sharedOpenAiRoutingSafe: sharedOpenAiRouting?["safe"] as? Bool == true
195
- )
188
+ return snapshot.chatgptOauthPresent
189
+ ? "ChatGPT OAuth mode: active · Codex LB Desktop routing not enabled. Choose Use Codex LB to switch."
190
+ : "ChatGPT OAuth mode available · ChatGPT sign-in is missing · run codex login, then Use Codex LB if needed."
196
191
  }
197
-
198
- private func modelSelectionSummary(_ json: [String: Any]) -> String? {
199
- guard let modelSelection = json["model_selection"] as? [String: Any] else { return nil }
200
- let model = modelSelection["model"] as? String
201
- let source = modelSelection["source"] as? String
202
- if let model = model, !model.isEmpty, let source = source, !source.isEmpty {
203
- return "model \(model) via \(source)"
192
+ private func describeCliStatus(_ snapshot: ProviderRoutingTruth.Snapshot) -> String {
193
+ if snapshot.cliCredentialsConfigured && snapshot.cliProviderStored {
194
+ return "CLI provider: configured · availability remains unverified until Test succeeds."
204
195
  }
205
- if let model = model, !model.isEmpty { return "model \(model)" }
206
- return nil
207
- }
208
-
209
- private func structuredReason(_ json: [String: Any], codexLb: [String: Any], readiness: CodexLbReadiness) -> String {
210
- if codexLb["env_key_configured"] as? Bool != true { return "API key is missing" }
211
- if (codexLb["base_url"] as? String)?.isEmpty != false { return "base URL is missing" }
212
- if let status = json["status"] as? String,
213
- ["transport_blocked", "first_request_failed", "missing_response_id", "second_request_failed", "previous_response_not_found", "tool_output_recovery_blocked"].contains(status) {
214
- return status.replacingOccurrences(of: "_", with: " ")
196
+ if snapshot.cliProviderStored {
197
+ return "CLI provider: configured, but credentials are incomplete · choose Configure / Update…"
215
198
  }
216
- if !readiness.selected { return "saved credentials are not selected" }
217
- if !readiness.providerReady { return "provider configuration is not ready" }
218
- if !readiness.authRoutingCoherent { return "provider and authentication routing disagree" }
219
- if !readiness.sharedOpenAiRoutingSafe { return "shared OpenAI routing guard is unsafe" }
220
- if let reason = json["reason"] as? String, !reason.isEmpty { return reason.replacingOccurrences(of: "_", with: " ") }
221
- return "response-chain verification did not pass"
199
+ return "CLI provider: not configured · choose Configure / Update…"
222
200
  }
223
-
224
- private func nextAction(status: String, codexLb: [String: Any], readiness: CodexLbReadiness) -> String {
225
- let credentialsSaved = codexLb["env_key_configured"] as? Bool == true
226
- && (codexLb["base_url"] as? String)?.isEmpty == false
227
- if status == "not_configured" {
228
- return credentialsSaved
229
- ? "click Use codex-lb to activate the saved credentials"
230
- : "click Set Domain and Key"
231
- }
232
- if status == "missing_env_key" || status == "missing_base_url" || !credentialsSaved {
233
- return "click Set Domain and Key"
234
- }
235
- if status == "model_unselected" { return "select a Codex model, then retry" }
236
- if !readiness.ready { return "click Use codex-lb; if it still fails, open Diagnostics" }
237
- return "verify the domain, key, and network, then retry"
201
+ private func capabilityPayload(_ json: [String: Any]) -> [String: Any] {
202
+ for key in ["report", "capabilities", "desktop_capabilities", "capability_report"] { if let payload = json[key] as? [String: Any] { return payload } }
203
+ return json
238
204
  }
239
-
240
- private func describeConnectionResult(_ json: [String: Any], processCode: Int32) -> (ok: Bool, message: String) {
241
- let codexLb = codexLbPayload(json)
242
- let readiness = codexLbReadiness(codexLb)
243
- let chainOk = processCode == 0 && json["ok"] as? Bool == true
244
- let status = json["status"] as? String ?? (processCode == 0 ? "unknown" : "command_failed")
245
- let model = modelSelectionSummary(json).map { " · \($0)" } ?? ""
246
- if chainOk {
247
- if readiness.ready {
248
- return (true, "Connection test passed (\(status)) · provider ready · routing safe\(model).")
249
- }
250
- return (true, "Connection test passed (\(status)) · saved credentials reached codex-lb\(model) · Activation required: click Use codex-lb.")
205
+ @discardableResult
206
+ private func renderCapabilityMatrix(_ json: [String: Any]) -> String? {
207
+ let payload = capabilityPayload(json)
208
+ let trustVerified = CapabilityVerificationTruth.deepEvidenceTrusted(in: json)
209
+ var recognized = false
210
+ var rendered: [String] = []
211
+ for definition in capabilityDefinitions {
212
+ let states = definition.keys.compactMap { capabilityState($0, payload: payload, allowVerified: trustVerified) }
213
+ if !states.isEmpty { recognized = true }
214
+ let state = states.max { capabilityStateRank($0) < capabilityStateRank($1) } ?? "skipped"
215
+ rendered.append(state)
216
+ let label = capabilityRows[definition.label]
217
+ label?.stringValue = state
218
+ label?.textColor = capabilityColor(state)
251
219
  }
252
- let reason = structuredReason(json, codexLb: codexLb, readiness: readiness)
253
- let next = nextAction(status: status, codexLb: codexLb, readiness: readiness)
254
- return (false, "Connection test: \(status) · Reason: \(reason) · Next: \(next)\(model).")
220
+ guard recognized else { capabilityStatus.stringValue = "Capability response did not contain recognized evidence. No state was assumed."; return nil }
221
+ let reportedOverall = normalizedCapabilityState(payload["overall"] as? String, source: nil)
222
+ ?? normalizedCapabilityState(payload["state"] as? String, source: nil)
223
+ let overall = reportedOverall == "verified" ? (trustVerified ? "verified" : "available_unverified") : (reportedOverall ?? "available_unverified")
224
+ let verifiedCount = rendered.filter { $0 == "verified" }.count
225
+ let blockers = CapabilityVerificationTruth.blockers(in: json)
226
+ let blockerText = blockers.isEmpty ? "" : " · blockers: \(blockers.prefix(3).joined(separator: ", "))"
227
+ let trustText = reportedOverall == "verified" && !trustVerified ? " · trusted deep evidence missing" : ""
228
+ capabilityStatus.stringValue = "Capabilities: \(overall) · \(verifiedCount)/\(rendered.count) verified\(trustText)\(blockerText)."
229
+ return overall
255
230
  }
256
-
257
- private func describeActivationResult(_ activation: [String: Any]?, status: [String: Any]?, activationCode: Int32, statusCode: Int32) -> (ok: Bool, message: String) {
258
- let authoritative = status ?? activation ?? [:]
259
- let codexLb = codexLbPayload(authoritative)
260
- let readiness = codexLbReadiness(codexLb)
261
- let activationOk = activationCode == 0 && activation?["ok"] as? Bool == true
262
- let postRestart = activation?["post_restart"] as? [String: Any]
263
- let activationStatus = postRestart?["status"] as? String
264
- ?? activation?["status"] as? String
265
- ?? (activationCode == 0 ? "unknown" : "command_failed")
266
- let postStatusOk = status == nil ? activationOk : statusCode == 0
267
- let ready = activationOk && postStatusOk && readiness.ready
268
- if ready {
269
- return (true, "Use codex-lb: active after restart · provider, auth, and routing ready.")
231
+ private func capabilityState(_ key: String, payload: [String: Any], allowVerified: Bool) -> String? {
232
+ let state: String?
233
+ if let evidence = payload[key] as? [String: Any] {
234
+ state = normalizedCapabilityState(evidence["state"] as? String, source: evidence["source"] as? String)
235
+ } else if let rawState = payload[key] as? String {
236
+ state = normalizedCapabilityState(rawState, source: nil)
237
+ } else {
238
+ var listedState: String?
239
+ for candidate in ["verified", "available_unverified", "unsupported", "skipped"] { if let keys = payload[candidate] as? [String], keys.contains(key) { listedState = candidate } }
240
+ if let blocked = payload["blocked"] as? [String: Any], blocked[key] != nil { listedState = "blocked" }
241
+ if key == "oauth_identity", let preserved = payload["oauth_preserved"] as? Bool { listedState = preserved ? "available_unverified" : "blocked" }
242
+ state = listedState
270
243
  }
271
- let reason = structuredReason(authoritative, codexLb: codexLb, readiness: readiness)
272
- let next = nextAction(status: activationStatus, codexLb: codexLb, readiness: readiness)
273
- return (false, "Use codex-lb: \(activationStatus) · Reason: \(reason) · Next: \(next).")
244
+ return state == "verified" && !allowVerified ? "available_unverified" : state
274
245
  }
275
-
276
- private func describeOAuthResult(_ json: [String: Any]?, processCode: Int32) -> (ok: Bool, message: String) {
277
- let noSwitch = "No OAuth switch was assumed."
278
- guard let json = json else {
279
- return (false, "Restore Chat / Pro failed · Invalid JSON. Retry or open Diagnostics. \(noSwitch)")
280
- }
281
- let status = json["status"] as? String ?? (processCode == 0 ? "unknown" : "command_failed")
282
- let mode = json["mode"] as? String
283
- let restartRequired = json["restart_required"] as? Bool == true
284
- let restartPerformed = json["restart_performed"] as? Bool == true
285
- let commandOk = processCode == 0 && json["ok"] as? Bool == true
286
- let ok = commandOk && mode == "oauth" && restartRequired && restartPerformed
287
- if ok {
288
- return (true, "Chat / Pro restored with ChatGPT OAuth · Codex App restarted · codex-lb credentials kept.")
289
- }
290
- if status == "no_backup" {
291
- return (false, "Restore Chat / Pro failed (no_backup) · Run codex login, then retry. \(noSwitch)")
292
- }
293
- if status == "auth_in_use" {
294
- return (false, "Restore Chat / Pro failed (auth_in_use) · Open Diagnostics; auth was not replaced. \(noSwitch)")
295
- }
296
- if !restartPerformed {
297
- return (false, "Restore Chat / Pro failed (restart_not_performed) · Retry, then reopen Codex App. \(noSwitch)")
246
+ private func normalizedCapabilityState(_ state: String?, source: String?) -> String? {
247
+ guard let state = state?.lowercased(), capabilityStates.contains(state) else { return nil }
248
+ if state == "verified", source == "config" || source == "manifest" { return "available_unverified" }
249
+ return state
250
+ }
251
+ private func capabilityStateRank(_ state: String) -> Int {
252
+ switch state {
253
+ case "blocked": return 5
254
+ case "unsupported": return 4
255
+ case "skipped": return 3
256
+ case "available_unverified": return 2
257
+ case "verified": return 1
258
+ default: return 0
298
259
  }
299
- return (false, "Restore Chat / Pro failed (\(status)) · Retry or open Diagnostics. \(noSwitch)")
300
260
  }
301
-
302
- private func refreshFastStatus() {
303
- if !busy { fastStatus.stringValue = "Fast Mode: checking current desktop setting…" }
304
- processClient.run(["fast-mode", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in
305
- guard let self = self else { return }
306
- guard result.code == 0, let json = self.json(result.output),
307
- let global = json["global"] as? [String: Any], let on = global["on"] as? Bool else {
308
- self.fastStatus.stringValue = "Fast Mode: unavailable — no state was assumed."
309
- return
310
- }
311
- let tier = global["service_tier"] as? String ?? (on ? "fast" : "default")
312
- self.fastStatus.stringValue = "Fast Mode: \(on ? "On" : "Off") (service_tier=\(tier))."
261
+ private func capabilityColor(_ state: String) -> NSColor {
262
+ switch state {
263
+ case "verified": return .systemGreen
264
+ case "blocked": return .systemRed
265
+ case "unsupported", "skipped": return .secondaryLabelColor
266
+ default: return .systemOrange
313
267
  }
314
268
  }
315
-
316
269
  func json(_ output: String) -> [String: Any]? {
317
270
  guard let data = output.data(using: .utf8) else { return nil }
318
271
  return try? JSONSerialization.jsonObject(with: data) as? [String: Any]
319
272
  }
320
-
321
273
  @objc private func setDomainAndKey() {
322
274
  guard let window = view.window else { return }
323
- AlertFactory.textSheet(
324
- window: window,
325
- title: "Codex LB Domain",
326
- message: "Enter a hostname or full base URL. https:// is optional — SKS adds https:// and /backend-api/codex when missing.\nExamples: lb.example.com or https://lb.example.com",
327
- secure: false,
328
- placeholder: "https://lb.example.com"
329
- ) { [weak self] host in
275
+ AlertFactory.textSheet(window: window, title: "Codex LB Domain", message: "Enter a hostname or full base URL. https:// is optional — SKS adds https:// and /backend-api/codex when missing.\nExamples: lb.example.com or https://lb.example.com", secure: false, placeholder: "https://lb.example.com") { [weak self] host in
330
276
  guard let self = self, let host = host else { return }
331
- self.promptForKey(window: window, args: ["codex-lb", "setup", "--host", host, "--api-key-stdin", "--yes", "--no-default-provider", "--preserve-auth", "--no-keychain", "--no-restart-app", "--json"], kind: "codex-lb-setup", title: "Save Codex LB credentials")
277
+ self.promptForKey(window: window, args: ["codex-lb", "setup", "--host", host, "--api-key-stdin", "--yes", "--write-env-file", "--keychain", "--json"], kind: "codex-lb-center-setup", title: "Save Codex LB credentials")
332
278
  }
333
279
  }
334
-
335
- @objc private func replaceKey() {
336
- guard let window = view.window else { return }
337
- promptForKey(window: window, args: ["codex-lb", "set-key", "--api-key-stdin", "--preserve-auth", "--restart-app", "--json"], kind: "codex-lb-set-key", title: "Replace Codex LB API key")
338
- }
339
-
340
280
  private func promptForKey(window: NSWindow, args: [String], kind: String, title: String) {
341
- promptForSecretKey(
342
- window: window,
343
- sheetTitle: "Codex LB API Key",
344
- sheetMessage: "Paste your Codex LB API key (usually starts with sk-clb-). It is shown here so you can verify the paste, then sent through stdin and never logged.",
345
- placeholder: "sk-clb-…",
346
- args: args,
347
- kind: kind,
348
- title: title,
349
- statusLabel: providerStatus,
350
- successSummary: "Codex LB API key saved",
351
- failSummary: "Codex LB API key save failed"
352
- )
281
+ promptForSecretKey(window: window, sheetTitle: "Codex LB API Key", sheetMessage: "Paste your Codex LB API key (usually starts with sk-clb-). The field stays masked; the key is sent through stdin and stored as the official SKS Center credential Desktop uses automatically.", placeholder: "sk-clb-…", args: args, kind: kind, title: title, statusLabel: cliProviderStatus, successSummary: "Codex LB credentials saved for Center/Desktop", failSummary: "Codex LB credential save failed")
353
282
  }
354
-
355
- func promptForSecretKey(
356
- window: NSWindow,
357
- sheetTitle: String,
358
- sheetMessage: String,
359
- placeholder: String,
360
- args: [String],
361
- kind: String,
362
- title: String,
363
- statusLabel: NSTextField,
364
- successSummary: String,
365
- failSummary: String
366
- ) {
367
- AlertFactory.textSheet(window: window, title: sheetTitle, message: sheetMessage, secure: false, placeholder: placeholder) { [weak self] key in
283
+ func promptForSecretKey(window: NSWindow, sheetTitle: String, sheetMessage: String, placeholder: String, args: [String], kind: String, title: String, statusLabel: NSTextField, successSummary: String, failSummary: String) {
284
+ AlertFactory.textSheet(window: window, title: sheetTitle, message: sheetMessage, secure: true, placeholder: placeholder) { [weak self] key in
368
285
  guard let self = self, let key = key else { return }
369
- guard !self.busy else {
370
- statusLabel.stringValue = "Another provider action is already running."
371
- return
372
- }
286
+ guard !self.busy else { statusLabel.stringValue = "Another provider action is already running."; return }
373
287
  guard let snapshot = self.operations.begin(kind: kind, mutationGroup: "codex-config", summary: title) else {
374
288
  statusLabel.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
375
289
  return
@@ -380,118 +294,151 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
380
294
  self.processClient.run(args, stdin: key + "\n", timeout: NativeView.mutationTimeout) { [weak self] result in
381
295
  guard let self = self else { return }
382
296
  self.setBusy(false)
383
- let ok = result.code == 0
297
+ let parsed = self.json(result.output)
298
+ let ok = result.code == 0 && parsed?["ok"] as? Bool == true
384
299
  _ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? successSummary : failSummary)
385
- statusLabel.stringValue = ok ? "\(successSummary). Next: test or activate explicitly." : "\(failSummary) · \(NativeView.redactPreview(result.output))"
300
+ statusLabel.stringValue = ok
301
+ ? "\(successSummary). Next: test the saved configuration."
302
+ : "\(failSummary) · \(self.structuredPublicDetail(parsed, fallback: result.output))"
386
303
  self.refresh()
387
304
  }
388
305
  }
389
306
  }
390
-
391
- @objc private func testConnection() {
392
- guard !busy else {
393
- providerStatus.stringValue = "Another provider action is already running."
394
- return
395
- }
396
- guard let snapshot = operations.begin(kind: "codex-lb-health", mutationGroup: nil, summary: "Test Codex LB connection") else {
307
+ @objc private func enableDesktopFull() {
308
+ performDesktopRouting(["codex-lb", "use-desktop-full", "--restart-app", "--json"], title: "Use Codex LB", kind: "codex-lb-use-desktop-full", expectedMode: "desktop-native-bridge")
309
+ }
310
+ @objc private func disableDesktopRouting() {
311
+ performDesktopRouting(["codex-lb", "disable", "--restart-app", "--json"], title: "Use ChatGPT OAuth Only", kind: "codex-lb-disable-desktop", expectedMode: "disabled")
312
+ }
313
+ private func performDesktopRouting(_ args: [String], title: String, kind: String, expectedMode: String) {
314
+ guard !busy else { providerStatus.stringValue = "Another provider action is already running."; return }
315
+ guard let snapshot = operations.begin(kind: kind, mutationGroup: "codex-config", summary: title) else {
397
316
  providerStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
398
317
  return
399
318
  }
400
319
  setBusy(true)
401
- providerStatus.stringValue = "Testing Codex LB connection…"
402
- _ = operations.update(snapshot, state: .running, stage: "running", progress: nil, summary: "Test Codex LB connection")
403
- processClient.run(["codex-lb", "health", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
320
+ providerStatus.stringValue = "\(title)…"
321
+ _ = operations.update(snapshot, state: .running, stage: "routing", progress: nil, summary: title)
322
+ processClient.run(args, timeout: NativeView.mutationTimeout) { [weak self] result in
404
323
  guard let self = self else { return }
405
324
  self.setBusy(false)
406
325
  let parsed = self.json(result.output)
407
- let outcome = parsed.map { self.describeConnectionResult($0, processCode: result.code) }
408
- ?? (ok: false, message: "Connection test: invalid response · Reason: health output was not structured JSON · Next: retry or open Diagnostics.")
409
- _ = self.operations.update(
410
- snapshot,
411
- state: outcome.ok ? .succeeded : .failed,
412
- stage: "complete",
413
- progress: 1,
414
- summary: outcome.ok ? "Codex LB connection ready" : "Codex LB connection needs action"
415
- )
416
- self.providerStatus.stringValue = outcome.message
326
+ let routing = parsed.map(ProviderRoutingTruth.snapshot(from:))
327
+ let mode = routing?.mode
328
+ let oauthPreserved = routing.flatMap { $0.oauthPreservedFlag }
329
+ let ok = result.code == 0 && parsed?["ok"] as? Bool == true && mode == expectedMode && oauthPreserved != false
330
+ _ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "\(title) completed" : "\(title) needs action")
331
+ guard ok else {
332
+ self.providerStatus.stringValue = "\(title) was not confirmed · \(self.structuredPublicDetail(parsed, fallback: result.output)) No routing or OAuth change was assumed."
333
+ return
334
+ }
335
+ self.desktopFullRoutingNow = expectedMode == "desktop-native-bridge"
336
+ self.codexLbSelectedNow = false
337
+ self.chatgptOauthPresentNow = routing?.chatgptOauthPresent ?? self.chatgptOauthPresentNow
338
+ self.renderActiveProviderSummary()
339
+ self.providerStatus.stringValue = self.desktopFullRoutingNow
340
+ ? "Codex LB mode enabled · OAuth preserved · capability verification still required."
341
+ : "ChatGPT OAuth mode active · Codex LB Desktop routing disabled."
342
+ self.refresh()
417
343
  }
418
344
  }
419
-
420
- @objc private func useOAuth() {
421
- guard !busy else {
422
- providerStatus.stringValue = "Another provider action is already running."
345
+ @objc private func verifyDesktopCapabilities() {
346
+ guard !busy else { capabilityStatus.stringValue = "Another provider action is already running."; return }
347
+ guard let snapshot = operations.begin(kind: "codex-lb-capabilities", mutationGroup: nil, summary: "Verify Desktop capabilities") else {
348
+ capabilityStatus.stringValue = "Another guarded operation is already running. Wait or open Diagnostics."
423
349
  return
424
350
  }
425
- guard let window = view.window else { return }
426
- AlertFactory.confirmSheet(window: window, title: "Restore Chat / Pro?", message: "Codex App restarts with ChatGPT OAuth. Saved codex-lb credentials stay available for later reuse.", destructive: false) { [weak self] approved in
427
- guard let self = self, approved else { return }
428
- guard let snapshot = self.operations.begin(kind: "codex-lb-use-oauth", mutationGroup: "codex-config", summary: "Restore Chat / Pro") else {
429
- self.providerStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
430
- return
431
- }
432
- self.setBusy(true)
433
- self.providerStatus.stringValue = "Restoring Chat / Pro with ChatGPT OAuth and restarting Codex App…"
434
- _ = self.operations.update(snapshot, state: .running, stage: "switching", progress: nil, summary: "Restore Chat / Pro")
435
- self.processClient.run(["codex-lb", "use-oauth", "--restart-app", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
436
- guard let self = self else { return }
437
- self.setBusy(false)
438
- let outcome = self.describeOAuthResult(self.json(result.output), processCode: result.code)
439
- _ = self.operations.update(
440
- snapshot,
441
- state: outcome.ok ? .succeeded : .failed,
442
- stage: "complete",
443
- progress: 1,
444
- summary: outcome.ok ? "Chat / Pro restored after restart" : "Chat / Pro restore needs action"
445
- )
446
- self.providerStatus.stringValue = outcome.message
447
- self.refreshFastStatus()
351
+ setBusy(true)
352
+ capabilityStatus.stringValue = "Verifying transport capabilities…"
353
+ _ = operations.update(snapshot, state: .running, stage: "verifying", progress: nil, summary: "Verify Desktop capabilities")
354
+ processClient.run(["codex-lb", "capabilities", "--level", "transport", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
355
+ guard let self = self else { return }
356
+ self.setBusy(false)
357
+ let parsed = self.json(result.output)
358
+ let overall = parsed.flatMap(self.renderCapabilityMatrix)
359
+ let verified = result.code == 0
360
+ && parsed?["ok"] as? Bool != false
361
+ && overall == "verified"
362
+ && parsed.map(CapabilityVerificationTruth.deepEvidenceTrusted) == true
363
+ _ = self.operations.update(snapshot, state: verified ? .succeeded : .failed, stage: "complete", progress: 1, summary: verified ? "Desktop capabilities verified" : "Desktop capability verification blocked")
364
+ if overall == nil {
365
+ self.capabilityStatus.stringValue = "Capability verification unavailable · structured evidence was not returned. No capability was assumed."
366
+ } else if !verified {
367
+ let blockers = parsed.map(CapabilityVerificationTruth.blockers) ?? []
368
+ let detail = blockers.isEmpty ? "overall=\(overall ?? "available_unverified")" : blockers.prefix(3).joined(separator: ", ")
369
+ self.capabilityStatus.stringValue = "Capability verification blocked · \(detail). No readiness was assumed."
448
370
  }
449
371
  }
450
372
  }
451
- @objc private func useCodexLb() {
452
- guard !busy else {
453
- providerStatus.stringValue = "Another provider action is already running."
373
+ @objc private func useCliProvider() {
374
+ performCliCommand(["codex-lb", "use-cli", "--json"], title: "Configure saved CLI provider", kind: "codex-lb-use-cli")
375
+ }
376
+ private func performCliCommand(_ args: [String], title: String, kind: String) {
377
+ guard !busy else { cliProviderStatus.stringValue = "Another provider action is already running."; return }
378
+ guard let snapshot = operations.begin(kind: kind, mutationGroup: "codex-config", summary: title) else {
379
+ cliProviderStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
454
380
  return
455
381
  }
456
- guard let window = view.window else { return }
457
- AlertFactory.confirmSheet(window: window, title: "Use codex-lb?", message: "codex-lb becomes the active Codex provider and Codex App restarts.", destructive: false) { [weak self] approved in
458
- guard let self = self, approved else { return }
459
- guard let snapshot = self.operations.begin(kind: "codex-lb-use-lb", mutationGroup: "codex-config", summary: "Use codex-lb") else {
460
- self.providerStatus.stringValue = "Another guarded mutation is already running. Wait or open Diagnostics."
382
+ setBusy(true)
383
+ cliProviderStatus.stringValue = "\(title)…"
384
+ _ = operations.update(snapshot, state: .running, stage: "configuring", progress: nil, summary: title)
385
+ processClient.run(args, timeout: NativeView.mutationTimeout) { [weak self] result in
386
+ guard let self = self else { return }
387
+ self.setBusy(false)
388
+ let parsed = self.json(result.output)
389
+ let routing = parsed.map(ProviderRoutingTruth.snapshot(from:))
390
+ let mode = routing?.mode
391
+ let oauthPreserved = routing.flatMap { $0.oauthPreservedFlag }
392
+ let authMutated = routing.flatMap { $0.authMutated }
393
+ let ok = result.code == 0 && parsed?["ok"] as? Bool == true && mode == "cli-provider" && oauthPreserved != false && authMutated != true
394
+ _ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "CLI provider configured" : "CLI provider configuration needs action")
395
+ self.cliProviderStatus.stringValue = ok
396
+ ? "CLI provider configured · Desktop OAuth and global provider selection unchanged · Test still required."
397
+ : "CLI provider was not confirmed · \(self.structuredPublicDetail(parsed, fallback: result.output))"
398
+ if ok { self.refresh() }
399
+ }
400
+ }
401
+ @objc private func testConnection() {
402
+ guard !busy else { cliProviderStatus.stringValue = "Another provider action is already running."; return }
403
+ guard let snapshot = operations.begin(kind: "codex-lb-health", mutationGroup: nil, summary: "Test CLI provider") else {
404
+ cliProviderStatus.stringValue = "Another guarded operation is already running. Wait or open Diagnostics."
405
+ return
406
+ }
407
+ setBusy(true)
408
+ cliProviderStatus.stringValue = "Testing CLI provider transport…"
409
+ _ = operations.update(snapshot, state: .running, stage: "testing", progress: nil, summary: "Test CLI provider")
410
+ processClient.run(["codex-lb", "health", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
411
+ guard let self = self else { return }
412
+ self.setBusy(false)
413
+ let parsed = self.json(result.output)
414
+ let ok = result.code == 0 && parsed?["ok"] as? Bool == true
415
+ let status = parsed?["status"] as? String ?? (ok ? "verified" : "failed")
416
+ _ = self.operations.update(snapshot, state: ok ? .succeeded : .failed, stage: "complete", progress: 1, summary: ok ? "CLI provider transport verified" : "CLI provider test needs action")
417
+ self.cliProviderStatus.stringValue = ok
418
+ ? "CLI provider transport verified (\(status)). Desktop routing and OAuth were not changed."
419
+ : "CLI provider test failed (\(status)) · \(self.structuredPublicDetail(parsed, fallback: result.output))"
420
+ }
421
+ }
422
+ @objc private func copyCliCommand() {
423
+ NSPasteboard.general.clearContents()
424
+ let copied = NSPasteboard.general.setString(cliLaunchCommand, forType: .string)
425
+ cliProviderStatus.stringValue = copied
426
+ ? "Copied CLI command: \(cliLaunchCommand)"
427
+ : "Could not copy the CLI command. Select it above and copy manually."
428
+ }
429
+ private func refreshFastStatus() {
430
+ if !busy { fastStatus.stringValue = "Codex Fast: checking the official service-tier setting…" }
431
+ processClient.run(["fast-mode", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in
432
+ guard let self = self else { return }
433
+ guard result.code == 0, let json = self.json(result.output),
434
+ let global = json["global"] as? [String: Any], let on = global["on"] as? Bool else {
435
+ self.fastStatus.stringValue = "Codex Fast: unavailable — no state was assumed."
461
436
  return
462
437
  }
463
- self.setBusy(true)
464
- self.providerStatus.stringValue = "Activating codex-lb and restarting Codex App…"
465
- _ = self.operations.update(snapshot, state: .running, stage: "activating", progress: nil, summary: "Use codex-lb")
466
- self.processClient.run(["codex-lb", "use-codex-lb", "--restart-app", "--json"], timeout: NativeView.mutationTimeout) { [weak self] activationResult in
467
- guard let self = self else { return }
468
- let activationJson = self.json(activationResult.output)
469
- _ = self.operations.update(snapshot, state: .running, stage: "verifying", progress: nil, summary: "Verify codex-lb after restart")
470
- self.providerStatus.stringValue = "Verifying codex-lb readiness after restart…"
471
- self.processClient.run(["codex-lb", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] statusResult in
472
- guard let self = self else { return }
473
- self.setBusy(false)
474
- let statusJson = self.json(statusResult.output)
475
- let outcome = self.describeActivationResult(
476
- activationJson,
477
- status: statusJson,
478
- activationCode: activationResult.code,
479
- statusCode: statusResult.code
480
- )
481
- _ = self.operations.update(
482
- snapshot,
483
- state: outcome.ok ? .succeeded : .failed,
484
- stage: "complete",
485
- progress: 1,
486
- summary: outcome.ok ? "codex-lb active after restart" : "codex-lb activation needs action"
487
- )
488
- self.providerStatus.stringValue = outcome.message
489
- self.refreshOpenRouterStatus()
490
- }
491
- }
438
+ let tier = global["service_tier"] as? String ?? (on ? "fast" : "default")
439
+ self.fastStatus.stringValue = "Codex Fast: \(on ? "On" : "Off") · official service_tier=\(tier) · model and reasoning remain separate."
492
440
  }
493
441
  }
494
-
495
- @objc private func fastOn() { run(["fast-mode", "on", "--json"], title: "Fast Mode On", kind: "fast-mode-on", group: "codex-config", timeout: NativeView.statusTimeout) { [weak self] in self?.refreshFastStatus() } }
496
- @objc private func fastOff() { run(["fast-mode", "off", "--json"], title: "Fast Mode Off", kind: "fast-mode-off", group: "codex-config", timeout: NativeView.statusTimeout) { [weak self] in self?.refreshFastStatus() } }
442
+ @objc private func fastOn() { run(["fast-mode", "on", "--json"], title: "Codex Fast On", kind: "fast-mode-on", group: "codex-config", timeout: NativeView.statusTimeout) { [weak self] in self?.refreshFastStatus() } }
443
+ @objc private func fastOff() { run(["fast-mode", "off", "--json"], title: "Codex Fast Off", kind: "fast-mode-off", group: "codex-config", timeout: NativeView.statusTimeout) { [weak self] in self?.refreshFastStatus() } }
497
444
  }