@iislee/opencodex 2.11.0 → 2.35.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 (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
package/src/cli/doctor.ts CHANGED
@@ -10,18 +10,41 @@
10
10
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { dirname, join } from "node:path";
13
- import { getConfigDir, getConfigPath, readConfigDiagnostics, readPid, readRuntimePort, resolveEnvValue } from "../config";
14
- import { findLiveProxy } from "../server/proxy-liveness";
15
- import { gracefulStopHost } from "../lib/process-control";
13
+ import { getConfigDir, getConfigPath, readConfigDiagnostics, resolveEnvValue } from "../config";
14
+ import { readPid } from "../config/process-state";
15
+ import { findLiveProxy, type LiveProxy } from "../server/proxy-liveness";
16
+ import { BUN_RUNTIME_SOURCES } from "../lib/bun-runtime";
17
+ import type { BunRuntimeSource } from "../lib/bun-runtime";
16
18
  import { maskAccountId } from "../lib/privacy";
19
+ import { tokenCollidesWithAdmin } from "../lib/admin-secrets";
20
+ import { readInstalledServiceToken } from "../lib/service-secrets";
17
21
  import { PROXY_ENV_KEYS, proxyEnvPresent } from "../lib/proxy-env";
18
- import { configuredAdminToken } from "../lib/admin-secrets";
22
+ import { LOCAL_MANAGEMENT_READ_PATHS } from "../lib/local-management-capability";
19
23
  import { readCodexTokens } from "../codex/auth-collision";
24
+ import { withNativeMainSharedClaim } from "../codex/native-main-claim";
25
+ import { probeNativeProfileRecoveryState, resolveNativeProfileContext } from "../codex/native-profile-store";
26
+ import { NativeProfileError } from "../codex/native-profile-types";
20
27
  import { collectOrcaCodexHomeDiagnostic, resolveCodexHomeDir as resolveCodexHomeDirImpl, isWslRuntime, listWslWindowsCodexHomes, wslAutomountRoot, type CodexHomeDeps } from "../codex/home";
28
+ import { scanCodexAgentRolesWithTomlModelFallback } from "../codex/subagent-model-fallback";
21
29
  import { findCodexOnPath, isWindowsInteropDir } from "../codex/shim";
22
30
  import { countPendingOpencodexHistory } from "../codex/history-provider";
31
+ import {
32
+ inspectCodexCoordinator,
33
+ recoverZeroByteCodexCoordinator,
34
+ type CodexCoordinatorDiagnostic,
35
+ } from "../codex/coordinator-doctor";
36
+ import {
37
+ inspectAbandonedResponseStateTemps,
38
+ reclaimAbandonedResponseStateTemps,
39
+ type ResponseStateTempRecoveryResult,
40
+ } from "../responses/state";
41
+ import {
42
+ CodexUserIdentityRefusal,
43
+ probeCodexCoordinatorNamespace,
44
+ resolveEffectiveUserIdentity,
45
+ } from "../codex/user-identity";
23
46
  import { collectProjectCodexConfigWarnings, formatProjectCodexConfigWarningsForDoctor } from "../codex/project-config-warnings";
24
- import { collectStartupHealth, startupHealthSummary } from "../codex/autostart-health";
47
+ import { collectStartupHealth, formatStartupRoutingDetail, startupHealthSummary } from "../codex/autostart-health";
25
48
  import {
26
49
  displayCodexRuntimePath,
27
50
  loadLastEffortClamp,
@@ -31,9 +54,41 @@ import {
31
54
  } from "../codex/runtime";
32
55
  import { CODEX_REAUTH_ACTION, collectOAuthHealthEntriesForCli, MASKED_ACCOUNT_FALLBACK, type OAuthHealthEntry } from "../oauth/health";
33
56
  import { getAuthRefreshIntentLockPath, getAuthStorePath } from "../oauth/store";
57
+ import {
58
+ fetchBoundLocalManagementRead,
59
+ type LocalManagementReadDeps,
60
+ } from "../server/local-management-read-client";
34
61
  export { resolveCodexHomeDir } from "../codex/home";
35
62
 
36
- export type OAuthDoctorCheck = { level: "OK" | "WARN"; message: string };
63
+ /**
64
+ * `FAIL` exists for a condition that makes the surface unusable rather than degraded.
65
+ * A review of the #2696 work pointed out that reporting a fully fenced management plane
66
+ * — every `/api/*` returning 503 — at the same level as a directory-permission note
67
+ * misleads the reader about severity.
68
+ *
69
+ * Doctor's own exit code still belongs to the uniform contract in wp3b (devlog 025);
70
+ * this type only fixes what the operator is told.
71
+ */
72
+ export type OAuthDoctorCheck = { level: "OK" | "WARN" | "FAIL"; message: string };
73
+
74
+ /**
75
+ * Whether any FAIL-level condition was seen during this `runDoctor` pass.
76
+ *
77
+ * Module-scoped and reset at the top of `runDoctor` rather than threaded through, because
78
+ * `runDoctor` reports by direct `console.log` across a dozen sections and has no checks
79
+ * collection to inspect. Reset matters for the test suite, which calls `runDoctor` several
80
+ * times in one process; a sticky flag would make the second call fail because the first did.
81
+ */
82
+ let doctorSawFailure = false;
83
+
84
+ function recordDoctorFailure(): void {
85
+ doctorSawFailure = true;
86
+ }
87
+
88
+ /** True when the last `runDoctor` pass saw a FAIL-level condition. */
89
+ export function doctorFailed(): boolean {
90
+ return doctorSawFailure;
91
+ }
37
92
 
38
93
  function pathIsWritable(path: string): boolean {
39
94
  try {
@@ -112,6 +167,50 @@ function describeDoctorHealth(entry: OAuthHealthEntry): string {
112
167
  }
113
168
  }
114
169
 
170
+ /**
171
+ * Detect the management/data-plane credential collision behind #2696.
172
+ *
173
+ * The service exports the service token file as `OPENCODEX_API_AUTH_TOKEN` before
174
+ * starting the proxy. When that value is the admin token, the server treats the
175
+ * management credential as a data-plane admission secret and fences the ENTIRE
176
+ * management plane closed at boot: every `/api/*` returns 503, including on a loopback
177
+ * install that never needed a data-plane secret.
178
+ *
179
+ * `assertNotAdminToken` in src/service.ts now refuses to create this state, but an
180
+ * install made before that guard existed is already broken on disk, and the symptom
181
+ * (every management command failing) points nowhere. This is the check that names it.
182
+ *
183
+ * Observe-only, like the rest of doctor: it compares shapes and never prints, logs, or
184
+ * returns a credential value.
185
+ */
186
+ export function dataPlaneCredentialCollisionCheck(
187
+ env: NodeJS.ProcessEnv = process.env,
188
+ installedServiceToken: string | null = readInstalledServiceToken(),
189
+ ): OAuthDoctorCheck {
190
+ const dataPlane = env.OPENCODEX_API_AUTH_TOKEN?.trim() || installedServiceToken?.trim() || "";
191
+ if (!dataPlane) {
192
+ return { level: "OK", message: "No data-plane token is set, so it cannot collide with the management token." };
193
+ }
194
+ // Same comparison as assertNotAdminToken: minted prefix or configuredAdminToken
195
+ // (env or admin-api-token file). The file token is the one the service wrapper
196
+ // actually exports; inspecting only the doctor process env reported OK on every
197
+ // already-broken install (#2696).
198
+ if (!tokenCollidesWithAdmin(dataPlane, env)) {
199
+ return { level: "OK", message: "Data-plane and management credentials are distinct." };
200
+ }
201
+ return {
202
+ // Not a degradation: while this holds, every /api/* returns 503 and no ocx
203
+ // management command can work at all.
204
+ level: "FAIL",
205
+ message:
206
+ "The data-plane secret (OPENCODEX_API_AUTH_TOKEN or the service token file) holds the "
207
+ + "management (admin) token, so the proxy fences the whole management API closed and "
208
+ + "every ocx management command fails with 503. "
209
+ + "Action: unset OPENCODEX_API_AUTH_TOKEN, replace the service token file with a distinct "
210
+ + "data-plane key, then re-run `ocx service install` and restart the proxy",
211
+ };
212
+ }
213
+
115
214
  /**
116
215
  * OAuth reliability checks for `ocx doctor`. Observe-only: never mutates
117
216
  * credentials, locks, or networking. Every WARN includes a recovery Action.
@@ -122,6 +221,8 @@ export async function collectOAuthDoctorChecks(
122
221
  ): Promise<OAuthDoctorCheck[]> {
123
222
  const checks: OAuthDoctorCheck[] = [];
124
223
 
224
+ checks.push(dataPlaneCredentialCollisionCheck());
225
+
125
226
  if (isOAuthCredentialStorageWritable()) {
126
227
  checks.push({ level: "OK", message: "OAuth credential storage directory is writable for atomic auth.json updates." });
127
228
  } else {
@@ -479,36 +580,75 @@ export type WhamProbeResult = {
479
580
  authenticated: boolean;
480
581
  };
481
582
 
583
+ type NativeMainDoctorClaim = <T>(operation: () => Promise<T>) => Promise<T>;
584
+
585
+ export interface WhamProbeDeps {
586
+ withNativeMainClaim?: NativeMainDoctorClaim;
587
+ probeNativeMainRecoveryState?: typeof probeNativeProfileRecoveryState;
588
+ }
589
+
482
590
  /**
483
591
  * Replicate the runtime WHAM fetch shape (same URL, 8s timeout, main-token
484
592
  * headers when present) so the probe fails exactly where the real path fails.
485
593
  * `fetchImpl` is injectable for testing.
486
594
  */
487
- export async function probeWham(fetchImpl: typeof fetch = fetch): Promise<WhamProbeResult> {
488
- const tokens = readCodexTokens();
489
- const headers: Record<string, string> = {};
490
- if (tokens) {
491
- headers.Authorization = `Bearer ${tokens.access_token}`;
492
- headers["ChatGPT-Account-Id"] = tokens.account_id;
493
- }
595
+ export async function probeWham(
596
+ fetchImpl: typeof fetch = fetch,
597
+ deps: WhamProbeDeps = {},
598
+ ): Promise<WhamProbeResult> {
494
599
  const start = performance.now();
600
+ let authenticated = false;
495
601
  try {
496
- const resp = await fetchImpl(WHAM_USAGE_URL, { headers, signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
497
- const durationMs = Math.round(performance.now() - start);
498
- return {
499
- ok: resp.ok,
500
- status: resp.status,
501
- durationMs,
502
- classification: resp.ok ? "ok" : `http_${resp.status}`,
503
- authenticated: !!tokens,
504
- };
602
+ const context = resolveNativeProfileContext();
603
+ const withClaim = deps.withNativeMainClaim
604
+ ?? (<T>(operation: () => Promise<T>) => withNativeMainSharedClaim(context, operation));
605
+ return await withClaim(async () => {
606
+ const recoveryState = (deps.probeNativeMainRecoveryState ?? probeNativeProfileRecoveryState)(context);
607
+ if (recoveryState !== "none") {
608
+ return {
609
+ ok: false,
610
+ status: null,
611
+ durationMs: Math.round(performance.now() - start),
612
+ classification: `native_main_recovery_${recoveryState}`,
613
+ authenticated: false,
614
+ };
615
+ }
616
+ const tokens = readCodexTokens();
617
+ const headers: Record<string, string> = {};
618
+ if (tokens) {
619
+ headers.Authorization = `Bearer ${tokens.access_token}`;
620
+ headers["ChatGPT-Account-Id"] = tokens.account_id;
621
+ }
622
+ authenticated = !!tokens;
623
+ const resp = await fetchImpl(WHAM_USAGE_URL, { headers, signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
624
+ const durationMs = Math.round(performance.now() - start);
625
+ return {
626
+ ok: resp.ok,
627
+ status: resp.status,
628
+ durationMs,
629
+ classification: resp.ok ? "ok" : `http_${resp.status}`,
630
+ authenticated,
631
+ };
632
+ });
505
633
  } catch (err) {
506
634
  const durationMs = Math.round(performance.now() - start);
635
+ if (
636
+ err instanceof NativeProfileError
637
+ && (err.code === "NATIVE_MAIN_CLAIM_BUSY" || err.code === "NATIVE_MAIN_CLAIM_UNAVAILABLE")
638
+ ) {
639
+ return {
640
+ ok: false,
641
+ status: null,
642
+ durationMs,
643
+ classification: err.code.toLowerCase(),
644
+ authenticated: false,
645
+ };
646
+ }
507
647
  const name = err instanceof Error ? err.name : String(err);
508
648
  const classification = name === "TimeoutError" || name === "AbortError"
509
649
  ? "timeout"
510
650
  : "connect_error";
511
- return { ok: false, status: null, durationMs, classification, authenticated: !!tokens };
651
+ return { ok: false, status: null, durationMs, classification, authenticated };
512
652
  }
513
653
  }
514
654
 
@@ -523,6 +663,8 @@ export async function probeWham(fetchImpl: typeof fetch = fetch): Promise<WhamPr
523
663
  export type ServiceMemoryData = {
524
664
  pid: number;
525
665
  bunVersion: string;
666
+ /** Launch-time provenance; absent for services installed before the marker existed. */
667
+ bunRuntimeSource?: BunRuntimeSource;
526
668
  platform: string;
527
669
  rss: number;
528
670
  heapUsed: number;
@@ -558,20 +700,28 @@ function observedMemory(data: { rss: number; external?: number; arrayBuffers?: n
558
700
  }
559
701
 
560
702
  export async function fetchServiceMemory(
561
- host: string,
562
- port: number,
563
- token: string | null,
564
- fetchImpl: typeof fetch = fetch,
703
+ target: LiveProxy,
704
+ deps: LocalManagementReadDeps = {},
565
705
  ): Promise<ServiceMemoryReport> {
566
706
  try {
567
- const res = await fetchImpl(`http://${host}:${port}/api/system/memory`, {
568
- headers: token ? { "x-opencodex-api-key": token } : {},
569
- signal: AbortSignal.timeout(SERVICE_MEMORY_TIMEOUT_MS),
707
+ const read = await fetchBoundLocalManagementRead(target, LOCAL_MANAGEMENT_READ_PATHS.systemMemory, {
708
+ ...deps,
709
+ timeoutMs: SERVICE_MEMORY_TIMEOUT_MS,
570
710
  });
711
+ if (read.kind === "unavailable") {
712
+ return read.reason === "transport"
713
+ ? { status: "unreachable", error: "fetch failed" }
714
+ : { status: "unauthorized" };
715
+ }
716
+ const { response: res, targetPid } = read;
571
717
  if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
572
718
  if (!res.ok) return { status: "unreachable", error: `http ${res.status}` };
573
719
  const body = await res.json() as Partial<ServiceMemoryData>;
574
- if (typeof body.pid !== "number" || typeof body.bunVersion !== "string" || typeof body.rss !== "number") {
720
+ if (
721
+ body.pid !== targetPid
722
+ || typeof body.bunVersion !== "string"
723
+ || typeof body.rss !== "number"
724
+ ) {
575
725
  return { status: "unreachable", error: "malformed response" };
576
726
  }
577
727
  return {
@@ -579,6 +729,9 @@ export async function fetchServiceMemory(
579
729
  data: {
580
730
  pid: body.pid,
581
731
  bunVersion: body.bunVersion,
732
+ // Allowlisted independently of the server: an unrecognized wire value is
733
+ // treated as absent rather than echoed into user-facing guidance.
734
+ bunRuntimeSource: BUN_RUNTIME_SOURCES.find(source => source === body.bunRuntimeSource),
582
735
  platform: typeof body.platform === "string" ? body.platform : "unknown",
583
736
  rss: body.rss,
584
737
  heapUsed: typeof body.heapUsed === "number" ? body.heapUsed : 0,
@@ -612,12 +765,118 @@ export async function fetchServiceMemory(
612
765
 
613
766
  const mb = (bytes: number): string => `${Math.round(bytes / (1024 * 1024))}MB`;
614
767
 
768
+ export const RECLAIM_RESPONSE_TEMPS_FLAG = "--reclaim-response-temps";
769
+ export const RECOVER_ZERO_BYTE_COORDINATOR_FLAG = "--recover-zero-byte-coordinator";
770
+ /** Matches the dry run's entry bound so report and reclaim agree on a large backlog. */
771
+ const RESPONSE_TEMP_RECLAIM_MAX_CLEANUPS = 4_096;
772
+ /** Names the subsystem: other components mint temps with the same shape and are not covered. */
773
+ const CLEAN_RESPONSE_TEMP_LINE = " ok No abandoned response-state temp files.";
774
+
775
+ /**
776
+ * Render the abandoned-temp section (testable without console capture).
777
+ *
778
+ * Report is the DEFAULT and reclaim is opt-in: `doctor` is a diagnostic an operator runs
779
+ * to understand a machine, so deleting files as a side effect of asking a question is the
780
+ * wrong default even for cache files.
781
+ *
782
+ * Counts come from `eligible`/`eligibleBytes`, never `matched`: `matched` is incremented
783
+ * before the file-type, age, boot-floor, and liveness gates, so reporting it would tell an
784
+ * operator that live-pid temps and young temps are "abandoned".
785
+ */
786
+ export function formatResponseTempLines(
787
+ result: ResponseStateTempRecoveryResult,
788
+ reclaimed: boolean,
789
+ ): string[] {
790
+ if (reclaimed) {
791
+ if (result.removed === 0 && result.failed === 0) return [CLEAN_RESPONSE_TEMP_LINE];
792
+ const lines = [` ok Reclaimed ${result.removed} abandoned response-state temp file(s), ${mb(result.bytesRemoved)} freed.`];
793
+ if (result.failed > 0) {
794
+ // Never "retried automatically": this command exists for the operator whose proxy will
795
+ // NOT start, and in that state nothing retries anything.
796
+ lines.push(` !! ${result.failed} file(s) could not be removed (in use or locked). Retried on the next reclaim — automatically while the proxy runs, otherwise re-run this command.`);
797
+ }
798
+ // `truncated`, not `eligible > removed + failed`: outside a dry run every eligible entry
799
+ // is unlinked or failed on the same iteration it is counted, so those two are always
800
+ // equal and the comparison never fired. An operator with a backlog past the budget was
801
+ // told the reclaim had finished.
802
+ if (result.truncated) {
803
+ lines.push(" !! Cleanup budget reached; files remain. Run the command again to continue.");
804
+ }
805
+ return lines;
806
+ }
807
+ if (result.eligible === 0) return [CLEAN_RESPONSE_TEMP_LINE];
808
+ const lines = [
809
+ ` !! ${result.eligible} abandoned response-state temp file(s), ${mb(result.eligibleBytes)} reclaimable.`,
810
+ " These are interrupted snapshot writes (continuation cache only) and are safe to remove.",
811
+ " Reclaim them with: ocx doctor --reclaim-response-temps",
812
+ ];
813
+ // The dry run skips the cleanup budget but is still bounded by the entry cap, so a large
814
+ // enough backlog makes this a floor rather than a total. Say so instead of letting an
815
+ // operator size the problem from a truncated count.
816
+ if (result.truncated) lines.push(" Scan stopped at its entry budget; the real total is higher.");
817
+ return lines;
818
+ }
819
+
820
+ export function formatCoordinatorDoctorLines(diagnostic: CodexCoordinatorDiagnostic): string[] {
821
+ const pathLine = diagnostic.path ? [` path: ${diagnostic.path}`] : [];
822
+ const evidenceLines = "evidence" in diagnostic && diagnostic.evidence
823
+ ? [
824
+ ` size: ${diagnostic.evidence.sizeBytes} bytes; user_version: ${diagnostic.evidence.schemaVersion}`,
825
+ ` tables: ${diagnostic.evidence.tables.length === 0 ? "none" : diagnostic.evidence.tables.join(", ")}`,
826
+ ` transition rows: ${diagnostic.evidence.transitionRows ?? "not inspected"}; singleton=1 rows: ${diagnostic.evidence.singletonRows ?? "not inspected"}`,
827
+ ]
828
+ : [];
829
+ switch (diagnostic.kind) {
830
+ case "absent":
831
+ return [" ok native-write coordinator not created yet", ...pathLine];
832
+ case "ready":
833
+ return [" ok native-write coordinator has an authoritative transition row", ...pathLine, ...evidenceLines];
834
+ case "zero-byte":
835
+ return [
836
+ " !! native-write coordinator is a zero-byte remnant and has no authority",
837
+ ...pathLine,
838
+ ...evidenceLines,
839
+ ` Action: stop the OpenCodex proxy/service, then run ocx doctor ${RECOVER_ZERO_BYTE_COORDINATOR_FLAG} --yes`,
840
+ ];
841
+ case "unversioned-empty":
842
+ return [
843
+ " !! native-write coordinator is a non-empty unversioned database; automatic recovery is refused",
844
+ ...pathLine,
845
+ ...evidenceLines,
846
+ ];
847
+ case "rowless":
848
+ return [
849
+ " !! native-write coordinator has schema version 1 but no authoritative row; automatic recovery is refused",
850
+ ...pathLine,
851
+ ...evidenceLines,
852
+ ];
853
+ case "unversioned-nonempty":
854
+ return [
855
+ " !! native-write coordinator is unversioned and contains unknown tables; automatic recovery is refused",
856
+ ...pathLine,
857
+ ...evidenceLines,
858
+ ];
859
+ case "unsupported":
860
+ return [
861
+ ` !! native-write coordinator schema version ${diagnostic.version} is unsupported; automatic recovery is refused`,
862
+ ...pathLine,
863
+ ...evidenceLines,
864
+ ];
865
+ case "changed":
866
+ return [" -- native-write coordinator changed during diagnosis; re-run ocx doctor", ...pathLine];
867
+ case "unsafe":
868
+ return [` !! native-write coordinator path is unsafe: ${diagnostic.reason}`, ...pathLine];
869
+ case "unreadable":
870
+ return [` !! native-write coordinator is unreadable: ${diagnostic.reason}`, ...pathLine, ...evidenceLines];
871
+ }
872
+ }
873
+
615
874
  /** Render the doctor "Memory / runtime" section lines (testable without console capture). */
616
875
  export function formatServiceMemoryLines(report: ServiceMemoryReport): string[] {
617
876
  const lines: string[] = [];
618
877
  lines.push(` -- doctor process Bun ${Bun.version} (this is NOT the service process)`);
619
878
  if (report.status === "unauthorized") {
620
- lines.push(" -- proxy reachable but rejected the request set OPENCODEX_ADMIN_AUTH_TOKEN to match the service");
879
+ lines.push(" -- local diagnostic capability unavailable restart the running proxy with this OpenCodex version");
621
880
  return lines;
622
881
  }
623
882
  if (report.status === "unreachable") {
@@ -652,12 +911,22 @@ export function formatServiceMemoryLines(report: ServiceMemoryReport): string[]
652
911
  } else {
653
912
  lines.push(" !! high RSS, indeterminate split — capture two doctor runs over time to see the trend");
654
913
  }
655
- // Version-claiming (never binary-claiming): the endpoint cannot distinguish
656
- // the bundled binary from an OPENCODEX_BUN_PATH override of the same version.
657
914
  if (d.platform === "win32" && d.eagerRelay?.reason === "auto-known-bad") {
658
915
  lines.push(` service is running Bun ${d.bunVersion} on Windows — a version affected by the upstream Bun memory issue.`);
659
- lines.push(" Options: wait for a bundled runtime update, or set OPENCODEX_BUN_PATH to a runtime you trust (unvalidated — own risk),");
660
- lines.push(" or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
916
+ // The remediation depends on how the SERVICE was launched, which only the
917
+ // launch-time marker can answer. Telling someone to set OPENCODEX_BUN_PATH
918
+ // when it is already set is the bug this branch exists to avoid (#848).
919
+ if (d.bunRuntimeSource === "override") {
920
+ lines.push(` OPENCODEX_BUN_PATH is already active for this service — the override runtime is itself an affected version (unvalidated — own risk).`);
921
+ lines.push(" Options: point the override at a different runtime, or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
922
+ } else if (d.bunRuntimeSource === undefined) {
923
+ lines.push(" this service records no runtime origin (installed before provenance tracking), so OpenCodex cannot tell whether an override is already active.");
924
+ lines.push(" Reinstall the service to record it, or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
925
+ } else {
926
+ const origin = d.bunRuntimeSource === "process" ? "the runtime that launched it" : "the bundled runtime";
927
+ lines.push(` the service is using ${origin}. Options: wait for a bundled runtime update, or set OPENCODEX_BUN_PATH to a runtime you trust (unvalidated — own risk),`);
928
+ lines.push(" or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
929
+ }
661
930
  }
662
931
  return lines;
663
932
  }
@@ -671,11 +940,21 @@ export function proxyDownRestartHint(input: {
671
940
  proxyRunning: boolean;
672
941
  port: number;
673
942
  serviceViable: boolean;
943
+ /** Absent means "unknown"; the hint then keeps its pre-repair wording. */
944
+ serviceInstalled?: boolean;
945
+ serviceConflict?: boolean;
674
946
  }): string | null {
675
947
  if (input.proxyRunning) return null;
948
+ // `serviceViable` alone conflates "no service at all" with "registered but stale or
949
+ // stopped". Only the first wants `install`: re-registering an existing service costs a
950
+ // UAC prompt on Windows and can switch a WinSW backend to Task Scheduler. A conflict
951
+ // still needs uninstall-then-install, which repairService() refuses outright.
952
+ const installedButBroken = input.serviceInstalled === true && input.serviceConflict !== true;
676
953
  const restart = input.serviceViable
677
954
  ? "Restart it with 'ocx service start' (service installed) or 'ocx start'."
678
- : "Restart it with 'ocx start', or install the persistent service: 'ocx service install'.";
955
+ : installedButBroken
956
+ ? "Restart it with 'ocx start', or refresh the installed service: 'ocx service repair'."
957
+ : "Restart it with 'ocx start', or install the persistent service: 'ocx service install'.";
679
958
  return `The ocx proxy is not running. Codex/Claude clients pinned to 127.0.0.1:${input.port} fail with errors like "error sending request for url (http://127.0.0.1:${input.port}/v1/responses)". ${restart}`;
680
959
  }
681
960
 
@@ -704,7 +983,37 @@ export async function runDoctor(args: string[] = []): Promise<void> {
704
983
  return;
705
984
  }
706
985
 
986
+ if (args.includes(RECOVER_ZERO_BYTE_COORDINATOR_FLAG)) {
987
+ if (!args.includes("--yes")) {
988
+ console.log(`Recovery is explicit and creates a same-directory backup. Re-run: ocx doctor ${RECOVER_ZERO_BYTE_COORDINATOR_FLAG} --yes`);
989
+ process.exitCode = 1;
990
+ return;
991
+ }
992
+ const diagnostics = readConfigDiagnostics().config;
993
+ const live = await findLiveProxy({
994
+ configFn: () => ({ port: diagnostics.port, hostname: diagnostics.hostname }),
995
+ });
996
+ if (live) {
997
+ console.log(`Recovery refused: OpenCodex proxy pid ${live.pid} is still running. Stop the proxy/service and retry.`);
998
+ process.exitCode = 1;
999
+ return;
1000
+ }
1001
+ const recovered = recoverZeroByteCodexCoordinator();
1002
+ if (!recovered.ok) {
1003
+ console.log(`Recovery refused: ${recovered.reason}.`);
1004
+ process.exitCode = 1;
1005
+ return;
1006
+ }
1007
+ console.log(`Moved the non-authoritative coordinator to ${recovered.backupPath}`);
1008
+ console.log("Run `ocx sync` to retry Codex config injection. The backup was preserved and no Codex config/catalog file was changed by recovery.");
1009
+ process.exitCode = 0;
1010
+ return;
1011
+ }
1012
+
707
1013
  console.log("opencodex doctor\n");
1014
+ // Reset per pass: the suite drives runDoctor several times in one process, and a sticky
1015
+ // flag would fail the second call because the first saw a problem.
1016
+ doctorSawFailure = false;
708
1017
 
709
1018
  // Ordering note: the memory/runtime section renders after "Running proxy
710
1019
  // process proxy env" below; helpers live above runDoctor for testability.
@@ -719,6 +1028,26 @@ export async function runDoctor(args: string[] = []): Promise<void> {
719
1028
  console.log(` ${row.exists ? "ok " : "-- "} ${row.label}: ${row.path}${flags ? ` (${flags})` : ""}`);
720
1029
  }
721
1030
 
1031
+ // Runs without the proxy on purpose: the worst accumulation happens when the proxy will
1032
+ // not start, which is exactly when the in-process periodic reclaim never ticks.
1033
+ const reclaimTemps = args.includes(RECLAIM_RESPONSE_TEMPS_FLAG);
1034
+ console.log("\nResponse-state temp files");
1035
+ // A typo must not silently degrade into "nothing to reclaim" — the operator would read the
1036
+ // report as an answer to a question they never actually asked.
1037
+ for (const arg of args) {
1038
+ if (arg !== RECLAIM_RESPONSE_TEMPS_FLAG && /^--reclaim/.test(arg)) {
1039
+ console.log(` !! Unrecognized flag ${arg}; did you mean ${RECLAIM_RESPONSE_TEMPS_FLAG}? Reporting only.`);
1040
+ }
1041
+ }
1042
+ for (const line of formatResponseTempLines(
1043
+ // The reclaim budget matches the report budget: a report bounded by entries and a removal
1044
+ // bounded by a smaller cleanup cap would tell an operator 816 and then silently free 512.
1045
+ reclaimTemps
1046
+ ? reclaimAbandonedResponseStateTemps({ maxCleanups: RESPONSE_TEMP_RECLAIM_MAX_CLEANUPS })
1047
+ : inspectAbandonedResponseStateTemps(),
1048
+ reclaimTemps,
1049
+ )) console.log(line);
1050
+
722
1051
  const orcaHome = collectOrcaCodexHomeDiagnostic();
723
1052
  console.log("\nCodex app home targeting");
724
1053
  console.log(` ${orcaHome.mismatch ? "!! " : "ok "} Effective Codex home: ${orcaHome.effectiveCodexHome}`);
@@ -733,7 +1062,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
733
1062
  const startup = collectStartupHealth(doctorConfig);
734
1063
  console.log("\nCodex restart safety");
735
1064
  console.log(` ${startup.rebootSafe ? "ok " : "!! "} ${startupHealthSummary(startup)}`);
736
- console.log(` routing=${startup.routingKind}, service=${startup.serviceViable ? "viable" : startup.serviceInstalled ? "installed-but-unhealthy" : "absent"}, shim=${startup.shimHealthy ? "healthy" : startup.shimInstalled ? "stale" : "absent"}`);
1065
+ console.log(` ${formatStartupRoutingDetail(startup)}`);
737
1066
 
738
1067
  console.log("\nCodex runtime selection");
739
1068
  {
@@ -769,10 +1098,20 @@ export async function runDoctor(args: string[] = []): Promise<void> {
769
1098
  const live = await findLiveProxy({
770
1099
  configFn: () => ({ port: doctorConfig.port, hostname: doctorConfig.hostname }),
771
1100
  });
772
- const livePid = live ? live.pid : readPid();
773
- const liveRuntime = live
774
- ? { pid: live.pid ?? 0, port: live.port, hostname: live.hostname }
775
- : (livePid ? readRuntimePort(livePid) : null);
1101
+
1102
+ // Mirrors `ocx status` through the same comparison rather than a second implementation:
1103
+ // two diagnostics disagreeing about whether an install is stale is worse than one (#2701).
1104
+ // No extra probe -- findLiveProxy already carried the version back.
1105
+ {
1106
+ const { packageVersion } = await import("./help");
1107
+ const { computeVersionSkew } = await import("./version-skew");
1108
+ const skew = computeVersionSkew(packageVersion(), live?.version);
1109
+ if (skew.skewed && skew.warning) {
1110
+ console.log(`!! ${skew.warning}`);
1111
+ } else if (skew.proxyVersion !== null) {
1112
+ console.log(`ok ocx ${skew.cliVersion} matches the running proxy`);
1113
+ }
1114
+ }
776
1115
 
777
1116
  const currentProxyEnv = collectProxyEnv();
778
1117
  const configuredProxy = collectConfiguredProxy();
@@ -812,13 +1151,11 @@ export async function runDoctor(args: string[] = []): Promise<void> {
812
1151
 
813
1152
  console.log("\nMemory / runtime");
814
1153
  {
815
- const runtime = liveRuntime;
816
- if (!runtime || !live) {
1154
+ if (!live) {
817
1155
  console.log(` -- doctor process Bun ${Bun.version} (this is NOT the service process)`);
818
1156
  console.log(" -- no running ocx proxy found (no live pid/runtime record)");
819
1157
  } else {
820
- const token = configuredAdminToken();
821
- const report = await fetchServiceMemory(gracefulStopHost(runtime.hostname), runtime.port, token);
1158
+ const report = await fetchServiceMemory(live);
822
1159
  for (const line of formatServiceMemoryLines(report)) console.log(line);
823
1160
  }
824
1161
  }
@@ -829,17 +1166,41 @@ export async function runDoctor(args: string[] = []): Promise<void> {
829
1166
  console.log(` ${probe.ok ? "ok " : "-- "} ${WHAM_USAGE_URL}`);
830
1167
  console.log(` ${detail}, ${probe.durationMs}ms, ${probe.authenticated ? "authenticated" : "unauthenticated"}`);
831
1168
 
832
- // Design B upgrade visibility: threads still tagged opencodex are invisible to the native
833
- // Codex app until the one-time migration lands. Read-only probe (readonly sqlite, 100ms
834
- // busy timeout) reports state, never mutates.
835
- console.log("\nCodex history migration");
1169
+ // Design B upgrade visibility: only the backup manifest authorizes restoring provider
1170
+ // metadata. Bare routed rows have unknown provenance and remain unchanged. This read-only
1171
+ // probe reports manifest work and database readability; it never mutates.
1172
+ console.log("\nCodex history metadata restore");
1173
+ // The history failure messages point here; make the visit worthwhile by
1174
+ // probing the coordinator namespace the locks live in. The probe exercises
1175
+ // identity, runtime-root, and permission checks without taking any lock or
1176
+ // creating anything (a doctor run must observe, not initialize).
1177
+ try {
1178
+ const identity = resolveEffectiveUserIdentity();
1179
+ const probe = probeCodexCoordinatorNamespace(identity);
1180
+ if (probe.status === "missing") {
1181
+ console.log(" ok history coordinator namespace not created yet (no history operation has run)");
1182
+ } else {
1183
+ console.log(" ok history coordinator namespace resolves");
1184
+ }
1185
+ } catch (cause) {
1186
+ const reason = cause instanceof CodexUserIdentityRefusal ? cause.message : String(cause);
1187
+ console.log(` -- history coordinator namespace refused: ${reason}`);
1188
+ }
1189
+ console.log("\nCodex native-write coordinator");
1190
+ for (const line of formatCoordinatorDoctorLines(inspectCodexCoordinator())) console.log(line);
836
1191
  const pending = countPendingOpencodexHistory();
837
1192
  if (pending.failed) {
838
- console.log(" -- state DB locked or unreadable (Codex app open?) — migration state unknown");
1193
+ if (pending.failureReason === "busy") {
1194
+ console.log(" -- history database, backup manifest, or rollout file is busy — exact metadata restore is pending");
1195
+ } else if (pending.failureReason === "permission") {
1196
+ console.log(" -- state DB or backup manifest access was denied — restore state unknown");
1197
+ } else {
1198
+ console.log(" -- backup manifest or restore target failed integrity checks — manual review required");
1199
+ }
839
1200
  } else if (pending.pendingRows === 0 && pending.backupEntries === 0) {
840
- console.log(" ok no legacy opencodex-tagged threads pending");
1201
+ console.log(" ok no manifest-backed provider metadata pending; untracked routed history is unchanged");
841
1202
  } else {
842
- console.log(` -- ${pending.pendingRows} thread(s) still tagged opencodex, ${pending.backupEntries} backup manifest entr${pending.backupEntries === 1 ? "y" : "ies"}`);
1203
+ console.log(` -- ${pending.backupEntries} backup manifest entr${pending.backupEntries === 1 ? "y" : "ies"} pending exact metadata restore`);
843
1204
  }
844
1205
 
845
1206
  console.log("\nProject Codex configs");
@@ -852,6 +1213,15 @@ export async function runDoctor(args: string[] = []): Promise<void> {
852
1213
  }
853
1214
  }
854
1215
 
1216
+ console.log("\nCodex agent role files");
1217
+ const tomlFallbackRoles = scanCodexAgentRolesWithTomlModelFallback(resolveCodexHomeDirImpl());
1218
+ if (tomlFallbackRoles.length === 0) {
1219
+ console.log(" ok no per-role model_fallback fields in $CODEX_HOME/agents/*.toml");
1220
+ } else {
1221
+ console.log(` [WARN] ${tomlFallbackRoles.length} agent role file${tomlFallbackRoles.length === 1 ? "" : "s"} contain${tomlFallbackRoles.length === 1 ? "s" : ""} \`model_fallback\`: ${tomlFallbackRoles.join(", ")}`);
1222
+ console.log(" Codex >= 0.146 rejects that field as unknown and skips the whole role. Move the chains to opencodex config `subagentModelFallbackByModel` (keyed by primary model) and remove the field from the TOML files.");
1223
+ }
1224
+
855
1225
  const dual = collectWslDualInstall();
856
1226
  if (dual.wsl) {
857
1227
  console.log("\nWSL Codex installs");
@@ -871,6 +1241,12 @@ export async function runDoctor(args: string[] = []): Promise<void> {
871
1241
  console.log("\nOAuth reliability");
872
1242
  for (const check of await collectOAuthDoctorChecks()) {
873
1243
  console.log(` [${check.level}] ${check.message}`);
1244
+ // A diagnostic that always exits 0 cannot gate anything, which defeats the point of
1245
+ // running it from a script (#2697's sibling defect). FAIL is the level reserved for a
1246
+ // surface that is unusable rather than degraded, so it -- and only it -- fails the
1247
+ // command. WARN stays exit 0 on purpose: warning on a degraded-but-working install
1248
+ // must not break a pipeline that is legitimately green.
1249
+ if (check.level === "FAIL") recordDoctorFailure();
874
1250
  }
875
1251
 
876
1252
  // #857: a running Codex app-server can keep an older in-memory catalog than
@@ -878,7 +1254,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
878
1254
  const { collectCodexAppServerCatalogState } = await import("../codex/app-server-processes");
879
1255
  const catalogState = collectCodexAppServerCatalogState();
880
1256
  if (catalogState.state === "stale") {
881
- console.log(` [WARN] Codex app-server (PID(s): ${catalogState.processes.map(p => p.pid).join(", ")}) started before the on-disk catalog changed; its in-memory model list disagrees with ocx. Action: restart Codex (or run \`ocx sync --restart-codex\`)`);
1257
+ console.log(` [WARN] Codex app-server (PID(s): ${catalogState.processes.map(p => p.pid).join(", ")}) started before the on-disk catalog changed; its in-memory model list disagrees with ocx. Action: restart Codex (or run \`ocx sync --restart-codex\`; on Windows the desktop app may need \`ocx sync --restart-desktop-app\`)`);
882
1258
  } else if (catalogState.state === "unknown") {
883
1259
  console.log(" [WARN] Could not verify whether the running Codex app-server's model catalog is current (start time or catalog unreadable). Action: if the model list looks stale, restart Codex");
884
1260
  } else if (catalogState.state === "fresh") {
@@ -891,6 +1267,8 @@ export async function runDoctor(args: string[] = []): Promise<void> {
891
1267
  proxyRunning: Boolean(live),
892
1268
  port: live?.port ?? doctorConfig.port ?? 10100,
893
1269
  serviceViable: startup.serviceViable,
1270
+ serviceInstalled: startup.serviceInstalled,
1271
+ serviceConflict: startup.serviceConflict,
894
1272
  });
895
1273
  if (proxyDown) hints.push(proxyDown);
896
1274
  for (const row of providerApiKeys) {
@@ -913,8 +1291,14 @@ export async function runDoctor(args: string[] = []): Promise<void> {
913
1291
  }
914
1292
  }
915
1293
  }
916
- if (pending.failed || pending.pendingRows > 0 || pending.backupEntries > 0) {
917
- hints.push("Legacy chat threads are still tagged opencodex (or the DB was locked). The running proxy retries the migration automatically; to force it now, close the Codex app and run 'ocx sync'.");
1294
+ if (pending.failed && pending.failureReason === "busy") {
1295
+ hints.push("Backed-up history metadata is pending or its state is unreadable. The running proxy retries exact restoration automatically; to force it now, close the Codex app and run 'ocx sync'. Untracked routed history is not relabeled.");
1296
+ } else if (pending.failed && pending.failureReason === "permission") {
1297
+ hints.push("Backed-up history metadata could not be inspected because access was denied. Fix access to the reported Codex history paths, then run 'ocx sync'; repeated retries do not repair permissions.");
1298
+ } else if (pending.failed) {
1299
+ hints.push("The history manifest or its target is invalid or changed. Preserve both, inspect the manifest/database/rollout identity, and do not repeatedly run 'ocx sync' until the mismatch is understood. Untracked routed history is not relabeled.");
1300
+ } else if (pending.backupEntries > 0) {
1301
+ hints.push("Backed-up history metadata is pending. The running proxy retries exact restoration automatically; to force it now, close the Codex app and run 'ocx sync'. Untracked routed history is not relabeled.");
918
1302
  }
919
1303
  if (dual.dualInstall && !dual.effectiveIsWindowsMount) {
920
1304
  hints.push(`Codex is installed on BOTH WSL and Windows. Each side keeps its own ~/.codex (logins, config, catalog are separate); ocx here manages the Linux one. To share a single home, set CODEX_HOME=${dual.windowsCodexHomes[0] ?? `${dual.automountRoot}/c/Users/<you>/.codex`} in WSL (drvfs file locking is less reliable).`);