@bitkyc08/opencodex 2.84.0 → 2.87.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 (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -1,4 +1,6 @@
1
+ import { MuseLoginError, museLoginErrorMessage } from "./meta-muse-errors";
1
2
  import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
3
+ import { mapFill } from "../providers/resolved-model-policy-merge";
2
4
  import { initializeProviderModelSelection } from "../providers/initial-model-selection";
3
5
  import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
4
6
  import { getDefaultConfig, initializePersistedConfigIfMissing, loadConfig, mutatePersistedConfig, observeInitialConfigState, saveConfig } from "../config";
@@ -48,8 +50,9 @@ import { validateDevinApiBaseUrl } from "./devin/api-base";
48
50
  import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUrl } from "./github-copilot";
49
51
  import { loginCommandCode, refreshCommandCodeToken } from "./command-code";
50
52
  import { loginMetaMuse, refreshMetaMuseToken } from "./meta-muse";
53
+ import { DSH_ACCOUNT_AUTH } from "./dsh";
51
54
  import { loginOrcaRouter, orcaRouterInferenceBaseUrl, refreshOrcaRouterKey } from "./orcarouter";
52
- import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
55
+ import { ANTIGRAVITY_DISCOVERY_UA } from "../adapters/google-antigravity-wire";
53
56
  import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
54
57
  import { apiKeyPoolEntryId, sanitizeApiKeyValue } from "../providers/api-keys";
55
58
  import { effectiveGoogleMode, getProviderRegistryEntry, mergeRegistryStaticHeaders, providerMatchesRegistryTransport } from "../providers/registry";
@@ -57,9 +60,12 @@ import { providerModelsUrl, resolveProviderModelDiscoveryUrl } from "../provider
57
60
  import { resolveProviderTransport } from "../providers/xai-transport";
58
61
  import { detectGrokCliToken, hasComparableGrokIdentity, isSameGrokIdentity, shouldAdoptGrokGeneration } from "./local-token-detect";
59
62
  import { logOAuthEvent } from "./log";
63
+ import { MAX_OAUTH_TOKEN_REFRESH_WAITERS, retireSettledOAuthRefreshFlight, tokenRefreshWaiters, type OAuthRefreshFlightEvidence } from "./refresh-flight-retirement";
64
+ export { MAX_OAUTH_TOKEN_REFRESH_WAITERS };
60
65
  import { captureConfigGeneration, sweepExpiredOnWrite } from "../lib/state-store-sweeper";
61
- import { clearManualCodeSlot, ensureManualCodeSlot, kiroLoginSettling, loginAbort, loginState, waitForManualLoginCode, type OAuthLoginHint } from "./login-flow-state";
62
- export { reconcileOAuthFlowState, submitManualLoginCode } from "./login-flow-state";
66
+ import { admitLoginFlow, classifyLoginCodeError, clearManualCodeSlot, CodeLoginUnsupportedError, ensureManualCodeSlot, kiroLoginSettling, latestLoginFlowKey, loginAbort, loginFlowProvider, loginState, recordStartedLoginFlow, settleLoginFlow, waitForManualLoginCode, type LoginCodeError, type OAuthLoginHint } from "./login-flow-state";
67
+ export { cancelLoginFlow, clearLoginState, CodeLoginUnsupportedError, isCodeLoginFlow, loginFlowAccountId, reconcileOAuthFlowState, submitLoginCode, submitManualLoginCode, waitForLoginSettled } from "./login-flow-state";
68
+ export type { LoginCodeError, LoginFlowState } from "./login-flow-state";
63
69
  import { randomUUID } from "node:crypto";
64
70
  export {
65
71
  CODEX_HEALTH_AUTH_FAILED_NOTE,
@@ -132,8 +138,7 @@ export type OAuthActiveTokenObservation =
132
138
 
133
139
  const MAX_OAUTH_TOKEN_REFRESH_FLIGHTS = 32;
134
140
  const OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000;
135
- interface OAuthRefreshFlightEvidence { flightId: string; dispatched: boolean }
136
- interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence { promise: Promise<OAuthAccessSnapshot>; startedAt: number; abort: AbortController }
141
+ interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence { promise: Promise<OAuthAccessSnapshot>; startedAt: number; abort: AbortController; settled?: boolean; retirementDeferredLogged?: boolean }
137
142
  const tokenRefreshes = new Map<string, OAuthTokenRefreshFlight>();
138
143
  export class OAuthTokenRefreshBusyError extends Error {
139
144
  readonly code = "OAUTH_TOKEN_REFRESH_BUSY";
@@ -157,7 +162,8 @@ export function seedOAuthTokenRefreshFlightsForTests(rows: Array<{ key: string;
157
162
  const promise = new Promise<OAuthAccessSnapshot>((_resolve, reject) => {
158
163
  abort.signal.addEventListener("abort", () => reject(abort.signal.reason), { once: true });
159
164
  });
160
- const flight = { promise, startedAt: startedAt ?? Date.now(), abort, flightId: flightId ?? randomUUID(), dispatched: dispatched ?? false };
165
+ const flight: OAuthTokenRefreshFlight = { promise, startedAt: startedAt ?? Date.now(), abort, flightId: flightId ?? randomUUID(), dispatched: dispatched ?? false };
166
+ abort.signal.addEventListener("abort", () => { flight.settled = true; }, { once: true });
161
167
  tokenRefreshes.set(key, flight);
162
168
  inserted.push(flight);
163
169
  return promise;
@@ -192,6 +198,8 @@ export interface LoginOpts {
192
198
  * listener (#3366). Ignored by every other provider.
193
199
  */
194
200
  flow?: ChatGPTLoginFlow;
201
+ /** Code-display login: no callback server; completes from a pasted code (needs `codeRedirectUri`). */
202
+ codeMode?: boolean;
195
203
  }
196
204
 
197
205
  export interface LoginFlowLifecycle {
@@ -220,6 +228,8 @@ export interface OAuthProviderDef {
220
228
  * overrides this. Default when unset here: "lazy-only".
221
229
  */
222
230
  defaultRefreshPolicy?: RefreshPolicy;
231
+ /** Provider-hosted page that DISPLAYS the code; its presence enables `codeMode`. */
232
+ codeRedirectUri?: string;
223
233
  }
224
234
 
225
235
  function oauthConfig(id: string): OcxProviderConfig {
@@ -235,6 +245,11 @@ function oauthDefaultModel(id: string): string {
235
245
  }
236
246
 
237
247
  export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
248
+ "dsh-account": {
249
+ ...DSH_ACCOUNT_AUTH,
250
+ providerConfig: oauthConfig("dsh-account"),
251
+ defaultModel: oauthDefaultModel("dsh-account"),
252
+ },
238
253
  "command-code": {
239
254
  // Add-account/reauth must not reimport the current local CLI credential.
240
255
  login: (ctrl, opts) => loginCommandCode(ctrl, { importLocal: opts?.forceLogin ? "off" : "fallback" }),
@@ -383,6 +398,11 @@ export function isPublicOAuthProvider(name: string): boolean {
383
398
  return name !== "chatgpt" && isOAuthProvider(name);
384
399
  }
385
400
 
401
+ /** Providers that can log in from a displayed code, with no inbound port and no browser here. */
402
+ export function supportsCodeLoginMode(name: string): boolean {
403
+ return OAUTH_PROVIDERS[name]?.codeRedirectUri !== undefined;
404
+ }
405
+
386
406
  function isRefreshPolicy(value: unknown): value is RefreshPolicy {
387
407
  return value === "proactive" || value === "lazy-only" || value === "disabled";
388
408
  }
@@ -452,6 +472,14 @@ export class OAuthProviderPublicationError extends Error {
452
472
  }
453
473
  }
454
474
 
475
+ /** A local configuration conflict found before any credential is written; its text is fixed. */
476
+ export class OAuthProviderNamespaceCollisionError extends Error {
477
+ constructor(message: string) {
478
+ super(message);
479
+ this.name = "OAuthProviderNamespaceCollisionError";
480
+ }
481
+ }
482
+
455
483
  export class OAuthReauthIdentityMismatchError extends Error {
456
484
  constructor() {
457
485
  super("Signed-in account does not match the selected account. Sign in with the same account.");
@@ -475,6 +503,7 @@ class OAuthLoginSupersededError extends Error {
475
503
 
476
504
  /** Project arbitrary OAuth failures onto the small, stable public error vocabulary. */
477
505
  export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
506
+ if (error instanceof MuseLoginError) return museLoginErrorMessage(error.kind, error.status);
478
507
  if (error instanceof OAuthMutationBusyError) {
479
508
  return error.message === "OAuth mutation queue wait timed out"
480
509
  ? "OAuth mutation queue wait timed out"
@@ -484,6 +513,7 @@ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
484
513
  (error instanceof OAuthLoginRequiredError && isOAuthProvider(error.provider))
485
514
  || error instanceof OAuthAccountPausedError
486
515
  || error instanceof OAuthProviderPublicationError
516
+ || error instanceof OAuthProviderNamespaceCollisionError
487
517
  // Reauth identity outcomes carry fixed, account-free remediation text. Dropping them to the
488
518
  // generic message hides WHICH failure the user must fix (sign in with the selected account).
489
519
  || error instanceof OAuthReauthIdentityMismatchError
@@ -608,45 +638,71 @@ async function resolveAccessSnapshotForAccount(
608
638
  const key = `${provider}\u0000${accountId}`;
609
639
  let existing = tokenRefreshes.get(key);
610
640
  let replacedStaleFlight: OAuthRefreshFlightEvidence | undefined;
611
- if (existing && Date.now() - existing.startedAt <= OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS) {
612
- logOAuthEvent("OAuth refresh joined existing operation", { provider, accountId });
613
- return existing.promise;
614
- }
615
- if (existing) {
641
+ if (existing && (existing.abort.signal.reason instanceof OAuthTokenRefreshStaleError
642
+ || Date.now() - existing.startedAt > OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS)) {
616
643
  replacedStaleFlight = { flightId: existing.flightId, dispatched: existing.dispatched };
617
644
  existing.abort.abort(new OAuthTokenRefreshStaleError());
618
- if (tokenRefreshes.get(key) === existing) tokenRefreshes.delete(key);
619
- existing = undefined;
620
645
  }
621
- if (tokenRefreshes.size >= MAX_OAUTH_TOKEN_REFRESH_FLIGHTS) throw new OAuthTokenRefreshBusyError();
622
-
623
- const abort = new AbortController();
624
- const flight: OAuthTokenRefreshFlight = {
625
- promise: undefined as unknown as Promise<OAuthAccessSnapshot>,
626
- startedAt: Date.now(),
627
- abort,
628
- flightId: randomUUID(),
629
- dispatched: false,
630
- };
631
- const refresh = (async (): Promise<OAuthAccessSnapshot> => {
632
- const accessToken = await refreshAndPersistAccessToken(provider, accountId, def, cred, abort.signal, flight, replacedStaleFlight);
633
- const persistedRow = getAccountCredentialWithStatus(provider, accountId);
634
- if (!persistedRow) throw new OAuthLoginRequiredError(provider);
635
- if (persistedRow.paused === true) throw new OAuthAccountPausedError();
636
- const persisted = persistedRow.credential;
637
- if (persisted.access !== accessToken) {
638
- throw new Error(`OAuth refresh persisted an unexpected access token for ${provider}`);
646
+ // Count every consumer, including same-flight joins, until its promise settles. Cancelling
647
+ // a client cannot recycle this permit. Stale-flight abort above still runs under pressure;
648
+ // no replacement starts until the old consumers have actually released enough capacity.
649
+ const waiter = tokenRefreshWaiters.tryAcquire();
650
+ if (!waiter) throw new OAuthTokenRefreshBusyError();
651
+ try {
652
+ if (existing && replacedStaleFlight) {
653
+ // A replacement would wait for this owner's lock anyway. Keep its registry slot
654
+ // through definitive-result cleanup and durable retirement, even with spare capacity.
655
+ // In particular, an ignored abort must not mark a late explicit rejection as uncertain.
656
+ await existing.promise.catch(() => {});
657
+ if (tokenRefreshes.get(key) === existing && !retireSettledOAuthRefreshFlight(tokenRefreshes, key, existing)) {
658
+ throw new OAuthTokenRefreshStaleError();
659
+ }
660
+ // Other admitted replacements may have resumed first and installed the next flight.
661
+ existing = tokenRefreshes.get(key);
639
662
  }
640
- return accessSnapshot(provider, accountId, persisted, oauthProvider);
641
- })().catch(error => {
642
- if (abort.signal.reason instanceof OAuthTokenRefreshStaleError) throw abort.signal.reason;
643
- throw error;
644
- }).finally(() => {
645
- if (tokenRefreshes.get(key) === flight) tokenRefreshes.delete(key);
646
- });
647
- flight.promise = refresh;
648
- tokenRefreshes.set(key, flight);
649
- return refresh;
663
+ if (existing) {
664
+ logOAuthEvent("OAuth refresh joined existing operation", { provider, accountId });
665
+ return await existing.promise;
666
+ }
667
+ if (tokenRefreshes.size >= MAX_OAUTH_TOKEN_REFRESH_FLIGHTS) {
668
+ for (const [staleKey, stale] of tokenRefreshes) {
669
+ if (stale.abort.signal.reason instanceof OAuthTokenRefreshStaleError) retireSettledOAuthRefreshFlight(tokenRefreshes, staleKey, stale);
670
+ }
671
+ if (tokenRefreshes.size >= MAX_OAUTH_TOKEN_REFRESH_FLIGHTS) throw new OAuthTokenRefreshBusyError();
672
+ }
673
+
674
+ const abort = new AbortController();
675
+ const flight: OAuthTokenRefreshFlight = {
676
+ promise: undefined as unknown as Promise<OAuthAccessSnapshot>,
677
+ startedAt: Date.now(),
678
+ abort,
679
+ flightId: randomUUID(),
680
+ dispatched: false,
681
+ };
682
+ const refresh = (async (): Promise<OAuthAccessSnapshot> => {
683
+ const accessToken = await refreshAndPersistAccessToken(provider, accountId, def, cred, abort.signal, flight, replacedStaleFlight);
684
+ const persistedRow = getAccountCredentialWithStatus(provider, accountId);
685
+ if (!persistedRow) throw new OAuthLoginRequiredError(provider);
686
+ if (persistedRow.paused === true) throw new OAuthAccountPausedError();
687
+ const persisted = persistedRow.credential;
688
+ if (persisted.access !== accessToken) {
689
+ throw new Error(`OAuth refresh persisted an unexpected access token for ${provider}`);
690
+ }
691
+ return accessSnapshot(provider, accountId, persisted, oauthProvider);
692
+ })().catch(error => {
693
+ if (abort.signal.reason instanceof OAuthTokenRefreshStaleError) throw abort.signal.reason;
694
+ throw error;
695
+ }).finally(() => {
696
+ flight.settled = true;
697
+ if (tokenRefreshes.get(key) !== flight) return;
698
+ if (abort.signal.reason instanceof OAuthTokenRefreshStaleError) {
699
+ retireSettledOAuthRefreshFlight(tokenRefreshes, key, flight);
700
+ } else tokenRefreshes.delete(key);
701
+ });
702
+ flight.promise = refresh;
703
+ tokenRefreshes.set(key, flight);
704
+ return await refresh;
705
+ } finally { waiter.release(); }
650
706
  }
651
707
 
652
708
  export async function getValidAccessTokenSnapshot(
@@ -666,6 +722,7 @@ const FORCE_REFRESH_PROVIDERS = new Set([
666
722
  "google-antigravity",
667
723
  "orcarouter-oauth",
668
724
  "devin",
725
+ "dsh-account",
669
726
  ]);
670
727
 
671
728
  export async function forceRefreshOAuthAccessSnapshot(
@@ -1161,7 +1218,9 @@ export function buildModelsRequest(
1161
1218
  if (effectiveGoogleMode(providerName, effectiveProvider) === "cloud-code-assist") {
1162
1219
  headers.Accept = "application/json";
1163
1220
  headers["Content-Type"] = "application/json";
1164
- headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA;
1221
+ // CLI UA: CCA lists Gemini 4 Argon's wire ids only to the CLI client family. Inference keeps
1222
+ // choosing per wire model (antigravityUserAgentForWireModel).
1223
+ headers["User-Agent"] = ANTIGRAVITY_DISCOVERY_UA;
1165
1224
  if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
1166
1225
  return guarded({
1167
1226
  method: "POST" as const,
@@ -1237,26 +1296,21 @@ const OAUTH_RECONCILE_FIELDS: (keyof OcxProviderConfig)[] = [
1237
1296
  // preserves explicit saved values.
1238
1297
 
1239
1298
  /**
1240
- * Output-budget fields an OAuth preset may refresh but must never erase.
1241
- *
1242
- * These stay on the reconcile list so a preset that does declare a budget still
1243
- * refreshes the saved row. What changes is the other branch: when the preset
1244
- * declares nothing, the operator's value survives instead of being deleted.
1245
- *
1246
- * Without that, the fields behaved as if they could not be configured at all.
1247
- * No OAuth preset seeds either one, so the delete branch was the only branch
1248
- * these two ever took, and a hand-edited `defaultMaxOutputTokens` was gone
1249
- * before the first turn of the next startup — leaving the adapter's own
1250
- * fallback as the only reachable output cap (#5190).
1251
- *
1252
- * Scoped to the output budget on purpose. The input side (`contextWindow`,
1253
- * `modelContextWindows`) describes what the account's models are, which the
1254
- * preset and live discovery do own; an output budget is a spend decision the
1255
- * operator makes.
1299
+ * Saved limits and per-model declarations are operator input, including Copilot's
1300
+ * long-context evidence. Presets supply missing defaults, not replacements for
1301
+ * configured entries. Catalog rosters, capability lists and seeded reasoning
1302
+ * ladders still refresh below; unseeded tiers remain operator input.
1303
+ * Without provenance, a saved seed cannot be distinguished from an operator edit.
1256
1304
  */
1305
+ const OAUTH_OPERATOR_MAP_FIELDS: ReadonlySet<keyof OcxProviderConfig> = new Set([
1306
+ "modelContextWindows", "modelMaxOutputTokens", "modelInputModalities",
1307
+ "modelReasoningEffortMap", "reasoningEffortMap",
1308
+ ]);
1309
+ const OAUTH_OPERATOR_LIMIT_FIELDS: ReadonlySet<keyof OcxProviderConfig> = new Set([
1310
+ "contextWindow", "defaultMaxOutputTokens",
1311
+ ]);
1257
1312
  const OAUTH_PRESERVE_WHEN_PRESET_UNSET: ReadonlySet<keyof OcxProviderConfig> = new Set([
1258
- "defaultMaxOutputTokens",
1259
- "modelMaxOutputTokens",
1313
+ "reasoningEfforts",
1260
1314
  ]);
1261
1315
 
1262
1316
  const GOOGLE_ANTIGRAVITY_PROVIDER = "google-antigravity";
@@ -1296,7 +1350,23 @@ function applyOAuthPresetCatalog(
1296
1350
  ): void {
1297
1351
  for (const field of OAUTH_RECONCILE_FIELDS) {
1298
1352
  if (JSON.stringify(provider[field]) === JSON.stringify(preset[field])) continue;
1299
- if (preset[field] !== undefined) {
1353
+ if (field === "modelReasoningEfforts") {
1354
+ // Known unmarked ladders are saved catalog seeds, not evidence of an
1355
+ // operator override. Preserve unseeded rows and explicit authoritative
1356
+ // overrides; refresh the preset-owned rows (e.g. xAI's effort ladder).
1357
+ const authoritative = provider.modelReasoningEffortsAuthoritative === true;
1358
+ provider.modelReasoningEfforts = mapFill(
1359
+ authoritative ? preset.modelReasoningEfforts : provider.modelReasoningEfforts,
1360
+ authoritative ? provider.modelReasoningEfforts : preset.modelReasoningEfforts,
1361
+ )[0];
1362
+ } else if (OAUTH_OPERATOR_MAP_FIELDS.has(field)) {
1363
+ provider[field] = mapFill<unknown>(
1364
+ preset[field] as Record<string, unknown> | undefined,
1365
+ provider[field] as Record<string, unknown> | undefined,
1366
+ )[0] as never;
1367
+ } else if (OAUTH_OPERATOR_LIMIT_FIELDS.has(field) && provider[field] !== undefined) {
1368
+ continue;
1369
+ } else if (preset[field] !== undefined) {
1300
1370
  provider[field] = cloneProviderField(preset[field]) as never;
1301
1371
  } else if (!OAUTH_PRESERVE_WHEN_PRESET_UNSET.has(field)) {
1302
1372
  delete provider[field];
@@ -1492,7 +1562,7 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
1492
1562
  if (!def) return;
1493
1563
  if (provider === GOOGLE_ANTIGRAVITY_PROVIDER) migrateLegacyAntigravityStaticCatalog(config);
1494
1564
  const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
1495
- if (namespaceCollision) throw new Error(namespaceCollision);
1565
+ if (namespaceCollision) throw new OAuthProviderNamespaceCollisionError(namespaceCollision);
1496
1566
  const existing = config.providers[provider];
1497
1567
  const providerConfig = def.resolveProviderConfig?.(config) ?? def.providerConfig;
1498
1568
  // Clone operator state, including xAI wire choices and their migration version.
@@ -1559,6 +1629,8 @@ interface RunLoginDeps {
1559
1629
  settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
1560
1630
  rollbackCredentialWrite?: typeof rollbackCredentialWriteIfMatch;
1561
1631
  assertCurrentOwner?: () => void;
1632
+ /** Told which account row the credential write landed in (an id, never the credential). */
1633
+ onAccountPersisted?: (accountId: string) => void;
1562
1634
  }
1563
1635
 
1564
1636
  /** Run the login flow, persist the credential + upsert the provider entry to disk, return cred. */
@@ -1589,7 +1661,7 @@ export async function runLogin(
1589
1661
  preflightConfig.codexAccountNamespaces,
1590
1662
  provider,
1591
1663
  );
1592
- if (namespaceCollision) throw new Error(namespaceCollision);
1664
+ if (namespaceCollision) throw new OAuthProviderNamespaceCollisionError(namespaceCollision);
1593
1665
  }
1594
1666
  if (provider === "anthropic2" && observeInitialConfigState() !== "exists") {
1595
1667
  throw new OAuthProviderPublicationError(provider, false);
@@ -1638,10 +1710,12 @@ export async function runLogin(
1638
1710
  // Proven same account: a reauth whose tier read failed keeps the stored label.
1639
1711
  const reauthPlan = mergeOAuthPlan(cred, existing);
1640
1712
  const reauthCred = reauthPlan === undefined ? cred : { ...cred, plan: reauthPlan };
1641
- await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, reauthCred, {
1713
+ const written = await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, reauthCred, {
1642
1714
  assertBeforePersist: deps.assertCurrentOwner,
1643
1715
  rotateLoginId: true,
1644
1716
  });
1717
+ // A reauth whose account was deleted meanwhile wrote nothing and owns no row to report.
1718
+ if (written) deps.onAccountPersisted?.(opts.reauthAccountId);
1645
1719
  } else {
1646
1720
  const saveOptions = {
1647
1721
  preserveIdentityless: opts?.forceLogin === true || (provider === "anthropic" && cred.source === "local-cli"),
@@ -1653,8 +1727,11 @@ export async function runLogin(
1653
1727
  cred,
1654
1728
  saveOptions,
1655
1729
  );
1730
+ if (kiroCredentialWrite) deps.onAccountPersisted?.(kiroCredentialWrite.accountId);
1656
1731
  } else {
1657
- await (deps.saveCredential ?? saveCredential)(provider, cred, saveOptions);
1732
+ // The receipt names the row written; a stubbed save may return none.
1733
+ const receipt = await (deps.saveCredential ?? saveCredential)(provider, cred, saveOptions);
1734
+ if (receipt) deps.onAccountPersisted?.(receipt.accountId);
1658
1735
  }
1659
1736
  }
1660
1737
  if (provider !== "chatgpt") {
@@ -1709,7 +1786,7 @@ export async function runLogin(
1709
1786
  if (provider !== "chatgpt") {
1710
1787
  try {
1711
1788
  const { clearModelCache } = await import("../codex/model-cache");
1712
- const { clearGatherRoutedModelsInflight } = await import("../codex/catalog");
1789
+ const { clearGatherRoutedModelsInflight } = await import("../codex/catalog/routed-gather");
1713
1790
  clearModelCache(provider);
1714
1791
  clearGatherRoutedModelsInflight();
1715
1792
  const { clearAccountQuotaCache, clearProviderQuotaCache } = await import("../providers/quota");
@@ -1764,9 +1841,12 @@ export interface OAuthAccountSummary {
1764
1841
  * the config at its request boundary and resolves the policy there with `emailMaskingEnabled`.
1765
1842
  * The default masks, so every existing caller keeps today's behaviour.
1766
1843
  */
1767
- export function getLoginStatus(provider: string, maskEmails = true): { loggedIn: boolean; email?: string; source?: OAuthCredentials["source"]; error?: string; done: boolean; hint?: OAuthLoginHint; activeAccountId?: string; accounts?: OAuthAccountSummary[] } {
1844
+ export function getLoginStatus(provider: string, maskEmails = true, flowId?: string): { loggedIn: boolean; email?: string; source?: OAuthCredentials["source"]; error?: string; errorCode?: LoginCodeError; done: boolean; status: "idle" | "pending" | "complete" | "error"; mode?: "callback" | "code"; flowId?: string; expiresAt?: number; hint?: OAuthLoginHint; activeAccountId?: string; accounts?: OAuthAccountSummary[] } {
1768
1845
  const cred = getCredential(provider);
1769
- const st = loginState.get(provider);
1846
+ // `flowId` reads one of several concurrent flows; without it, the provider's newest flow.
1847
+ const flowKey = flowId ?? latestLoginFlowKey(provider);
1848
+ // An explicit flow id from another provider must not leak that flow's lifecycle or hint.
1849
+ const st = flowKey === undefined || loginFlowProvider(flowKey) !== provider ? undefined : loginState.get(flowKey);
1770
1850
  const set = getAccountSet(provider);
1771
1851
  const accounts: OAuthAccountSummary[] | undefined = set?.accounts.map(a => ({
1772
1852
  id: a.id,
@@ -1792,7 +1872,14 @@ export function getLoginStatus(provider: string, maskEmails = true): { loggedIn:
1792
1872
  email: projectEmail(cred?.email, maskEmails) ?? undefined,
1793
1873
  source: cred?.source,
1794
1874
  error: st?.error,
1875
+ ...(st?.errorCode ? { errorCode: st.errorCode } : {}),
1795
1876
  done: st?.done ?? false,
1877
+ // The flow's own lifecycle, which `done` alone cannot express: a finished flow that failed
1878
+ // and one that succeeded both report done:true. Callers poll this.
1879
+ status: !st ? "idle" : !st.done ? "pending" : st.error ? "error" : "complete",
1880
+ ...(st?.mode ? { mode: st.mode } : {}),
1881
+ ...(st?.flowId ? { flowId: st.flowId } : {}),
1882
+ ...(st?.expiresAt !== undefined ? { expiresAt: st.expiresAt } : {}),
1796
1883
  ...(st?.hint && !st.done ? { hint: { url: st.hint.url, instructions: st.hint.instructions, deviceCode: st.hint.deviceCode } } : {}),
1797
1884
  ...(set ? { activeAccountId: set.activeAccountId, accounts } : {}),
1798
1885
  };
@@ -1809,65 +1896,53 @@ export function oauthLoginSummary(maskEmails = true): Array<{ provider: string;
1809
1896
  });
1810
1897
  }
1811
1898
 
1812
- export function clearLoginState(provider: string): void {
1813
- loginAbort.get(provider)?.controller.abort("cleared");
1814
- loginAbort.delete(provider);
1815
- clearManualCodeSlot(provider);
1816
- loginState.delete(provider);
1817
- }
1818
-
1819
- export function cancelLoginFlow(provider: string, flowId?: string): boolean {
1820
- const active = loginAbort.get(provider);
1821
- const existing = loginState.get(provider);
1822
- if (flowId !== undefined && active?.flowId !== flowId) return false;
1823
- if (!active && (!existing || existing.done)) return false;
1824
- active?.controller.abort("cancelled");
1825
- loginAbort.delete(provider);
1826
- clearManualCodeSlot(provider);
1827
- loginState.set(provider, { done: true, error: "Login cancelled" });
1828
- return true;
1829
- }
1830
-
1831
1899
  export async function startLoginFlow(
1832
1900
  provider: string,
1833
1901
  opts?: LoginOpts,
1834
1902
  lifecycle?: LoginFlowLifecycle,
1835
- ): Promise<{ url: string; instructions?: string; deviceCode?: string }> {
1903
+ ): Promise<{ url: string; instructions?: string; deviceCode?: string; expiresAt?: number }> {
1836
1904
  const def = OAUTH_PROVIDERS[provider];
1837
1905
  if (!def) throw new UnsupportedOAuthProviderError(provider);
1838
- const existing = loginState.get(provider);
1839
- if ((existing && !existing.done) || (provider === "kiro" && kiroLoginSettling.has(provider))) {
1840
- throw new Error(`A login for ${provider} is already in progress`);
1841
- }
1842
- clearManualCodeSlot(provider);
1843
- loginState.set(provider, { done: false });
1906
+ // Never silently downgrade to the localhost redirect: the caller asked for code mode
1907
+ // precisely because it cannot reach a localhost page.
1908
+ if (opts?.codeMode && !supportsCodeLoginMode(provider)) throw new CodeLoginUnsupportedError(provider);
1909
+ admitLoginFlow(provider, opts?.codeMode === true);
1910
+ // The flow id is the key of every map below. Callers that own one (the management route, the
1911
+ // codex auth API) supply it so a later paste can be pinned to this exact attempt.
1912
+ const flowId = lifecycle?.flowId ?? randomUUID();
1913
+ const expiresAt = recordStartedLoginFlow(provider, flowId, opts?.codeMode === true);
1844
1914
  const abort = new AbortController();
1845
- loginAbort.set(provider, { controller: abort, flowId: lifecycle?.flowId });
1915
+ loginAbort.set(flowId, { controller: abort, provider });
1846
1916
  if (provider === "kiro") kiroLoginSettling.add(provider);
1847
1917
  return new Promise((resolve, reject) => {
1848
1918
  let urlResolved = false;
1849
1919
  const ctrl: OAuthController = {
1850
1920
  onAuth: ({ url, instructions, deviceCode }) => {
1851
- if (abort.signal.aborted || loginAbort.get(provider)?.controller !== abort) return;
1921
+ if (abort.signal.aborted || loginAbort.get(flowId)?.controller !== abort) return;
1852
1922
  // Device approval can fall back to manual input. Replace, never merge: the
1853
1923
  // previous device code must disappear when the provider changes the next step.
1854
1924
  const hint = { url, instructions, deviceCode };
1855
- loginState.set(provider, { done: false, hint });
1925
+ // Merged, not replaced: the flow's own identity (mode/flowId/expiresAt) was recorded
1926
+ // before the provider produced a URL and must survive every hint update.
1927
+ loginState.set(flowId, { ...loginState.get(flowId), done: false, hint });
1856
1928
  if (!urlResolved) {
1857
1929
  urlResolved = true;
1858
- resolve({ ...hint });
1930
+ resolve({ ...hint, ...(expiresAt !== undefined ? { expiresAt } : {}) });
1859
1931
  }
1860
1932
  },
1861
1933
  onProgress: () => {},
1862
1934
  // GUI fallback when the browser cannot hit the loopback callback server.
1863
- onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(provider, abort.signal, expectedState),
1935
+ onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(flowId, abort.signal, expectedState),
1864
1936
  signal: abort.signal,
1865
1937
  };
1866
1938
  const abandonIfNotOwner = (error?: unknown): boolean => {
1867
- if (loginAbort.get(provider)?.controller === abort) return false;
1939
+ if (loginAbort.get(flowId)?.controller === abort) return false;
1868
1940
  if (!urlResolved) reject(error ?? new Error("OAuth login was superseded"));
1869
1941
  return true;
1870
1942
  };
1943
+ // The row this flow's own credential write landed in, from the store's receipt. A token match
1944
+ // after the fact would miss it once a concurrent login for the same account rotates the token.
1945
+ let persistedAccountId: string | undefined;
1871
1946
  const settle = async (error?: unknown): Promise<void> => {
1872
1947
  // Cancellation deletes this controller and records its own terminal result. A late provider
1873
1948
  // rejection (or an older flow settling after a replacement starts) must not overwrite that
@@ -1883,9 +1958,10 @@ export async function startLoginFlow(
1883
1958
  }
1884
1959
  if (abandonIfNotOwner(finalError)) return;
1885
1960
  if (finalError === undefined) {
1886
- loginAbort.delete(provider);
1887
- clearManualCodeSlot(provider);
1888
- loginState.set(provider, { done: true });
1961
+ loginAbort.delete(flowId);
1962
+ clearManualCodeSlot(flowId);
1963
+ // Answer this flow's caller with the account it wrote, not whichever one is active now.
1964
+ settleLoginFlow(flowId, persistedAccountId ? { accountId: persistedAccountId } : {});
1889
1965
  // Local-token import (grok-cli / Claude Code keychain) completes WITHOUT firing onAuth —
1890
1966
  // resolve so the GUI call returns instead of hanging.
1891
1967
  if (!urlResolved) resolve({ url: "", instructions: "Logged in via an existing local CLI/keychain token — no browser needed." });
@@ -1893,27 +1969,28 @@ export async function startLoginFlow(
1893
1969
  }
1894
1970
 
1895
1971
  const e = finalError;
1896
- loginAbort.delete(provider);
1897
- clearManualCodeSlot(provider);
1972
+ loginAbort.delete(flowId);
1973
+ clearManualCodeSlot(flowId);
1898
1974
  const msg = publicOAuthAuthenticationErrorMessage(e);
1899
- loginState.set(provider, { done: true, error: msg });
1975
+ settleLoginFlow(flowId, { error: msg, errorCode: classifyLoginCodeError(e) });
1900
1976
  if (!urlResolved) reject(e);
1901
1977
  };
1902
1978
  // Background: runLogin persists the credential + provider entry to disk. The lifecycle hook
1903
1979
  // lets a long-lived server config adopt that settled state before clients observe done=true.
1904
1980
  const assertCurrentOwner = (): void => {
1905
- if (loginAbort.get(provider)?.controller !== abort) throw new OAuthLoginSupersededError();
1981
+ if (loginAbort.get(flowId)?.controller !== abort) throw new OAuthLoginSupersededError();
1906
1982
  };
1907
- void runLogin(provider, ctrl, opts, { assertCurrentOwner }).then(
1983
+ const onAccountPersisted = (accountId: string): void => { persistedAccountId = accountId; };
1984
+ void runLogin(provider, ctrl, opts, { assertCurrentOwner, onAccountPersisted }).then(
1908
1985
  () => settle(),
1909
1986
  (e: unknown) => settle(e),
1910
1987
  ).catch((e: unknown) => {
1911
1988
  // settle catches lifecycle failures, so this is only a defensive promise-boundary guard.
1912
1989
  if (abandonIfNotOwner(e)) return;
1913
- loginAbort.delete(provider);
1914
- clearManualCodeSlot(provider);
1990
+ loginAbort.delete(flowId);
1991
+ clearManualCodeSlot(flowId);
1915
1992
  const msg = publicOAuthAuthenticationErrorMessage(e);
1916
- loginState.set(provider, { done: true, error: msg });
1993
+ settleLoginFlow(flowId, { error: msg, errorCode: classifyLoginCodeError(e) });
1917
1994
  if (!urlResolved) reject(e);
1918
1995
  }).finally(() => {
1919
1996
  if (provider === "kiro") kiroLoginSettling.delete(provider);
@@ -250,9 +250,10 @@ async function poll(flow: Flow, principal: KiroDevicePrincipal): Promise<KiroDev
250
250
  if (data.status === "authorization_pending") return publicView(flow);
251
251
  if (data.status === "expired_token" || data.status === "expired") return terminal(flow, "expired");
252
252
  }
253
+ // The social poll reports a completed grant as status "authorized"; "approved" is kept as an alias.
253
254
  if (result.status !== 200 || Object.hasOwn(data, "error") || error || (builder && (data.status !== undefined || !positive(data.expiresIn) || !isStorableKiroClientPart(flow.clientId)
254
255
  || !isStorableKiroClientPart(flow.clientSecret)))
255
- || (!builder && (data.status !== undefined && data.status !== "approved"))) return terminal(flow, "failed");
256
+ || (!builder && (data.status !== undefined && data.status !== "authorized" && data.status !== "approved"))) return terminal(flow, "failed");
256
257
  if (!code(data.accessToken) || !code(data.refreshToken)
257
258
  || (!builder && (typeof data.profileArn !== "string" || !PROFILE_ARN.test(data.profileArn)))) return terminal(flow, "failed");
258
259
  const credential: OAuthCredentials = {
package/src/oauth/kiro.ts CHANGED
@@ -45,6 +45,10 @@ const KIRO_TERMINAL_REFRESH_ERRORS = new Set([
45
45
  "access_denied",
46
46
  "expired_token",
47
47
  ]);
48
+ // The desktop refresh endpoint rejects a dead refresh token with HTTP 401 and a bare
49
+ // `{"message":"Bad credentials"}` body, without an OAuth `error` code. Only that exact
50
+ // pair is a terminal verdict; it is surfaced under this fixed code, never the raw text.
51
+ const KIRO_DESKTOP_BAD_CREDENTIALS = "bad_credentials";
48
52
 
49
53
  interface ImportedKiroToken {
50
54
  access: string;
@@ -545,12 +549,14 @@ async function kiroTokenRefreshError(response: Response, awsSsoRefresh = false):
545
549
  let oauthError: string | undefined;
546
550
  let refreshAttention = false;
547
551
  try {
548
- const payload = await response.json() as { error?: unknown };
552
+ const payload = await response.json() as { error?: unknown; message?: unknown };
549
553
  // This input-error code is attention evidence only for our AWS SSO refresh request,
550
554
  // never a terminal grant verdict. Do not surface arbitrary upstream descriptions.
551
555
  refreshAttention = awsSsoRefresh && response.status === 400 && payload.error === "invalid_request";
552
556
  if (typeof payload.error === "string" && KIRO_TERMINAL_REFRESH_ERRORS.has(payload.error)) {
553
557
  oauthError = payload.error;
558
+ } else if (!awsSsoRefresh && response.status === 401 && payload.message === "Bad credentials") {
559
+ oauthError = KIRO_DESKTOP_BAD_CREDENTIALS;
554
560
  }
555
561
  } catch {
556
562
  // Error bodies are untrusted and intentionally excluded from the surfaced message.
@@ -3,7 +3,7 @@
3
3
  * Read-only: never writes to external credential stores.
4
4
  * Ported from jawcode packages/ai/src/utils/oauth/local-token-detect.ts (xAI portion).
5
5
  */
6
- import { execSync } from "node:child_process";
6
+ import { execFile, execSync } from "node:child_process";
7
7
  import { existsSync, readFileSync } from "node:fs";
8
8
  import { homedir } from "node:os";
9
9
  import { join } from "node:path";
@@ -109,6 +109,26 @@ function readClaudeSecureStorage(): string | null {
109
109
  return readClaudeKeychain() ?? readClaudeCredentialsFile();
110
110
  }
111
111
 
112
+ /** Non-blocking Keychain read for request paths; same item and timeout as the sync reader. */
113
+ function readClaudeKeychainAsync(): Promise<string | null> {
114
+ if (process.platform !== "darwin") return Promise.resolve(null);
115
+ return new Promise(resolve => {
116
+ execFile("security", ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"], { encoding: "utf8", timeout: 5000 },
117
+ (error, stdout) => resolve(error ? null : stdout.trim() || null));
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Async variant of {@link detectClaudeCodeToken} with the same source precedence. A request-path
123
+ * caller must not hold the event loop for a Keychain lookup (up to five seconds).
124
+ */
125
+ export async function detectClaudeCodeTokenAsync(): Promise<OAuthCredentials | null> {
126
+ const raw = process.env.CLAUDE_CONFIG_DIR?.trim()
127
+ ? readClaudeCredentialsFile() ?? await readClaudeKeychainAsync()
128
+ : await readClaudeKeychainAsync() ?? readClaudeCredentialsFile();
129
+ return raw ? parseClaudeOauthPayload(raw) : null;
130
+ }
131
+
112
132
  export function parseClaudeOauthPayload(raw: string): OAuthCredentials | null {
113
133
  try {
114
134
  const data = JSON.parse(raw) as { claudeAiOauth?: { accessToken?: string; refreshToken?: string; expiresAt?: number } };