@iislee/opencodex 2.11.0 → 2.31.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 (675) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +114 -19
  3. package/bin/ocx.mjs +164 -36
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-BF38heuV.js +104 -0
  6. package/gui/dist/assets/index-DMiI18Kv.css +1 -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 +19 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +376 -52
  44. package/src/adapters/base.ts +54 -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 +601 -0
  48. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  49. package/src/adapters/cursor/cursor-errors.ts +129 -6
  50. package/src/adapters/cursor/discovery.ts +61 -4
  51. package/src/adapters/cursor/effort-map.ts +27 -3
  52. package/src/adapters/cursor/framing.ts +39 -0
  53. package/src/adapters/cursor/h2-pool.ts +123 -0
  54. package/src/adapters/cursor/http1-bidi.ts +361 -0
  55. package/src/adapters/cursor/images.ts +704 -0
  56. package/src/adapters/cursor/live-models.ts +174 -58
  57. package/src/adapters/cursor/live-transport.ts +609 -170
  58. package/src/adapters/cursor/native-exec-common.ts +23 -2
  59. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  60. package/src/adapters/cursor/native-exec-network.ts +1 -1
  61. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  62. package/src/adapters/cursor/native-exec.ts +101 -14
  63. package/src/adapters/cursor/protobuf-events.ts +829 -11
  64. package/src/adapters/cursor/protobuf-request.ts +383 -65
  65. package/src/adapters/cursor/request-builder.ts +237 -31
  66. package/src/adapters/cursor/tool-definitions.ts +242 -12
  67. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  68. package/src/adapters/cursor/transport.ts +22 -0
  69. package/src/adapters/cursor/types.ts +28 -1
  70. package/src/adapters/cursor.ts +133 -7
  71. package/src/adapters/google-antigravity-replay.ts +558 -34
  72. package/src/adapters/google-antigravity-wire.ts +43 -10
  73. package/src/adapters/google-http.ts +38 -10
  74. package/src/adapters/google-tool-schema.ts +80 -15
  75. package/src/adapters/google-truncation.ts +11 -0
  76. package/src/adapters/google.ts +618 -74
  77. package/src/adapters/identity.ts +39 -6
  78. package/src/adapters/kiro-errors.ts +11 -0
  79. package/src/adapters/kiro-events.ts +19 -1
  80. package/src/adapters/kiro-thinking.ts +18 -2
  81. package/src/adapters/kiro-tools.ts +10 -1
  82. package/src/adapters/kiro.ts +92 -54
  83. package/src/adapters/mimo-free.ts +17 -0
  84. package/src/adapters/openai-chat-url.ts +11 -0
  85. package/src/adapters/openai-chat.ts +1311 -336
  86. package/src/adapters/openai-responses-url.ts +16 -0
  87. package/src/adapters/openai-responses.ts +830 -56
  88. package/src/adapters/registry.ts +175 -0
  89. package/src/adapters/responses-tool-schema.ts +67 -0
  90. package/src/adapters/tool-call-id.ts +119 -0
  91. package/src/adapters/tool-catalog-nudge.ts +104 -21
  92. package/src/adapters/xai-web-search.ts +185 -0
  93. package/src/bridge.ts +428 -61
  94. package/src/chat/inbound.ts +43 -19
  95. package/src/chat/outbound.ts +82 -26
  96. package/src/claude/agents-inject.ts +32 -9
  97. package/src/claude/context-windows.ts +21 -5
  98. package/src/claude/desktop-3p.ts +243 -9
  99. package/src/claude/gateway-cache.ts +41 -4
  100. package/src/claude/inbound.ts +72 -3
  101. package/src/claude/model-info.ts +38 -15
  102. package/src/claude/outbound.ts +70 -16
  103. package/src/cli/account-api.ts +35 -1
  104. package/src/cli/account-auth.ts +33 -6
  105. package/src/cli/account-catalog-refresh.ts +14 -0
  106. package/src/cli/account-extended.ts +389 -2
  107. package/src/cli/account-main.ts +317 -0
  108. package/src/cli/account.ts +30 -6
  109. package/src/cli/agent.ts +45 -1
  110. package/src/cli/claude-agent-startup-sync.ts +73 -0
  111. package/src/cli/claude-desktop.ts +29 -4
  112. package/src/cli/claude.ts +103 -34
  113. package/src/cli/codex-log-guard-doctor.ts +103 -0
  114. package/src/cli/codex-shim-autorestore.ts +2 -0
  115. package/src/cli/codex-shim-readiness.ts +76 -0
  116. package/src/cli/combo.ts +8 -0
  117. package/src/cli/config-command.ts +74 -10
  118. package/src/cli/dispatch.ts +593 -0
  119. package/src/cli/doctor.ts +315 -43
  120. package/src/cli/ensure-desired-integrations.ts +152 -0
  121. package/src/cli/export-command.ts +46 -20
  122. package/src/cli/help.ts +20 -271
  123. package/src/cli/index.ts +378 -519
  124. package/src/cli/init.ts +4 -17
  125. package/src/cli/integrations.ts +120 -2
  126. package/src/cli/lab.ts +607 -0
  127. package/src/cli/launcher-context.ts +77 -0
  128. package/src/cli/minimax.ts +497 -0
  129. package/src/cli/models-runtime.ts +35 -2
  130. package/src/cli/models.ts +100 -14
  131. package/src/cli/observe.ts +92 -3
  132. package/src/cli/opencode.ts +4 -2
  133. package/src/cli/provider-runtime.ts +18 -1
  134. package/src/cli/provider.ts +24 -3
  135. package/src/cli/ready.ts +301 -0
  136. package/src/cli/registry.ts +437 -0
  137. package/src/cli/root.ts +86 -0
  138. package/src/cli/route-policy.ts +92 -0
  139. package/src/cli/runtime-api.ts +6 -3
  140. package/src/cli/star-prompt.ts +71 -15
  141. package/src/cli/status.ts +10 -3
  142. package/src/cli/system-restart-client.ts +146 -0
  143. package/src/cli/tray-proxy.ts +153 -6
  144. package/src/cli/v2.ts +105 -10
  145. package/src/cli.ts +1 -1
  146. package/src/clients/config-export.ts +1358 -21
  147. package/src/codex/account-label.ts +14 -1
  148. package/src/codex/account-lifecycle.ts +130 -13
  149. package/src/codex/account-namespaces.ts +49 -3
  150. package/src/codex/account-priority.ts +83 -0
  151. package/src/codex/account-store.ts +29 -2
  152. package/src/codex/account-usability.ts +25 -2
  153. package/src/codex/admission.ts +256 -0
  154. package/src/codex/affinity-debug.ts +162 -0
  155. package/src/codex/app-server-processes.ts +493 -106
  156. package/src/codex/app-server-restart-service.ts +232 -0
  157. package/src/codex/auth-api.ts +849 -242
  158. package/src/codex/auth-collision.ts +5 -3
  159. package/src/codex/auth-context.ts +345 -32
  160. package/src/codex/autostart-health.ts +8 -1
  161. package/src/codex/catalog/account-models.ts +67 -0
  162. package/src/codex/catalog/aggregation.ts +68 -10
  163. package/src/codex/catalog/bundled.ts +331 -33
  164. package/src/codex/catalog/effort.ts +121 -30
  165. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  166. package/src/codex/catalog/kinds.ts +2 -0
  167. package/src/codex/catalog/metadata.ts +529 -45
  168. package/src/codex/catalog/native-models.ts +72 -0
  169. package/src/codex/catalog/parsing.ts +224 -30
  170. package/src/codex/catalog/provider-fetch.ts +1460 -134
  171. package/src/codex/catalog/sync.ts +1449 -186
  172. package/src/codex/catalog-admission.ts +199 -0
  173. package/src/codex/catalog-refresh-status.ts +105 -0
  174. package/src/codex/catalog-write-serialization.ts +242 -0
  175. package/src/codex/catalog.ts +6 -3
  176. package/src/codex/codex-write-lock.ts +384 -0
  177. package/src/codex/convergence-types.ts +614 -0
  178. package/src/codex/convergence.ts +651 -0
  179. package/src/codex/coordinator-doctor.ts +332 -0
  180. package/src/codex/custom-model-catalog-migration.ts +176 -0
  181. package/src/codex/desired-state.ts +230 -0
  182. package/src/codex/features.ts +636 -39
  183. package/src/codex/generation.ts +202 -0
  184. package/src/codex/history-job.ts +407 -0
  185. package/src/codex/history-lock.ts +242 -0
  186. package/src/codex/history-migration-guardian.ts +26 -20
  187. package/src/codex/history-provider.ts +231 -28
  188. package/src/codex/history-transition.ts +105 -0
  189. package/src/codex/history-worker.ts +220 -0
  190. package/src/codex/inject-coordination.ts +290 -0
  191. package/src/codex/inject.ts +1073 -152
  192. package/src/codex/injected-marker.ts +37 -3
  193. package/src/codex/integration-record.ts +266 -0
  194. package/src/codex/internal/catalog-writer.ts +203 -0
  195. package/src/codex/internal/history-writer.ts +80 -0
  196. package/src/codex/journal.ts +66 -4
  197. package/src/codex/log-guard/inspect.ts +506 -0
  198. package/src/codex/log-guard/lock.ts +150 -0
  199. package/src/codex/log-guard/maintenance.ts +403 -0
  200. package/src/codex/log-guard/path-safety.ts +88 -0
  201. package/src/codex/log-guard/policy.ts +44 -0
  202. package/src/codex/log-guard/processes.ts +205 -0
  203. package/src/codex/log-guard/protection.ts +489 -0
  204. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  205. package/src/codex/main-account-cache.ts +24 -0
  206. package/src/codex/main-account.ts +29 -1
  207. package/src/codex/management-convergence.ts +167 -0
  208. package/src/codex/model-cache.ts +56 -10
  209. package/src/codex/model-entitlements.ts +353 -0
  210. package/src/codex/native-main-admission.ts +47 -0
  211. package/src/codex/native-main-auth-temp.ts +187 -0
  212. package/src/codex/native-main-claim.ts +178 -0
  213. package/src/codex/native-main-lock-file.ts +162 -0
  214. package/src/codex/native-main-owner.ts +329 -0
  215. package/src/codex/native-profile-api.ts +247 -0
  216. package/src/codex/native-profile-manager.ts +1531 -0
  217. package/src/codex/native-profile-processes.ts +121 -0
  218. package/src/codex/native-profile-recovery.ts +99 -0
  219. package/src/codex/native-profile-stage-store.ts +387 -0
  220. package/src/codex/native-profile-startup.ts +492 -0
  221. package/src/codex/native-profile-store.ts +855 -0
  222. package/src/codex/native-profile-types.ts +120 -0
  223. package/src/codex/native-residue.ts +682 -0
  224. package/src/codex/paths.ts +80 -1
  225. package/src/codex/plan-from-token.ts +140 -0
  226. package/src/codex/plan.ts +40 -0
  227. package/src/codex/plugins-doctor.ts +1 -1
  228. package/src/codex/pool-rotation.ts +74 -4
  229. package/src/codex/project-config-warnings.ts +20 -6
  230. package/src/codex/prompt-journal.ts +352 -0
  231. package/src/codex/prompt-layers.ts +967 -0
  232. package/src/codex/prompt-lock.ts +143 -0
  233. package/src/codex/quota-rejection.ts +298 -0
  234. package/src/codex/quota.ts +175 -13
  235. package/src/codex/refresh.ts +11 -2
  236. package/src/codex/reset-credit-recovery.ts +1044 -0
  237. package/src/codex/routing.ts +505 -94
  238. package/src/codex/runtime.ts +159 -38
  239. package/src/codex/shim.ts +1009 -28
  240. package/src/codex/subagent-model-fallback.ts +350 -35
  241. package/src/codex/sync.ts +191 -2
  242. package/src/codex/transition-state.ts +612 -0
  243. package/src/codex/upstream-host-health.ts +368 -0
  244. package/src/codex/user-identity.ts +557 -0
  245. package/src/codex/warmup.ts +187 -81
  246. package/src/codex/write-coordination.ts +114 -0
  247. package/src/combos/failover.ts +20 -0
  248. package/src/combos/index.ts +4 -0
  249. package/src/combos/request.ts +32 -0
  250. package/src/combos/types.ts +81 -9
  251. package/src/config/provider-name.ts +24 -0
  252. package/src/config.ts +1762 -140
  253. package/src/generated/compatibility-version.json +3164 -0
  254. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  255. package/src/grok/inject.ts +16 -5
  256. package/src/grok/inspect.ts +45 -0
  257. package/src/grok/sync.ts +2 -2
  258. package/src/images/loop.ts +152 -29
  259. package/src/images/plan.ts +23 -13
  260. package/src/integrations/config-io.ts +269 -0
  261. package/src/integrations/journal.ts +315 -0
  262. package/src/integrations/merge.ts +135 -0
  263. package/src/integrations/mutation-flight.ts +71 -0
  264. package/src/integrations/native/ownership-preflight.ts +202 -0
  265. package/src/integrations/omp-yaml-source.ts +358 -0
  266. package/src/integrations/owned-refresh.ts +74 -0
  267. package/src/integrations/ownership.ts +111 -0
  268. package/src/integrations/registry.ts +159 -0
  269. package/src/integrations/serialize.ts +314 -0
  270. package/src/integrations/state.ts +361 -0
  271. package/src/integrations/store.ts +103 -0
  272. package/src/integrations/writer-lock.ts +98 -0
  273. package/src/integrations/writer.ts +691 -0
  274. package/src/lab/artifacts/sanitize.ts +586 -0
  275. package/src/lab/artifacts/secure-fs.ts +475 -0
  276. package/src/lab/artifacts/store.ts +310 -0
  277. package/src/lab/automation/budgets.ts +78 -0
  278. package/src/lab/automation/config-persistence.ts +256 -0
  279. package/src/lab/automation/constants.ts +39 -0
  280. package/src/lab/automation/cooldown.ts +103 -0
  281. package/src/lab/automation/dispatch.ts +211 -0
  282. package/src/lab/automation/index.ts +13 -0
  283. package/src/lab/automation/orchestrator.ts +499 -0
  284. package/src/lab/automation/persistence.ts +512 -0
  285. package/src/lab/automation/planner.ts +371 -0
  286. package/src/lab/automation/policy.ts +136 -0
  287. package/src/lab/automation/queue.ts +191 -0
  288. package/src/lab/automation/recovery.ts +24 -0
  289. package/src/lab/automation/route-context.ts +21 -0
  290. package/src/lab/automation/run-key.ts +44 -0
  291. package/src/lab/automation/runs-query.ts +34 -0
  292. package/src/lab/automation/types.ts +160 -0
  293. package/src/lab/conformance/assertion.ts +325 -0
  294. package/src/lab/conformance/digest.ts +22 -0
  295. package/src/lab/conformance/executor.ts +741 -0
  296. package/src/lab/conformance/fixture-provider.ts +27 -0
  297. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  298. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  299. package/src/lab/conformance/harness-budget.ts +47 -0
  300. package/src/lab/conformance/index.ts +5 -0
  301. package/src/lab/conformance/jcs.ts +64 -0
  302. package/src/lab/conformance/json-pointer.ts +39 -0
  303. package/src/lab/conformance/manifest.ts +180 -0
  304. package/src/lab/conformance/mcp-stub.ts +179 -0
  305. package/src/lab/conformance/negative-controls.ts +164 -0
  306. package/src/lab/conformance/observation.ts +355 -0
  307. package/src/lab/conformance/runner.ts +68 -0
  308. package/src/lab/conformance/sse-normalize.ts +59 -0
  309. package/src/lab/conformance/suite-manifest.ts +78 -0
  310. package/src/lab/conformance/types.ts +214 -0
  311. package/src/lab/constants.ts +126 -0
  312. package/src/lab/digest.ts +64 -0
  313. package/src/lab/events/errors.ts +9 -0
  314. package/src/lab/events/limits.ts +117 -0
  315. package/src/lab/events/types.ts +229 -0
  316. package/src/lab/events/validate.ts +781 -0
  317. package/src/lab/fabric/constants.ts +40 -0
  318. package/src/lab/fabric/executor.ts +492 -0
  319. package/src/lab/fabric/index.ts +80 -0
  320. package/src/lab/fabric/manifest.ts +222 -0
  321. package/src/lab/fabric/observe.ts +489 -0
  322. package/src/lab/fabric/patch.ts +79 -0
  323. package/src/lab/fabric/producer-child.ts +139 -0
  324. package/src/lab/fabric/producer-isolate.ts +276 -0
  325. package/src/lab/fabric/producer-protocol.ts +61 -0
  326. package/src/lab/fabric/scratch.ts +439 -0
  327. package/src/lab/fabric/subject.ts +106 -0
  328. package/src/lab/fabric/types.ts +134 -0
  329. package/src/lab/fabric/verifier.ts +98 -0
  330. package/src/lab/index.ts +54 -0
  331. package/src/lab/ledger/artifact-refs.ts +127 -0
  332. package/src/lab/ledger/invalidation.ts +136 -0
  333. package/src/lab/ledger/purge.ts +310 -0
  334. package/src/lab/ledger/store.ts +532 -0
  335. package/src/lab/live/credential-lease.ts +53 -0
  336. package/src/lab/live/destination.ts +155 -0
  337. package/src/lab/live/executor.ts +336 -0
  338. package/src/lab/live/inert-tools.ts +56 -0
  339. package/src/lab/live/manifest.ts +85 -0
  340. package/src/lab/live/mcp-loopback.ts +57 -0
  341. package/src/lab/live/runner.ts +19 -0
  342. package/src/lab/live/sandbox.ts +61 -0
  343. package/src/lab/live/suite-manifest.ts +41 -0
  344. package/src/lab/live/transport.ts +118 -0
  345. package/src/lab/live/types.ts +197 -0
  346. package/src/lab/observe/from-conformance.ts +301 -0
  347. package/src/lab/observe/from-live.ts +117 -0
  348. package/src/lab/paths.ts +153 -0
  349. package/src/lab/projection/rebuild.ts +495 -0
  350. package/src/lab/projection/schema.ts +135 -0
  351. package/src/lab/projection/verdicts.ts +474 -0
  352. package/src/lab/projection/verification.ts +412 -0
  353. package/src/lab/public/bundle.ts +217 -0
  354. package/src/lab/public/community-authority.ts +175 -0
  355. package/src/lab/public/community-files.ts +29 -0
  356. package/src/lab/public/community.ts +479 -0
  357. package/src/lab/public/file-safety.ts +155 -0
  358. package/src/lab/public/ids.ts +26 -0
  359. package/src/lab/public/index.ts +16 -0
  360. package/src/lab/public/mutation-lock.ts +424 -0
  361. package/src/lab/public/operator.ts +353 -0
  362. package/src/lab/public/origin-purge.ts +79 -0
  363. package/src/lab/public/origin.ts +203 -0
  364. package/src/lab/public/privacy.ts +143 -0
  365. package/src/lab/public/private-file.ts +261 -0
  366. package/src/lab/public/project.ts +124 -0
  367. package/src/lab/public/purge-test-fault.ts +21 -0
  368. package/src/lab/public/purge.ts +223 -0
  369. package/src/lab/public/registry.ts +44 -0
  370. package/src/lab/public/revocation.ts +252 -0
  371. package/src/lab/public/signature.ts +243 -0
  372. package/src/lab/public/storage.ts +105 -0
  373. package/src/lab/public/strict-json.ts +206 -0
  374. package/src/lab/public/time.ts +26 -0
  375. package/src/lab/public/types.ts +172 -0
  376. package/src/lab/public/validate.ts +391 -0
  377. package/src/lab/query/catalog.ts +101 -0
  378. package/src/lab/query/connection.ts +107 -0
  379. package/src/lab/query/constants.ts +4 -0
  380. package/src/lab/query/cursor.ts +132 -0
  381. package/src/lab/query/dto-map.ts +277 -0
  382. package/src/lab/query/errors.ts +22 -0
  383. package/src/lab/query/freshness.ts +53 -0
  384. package/src/lab/query/index.ts +45 -0
  385. package/src/lab/query/latest-observation.ts +59 -0
  386. package/src/lab/query/passive-production.ts +159 -0
  387. package/src/lab/query/queries.ts +444 -0
  388. package/src/lab/query/types.ts +266 -0
  389. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  390. package/src/lab/subject/installation-salt.ts +112 -0
  391. package/src/lab/subject/protocol-subject.ts +80 -0
  392. package/src/lab/subject/route-subject.ts +74 -0
  393. package/src/lib/app-owned-memory-stores.ts +22 -0
  394. package/src/lib/bounded-body.ts +153 -9
  395. package/src/lib/bun-runtime.ts +125 -12
  396. package/src/lib/bun-stream-caps.ts +13 -9
  397. package/src/lib/codex-restart-contract.ts +120 -0
  398. package/src/lib/config-ownership.ts +6 -2
  399. package/src/lib/destination-policy.ts +65 -1
  400. package/src/lib/errors.ts +44 -2
  401. package/src/lib/fabric-task-execution-authority.ts +7 -0
  402. package/src/lib/fabric-task-host.ts +29 -0
  403. package/src/lib/lab-activation.ts +223 -0
  404. package/src/lib/lab-live-execution-authority.ts +13 -0
  405. package/src/lib/lab-live-host.ts +30 -0
  406. package/src/lib/lab-live-pinned-sender.ts +56 -0
  407. package/src/lib/lab-live-route-production.ts +130 -0
  408. package/src/lib/lab-passive-linker-registration.ts +26 -0
  409. package/src/lib/local-management-attestation.ts +51 -0
  410. package/src/lib/local-management-capability.ts +100 -0
  411. package/src/lib/local-provider-reload-contract.ts +100 -0
  412. package/src/lib/optional-shutdown-hooks.ts +57 -0
  413. package/src/lib/pinned-http.ts +145 -26
  414. package/src/lib/process-control.ts +4 -1
  415. package/src/lib/provider-outbound.ts +49 -9
  416. package/src/lib/redact.ts +419 -3
  417. package/src/lib/self-launch-argv.ts +15 -0
  418. package/src/lib/server-resource-ownership.ts +71 -0
  419. package/src/lib/shadow-call.ts +35 -4
  420. package/src/lib/sse-decoder.ts +41 -0
  421. package/src/lib/state-store-registrations.ts +10 -2
  422. package/src/lib/system-restart-contract.ts +73 -0
  423. package/src/lib/token-estimate.ts +19 -2
  424. package/src/lib/tool-argument-integers.ts +202 -0
  425. package/src/lib/translator-budget.ts +44 -0
  426. package/src/lib/upstream-http-version.ts +57 -0
  427. package/src/lib/upstream-reachability.ts +95 -0
  428. package/src/lib/upstream-retry.ts +156 -3
  429. package/src/lib/windows-atomic-replace.ts +155 -0
  430. package/src/lib/windows-elevation.ts +70 -2
  431. package/src/lib/windows-secret-acl.ts +409 -69
  432. package/src/lib/windows-service-wrappers.ts +72 -0
  433. package/src/lib/windows-text.ts +106 -0
  434. package/src/lib/windows-user-principal.ts +341 -0
  435. package/src/lib/winsw.ts +33 -5
  436. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  437. package/src/oauth/account-import/index.ts +15 -0
  438. package/src/oauth/account-import/parser.ts +83 -0
  439. package/src/oauth/account-import/registry.ts +18 -0
  440. package/src/oauth/account-import/service.ts +75 -0
  441. package/src/oauth/account-import/types.ts +91 -0
  442. package/src/oauth/anthropic.ts +12 -1
  443. package/src/oauth/callback-server.ts +8 -2
  444. package/src/oauth/chatgpt.ts +12 -1
  445. package/src/oauth/command-code.ts +239 -0
  446. package/src/oauth/cursor.ts +46 -5
  447. package/src/oauth/google-antigravity.ts +35 -3
  448. package/src/oauth/health.ts +20 -12
  449. package/src/oauth/index.ts +398 -66
  450. package/src/oauth/key-providers.ts +16 -0
  451. package/src/oauth/kimi.ts +16 -2
  452. package/src/oauth/kiro.ts +50 -6
  453. package/src/oauth/local-token-detect.ts +11 -2
  454. package/src/oauth/log.ts +3 -1
  455. package/src/oauth/login-cli.ts +88 -28
  456. package/src/oauth/nous.ts +798 -0
  457. package/src/oauth/store.ts +119 -21
  458. package/src/oauth/token-guardian.ts +9 -3
  459. package/src/pi/models.ts +2 -2
  460. package/src/providers/alibaba-region-migration.ts +1 -1
  461. package/src/providers/antigravity-models.ts +521 -31
  462. package/src/providers/base-url-choices.ts +10 -0
  463. package/src/providers/codex-capacity.ts +292 -0
  464. package/src/providers/command-code-efforts.ts +144 -0
  465. package/src/providers/context-cap.ts +22 -5
  466. package/src/providers/cursor-pool.ts +72 -0
  467. package/src/providers/derive.ts +253 -6
  468. package/src/providers/fastwire.ts +501 -0
  469. package/src/providers/free-directory.ts +10 -7
  470. package/src/providers/google-vertex-location.ts +14 -0
  471. package/src/providers/key-failover.ts +71 -3
  472. package/src/providers/label.ts +1 -1
  473. package/src/providers/model-discovery-limits.ts +16 -0
  474. package/src/providers/model-discovery.ts +115 -22
  475. package/src/providers/model-rename-migration.ts +255 -0
  476. package/src/providers/model-rename-startup.ts +28 -0
  477. package/src/providers/openai-sidecar.ts +72 -4
  478. package/src/providers/openai-tier-startup.ts +31 -2
  479. package/src/providers/openai-tiers.ts +119 -4
  480. package/src/providers/openai-virtual-models.ts +1 -0
  481. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  482. package/src/providers/provider-id-rewrite.ts +29 -0
  483. package/src/providers/quota.ts +1319 -38
  484. package/src/providers/registry.ts +1429 -111
  485. package/src/providers/request-pacing.ts +310 -0
  486. package/src/providers/service-tier.ts +277 -0
  487. package/src/providers/slug-codec.ts +42 -6
  488. package/src/providers/static-model-discovery.ts +86 -0
  489. package/src/providers/xai-responses-opt-in.ts +15 -0
  490. package/src/providers/xai-transport.ts +11 -4
  491. package/src/reasoning-effort.ts +49 -1
  492. package/src/responses/compaction.ts +26 -1
  493. package/src/responses/custom-tool-compat.ts +266 -0
  494. package/src/responses/hosted-tool-policy.ts +9 -0
  495. package/src/responses/namespace-tool-compat.ts +355 -0
  496. package/src/responses/parser.ts +220 -38
  497. package/src/responses/provider-continuation.ts +98 -0
  498. package/src/responses/provider-opaque-metadata.ts +73 -0
  499. package/src/responses/reasoning-envelope.ts +9 -1
  500. package/src/responses/reasoning-replay-cache.ts +426 -0
  501. package/src/responses/schema.ts +7 -1
  502. package/src/responses/spill-store.ts +75 -10
  503. package/src/responses/state.ts +565 -27
  504. package/src/responses/thought-signature-replay.ts +347 -0
  505. package/src/responses/tool-search-compat.ts +301 -0
  506. package/src/responses/truncated-stop-reason.ts +60 -0
  507. package/src/router.ts +366 -30
  508. package/src/routing/analytics.ts +378 -0
  509. package/src/routing/capability.ts +244 -0
  510. package/src/routing/compatibility/assemble.ts +73 -0
  511. package/src/routing/compatibility/behavior.ts +278 -0
  512. package/src/routing/compatibility/catalog.ts +99 -0
  513. package/src/routing/compatibility/endpoint.ts +52 -0
  514. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  515. package/src/routing/compatibility/policy.ts +181 -0
  516. package/src/routing/compatibility/provider-slot.ts +56 -0
  517. package/src/routing/compatibility/reader.ts +110 -0
  518. package/src/routing/compatibility/subject.ts +191 -0
  519. package/src/routing/compatibility/types.ts +64 -0
  520. package/src/routing/compatibility/version.ts +104 -0
  521. package/src/routing/cost.ts +77 -0
  522. package/src/routing/evaluator.ts +495 -0
  523. package/src/routing/health.ts +412 -0
  524. package/src/routing/history/cursor.ts +43 -0
  525. package/src/routing/history/indexer.ts +605 -0
  526. package/src/routing/history/schema.ts +72 -0
  527. package/src/routing/profile-namespace.ts +15 -0
  528. package/src/routing/profile.ts +547 -0
  529. package/src/routing/quota.ts +145 -0
  530. package/src/routing/request-evidence.ts +45 -0
  531. package/src/routing/trace.ts +776 -0
  532. package/src/server/adapter-resolve.ts +2 -29
  533. package/src/server/auth-cors.ts +267 -46
  534. package/src/server/background-lifecycle.ts +182 -0
  535. package/src/server/chat-completions.ts +130 -56
  536. package/src/server/chat-native-sse.ts +331 -0
  537. package/src/server/chat-native.ts +426 -0
  538. package/src/server/claude-messages.ts +159 -43
  539. package/src/server/direct-local-http.ts +347 -0
  540. package/src/server/effort-policy.ts +18 -0
  541. package/src/server/github-copilot-responses-repair.ts +338 -0
  542. package/src/server/gui-static.ts +39 -10
  543. package/src/server/images.ts +94 -12
  544. package/src/server/index.ts +865 -181
  545. package/src/server/lifecycle.ts +284 -13
  546. package/src/server/live.ts +136 -17
  547. package/src/server/local-management-read-client.ts +90 -0
  548. package/src/server/local-provider-reload-client.ts +137 -0
  549. package/src/server/management/agent-settings-routes.ts +398 -103
  550. package/src/server/management/api-key-usage.ts +31 -5
  551. package/src/server/management/body.ts +6 -0
  552. package/src/server/management/combo-routes.ts +62 -24
  553. package/src/server/management/config-routes.ts +464 -51
  554. package/src/server/management/context.ts +80 -2
  555. package/src/server/management/integration-routes.ts +498 -0
  556. package/src/server/management/lab-automation-routes.ts +206 -0
  557. package/src/server/management/lab-routes.ts +563 -0
  558. package/src/server/management/logs-usage-routes.ts +101 -32
  559. package/src/server/management/model-routes.ts +189 -131
  560. package/src/server/management/model-rows.ts +163 -0
  561. package/src/server/management/native-integration-routes.ts +769 -0
  562. package/src/server/management/oauth-account-routes.ts +80 -4
  563. package/src/server/management/provider-capability-config.ts +48 -0
  564. package/src/server/management/provider-routes.ts +764 -157
  565. package/src/server/management/request-history-routes.ts +191 -0
  566. package/src/server/management/routing-analytics-routes.ts +74 -0
  567. package/src/server/management/routing-profile-routes.ts +380 -0
  568. package/src/server/management/shared.ts +27 -11
  569. package/src/server/management/sidebar-routes.ts +47 -31
  570. package/src/server/management/storage-log-guard-routes.ts +186 -0
  571. package/src/server/management/sync-response.ts +69 -0
  572. package/src/server/management/system-restart.ts +288 -32
  573. package/src/server/management/system-routes.ts +77 -0
  574. package/src/server/management/usage-summary-cache.ts +9 -1
  575. package/src/server/management/vision-sidecar-options.ts +167 -0
  576. package/src/server/management/web-search-sidecar-options.ts +120 -0
  577. package/src/server/management-api.ts +115 -14
  578. package/src/server/management-auth.ts +220 -5
  579. package/src/server/passive-route-linker.ts +66 -0
  580. package/src/server/ports.ts +41 -1
  581. package/src/server/proxy-liveness.ts +132 -5
  582. package/src/server/readiness.ts +99 -0
  583. package/src/server/relay-eager.ts +82 -42
  584. package/src/server/relay.ts +236 -76
  585. package/src/server/request-decompress.ts +113 -6
  586. package/src/server/request-log.ts +235 -22
  587. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  588. package/src/server/responses/agent-task-recovery.ts +465 -0
  589. package/src/server/responses/collaboration.ts +204 -35
  590. package/src/server/responses/compact.ts +442 -55
  591. package/src/server/responses/core.ts +2872 -331
  592. package/src/server/responses/empty-completion-guard.ts +276 -0
  593. package/src/server/responses/encrypted-payload.ts +62 -39
  594. package/src/server/responses/fetch-helpers.ts +79 -4
  595. package/src/server/responses/input-admission.ts +185 -0
  596. package/src/server/responses/pacing-overload.ts +13 -0
  597. package/src/server/responses/policy-fallback.ts +178 -0
  598. package/src/server/responses/responses-field-backfill.ts +251 -0
  599. package/src/server/responses/terminal-guard.ts +26 -5
  600. package/src/server/responses/upstream-error.ts +5 -0
  601. package/src/server/responses/ws-upstream.ts +308 -0
  602. package/src/server/responses-custom-tool-repair.ts +282 -0
  603. package/src/server/responses-item-id-repair.ts +54 -6
  604. package/src/server/responses-json-events.ts +90 -0
  605. package/src/server/responses-model-rewrite.ts +29 -0
  606. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  607. package/src/server/responses-snapshot-repair.ts +621 -0
  608. package/src/server/responses-terminal-repair.ts +342 -0
  609. package/src/server/responses-tool-search-repair.ts +267 -0
  610. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  611. package/src/server/responses.ts +18 -2
  612. package/src/server/search.ts +78 -13
  613. package/src/server/sse-frame-buffer.ts +292 -0
  614. package/src/server/sse-payload-rewrite.ts +110 -22
  615. package/src/server/startup-action-control.ts +8 -1
  616. package/src/server/startup-health-cache.ts +19 -1
  617. package/src/server/system-env.ts +80 -9
  618. package/src/server/ws-bridge.ts +39 -38
  619. package/src/service-manager-probe.ts +892 -0
  620. package/src/service.ts +1111 -90
  621. package/src/sidecar/auth.ts +92 -0
  622. package/src/sidecar/candidates.ts +83 -0
  623. package/src/storage/cleanup.ts +2 -2
  624. package/src/storage/scanner.ts +1 -1
  625. package/src/storage/worker-lifecycle.ts +14 -14
  626. package/src/tray/windows-tray.ps1 +83 -9
  627. package/src/tray/windows.ts +43 -16
  628. package/src/types/accounts.ts +37 -0
  629. package/src/types/config.ts +845 -0
  630. package/src/types/provider.ts +545 -0
  631. package/src/types/request.ts +384 -0
  632. package/src/types/tools.ts +131 -0
  633. package/src/types/wire.ts +80 -0
  634. package/src/types.ts +104 -1236
  635. package/src/update/index.ts +32 -19
  636. package/src/update/job.ts +442 -67
  637. package/src/update/notify.ts +12 -6
  638. package/src/update/npm-cache-preflight.d.mts +47 -0
  639. package/src/update/npm-cache-preflight.mjs +201 -0
  640. package/src/update/transactional-install.d.mts +22 -0
  641. package/src/update/transactional-install.mjs +259 -0
  642. package/src/usage/cost.ts +0 -0
  643. package/src/usage/expected-prices.ts +268 -16
  644. package/src/usage/log.ts +606 -41
  645. package/src/usage/summary.ts +177 -9
  646. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  647. package/src/usage/user-cost-overlays.ts +314 -0
  648. package/src/vision/anthropic-describe.ts +10 -6
  649. package/src/vision/backends.ts +97 -0
  650. package/src/vision/describe.ts +9 -3
  651. package/src/vision/eligibility.ts +250 -0
  652. package/src/vision/index.ts +238 -24
  653. package/src/vision/reasoning.ts +55 -0
  654. package/src/vision/routed-describe.ts +175 -0
  655. package/src/vision/timeout-bounds.ts +9 -0
  656. package/src/web-search/anthropic-executor.ts +13 -7
  657. package/src/web-search/backends.ts +108 -0
  658. package/src/web-search/exa-executor.ts +88 -0
  659. package/src/web-search/executor.ts +11 -3
  660. package/src/web-search/gemini-executor.ts +141 -0
  661. package/src/web-search/index.ts +150 -15
  662. package/src/web-search/loop.ts +279 -50
  663. package/src/web-search/parse.ts +125 -30
  664. package/src/web-search/sources.ts +60 -0
  665. package/src/web-search/xai-executor.ts +219 -0
  666. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  667. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  668. package/gui/dist/provider-icons/antigravity.svg +0 -1
  669. package/gui/dist/provider-icons/claude.svg +0 -1
  670. package/gui/dist/provider-icons/copilot.svg +0 -1
  671. package/gui/dist/provider-icons/cursor.svg +0 -2
  672. package/gui/dist/provider-icons/gemini.svg +0 -1
  673. package/gui/dist/provider-icons/grok-color.svg +0 -1
  674. package/gui/dist/provider-icons/kiro.svg +0 -14
  675. package/src/cli/internal-dispatch.ts +0 -20
@@ -1,19 +1,23 @@
1
1
  import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
2
2
  import { parseCallbackInput } from "./callback-server";
3
3
  import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
4
+ import { maskEmail } from "../lib/privacy";
4
5
  import { loadConfig, resolveEnvValue, saveConfig } from "../config";
5
6
  import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro";
6
- import { getAccountCredential, getAccountSet, removeAccount, saveAccountCredential, saveCredential, setActiveAccount, getCredential, credentialGeneration, createOAuthRefreshIntentLock, mergeAccountCredential, markAccountNeedsReauthIfGeneration, readOAuthRefreshIntent, writeOAuthRefreshIntent, markOAuthRefreshIntentStaleOwner, clearOAuthRefreshIntent, OAuthMutationBusyError } from "./store";
7
+ import { getAccountCredential, getAccountSet, removeAccount, saveAccountCredential, saveCredential, setActiveAccount, getCredential, credentialGeneration, createOAuthRefreshIntentLock, mergeAccountCredential, markAccountNeedsReauthIfGeneration, readOAuthRefreshIntent, writeOAuthRefreshIntent, markOAuthRefreshIntentStaleOwner, clearOAuthRefreshIntent, normalizeAuthStoreBuffer, OAuthMutationBusyError } from "./store";
7
8
  import { loginXai, refreshXaiToken, XAI_LOCAL_CLI_DETACH_WARNING, XaiTokenRequestError } from "./xai";
8
9
  import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError, loginAnthropic, refreshAnthropicToken } from "./anthropic";
9
10
  import { loginKimi, refreshKimiToken } from "./kimi";
11
+ import { loginNous, NousTokenError, refreshNousToken, clearNousRefreshIntent, RefreshIntentIOError } from "./nous";
10
12
  import { loginChatGPT, refreshChatGPTToken } from "./chatgpt";
11
13
  import { loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
12
14
  import { loginCursor, refreshCursorToken } from "./cursor";
13
15
  import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUrl } from "./github-copilot";
16
+ import { loginCommandCode, refreshCommandCodeToken } from "./command-code";
17
+ import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
14
18
  import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
15
19
  import { apiKeyPoolEntryId, sanitizeApiKeyValue } from "../providers/api-keys";
16
- import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../providers/registry";
20
+ import { effectiveGoogleMode, getProviderRegistryEntry, mergeRegistryStaticHeaders, providerMatchesRegistryTransport } from "../providers/registry";
17
21
  import { resolveProviderModelDiscoveryUrl } from "../providers/model-discovery";
18
22
  import { resolveProviderTransport } from "../providers/xai-transport";
19
23
  import { detectClaudeCodeToken, detectGrokCliToken, hasComparableGrokIdentity, isSameGrokIdentity, shouldAdoptGrokGeneration } from "./local-token-detect";
@@ -51,10 +55,26 @@ export interface OAuthAccessSnapshot {
51
55
  accountId: string;
52
56
  generation: string;
53
57
  accessToken: string;
58
+ /** Cloud Code Assist project selected during Antigravity login. */
59
+ projectId?: string;
54
60
  /** Safe request-routing subset; refresh-only Kiro client secrets never leave the credential store. */
55
61
  kiro?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion">;
56
62
  }
57
63
 
64
+ export interface ObservedOAuthAccessSnapshot extends OAuthAccessSnapshot {
65
+ /** Allowlisted provider API origin consumed by GitHub Copilot model discovery. */
66
+ apiBaseUrl?: string;
67
+ }
68
+
69
+ export type OAuthActiveTokenObservation =
70
+ | { readonly kind: "available"; readonly snapshot: ObservedOAuthAccessSnapshot }
71
+ | { readonly kind: "missing" }
72
+ | { readonly kind: "malformed" }
73
+ | { readonly kind: "needs-reauth" }
74
+ | { readonly kind: "expired" }
75
+ | { readonly kind: "near-expiry" }
76
+ | { readonly kind: "unsupported" };
77
+
58
78
  const MAX_OAUTH_TOKEN_REFRESH_FLIGHTS = 32;
59
79
  const OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000;
60
80
  interface OAuthRefreshFlightEvidence { flightId: string; dispatched: boolean }
@@ -145,6 +165,14 @@ function oauthDefaultModel(id: string): string {
145
165
  }
146
166
 
147
167
  export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
168
+ "command-code": {
169
+ // Add-account/reauth must not reimport the current local CLI credential.
170
+ login: (ctrl, opts) => loginCommandCode(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
171
+ refresh: refreshCommandCodeToken,
172
+ providerConfig: oauthConfig("command-code"),
173
+ defaultModel: oauthDefaultModel("command-code"),
174
+ defaultRefreshPolicy: "disabled",
175
+ },
148
176
  xai: {
149
177
  // forceLogin skips the local grok-cli import so a SECOND account can be chosen in the browser.
150
178
  login: (ctrl, opts) => loginXai(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
@@ -167,6 +195,22 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
167
195
  providerConfig: oauthConfig("kimi"),
168
196
  defaultModel: oauthDefaultModel("kimi"),
169
197
  },
198
+ nous: {
199
+ // Nous Portal device-grant login (RFC 8628) against portal.nousresearch.com.
200
+ // The access token is the per-request inference JWT (scope inference:invoke).
201
+ // Refresh tokens are single-use and rotated server-side on every refresh:
202
+ // keep background refresh lazy-only (the default) so concurrent refreshes
203
+ // cannot trip the Portal's token-reuse revocation.
204
+ login: (ctrl) => loginNous(ctrl),
205
+ refresh: (rt, signal) => refreshNousToken(rt, signal),
206
+ providerConfig: oauthConfig("nous"),
207
+ defaultModel: oauthDefaultModel("nous"),
208
+ // Single-use rotating refresh tokens must never be background-refreshed
209
+ // proactively: concurrent refreshes would trip the Portal's reuse
210
+ // revocation. Anchor the lazy-only default explicitly so it cannot be
211
+ // silently overridden to proactive.
212
+ defaultRefreshPolicy: "lazy-only",
213
+ },
170
214
  kiro: {
171
215
  login: (ctrl, opts) => loginKiro(ctrl, { forceLogin: opts?.forceLogin }),
172
216
  refresh: (rt, signal, credential) => refreshKiroToken(rt, signal, credential),
@@ -180,7 +224,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
180
224
  defaultModel: oauthDefaultModel("google-antigravity"),
181
225
  },
182
226
  cursor: {
183
- login: (ctrl) => loginCursor(ctrl),
227
+ login: (ctrl, opts) => loginCursor(ctrl, undefined, { forceLogin: opts?.forceLogin }),
184
228
  refresh: refreshCursorToken,
185
229
  providerConfig: oauthConfig("cursor"),
186
230
  defaultModel: oauthDefaultModel("cursor"),
@@ -248,12 +292,63 @@ export class UnsupportedOAuthProviderError extends Error {
248
292
  }
249
293
 
250
294
  export class OAuthLoginRequiredError extends Error {
295
+ readonly provider: string;
296
+
251
297
  constructor(provider: string) {
252
298
  super(`Not logged in to ${provider}. Run: ocx login ${provider}`);
253
299
  this.name = "OAuthLoginRequiredError";
300
+ this.provider = provider;
301
+ }
302
+ }
303
+
304
+ export class OAuthProviderPublicationError extends Error {
305
+ constructor() {
306
+ super("OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login.");
307
+ this.name = "OAuthProviderPublicationError";
308
+ }
309
+ }
310
+
311
+ export class OAuthReauthIdentityMismatchError extends Error {
312
+ constructor() {
313
+ super("Signed-in account does not match the selected account. Sign in with the same account.");
314
+ this.name = "OAuthReauthIdentityMismatchError";
315
+ }
316
+ }
317
+
318
+ export class OAuthReauthIdentityUnverifiedError extends Error {
319
+ constructor() {
320
+ super("Could not verify signed-in account identity for reauth.");
321
+ this.name = "OAuthReauthIdentityUnverifiedError";
254
322
  }
255
323
  }
256
324
 
325
+ class OAuthLoginSupersededError extends Error {
326
+ constructor() {
327
+ super("OAuth login was superseded before credential persistence");
328
+ this.name = "OAuthLoginSupersededError";
329
+ }
330
+ }
331
+
332
+ /** Project arbitrary OAuth failures onto the small, stable public error vocabulary. */
333
+ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
334
+ if (error instanceof OAuthMutationBusyError) {
335
+ return error.message === "OAuth mutation queue wait timed out"
336
+ ? "OAuth mutation queue wait timed out"
337
+ : "OAuth mutation queue is busy";
338
+ }
339
+ if (
340
+ (error instanceof OAuthLoginRequiredError && isOAuthProvider(error.provider))
341
+ || error instanceof OAuthProviderPublicationError
342
+ // Reauth identity outcomes carry fixed, account-free remediation text. Dropping them to the
343
+ // generic message hides WHICH failure the user must fix (sign in with the selected account).
344
+ || error instanceof OAuthReauthIdentityMismatchError
345
+ || error instanceof OAuthReauthIdentityUnverifiedError
346
+ || error instanceof OAuthTokenRefreshBusyError
347
+ || error instanceof OAuthTokenRefreshStaleError
348
+ ) return error.message;
349
+ return "OAuth authentication failed. Check the OpenCodex account status and retry.";
350
+ }
351
+
257
352
  function accessSnapshot(provider: string, accountId: string, cred: OAuthCredentials): OAuthAccessSnapshot {
258
353
  const storedKiroRouting = {
259
354
  ...(cred.kiro?.profileArn ? { profileArn: cred.kiro.profileArn } : {}),
@@ -265,6 +360,7 @@ function accessSnapshot(provider: string, accountId: string, cred: OAuthCredenti
265
360
  accountId,
266
361
  generation: credentialGeneration(cred),
267
362
  accessToken: cred.access,
363
+ ...(cred.projectId ? { projectId: cred.projectId } : {}),
268
364
  // Stored account metadata remains authoritative. Metadata-less legacy/environment credentials
269
365
  // may use explicit environment routing, but never borrow the currently signed-in local CLI account.
270
366
  ...(provider === "kiro"
@@ -277,6 +373,38 @@ function accessSnapshot(provider: string, accountId: string, cred: OAuthCredenti
277
373
  };
278
374
  }
279
375
 
376
+ /**
377
+ * Observe the active OAuth token from an auth-store buffer supplied by its filesystem owner.
378
+ * Missing, malformed, reauth-required, and expiring credentials are typed no-token outcomes;
379
+ * this path never refreshes, locks, hardens, backs up, or persists credentials.
380
+ */
381
+ export function observeActiveOAuthAccessToken(
382
+ provider: string,
383
+ authStoreBuffer: Uint8Array | null,
384
+ now = Date.now(),
385
+ ): OAuthActiveTokenObservation {
386
+ const authStore = normalizeAuthStoreBuffer(authStoreBuffer);
387
+ if (authStore.kind === "absent") return { kind: "missing" };
388
+ if (authStore.kind === "malformed") return { kind: "malformed" };
389
+ if (!isOAuthProvider(provider)) return { kind: "unsupported" };
390
+
391
+ const accountSet = authStore.store[provider];
392
+ const account = accountSet?.accounts.find(candidate => candidate.id === accountSet.activeAccountId);
393
+ if (!account) return { kind: "missing" };
394
+ if (account.needsReauth) return { kind: "needs-reauth" };
395
+ if (account.credential.expires <= now) return { kind: "expired" };
396
+ if (account.credential.expires <= now + REFRESH_SKEW_MS) return { kind: "near-expiry" };
397
+
398
+ const apiBaseUrl = validateCopilotApiBaseUrl(account.credential.apiBaseUrl);
399
+ return {
400
+ kind: "available",
401
+ snapshot: {
402
+ ...accessSnapshot(provider, account.id, account.credential),
403
+ ...(apiBaseUrl ? { apiBaseUrl } : {}),
404
+ },
405
+ };
406
+ }
407
+
280
408
  async function resolveAccessSnapshotForAccount(
281
409
  provider: string,
282
410
  accountId: string,
@@ -376,6 +504,11 @@ function terminal(error:unknown):boolean{
376
504
  if(error instanceof XaiTokenRequestError)return ["invalid_grant","refresh_token_reused","revoked_token"].includes(error.oauthError??"");
377
505
  if(error instanceof AnthropicTokenError)return (error.httpStatus===400||error.httpStatus===401)&&["invalid_grant","refresh_token_reused","revoked","revoked_token","refresh_token_revoked"].includes(error.oauthError??"");
378
506
  if(error instanceof KiroTokenRefreshError)return (error.httpStatus===400||error.httpStatus===401)&&error.oauthError!==undefined;
507
+ if(error instanceof NousTokenError)return error.terminal===true||["invalid_grant","refresh_token_reused","revoked","revoked_token","expired_token"].includes(error.oauthError??"");
508
+ // Local durable-write/read/cleanup failures are operational, not credential
509
+ // death: the provider credential was never rejected or consumed. Never mark
510
+ // the account needsReauth for broken local persistence infrastructure.
511
+ if (error instanceof RefreshIntentIOError) return false;
379
512
  return isTerminalRefreshError(error);
380
513
  }
381
514
  function authoritative(stored:OAuthCredentials,active:boolean,now:()=>number):OAuthCredentials{if(stored.source!=="local-cli")return stored;const disk=detectGrokCliToken();if(!disk)return stored;const allowed=isSameGrokIdentity(stored,disk)||(active&&!hasComparableGrokIdentity(stored,disk));return allowed&&shouldAdoptGrokGeneration(stored,disk,now(),REFRESH_SKEW_MS)?disk:stored;}
@@ -399,6 +532,47 @@ function newerClaudeCredential(stored: OAuthCredentials, now: number): OAuthCred
399
532
  return credentialGeneration(disk) !== credentialGeneration(stored) ? disk : undefined;
400
533
  }
401
534
 
535
+ /**
536
+ * Preserve an already-rotated Nous refresh token (RT-B) after a terminal refresh
537
+ * error (e.g. the returned access JWT lacked `inference:invoke`). The unusable
538
+ * access token is NOT persisted as valid: the recovery credential carries an
539
+ * empty access placeholder with a past expiry so it can never be routed, and the
540
+ * account is marked needsReauth by the caller. Generation-safe: a concurrent
541
+ * newer write wins and is never overwritten.
542
+ */
543
+ async function preserveNousRotatedRefresh(
544
+ provider: string,
545
+ accountId: string,
546
+ rotatedRefresh: string,
547
+ expectedGeneration: string,
548
+ previous: OAuthCredentials,
549
+ ): Promise<{ kind: "persisted"; generation: string } | { kind: "superseded" } | { kind: "failed" }> {
550
+ try {
551
+ const recovery: OAuthCredentials = {
552
+ refresh: rotatedRefresh,
553
+ // Never persist the unusable access token: an empty placeholder with a
554
+ // past expiry can never be observed as a valid credential.
555
+ access: "",
556
+ expires: 0,
557
+ ...(previous.accountId ? { accountId: previous.accountId } : {}),
558
+ ...(previous.email ? { email: previous.email } : {}),
559
+ ...(previous.source ? { source: previous.source } : {}),
560
+ };
561
+ const outcome = await mergeAccountCredential(provider, accountId, recovery, { expectedGeneration });
562
+ if (outcome.superseded) return { kind: "superseded" };
563
+ // Return the exact generation this write produced, so the caller never
564
+ // re-reads the store (a concurrent writer could otherwise supply a different
565
+ // credential generation and be marked needsReauth by mistake).
566
+ return { kind: "persisted", generation: credentialGeneration(recovery) };
567
+ } catch (error) {
568
+ // A store-mutation busy outcome is transient and retryable; surface it
569
+ // unchanged so the caller can retry rather than treating it as a permanent
570
+ // RT-B persistence failure.
571
+ if (error instanceof OAuthMutationBusyError) throw error;
572
+ return { kind: "failed" };
573
+ }
574
+ }
575
+
402
576
  export async function refreshAnthropicAccountWithLock(
403
577
  provider: string,
404
578
  accountId: string,
@@ -512,10 +686,69 @@ export async function refreshGenericAccountWithLock(
512
686
  throw new OAuthLoginRequiredError(provider);
513
687
  }
514
688
  logOAuthEvent("OAuth credentials rotated and persisted", { provider, accountId });
689
+ // Best-effort bookkeeping cleanup: the rotated credential is already
690
+ // durably persisted. A failure to unlink the old-token intent file
691
+ // (EACCES/EPERM/EBUSY/EROFS) must not turn a committed rotation into a
692
+ // failed refresh. The stale intent keys the OLD token, which is no longer
693
+ // stored, so leaving it behind blocks nothing and is safe. It must also
694
+ // never route through the generic refresh error path (no needsReauth).
695
+ if (provider === "nous") {
696
+ try {
697
+ clearNousRefreshIntent(stored.refresh);
698
+ } catch (cleanupErr) {
699
+ logOAuthEvent("OAuth refresh intent cleanup failed (non-fatal)", {
700
+ provider,
701
+ accountId,
702
+ cause: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr),
703
+ });
704
+ }
705
+ }
515
706
  return fresh.access;
516
707
  } catch (error) {
517
708
  if (error instanceof OAuthMutationBusyError) throw error;
518
709
  if (!terminal(error)) throw error;
710
+ // Nous-specific failure-atomicity: a terminal refresh error that carries
711
+ // an already-issued rotated refresh token (e.g. the access JWT lacked the
712
+ // required `inference:invoke` scope) means the server consumed RT-A and
713
+ // issued RT-B. RT-B must be preserved generation-safely BEFORE forcing
714
+ // reauthentication; discarding it would lose the only usable refresh
715
+ // material and force a full re-auth for no reason.
716
+ if (provider === "nous" && error instanceof NousTokenError) {
717
+ const rotated = error.getRotatedRefresh();
718
+ if (rotated !== undefined && rotated !== stored.refresh) {
719
+ const outcome = await preserveNousRotatedRefresh(
720
+ provider,
721
+ accountId,
722
+ rotated,
723
+ generation,
724
+ stored,
725
+ );
726
+ if (outcome.kind === "persisted") {
727
+ // RT-A's intent is cleared only after RT-B is durably persisted;
728
+ // cleanup itself stays best-effort (a stale RT-A intent keys a token
729
+ // that is no longer stored).
730
+ try {
731
+ clearNousRefreshIntent(stored.refresh);
732
+ } catch (cleanupErr) {
733
+ logOAuthEvent("OAuth refresh intent cleanup failed (non-fatal)", {
734
+ provider,
735
+ accountId,
736
+ cause: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr),
737
+ });
738
+ }
739
+ // Mark exactly the generation this write produced — never a
740
+ // credential written by a concurrent login between the merge and
741
+ // this step.
742
+ await markAccountNeedsReauthIfGeneration(provider, accountId, outcome.generation, writerGeneration);
743
+ } else {
744
+ // RT-B persistence failed or a newer generation superseded it:
745
+ // never clear RT-A's intent (RT-A was consumed), and mark the old
746
+ // generation needsReauth (a no-op if a newer generation won).
747
+ await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration);
748
+ }
749
+ throw new OAuthLoginRequiredError(provider);
750
+ }
751
+ }
519
752
  await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration);
520
753
  throw new OAuthLoginRequiredError(provider);
521
754
  }
@@ -572,31 +805,63 @@ function modelDiscoveryTransportSeed(providerName: string, prov: OcxProviderConf
572
805
  }
573
806
 
574
807
  /**
575
- * Provider-correct `GET /models` request (URL + headers), so both model-listing paths fetch the
808
+ * Provider-correct model-discovery request (URL + headers), so both model-listing paths fetch the
576
809
  * LIVE catalog correctly per adapter. Anthropic is the special case: its endpoint is `/v1/models`
577
810
  * (not `/models`), it needs `anthropic-version`, and it authenticates with `x-api-key` by default
578
811
  * (or `Authorization: Bearer` when `apiKeyTransport = "bearer"`), plus the OAuth beta for oauth
579
812
  * mode — not a bare Bearer. Google (ai-studio mode)
580
813
  * is the other special case: `x-goog-api-key` + `/v1beta/models`, returning `{ models: [...] }`.
581
814
  * The catalog authority gate intentionally degrades that non-OpenAI shape to stale/static data.
582
- * Everyone else uses the OpenAI-style `/models` + Bearer with a `{ data: [{ id, owned_by? }] }`
583
- * response.
815
+ * Antigravity uses its CCA `:fetchAvailableModels` RPC; everyone else uses the OpenAI-style
816
+ * `/models` + Bearer with a `{ data: [{ id, owned_by? }] }` response.
584
817
  */
585
- export function buildModelsRequest(prov: OcxProviderConfig, apiKey: string | undefined, providerName = ""): { url: string; headers: Record<string, string> } {
818
+ export interface ModelsRequestObservedAuth {
819
+ readonly oauthApiBaseUrl?: string;
820
+ }
821
+
822
+ export function buildModelsRequest(
823
+ prov: OcxProviderConfig,
824
+ apiKey: string | undefined,
825
+ providerName = "",
826
+ observedAuth?: ModelsRequestObservedAuth,
827
+ ): { method?: "POST"; url: string; headers: Record<string, string> } {
586
828
  const transportSeed = modelDiscoveryTransportSeed(providerName, prov);
829
+ const copilotApiBaseUrl = observedAuth === undefined
830
+ ? (providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(providerName) : undefined)
831
+ : observedAuth.oauthApiBaseUrl;
587
832
  const effectiveProvider = resolveProviderTransport(
588
833
  providerName,
589
834
  transportSeed,
590
835
  undefined,
591
- providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(providerName) : undefined,
836
+ copilotApiBaseUrl,
592
837
  );
593
- const headers: Record<string, string> = { ...(effectiveProvider.headers ?? {}) };
838
+ // Model discovery is an upstream request like any other, so it carries the same registry
839
+ // static headers the inference path does. Without this a provider is identified correctly
840
+ // when it answers a completion but anonymously when it lists its own models, which is the
841
+ // kind of split fingerprint an upstream rate limiter reads as two different clients.
842
+ const registryStaticHeaders = providerMatchesRegistryTransport(providerName, effectiveProvider)
843
+ ? getProviderRegistryEntry(providerName)?.staticHeaders
844
+ : undefined;
845
+ const headers: Record<string, string> = {
846
+ ...(mergeRegistryStaticHeaders(registryStaticHeaders, effectiveProvider.headers) ?? {}),
847
+ };
594
848
  const discoveryUrl = (defaultUrl: string): string => resolveProviderModelDiscoveryUrl(
595
849
  providerName,
596
850
  prov,
597
851
  effectiveProvider.baseUrl,
598
852
  defaultUrl,
599
853
  );
854
+ if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
855
+ headers.Accept = "application/json";
856
+ headers["Content-Type"] = "application/json";
857
+ headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA;
858
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
859
+ return {
860
+ method: "POST",
861
+ url: discoveryUrl(`${effectiveProvider.baseUrl.replace(/\/+$/, "")}/v1internal:fetchAvailableModels`),
862
+ headers,
863
+ };
864
+ }
600
865
  if (effectiveGoogleMode(providerName, effectiveProvider) === "ai-studio") {
601
866
  // Generative Language API: API key goes in x-goog-api-key (never Authorization: Bearer),
602
867
  // models live under /v1beta (v1 misses preview models), and pageSize maxes at 1000 —
@@ -630,9 +895,7 @@ export function buildModelsRequest(prov: OcxProviderConfig, apiKey: string | und
630
895
  *
631
896
  * Only touches providers that are registry-managed AND still `authMode: "oauth"`. Preset fields
632
897
  * are refreshed, while the registry's `liveModels` default is normally filled only when no value
633
- * is stored. Antigravity has one versioned exception below because its old GUI-generated `true`
634
- * cannot be distinguished from a hand-written pre-migration `true`. Persists + returns true when
635
- * anything changed.
898
+ * is stored. Persists + returns true when anything changed.
636
899
  */
637
900
  function cloneProviderField(value: unknown): unknown {
638
901
  if (Array.isArray(value)) return [...value];
@@ -659,31 +922,62 @@ const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
659
922
  "autoToolChoiceOnlyModels",
660
923
  "preserveReasoningContentModels",
661
924
  ];
925
+ // `requiresReasoningPlaceholderModels` is deliberately NOT reconciled here: no
926
+ // OAuth preset seeds it, so the delete-when-preset-undefined branch would wipe
927
+ // an explicit user opt-out (`[]`) on every startup. Registry seeds still reach
928
+ // existing rows through enrichProviderFromRegistry, which is fill-only and
929
+ // preserves explicit saved values.
662
930
 
663
931
  const GOOGLE_ANTIGRAVITY_PROVIDER = "google-antigravity";
664
- const GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION = 1 as const;
932
+ const GOOGLE_ANTIGRAVITY_LIVE_DISCOVERY_VERSION = 2 as const;
933
+
934
+ /** Only migrate the three-model experimental seed; an operator's later `liveModels: false` wins. */
935
+ function isLegacyCommandCodeStaticCatalog(provider: OcxProviderConfig): boolean {
936
+ return provider.liveModels === false
937
+ && provider.defaultModel === "deepseek-v4-flash"
938
+ && JSON.stringify(provider.models) === JSON.stringify(["deepseek-v4-flash", "kimi-k3", "glm-5.2"]);
939
+ }
940
+
941
+ function isLegacyAntigravityStaticCatalog(provider: OcxProviderConfig): boolean {
942
+ // A fingerprint of the shape version 1 actually shipped, NOT of the current registry.
943
+ // These literals must stay frozen as the model list moves on: matching them is how we
944
+ // know the row is the untouched v1 seed rather than a user's own selection.
945
+ return provider.liveModels === false
946
+ && provider.adapter === "google"
947
+ && provider.baseUrl === "https://daily-cloudcode-pa.googleapis.com"
948
+ && provider.authMode === "oauth"
949
+ && provider.googleMode === "cloud-code-assist"
950
+ && provider.defaultModel === "gemini-3.6-flash"
951
+ && JSON.stringify(provider.models) === JSON.stringify([
952
+ "gemini-3.6-flash",
953
+ "gemini-3.1-pro",
954
+ "gemini-3.1-flash-image",
955
+ "claude-sonnet-4-6",
956
+ "claude-opus-4-6-thinking",
957
+ "gpt-oss-120b-medium",
958
+ ]);
959
+ }
960
+
961
+ /** Promote only the versioned canonical static seed; unmarked `liveModels: false` remains user intent. */
962
+ function migrateLegacyAntigravityStaticCatalog(config: OcxConfig): boolean {
963
+ if (config.googleAntigravityStaticCatalogVersion !== 1) return false;
964
+ const provider = config.providers[GOOGLE_ANTIGRAVITY_PROVIDER];
965
+ if (provider && isLegacyAntigravityStaticCatalog(provider)) provider.liveModels = true;
966
+ config.googleAntigravityStaticCatalogVersion = GOOGLE_ANTIGRAVITY_LIVE_DISCOVERY_VERSION;
967
+ return true;
968
+ }
665
969
 
666
970
  export function reconcileOAuthProviders(config: OcxConfig): boolean {
667
- let changed = false;
668
- const migrateAntigravityStaticCatalog =
669
- config.googleAntigravityStaticCatalogVersion !== GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
971
+ let changed = migrateLegacyAntigravityStaticCatalog(config);
670
972
  for (const [name, prov] of Object.entries(config.providers)) {
671
973
  const def = OAUTH_PROVIDERS[name];
672
- // Normalize the canonical row before the OAuth-only reconciliation guard. The old GUI and a
673
- // manual edit both persist the same bare `true`, with no source metadata, so every ambiguous
674
- // pre-marker value is reset once. A deliberate live-discovery choice can be re-enabled after
675
- // the marker and is then preserved. Do this before the guard so omitted/non-OAuth authMode
676
- // rows do not get stamped without actually receiving the new static default.
677
- if (name === GOOGLE_ANTIGRAVITY_PROVIDER && migrateAntigravityStaticCatalog && prov.liveModels !== false) {
678
- prov.liveModels = false;
974
+ if (name === "command-code" && isLegacyCommandCodeStaticCatalog(prov)) {
975
+ // The former experimental preset was the exact three-model seed above. It was not a user
976
+ // choice to disable discovery, so promote only that shape to the account live catalog.
977
+ prov.liveModels = true;
679
978
  changed = true;
680
979
  }
681
- // During the one-time Antigravity static-catalog migration, also refresh preset catalog
682
- // fields when authMode is omitted or non-oauth. Otherwise liveModels flips to static while
683
- // a stale models[] remains the published catalog forever.
684
- const migrateAntigravityCatalogFields =
685
- name === GOOGLE_ANTIGRAVITY_PROVIDER && migrateAntigravityStaticCatalog;
686
- if (!def || (prov.authMode !== "oauth" && !migrateAntigravityCatalogFields)) continue;
980
+ if (!def || prov.authMode !== "oauth") continue;
687
981
  const preset = def.providerConfig;
688
982
  for (const field of OAUTH_RECONCILE_FIELDS) {
689
983
  if (JSON.stringify(prov[field]) === JSON.stringify(preset[field])) continue;
@@ -694,23 +988,19 @@ export function reconcileOAuthProviders(config: OcxConfig): boolean {
694
988
  }
695
989
  changed = true;
696
990
  }
697
- // Before this marker existed, the GUI materialized an omitted `liveModels` as `true` on any
698
- // settings save. Since persisted values have no provenance, the pre-guard normalization above
699
- // intentionally resets all pre-marker `true` values once. Later choices are version-bounded.
700
991
  if (prov.liveModels === undefined && preset.liveModels !== undefined) {
701
992
  prov.liveModels = preset.liveModels;
702
993
  changed = true;
703
994
  }
704
995
  // Heal a defaultModel that no longer exists in the refreshed list (e.g. a deprecated snapshot).
705
- if (prov.defaultModel && preset.defaultModel && !(prov.models ?? []).includes(prov.defaultModel)) {
996
+ // Skip providers without a static preset `models` list: for live-discovery providers
997
+ // (e.g. command-code OAuth) the account-scoped catalog is not enumerable here, so any
998
+ // persisted defaultModel is a user selection and must not be overwritten by the seed.
999
+ if (prov.defaultModel && preset.defaultModel && preset.models && preset.models.length > 0 && !(prov.models ?? []).includes(prov.defaultModel)) {
706
1000
  prov.defaultModel = preset.defaultModel;
707
1001
  changed = true;
708
1002
  }
709
1003
  }
710
- if (migrateAntigravityStaticCatalog) {
711
- config.googleAntigravityStaticCatalogVersion = GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
712
- changed = true;
713
- }
714
1004
  if (changed) saveConfig(config);
715
1005
  return changed;
716
1006
  }
@@ -767,19 +1057,28 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
767
1057
  if (provider === "chatgpt") return;
768
1058
  const def = OAUTH_PROVIDERS[provider];
769
1059
  if (!def) return;
1060
+ if (provider === GOOGLE_ANTIGRAVITY_PROVIDER) migrateLegacyAntigravityStaticCatalog(config);
770
1061
  const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
771
1062
  if (namespaceCollision) throw new Error(namespaceCollision);
772
1063
  const existing = config.providers[provider];
773
1064
  const next: OcxProviderConfig = { ...def.providerConfig };
774
- // `liveModels` is a user-facing provider toggle. A registry default seeds new rows, but an
775
- // explicit post-migration choice must survive re-login and the latest-config upsert. Old GUI
776
- // saves and manual edits left identical pre-marker `true` values, so that ambiguous state is
777
- // reset once; users who deliberately forced discovery can re-enable it after migration.
778
- const preserveExistingLiveModels = provider !== GOOGLE_ANTIGRAVITY_PROVIDER
779
- || config.googleAntigravityStaticCatalogVersion === GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
780
- if (preserveExistingLiveModels && typeof existing?.liveModels === "boolean") {
1065
+ // `liveModels` is a user-facing provider toggle. Preserve either explicit setting across login;
1066
+ // Antigravity's CCA discovery now uses its real RPC, so legacy `true` remains a valid choice.
1067
+ if (typeof existing?.liveModels === "boolean" && !isLegacyCommandCodeStaticCatalog(existing)) {
781
1068
  next.liveModels = existing.liveModels;
782
1069
  }
1070
+ // The Command Code protocol-version pin is an operator compatibility control. A re-login,
1071
+ // add-account, or reauth rebuilds the row from the preset, which has no version; carry the
1072
+ // existing pin so authentication changes do not silently revert the documented control.
1073
+ if (existing?.commandCodeVersion !== undefined) {
1074
+ next.commandCodeVersion = existing.commandCodeVersion;
1075
+ }
1076
+ // User-configured price overlays are operator data, not preset state; a
1077
+ // re-login, add-account, or reauth must not silently drop them from the
1078
+ // Logs/Usage estimates.
1079
+ if (existing?.modelCosts !== undefined) {
1080
+ next.modelCosts = existing.modelCosts;
1081
+ }
783
1082
  if (existing && getProviderRegistryEntry(provider)?.allowKeyAuthOverride === true) {
784
1083
  // Shared sanitizeApiKeyValue trim / no-CRLF checks from api-key pool writes.
785
1084
  let storedApiKey = sanitizeApiKeyValue(existing.apiKey);
@@ -803,9 +1102,6 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
803
1102
  }
804
1103
  }
805
1104
  config.providers[provider] = next;
806
- if (provider === GOOGLE_ANTIGRAVITY_PROVIDER) {
807
- config.googleAntigravityStaticCatalogVersion = GOOGLE_ANTIGRAVITY_STATIC_CATALOG_VERSION;
808
- }
809
1105
  }
810
1106
 
811
1107
  interface RunLoginDeps {
@@ -816,6 +1112,7 @@ interface RunLoginDeps {
816
1112
  settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
817
1113
  removeAccount?: typeof removeAccount;
818
1114
  setActiveAccount?: typeof setActiveAccount;
1115
+ assertCurrentOwner?: () => void;
819
1116
  }
820
1117
 
821
1118
  /** Roll back only accounts created by this forced login, preserving concurrent refreshes of others. */
@@ -865,6 +1162,7 @@ export async function runLogin(
865
1162
  const cred: OAuthCredentials = rawCred.source ? rawCred : { ...rawCred, source: "oauth" };
866
1163
  const settleKiroTransaction = deps.settleKiroLoginTransaction ?? settleKiroLoginTransaction;
867
1164
  try {
1165
+ deps.assertCurrentOwner?.();
868
1166
  // Validate the provider row before credential persistence. A namespace claimed during the
869
1167
  // credential write is handled again below before the latest row is re-upserted.
870
1168
  if (provider !== "chatgpt") {
@@ -875,7 +1173,7 @@ export async function runLogin(
875
1173
  const existing = getAccountCredential(provider, opts.reauthAccountId);
876
1174
  if (!existing) throw new Error(`Unknown account for reauth: ${opts.reauthAccountId}`);
877
1175
  if (!existing.accountId && !existing.email) {
878
- throw new Error("Could not verify signed-in account identity for reauth.");
1176
+ throw new OAuthReauthIdentityUnverifiedError();
879
1177
  }
880
1178
  const identityMatches = existing.accountId && cred.accountId
881
1179
  ? existing.accountId === cred.accountId
@@ -883,12 +1181,15 @@ export async function runLogin(
883
1181
  ? existing.email.toLowerCase() === cred.email.toLowerCase()
884
1182
  : false;
885
1183
  if (!identityMatches) {
886
- throw new Error("Signed-in account does not match the selected account. Sign in with the same account.");
1184
+ throw new OAuthReauthIdentityMismatchError();
887
1185
  }
888
- await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred);
1186
+ await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred, {
1187
+ assertBeforePersist: deps.assertCurrentOwner,
1188
+ });
889
1189
  } else {
890
1190
  await (deps.saveCredential ?? saveCredential)(provider, cred, {
891
- preserveIdentityless: provider === "kiro" && opts?.forceLogin === true,
1191
+ preserveIdentityless: opts?.forceLogin === true,
1192
+ assertBeforePersist: deps.assertCurrentOwner,
892
1193
  });
893
1194
  }
894
1195
  if (provider !== "chatgpt") {
@@ -900,10 +1201,7 @@ export async function runLogin(
900
1201
  provider,
901
1202
  );
902
1203
  if (lateCollision) {
903
- throw new Error(
904
- `${lateCollision}. The credential for "${provider}" was saved, but the provider entry was not written. `
905
- + "Rename the account selector, then re-run the login.",
906
- );
1204
+ throw new OAuthProviderPublicationError();
907
1205
  }
908
1206
  upsertOAuthProvider(latestConfig, provider);
909
1207
  saveLatestConfig(latestConfig);
@@ -933,11 +1231,15 @@ export async function runLogin(
933
1231
  settleKiroTransaction(rawCred, true);
934
1232
  if (provider !== "chatgpt") {
935
1233
  try {
1234
+ const { clearModelCache } = await import("../codex/model-cache");
1235
+ const { clearGatherRoutedModelsInflight } = await import("../codex/catalog");
1236
+ clearModelCache(provider);
1237
+ clearGatherRoutedModelsInflight();
936
1238
  const { clearAccountQuotaCache, clearProviderQuotaCache } = await import("../providers/quota");
937
1239
  clearProviderQuotaCache();
938
1240
  clearAccountQuotaCache(provider);
939
1241
  } catch {
940
- // Quota module may be unavailable in tightly scoped unit tests.
1242
+ // Optional state modules may be unavailable in tightly scoped unit tests.
941
1243
  }
942
1244
  }
943
1245
  return cred;
@@ -954,6 +1256,7 @@ export async function runLogin(
954
1256
  */
955
1257
  const loginState = new Map<string, { error?: string; done: boolean }>();
956
1258
  const loginAbort = new Map<string, AbortController>();
1259
+ const kiroLoginSettling = new Set<string>();
957
1260
 
958
1261
  /** Pending paste for a login in progress: either a waiter or a stashed early submission. */
959
1262
  interface ManualCodeSlot {
@@ -1036,7 +1339,11 @@ export function submitManualLoginCode(provider: string, input: string): { ok: tr
1036
1339
  // protected. Early posts (flow not yet waiting, no expectedState) are stashed and
1037
1340
  // re-validated by the callback loop.
1038
1341
  const parsed = parseCallbackInput(trimmed);
1039
- if (!parsed.code) return { ok: false, error: "no authorization code found in input" };
1342
+ // Command Code's manual fallback accepts a pasted JSON callback payload
1343
+ // (`{ apiKey, state, ... }`) which has no `code` param. Let it through the
1344
+ // shared gate so the provider-specific parser can validate it.
1345
+ const isCommandCodeJson = provider === "command-code" && trimmed.startsWith("{") && !parsed.code;
1346
+ if (!parsed.code && !isCommandCodeJson) return { ok: false, error: "no authorization code found in input" };
1040
1347
  if (parsed.kind !== "raw" && slot.expectedState !== undefined) {
1041
1348
  if (parsed.state === undefined) return { ok: false, error: "redirect URL is missing the state parameter" };
1042
1349
  if (parsed.state !== slot.expectedState) return { ok: false, error: "state mismatch — paste the redirect URL from THIS login attempt" };
@@ -1058,19 +1365,27 @@ export function getLoginStatus(provider: string): { loggedIn: boolean; email?: s
1058
1365
  const cred = getCredential(provider);
1059
1366
  const st = loginState.get(provider);
1060
1367
  const set = getAccountSet(provider);
1061
- // Full email on purpose: local dashboard/CLI account management needs to distinguish
1062
- // multiauth slots (masked local-parts collide). Tokens stay out of this surface.
1368
+ // Masked emails on purpose: status DTOs are shared with GUI/CLI surfaces that
1369
+ // must not leak full local-parts. Tokens stay out of this surface entirely.
1063
1370
  const accounts: OAuthAccountSummary[] | undefined = set?.accounts.map(a => ({
1064
1371
  id: a.id,
1065
1372
  ...(a.alias ? { alias: a.alias } : {}),
1066
- email: a.credential.email ?? undefined,
1373
+ email: maskEmail(a.credential.email) ?? undefined,
1067
1374
  active: a.id === set.activeAccountId,
1068
1375
  ...(a.needsReauth ? { needsReauth: true } : {}),
1069
1376
  expiresAt: a.credential.expires,
1070
1377
  }));
1378
+
1379
+ // A stored credential counts as "logged in" when it exists and is not marked for
1380
+ // re-authentication. An expired access token with a valid refresh token is still
1381
+ // logged in: request resolution refreshes expired/near-expiry credentials lazily.
1382
+ // Invalid/unknown local-import expiries are handled at parse/adoption time
1383
+ // (local-token-detect.ts), never by over-reporting login state here.
1384
+ const activeNeedsReauth = set?.accounts
1385
+ .find(a => a.id === set.activeAccountId)?.needsReauth === true;
1071
1386
  return {
1072
- loggedIn: !!cred,
1073
- email: cred?.email ?? undefined,
1387
+ loggedIn: !!cred && !activeNeedsReauth,
1388
+ email: maskEmail(cred?.email) ?? undefined,
1074
1389
  source: cred?.source,
1075
1390
  error: st?.error,
1076
1391
  done: st?.done ?? false,
@@ -1112,13 +1427,14 @@ export async function startLoginFlow(
1112
1427
  const def = OAUTH_PROVIDERS[provider];
1113
1428
  if (!def) throw new UnsupportedOAuthProviderError(provider);
1114
1429
  const existing = loginState.get(provider);
1115
- if (existing && !existing.done) {
1430
+ if ((existing && !existing.done) || (provider === "kiro" && kiroLoginSettling.has(provider))) {
1116
1431
  throw new Error(`A login for ${provider} is already in progress`);
1117
1432
  }
1118
1433
  clearManualCodeSlot(provider);
1119
1434
  loginState.set(provider, { done: false });
1120
1435
  const abort = new AbortController();
1121
1436
  loginAbort.set(provider, abort);
1437
+ if (provider === "kiro") kiroLoginSettling.add(provider);
1122
1438
  return new Promise((resolve, reject) => {
1123
1439
  let urlResolved = false;
1124
1440
  const ctrl: OAuthController = {
@@ -1131,7 +1447,16 @@ export async function startLoginFlow(
1131
1447
  onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(provider, abort.signal, expectedState),
1132
1448
  signal: abort.signal,
1133
1449
  };
1450
+ const abandonIfNotOwner = (error?: unknown): boolean => {
1451
+ if (loginAbort.get(provider) === abort) return false;
1452
+ if (!urlResolved) reject(error ?? new Error("OAuth login was superseded"));
1453
+ return true;
1454
+ };
1134
1455
  const settle = async (error?: unknown): Promise<void> => {
1456
+ // Cancellation deletes this controller and records its own terminal result. A late provider
1457
+ // rejection (or an older flow settling after a replacement starts) must not overwrite that
1458
+ // state or delete the replacement flow's controller/manual-code slot.
1459
+ if (abandonIfNotOwner(error)) return;
1135
1460
  let finalError = error;
1136
1461
  try {
1137
1462
  await lifecycle?.onSettled?.();
@@ -1140,6 +1465,7 @@ export async function startLoginFlow(
1140
1465
  // runtime config. For an already-failed login, keep the original recovery error.
1141
1466
  if (finalError === undefined) finalError = settleError;
1142
1467
  }
1468
+ if (abandonIfNotOwner(finalError)) return;
1143
1469
  if (finalError === undefined) {
1144
1470
  loginAbort.delete(provider);
1145
1471
  clearManualCodeSlot(provider);
@@ -1153,22 +1479,28 @@ export async function startLoginFlow(
1153
1479
  const e = finalError;
1154
1480
  loginAbort.delete(provider);
1155
1481
  clearManualCodeSlot(provider);
1156
- const msg = e instanceof Error ? e.message : String(e);
1482
+ const msg = publicOAuthAuthenticationErrorMessage(e);
1157
1483
  loginState.set(provider, { done: true, error: msg });
1158
1484
  if (!urlResolved) reject(e);
1159
1485
  };
1160
1486
  // Background: runLogin persists the credential + provider entry to disk. The lifecycle hook
1161
1487
  // lets a long-lived server config adopt that settled state before clients observe done=true.
1162
- void runLogin(provider, ctrl, opts).then(
1488
+ const assertCurrentOwner = (): void => {
1489
+ if (loginAbort.get(provider) !== abort) throw new OAuthLoginSupersededError();
1490
+ };
1491
+ void runLogin(provider, ctrl, opts, { assertCurrentOwner }).then(
1163
1492
  () => settle(),
1164
1493
  (e: unknown) => settle(e),
1165
1494
  ).catch((e: unknown) => {
1166
1495
  // settle catches lifecycle failures, so this is only a defensive promise-boundary guard.
1496
+ if (abandonIfNotOwner(e)) return;
1167
1497
  loginAbort.delete(provider);
1168
1498
  clearManualCodeSlot(provider);
1169
- const msg = e instanceof Error ? e.message : String(e);
1499
+ const msg = publicOAuthAuthenticationErrorMessage(e);
1170
1500
  loginState.set(provider, { done: true, error: msg });
1171
1501
  if (!urlResolved) reject(e);
1502
+ }).finally(() => {
1503
+ if (provider === "kiro") kiroLoginSettling.delete(provider);
1172
1504
  });
1173
1505
  });
1174
1506
  }