@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
@@ -2,13 +2,16 @@
2
2
  * Claude Code outbound: internal /v1/responses output -> Anthropic Messages API shapes.
3
3
  *
4
4
  * Wire contract pinned in devlog/260711_claude_inbound/003_evidence.md (all Tier 2):
5
- * - SSE order: message_start -> (content_block_start -> deltas -> content_block_stop)*
6
- * -> message_delta -> message_stop; any number of `ping`.
5
+ * - Transport-only `ping` events may appear at any point, including before
6
+ * message_start. Semantic framing stays message_start ->
7
+ * (content_block_start -> deltas -> content_block_stop)* -> message_delta -> message_stop.
7
8
  * - thinking blocks get thinking_delta(s) then ONE synthetic signature_delta just
8
9
  * before content_block_stop (CCR precedent: Claude Code does not verify signatures).
9
10
  * - message_delta.usage is cumulative; message_start embeds a full message snapshot.
10
11
  * - errors: {type:"error", error:{type,message}}; may arrive mid-stream after HTTP 200.
11
12
  */
13
+ import { createHash } from "node:crypto";
14
+ import { httpStatusFromTerminalError } from "../lib/errors";
12
15
  import { isTransientUpstreamStatus } from "../lib/upstream-retry";
13
16
  import {
14
17
  isTranslatorBudgetExceededError,
@@ -16,6 +19,7 @@ import {
16
19
  TranslatorBudgetExceededError,
17
20
  type TranslatorBudget,
18
21
  } from "../lib/translator-budget";
22
+ import { sseFieldOffset, sseFieldValue } from "../lib/sse-decoder";
19
23
 
20
24
  type Rec = Record<string, unknown>;
21
25
 
@@ -23,6 +27,27 @@ function isRec(v: unknown): v is Rec {
23
27
  return !!v && typeof v === "object" && !Array.isArray(v);
24
28
  }
25
29
 
30
+ function reasoningIdentityDigest(value: string): string {
31
+ return createHash("sha256").update(value).digest("base64url");
32
+ }
33
+
34
+ /** Fixed-size identity that preserves protocol boundaries without retaining upstream strings. */
35
+ function boundedReasoningIdentity(value: unknown): string {
36
+ if (typeof value === "number") {
37
+ if (Number.isSafeInteger(value) && value >= 0) return `n${value}`;
38
+ if (Number.isFinite(value)) return `d${value}`;
39
+ return Number.isNaN(value) ? "dnan" : value > 0 ? "dinf" : "d-inf";
40
+ }
41
+ if (typeof value === "string") {
42
+ return `s${reasoningIdentityDigest(value)}`;
43
+ }
44
+ if (value === null) return "z";
45
+ if (typeof value === "boolean") return value ? "b1" : "b0";
46
+ if (Array.isArray(value)) return `a${reasoningIdentityDigest(JSON.stringify(value) ?? "[]")}`;
47
+ // Preserve the prior String(record) category semantics without serializing untrusted trees.
48
+ return typeof value === "object" ? "o" : "u";
49
+ }
50
+
26
51
  function uuid(): string {
27
52
  return crypto.randomUUID().replace(/-/g, "");
28
53
  }
@@ -187,6 +212,8 @@ interface OpenBlock {
187
212
  argsBufBytes?: number;
188
213
  webSearchArgsEmitted?: boolean;
189
214
  callId?: string;
215
+ /** Last fixed-size reasoning identity (item + summary/content index) seen by this block. */
216
+ reasoningPartKey?: string;
190
217
  }
191
218
 
192
219
  /** Streaming: Responses SSE bytes -> Anthropic Messages SSE bytes. */
@@ -242,14 +269,13 @@ export function responsesSseToAnthropicSse(
242
269
  emit("message_start", { type: "message_start", message: messageSnapshot(model) });
243
270
  emit("ping", { type: "ping" });
244
271
  };
245
- // Idle keepalive (devlog 100): real Anthropic streams may interleave pings anywhere;
246
- // synthesizing one during upstream silence protects remote deployments behind
247
- // LB/NAT idle timeouts and covers slow first tokens. Cheap and spec-legal.
272
+ // Keepalive pings protect remote deployments behind LB/NAT idle timeouts even
273
+ // before semantic output. They are transport-only and must not manufacture a
274
+ // message before a possible initial error.
248
275
  if (pingIntervalMs > 0) {
249
276
  pingTimer = setInterval(() => {
250
- if (terminated) return;
277
+ if (terminated || (controller.desiredSize ?? 0) <= 0) return;
251
278
  try {
252
- ensureStarted();
253
279
  emit("ping", { type: "ping" });
254
280
  } catch { /* controller torn down; the read loop is ending anyway */ }
255
281
  }, pingIntervalMs);
@@ -325,20 +351,25 @@ export function responsesSseToAnthropicSse(
325
351
  ))));
326
352
  return;
327
353
  }
328
- ensureStarted();
329
- closeOpenBlock();
330
354
  const type = upstreamDerived && isTransientUpstreamStatus(status) ? "overloaded_error" : undefined;
355
+ if (!started) {
356
+ // An initial upstream failure is an Anthropic error stream, not a partial message.
357
+ // Do not manufacture message_start before the terminal error. Earlier transport-only
358
+ // pings remain valid and do not turn the failure into a partial message.
359
+ emit("error", anthropicErrorBody(status, message, type, code));
360
+ return;
361
+ }
362
+ closeOpenBlock();
331
363
  emit("error", anthropicErrorBody(status, message, type, code));
332
364
  };
333
365
 
334
366
  const handleFrame = (eventName: string, data: Rec) => {
335
367
  switch (eventName) {
336
368
  case "response.created":
337
- ensureStarted();
369
+ // Transport prelude only. Start Anthropic framing on semantic output or completion.
338
370
  break;
339
371
  case "response.heartbeat":
340
- ensureStarted();
341
- emit("ping", { type: "ping" });
372
+ if ((controller.desiredSize ?? 0) > 0) emit("ping", { type: "ping" });
342
373
  break;
343
374
  case "response.output_text.delta": {
344
375
  if (typeof data.delta !== "string" || data.delta.length === 0) break;
@@ -353,6 +384,24 @@ export function responsesSseToAnthropicSse(
353
384
  case "response.reasoning_text.delta": {
354
385
  if (typeof data.delta !== "string" || data.delta.length === 0) break;
355
386
  ensureBlock("thinking");
387
+ // The JSON path joins reasoning summary/content parts with "\n\n"
388
+ // (responsesJsonToAnthropicMessage); mirror that at part and item boundaries
389
+ // so multi-part summaries do not glue into one run-on paragraph. Frames
390
+ // without part indices produce a constant key and never get a separator.
391
+ const slot = eventName === "response.reasoning_summary_text.delta"
392
+ ? `s${boundedReasoningIdentity(data.summary_index)}`
393
+ : `c${boundedReasoningIdentity(data.content_index)}`;
394
+ // Upstream string metadata can be arbitrarily large. Hash strings into fixed-size
395
+ // components while retaining item and part equality, rather than dropping item_id and
396
+ // accidentally joining distinct malformed reasoning items.
397
+ const partKey = `${boundedReasoningIdentity(data.item_id)}:${slot}`;
398
+ if (open!.reasoningPartKey !== undefined && open!.reasoningPartKey !== partKey) {
399
+ emit("content_block_delta", {
400
+ type: "content_block_delta", index: open!.index,
401
+ delta: { type: "thinking_delta", thinking: "\n\n" },
402
+ });
403
+ }
404
+ open!.reasoningPartKey = partKey;
356
405
  emit("content_block_delta", {
357
406
  type: "content_block_delta", index: open!.index,
358
407
  delta: { type: "thinking_delta", thinking: data.delta },
@@ -507,9 +556,19 @@ export function responsesSseToAnthropicSse(
507
556
  }
508
557
  const status = code === "translation_buffer_limit"
509
558
  ? 413
510
- : typeof error.status === "number" ? error.status : 500;
511
- // status-absent response.failed (relaySseWithFailedTail synthetic tail) defaults
512
- // to 500, which is in the transient set the mid-stream reset shape maps to
559
+ : typeof error.status === "number"
560
+ ? error.status
561
+ // Internal response.failed envelopes carry the classified {type, code, message}
562
+ // but no numeric status. Derive it with the same mapping /api/logs uses so a
563
+ // classified 429/401/400 reaches Claude Code as its real Anthropic error type
564
+ // instead of being masked as retryable overload.
565
+ : httpStatusFromTerminalError({
566
+ type: typeof error.type === "string" ? error.type : undefined,
567
+ code: typeof error.code === "string" ? error.code : null,
568
+ message,
569
+ });
570
+ // Unclassified status-absent response.failed (relaySseWithFailedTail synthetic
571
+ // tail) still lands on a transient 5xx here — the mid-stream reset shape maps to
513
572
  // overloaded_error by design.
514
573
  fail(
515
574
  status,
@@ -571,10 +630,16 @@ export function responsesSseToAnthropicSse(
571
630
  while (lineStart <= rawFrame.length) {
572
631
  const newline = rawFrame.indexOf("\n", lineStart);
573
632
  const lineEnd = newline === -1 ? rawFrame.length : newline;
574
- if (rawFrame.startsWith("event: ", lineStart)) {
575
- eventName = rawFrame.slice(lineStart + 7, lineEnd).trim();
576
- } else if (rawFrame.startsWith("data: ", lineStart)) {
577
- const fragmentStart = lineStart + 6;
633
+ // The space after the colon is optional in text/event-stream (#1170);
634
+ // compute the value offset the same way sseFieldValue does, without
635
+ // slicing the line first the byte accounting below is keyed to
636
+ // offsets into rawFrame.
637
+ const eventOffset = sseFieldOffset(rawFrame, lineStart, lineEnd, "event");
638
+ const dataOffset = sseFieldOffset(rawFrame, lineStart, lineEnd, "data");
639
+ if (eventOffset !== -1) {
640
+ eventName = rawFrame.slice(eventOffset, lineEnd).trim();
641
+ } else if (dataOffset !== -1) {
642
+ const fragmentStart = dataOffset;
578
643
  const fragmentBytes = utf8SliceBytes(rawFrame, fragmentStart, lineEnd);
579
644
  const fragmentReservation = translatorBudget.reserveTransient(fragmentBytes, { kind: "live_transient" });
580
645
  let fragmentCommitted = false;
@@ -844,8 +909,10 @@ export async function collectAnthropicMessage(
844
909
  let eventName = "";
845
910
  let dataLine = "";
846
911
  for (const line of rawFrame.split("\n")) {
847
- if (line.startsWith("event: ")) eventName = line.slice(7).trim();
848
- else if (line.startsWith("data: ")) dataLine += line.slice(6);
912
+ const eventValue = sseFieldValue(line, "event");
913
+ if (eventValue !== null) { eventName = eventValue.trim(); continue; }
914
+ const dataValue = sseFieldValue(line, "data");
915
+ if (dataValue !== null) dataLine += dataValue;
849
916
  }
850
917
  if (!eventName || !dataLine) continue;
851
918
  let data: unknown;
package/src/cli/access.ts CHANGED
@@ -17,6 +17,51 @@ const USAGE = `Usage:
17
17
  ocx access models [--json]
18
18
  ocx access test <model> [--protocol <chat|responses|messages>] [--json]`;
19
19
 
20
+ /**
21
+ * Render the key table with the usage fields the API already returns (#2705).
22
+ *
23
+ * `usage` is a DISCRIMINATED UNION server-side (`api-key-usage.ts`): the `{ambiguous:true}`
24
+ * variant carries no numbers at all, because when two config entries share an id there IS no
25
+ * per-key total. The union exists specifically so a consumer cannot print a number beside an
26
+ * ambiguity marker, so this renders the word `ambiguous` across the numeric columns rather
27
+ * than a fabricated 0 -- reporting 0 requests for a key that may be in heavy use is the
28
+ * dangerous answer to hand someone deciding what to delete.
29
+ *
30
+ * `attributionSince` and `historyTruncated` describe the DATA SET, not a key, so they print
31
+ * once as a footer. Without `attributionSince`, an absent `lastUsedAt` is unreadable: it
32
+ * could mean "never used" or "nothing is attributable yet".
33
+ */
34
+ function formatKeyRows(payload: Record<string, unknown>, keys: Array<Record<string, unknown>>): string[] {
35
+ const cells: string[][] = [["ID", "NAME", "PREFIX", "REQ 7D", "TOTAL", "LAST USED"]];
36
+ for (const entry of keys) {
37
+ const usage = (entry.usage ?? {}) as Record<string, unknown>;
38
+ const ambiguous = usage.ambiguous === true;
39
+ const num = (value: unknown): string => (typeof value === "number" ? value.toLocaleString("en-US") : "-");
40
+ cells.push([
41
+ String(entry.id ?? ""),
42
+ String(entry.name ?? ""),
43
+ String(entry.prefix ?? ""),
44
+ // One marker spanning both numeric columns: the union guarantees neither exists.
45
+ ambiguous ? "ambiguous" : num(usage.requests7d),
46
+ ambiguous ? "" : num(usage.totalRequests),
47
+ ambiguous ? "" : (typeof usage.lastUsedAt === "string" ? usage.lastUsedAt : "never"),
48
+ ]);
49
+ }
50
+ const widths = cells[0]!.map((_, column) => Math.max(...cells.map(row => (row[column] ?? "").length)));
51
+ const lines = cells.map(row => row.map((cell, i) => (cell ?? "").padEnd(widths[i]!)).join(" ").trimEnd());
52
+ const footer: string[] = [];
53
+ if (typeof payload.attributionSince === "string") {
54
+ footer.push(`attribution since ${payload.attributionSince}`);
55
+ }
56
+ if (payload.historyTruncated === true) {
57
+ footer.push("older history truncated");
58
+ }
59
+ if (keys.some(entry => (entry.usage as Record<string, unknown> | undefined)?.ambiguous === true)) {
60
+ footer.push("ambiguous: two configured keys share an id, so per-key totals do not exist");
61
+ }
62
+ return footer.length > 0 ? [...lines, "", ...footer] : lines;
63
+ }
64
+
20
65
  async function key(argv: string[], deps: RuntimeApiDeps): Promise<void> {
21
66
  const args = [...argv];
22
67
  const action = (args.shift() ?? "list").toLowerCase();
@@ -25,9 +70,7 @@ async function key(argv: string[], deps: RuntimeApiDeps): Promise<void> {
25
70
  rejectArgs(args, USAGE);
26
71
  const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {}, deps);
27
72
  const keys = Array.isArray(result.keys) ? result.keys as Array<Record<string, unknown>> : [];
28
- printData(result, wantsJson, keys.length
29
- ? keys.map(entry => `${String(entry.id)} ${String(entry.name)} ${String(entry.prefix ?? "")}`)
30
- : ["No API access keys configured."]);
73
+ printData(result, wantsJson, keys.length ? formatKeyRows(result, keys) : ["No API access keys configured."]);
31
74
  return;
32
75
  }
33
76
  if (action === "create") {
@@ -21,13 +21,34 @@ export interface AccountRow {
21
21
  masked?: string;
22
22
  active: boolean;
23
23
  needsReauth?: boolean;
24
+ /** Codex pool selection order, higher used earlier. Absent where ordering does not apply. */
25
+ priority?: number;
24
26
  quota?: CodexQuotaDto | null;
27
+ /**
28
+ * Whether the pool is holding this account out of rotation.
29
+ *
30
+ * The server has always sent it (auth-api.ts:286 for pool accounts, :1315 for main) and the
31
+ * CLI dropped it, so a paused account was indistinguishable from an available one in every
32
+ * human listing (#2703).
33
+ */
34
+ paused?: boolean;
25
35
  }
26
36
 
27
37
  export type ClassifyResult = { type: AccountType } | { error: string };
28
38
 
29
39
  export type AccountStdin = NodeJS.ReadableStream & { isTTY?: boolean };
30
40
 
41
+ export interface NativeMainLoginChild {
42
+ exited: Promise<number>;
43
+ kill(signal?: number | NodeJS.Signals): void;
44
+ }
45
+
46
+ export interface StageLeaseClock {
47
+ now(): number;
48
+ setTimeout(callback: () => void, ms: number): ReturnType<typeof setTimeout>;
49
+ clearTimeout(timer: ReturnType<typeof setTimeout>): void;
50
+ }
51
+
31
52
  export interface AccountDeps {
32
53
  /** Test injection: skip findLiveProxy and call the API at this base URL. */
33
54
  baseUrl?: string;
@@ -35,6 +56,16 @@ export interface AccountDeps {
35
56
  loadConfigImpl?: () => OcxConfig;
36
57
  stdinImpl?: AccountStdin;
37
58
  stdinTimeoutMs?: number;
59
+ /** Internal test seam for the account-import POST; production is capped at ten minutes. */
60
+ importTimeoutMs?: number;
61
+ /** Test/platform injection for the official Codex login in a restricted staging home. */
62
+ spawnCodexLoginImpl?: (codexHome: string) => NativeMainLoginChild;
63
+ /** Legacy test seam. Production always uses the spawned child handle above. */
64
+ runCodexLoginImpl?: (codexHome: string) => Promise<number>;
65
+ /** Test-only heartbeat cadence floor. Production keeps the five-second floor. */
66
+ stageHeartbeatIntervalMinMs?: number;
67
+ /** Test-only clock for deterministic native-profile lease deadline coverage. */
68
+ stageLeaseClock?: StageLeaseClock;
38
69
  }
39
70
 
40
71
  export function classifyAccount(config: OcxConfig, name: string): ClassifyResult {
@@ -60,6 +91,12 @@ export interface ApiResult {
60
91
  /** 0 = network-level failure (proxy unreachable). */
61
92
  status: number;
62
93
  json: Record<string, unknown>;
94
+ /**
95
+ * Message from the thrown transport error when `status` is 0. Previously the
96
+ * error was swallowed by a catch block with an empty body, so an unreachable proxy, a DNS
97
+ * failure and a TLS error were indistinguishable (#2698).
98
+ */
99
+ transportError?: string;
63
100
  }
64
101
 
65
102
  export async function apiJson(
@@ -68,6 +105,7 @@ export async function apiJson(
68
105
  method: "GET" | "PUT" | "POST" | "DELETE",
69
106
  path: string,
70
107
  body?: unknown,
108
+ options: { signal?: AbortSignal } = {},
71
109
  ): Promise<ApiResult> {
72
110
  const fetchImpl = deps.fetchImpl ?? fetch;
73
111
  try {
@@ -75,11 +113,18 @@ export async function apiJson(
75
113
  method,
76
114
  headers: runningProxyUpdateHeaders(),
77
115
  body: body === undefined ? undefined : JSON.stringify(body),
116
+ signal: options.signal,
78
117
  });
79
118
  const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;
80
119
  return { status: res.status, json };
81
- } catch {
82
- return { status: 0, json: {} };
120
+ } catch (error) {
121
+ // status 0 stays the transport sentinel, but keep the cause: callers can now
122
+ // tell the operator why the request never reached the proxy (#2698).
123
+ return {
124
+ status: 0,
125
+ json: {},
126
+ transportError: error instanceof Error ? error.message : String(error),
127
+ };
83
128
  }
84
129
  }
85
130
 
@@ -90,15 +135,43 @@ export async function resolveBaseUrl(deps: AccountDeps): Promise<string | null>
90
135
  return `http://${probeHostname(live.hostname)}:${live.port}`;
91
136
  }
92
137
 
93
- export function proxyUnreachable(): number {
138
+ export function proxyUnreachable(transportError?: string): number {
94
139
  console.error("Proxy not reachable. Start it with 'ocx start' or 'ocx ensure'.");
140
+ // Naming the transport cause distinguishes "nothing is listening" from a refused
141
+ // or reset connection, which is what made #2696-class breakage undiagnosable.
142
+ if (transportError) console.error(`reason: ${transportError}`);
95
143
  return 1;
96
144
  }
97
145
 
98
- export function apiError(json: Record<string, unknown>, fallback: string): number {
99
- const message = typeof json.error === "string" ? json.error : fallback;
100
- console.error(`Error: ${message}`);
101
- return 1;
146
+ function accountStringField(json: Record<string, unknown>, key: string): string | undefined {
147
+ const value = json[key];
148
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
149
+ }
150
+
151
+ /**
152
+ * Report a failed management call from the account family.
153
+ *
154
+ * `reason` and `hint` are the actionable fields on a refusal — the management plane
155
+ * sets both on a 503, and several routes return `reason` with no `error` key at all,
156
+ * which used to print only the generic fallback (#2698).
157
+ *
158
+ * `status` selects the exit code so the account client speaks the same vocabulary as
159
+ * runtime-api.ts: 4 for not-found, 5 for conflict, 1 otherwise. Previously every
160
+ * failure exited 1, so a script could not distinguish a missing account from a
161
+ * concurrent mutation.
162
+ */
163
+ export function apiError(json: Record<string, unknown>, fallback: string, status: number): number {
164
+ const primary = accountStringField(json, "error") ?? fallback;
165
+ const lines = [`Error: ${primary}`];
166
+ const reason = accountStringField(json, "reason");
167
+ if (reason && reason !== primary) lines.push(`reason: ${reason}`);
168
+ const hint = accountStringField(json, "hint");
169
+ if (hint && hint !== primary) lines.push(`hint: ${hint}`);
170
+ for (const line of lines) console.error(line);
171
+ if (json.cleanupRequired === true) {
172
+ console.error("Warning: native-login staging cleanup is still required; run 'ocx account main doctor'.");
173
+ }
174
+ return status === 404 ? 4 : status === 409 ? 5 : 1;
102
175
  }
103
176
 
104
177
  export interface FamilyRows {
@@ -106,10 +179,12 @@ export interface FamilyRows {
106
179
  activeId: string | null;
107
180
  autoSwitchThreshold?: number;
108
181
  /** HTTP status for a completed family read, including failures. */
109
- status?: number;
182
+ status: number;
110
183
  /** Set when the family endpoint returned an error. */
111
184
  errorJson?: Record<string, unknown>;
112
185
  networkDown?: boolean;
186
+ /** Transport cause when `networkDown` is set. Callers must forward this to `proxyUnreachable`. */
187
+ transportError?: string;
113
188
  }
114
189
 
115
190
  export interface CodexQuotaDto {
@@ -117,6 +192,18 @@ export interface CodexQuotaDto {
117
192
  monthlyPercent?: number;
118
193
  weeklyResetAt?: number;
119
194
  monthlyResetAt?: number;
195
+ /**
196
+ * Five-hour window, as the per-account provider probe reports it
197
+ * (`/api/oauth/accounts?quota=1`). Distinct from `shortPercent`, which is the Codex pool's
198
+ * self-declared burst window; the two surfaces name the same idea differently and both reach
199
+ * this DTO.
200
+ */
201
+ fiveHourPercent?: number;
202
+ fiveHourResetAt?: number;
203
+ /** Sub-day burst window, when upstream declares one (#1791). */
204
+ shortPercent?: number;
205
+ shortResetAt?: number;
206
+ shortWindowSeconds?: number;
120
207
  }
121
208
 
122
209
  export interface ProviderQuotaWindowDto {
@@ -148,13 +235,21 @@ interface CodexAccountDto {
148
235
  plan?: string;
149
236
  isMain?: boolean;
150
237
  needsReauth?: boolean;
238
+ priority?: number;
151
239
  quota?: CodexQuotaDto | null;
240
+ paused?: boolean;
152
241
  }
153
242
 
154
243
  function projectQuota(quota: CodexQuotaDto | null | undefined): CodexQuotaDto | null {
155
244
  if (!quota) return null;
156
245
  const projected: CodexQuotaDto = {};
157
- for (const key of ["weeklyPercent", "monthlyPercent", "weeklyResetAt", "monthlyResetAt"] as const) {
246
+ // `fiveHourPercent`/`fiveHourResetAt` were declared on the DTO and read by two renderers
247
+ // -- `quotaText`'s `quota.fiveHourPercent ?? quota.shortPercent` (account.ts:89) and
248
+ // `quotaParts` (account-extended.ts:275) -- but omitted from this whitelist, so the first
249
+ // operand was unreachable and a 5h-only account rendered as unknown (#2703). A projection
250
+ // that silently drops a field its own type declares is worse than one that never had it:
251
+ // the type checks, the renderer looks correct, and only the output is wrong.
252
+ for (const key of ["fiveHourPercent", "fiveHourResetAt", "weeklyPercent", "monthlyPercent", "weeklyResetAt", "monthlyResetAt", "shortPercent", "shortResetAt", "shortWindowSeconds"] as const) {
158
253
  if (typeof quota[key] === "number" && Number.isFinite(quota[key])) projected[key] = quota[key];
159
254
  }
160
255
  return projected;
@@ -164,6 +259,7 @@ export async function fetchCodexRows(
164
259
  deps: AccountDeps,
165
260
  baseUrl: string,
166
261
  forceRefresh = false,
262
+ includeQuota = forceRefresh,
167
263
  ): Promise<FamilyRows> {
168
264
  const accountsPath = `/api/codex-auth/accounts${forceRefresh ? "?refresh=1" : ""}`;
169
265
  const [accountsRes, activeRes] = await Promise.all([
@@ -177,7 +273,13 @@ export async function fetchCodexRows(
177
273
  return { rows: [], activeId: null, status: activeRes.status, errorJson: activeRes.json };
178
274
  }
179
275
  if (accountsRes.status === 0 || activeRes.status === 0) {
180
- return { rows: [], activeId: null, status: 0, networkDown: true };
276
+ return {
277
+ rows: [],
278
+ activeId: null,
279
+ status: 0,
280
+ networkDown: true,
281
+ transportError: accountsRes.transportError ?? activeRes.transportError,
282
+ };
181
283
  }
182
284
  const activeId = typeof activeRes.json.activeCodexAccountId === "string"
183
285
  ? activeRes.json.activeCodexAccountId
@@ -195,7 +297,9 @@ export async function fetchCodexRows(
195
297
  plan: a.plan,
196
298
  active: a.id === activeId,
197
299
  needsReauth: a.needsReauth,
198
- ...(forceRefresh ? { quota: projectQuota(a.quota) } : {}),
300
+ priority: typeof a.priority === "number" ? a.priority : 0,
301
+ paused: a.paused === true,
302
+ ...(includeQuota ? { quota: projectQuota(a.quota) } : {}),
199
303
  }));
200
304
  return { rows, activeId, autoSwitchThreshold, status: 200 };
201
305
  }
@@ -206,11 +310,25 @@ interface OAuthAccountDto {
206
310
  email?: string;
207
311
  active?: boolean;
208
312
  needsReauth?: boolean;
313
+ quota?: CodexQuotaDto | null;
314
+ quotaUnavailable?: boolean;
209
315
  }
210
316
 
211
- async function fetchOAuthRows(deps: AccountDeps, baseUrl: string, name: string): Promise<FamilyRows> {
212
- const res = await apiJson(deps, baseUrl, "GET", `/api/oauth/accounts?provider=${encodeURIComponent(name)}`);
213
- if (res.status === 0) return { rows: [], activeId: null, status: 0, networkDown: true };
317
+ async function fetchOAuthRows(
318
+ deps: AccountDeps,
319
+ baseUrl: string,
320
+ name: string,
321
+ quota?: { refresh?: boolean },
322
+ ): Promise<FamilyRows> {
323
+ // Quota is opt-in: the server probes the upstream once per stored credential when `quota=1`
324
+ // is present, so the default listing must stay a cheap local read (#2566).
325
+ const query = quota
326
+ ? `?provider=${encodeURIComponent(name)}&quota=1${quota.refresh ? "&refresh=1" : ""}`
327
+ : `?provider=${encodeURIComponent(name)}`;
328
+ const res = await apiJson(deps, baseUrl, "GET", `/api/oauth/accounts${query}`);
329
+ if (res.status === 0) {
330
+ return { rows: [], activeId: null, status: 0, networkDown: true, transportError: res.transportError };
331
+ }
214
332
  if (res.status !== 200) return { rows: [], activeId: null, status: res.status, errorJson: res.json };
215
333
  const activeId = typeof res.json.activeAccountId === "string" ? res.json.activeAccountId : null;
216
334
  const accounts = Array.isArray(res.json.accounts) ? res.json.accounts as OAuthAccountDto[] : [];
@@ -222,6 +340,8 @@ async function fetchOAuthRows(deps: AccountDeps, baseUrl: string, name: string):
222
340
  email: a.email,
223
341
  active: a.active ?? a.id === activeId,
224
342
  needsReauth: a.needsReauth,
343
+ ...(a.quota !== undefined ? { quota: a.quota } : {}),
344
+ ...(a.quotaUnavailable !== undefined ? { quotaUnavailable: a.quotaUnavailable } : {}),
225
345
  }));
226
346
  return { rows, activeId, status: 200 };
227
347
  }
@@ -235,7 +355,9 @@ interface ApiKeyDto {
235
355
 
236
356
  async function fetchKeyRows(deps: AccountDeps, baseUrl: string, name: string): Promise<FamilyRows> {
237
357
  const res = await apiJson(deps, baseUrl, "GET", `/api/providers/keys?name=${encodeURIComponent(name)}`);
238
- if (res.status === 0) return { rows: [], activeId: null, status: 0, networkDown: true };
358
+ if (res.status === 0) {
359
+ return { rows: [], activeId: null, status: 0, networkDown: true, transportError: res.transportError };
360
+ }
239
361
  if (res.status !== 200) return { rows: [], activeId: null, status: res.status, errorJson: res.json };
240
362
  const activeId = typeof res.json.activeId === "string" ? res.json.activeId : null;
241
363
  const keys = Array.isArray(res.json.keys) ? res.json.keys as ApiKeyDto[] : [];
@@ -250,9 +372,15 @@ async function fetchKeyRows(deps: AccountDeps, baseUrl: string, name: string): P
250
372
  return { rows, activeId, status: 200 };
251
373
  }
252
374
 
253
- export function fetchRows(deps: AccountDeps, baseUrl: string, name: string, type: AccountType): Promise<FamilyRows> {
254
- if (type === "codex") return fetchCodexRows(deps, baseUrl);
255
- if (type === "oauth") return fetchOAuthRows(deps, baseUrl, name);
375
+ export function fetchRows(
376
+ deps: AccountDeps,
377
+ baseUrl: string,
378
+ name: string,
379
+ type: AccountType,
380
+ quota?: { refresh?: boolean },
381
+ ): Promise<FamilyRows> {
382
+ if (type === "codex") return fetchCodexRows(deps, baseUrl, Boolean(quota?.refresh), quota !== undefined);
383
+ if (type === "oauth") return fetchOAuthRows(deps, baseUrl, name, quota);
256
384
  return fetchKeyRows(deps, baseUrl, name);
257
385
  }
258
386
 
@@ -260,8 +388,11 @@ export async function fetchProviderQuotaReport(
260
388
  deps: AccountDeps,
261
389
  baseUrl: string,
262
390
  name: string,
263
- ): Promise<{ status: number; report: ProviderQuotaReportDto | null; errorJson?: Record<string, unknown> }> {
391
+ ): Promise<{ status: number; report: ProviderQuotaReportDto | null; errorJson?: Record<string, unknown>; transportError?: string }> {
264
392
  const res = await apiJson(deps, baseUrl, "GET", "/api/provider-quotas?refresh=1");
393
+ if (res.status === 0) {
394
+ return { status: 0, report: null, errorJson: res.json, transportError: res.transportError };
395
+ }
265
396
  if (res.status !== 200) return { status: res.status, report: null, errorJson: res.json };
266
397
  const reports = Array.isArray(res.json.reports) ? res.json.reports as ProviderQuotaReportDto[] : [];
267
398
  return { status: 200, report: reports.find(report => report?.provider === name) ?? null };
@@ -1,3 +1,5 @@
1
+ import { writeSync } from "node:fs";
2
+ import { warnIfCodexCatalogRefreshPending } from "./account-catalog-refresh";
1
3
  import {
2
4
  CliUsageError,
3
5
  printData,
@@ -12,6 +14,22 @@ import {
12
14
  type RuntimeApiDeps,
13
15
  } from "./runtime-api";
14
16
 
17
+ /**
18
+ * Write the whole block to fd 1 synchronously (#1007). `console.log` can
19
+ * buffer behind a pipe, which hid the authorization URL for the entire
20
+ * polling window under non-TTY stdout. A partial write loops; a zero-byte
21
+ * write is a hard failure, never silent progress.
22
+ */
23
+ function writeStdoutFully(text: string): void {
24
+ const bytes = Buffer.from(text, "utf8");
25
+ let offset = 0;
26
+ while (offset < bytes.length) {
27
+ const written = writeSync(1, bytes, offset, bytes.length - offset);
28
+ if (written <= 0) throw new CliUsageError("failed to write login instructions to stdout");
29
+ offset += written;
30
+ }
31
+ }
32
+
15
33
  const USAGE = `Usage:
16
34
  ocx account login <provider> [--id <account-id>] [--reauth] [--code -] [--no-wait] [--json]
17
35
  ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
@@ -82,9 +100,14 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
82
100
  body: JSON.stringify({ ...(id ? { id } : {}), ...(reauth ? { reauth: true } : {}) }),
83
101
  }, deps);
84
102
  if (!wantsJson) {
85
- if (start.url) console.log(`Open this URL to sign in:\n${start.url}`);
86
- if (start.instructions) console.log(start.instructions);
87
- if (start.flowId) console.log(`Flow: ${start.flowId}`);
103
+ // One atomic pre-poll block, flushed synchronously so a piped parent
104
+ // reads the URL before the polling window starts (#1007).
105
+ const block = [
106
+ start.url ? `Open this URL to sign in:\n${start.url}` : "",
107
+ start.instructions ?? "",
108
+ start.flowId ? `Flow: ${start.flowId}` : "",
109
+ ].filter(line => line !== "").join("\n");
110
+ if (block) writeStdoutFully(`${block}\n`);
88
111
  }
89
112
  if (code && start.flowId) {
90
113
  await runtimeRequest("/api/codex-auth/login/code", {
@@ -105,6 +128,7 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
105
128
  );
106
129
  if (state.status === "done") {
107
130
  printData(state, wantsJson, [`Logged in${state.email ? ` as ${String(state.email)}` : ""}.`]);
131
+ if (!wantsJson) warnIfCodexCatalogRefreshPending(state);
108
132
  return;
109
133
  }
110
134
  if (state.status === "error" || state.status === "expired") {
@@ -120,9 +144,12 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
120
144
  body: JSON.stringify({ provider, addAccount: !reauth, ...(reauth && id ? { accountId: id, reauth: true } : {}) }),
121
145
  }, deps);
122
146
  if (!wantsJson) {
123
- if (start.url) console.log(`Open this URL to sign in:\n${start.url}`);
124
- if (start.instructions) console.log(start.instructions);
125
- if (start.deviceCode) console.log(`Device code: ${start.deviceCode}`);
147
+ const block = [
148
+ start.url ? `Open this URL to sign in:\n${start.url}` : "",
149
+ start.instructions ?? "",
150
+ start.deviceCode ? `Device code: ${start.deviceCode}` : "",
151
+ ].filter(line => line !== "").join("\n");
152
+ if (block) writeStdoutFully(`${block}\n`);
126
153
  }
127
154
  if (code) {
128
155
  await runtimeRequest("/api/oauth/login/code", {
@@ -0,0 +1,14 @@
1
+ export const CODEX_CATALOG_REFRESH_PENDING_WARNING =
2
+ "Warning: the account change was saved, but the Codex model catalog refresh is pending. Run 'ocx sync' to retry.";
3
+
4
+ /** Read only the public completion flag from an account mutation response. */
5
+ export function codexCatalogRefreshPending(value: unknown): boolean {
6
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
7
+ const descriptor = Object.getOwnPropertyDescriptor(value, "catalogRefreshPending");
8
+ return descriptor !== undefined && "value" in descriptor && descriptor.value === true;
9
+ }
10
+
11
+ /** Print fixed recovery guidance without reflecting server or account details. */
12
+ export function warnIfCodexCatalogRefreshPending(value: unknown): void {
13
+ if (codexCatalogRefreshPending(value)) console.error(CODEX_CATALOG_REFRESH_PENDING_WARNING);
14
+ }