@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
@@ -0,0 +1,41 @@
1
+ /** Closed, account-free diagnostics: never project a provider's raw error text. */
2
+ const messages = {
3
+ "cancelled": "Muse Code login cancelled.",
4
+ "pointer-unreadable": "Could not read the Muse Code credential file. Check its permissions or run `muse login` again.",
5
+ "pointer-invalid": "The Muse Code credential file is not valid JSON or has an invalid shape. Run `muse login` to rewrite it.",
6
+ "storage-unsupported": "Muse Code stored its credential with an unsupported backend; only the macOS Keychain import is verified. Retry import or paste an existing Muse Code key when prompted.",
7
+ "keychain-timeout": "The Muse Code Keychain approval timed out after 120s. Unlock the login keychain, approve the prompt and retry, or paste an existing Muse Code key when prompted.",
8
+ "keychain-denied": "Access to the Muse Code Keychain entry was denied or cancelled. Approve the Keychain prompt and retry, or paste an existing Muse Code key when prompted.",
9
+ "keychain-locked": "The Muse Code Keychain entry requires user interaction. Unlock the login keychain and retry from an interactive macOS session, or paste an existing Muse Code key when prompted.",
10
+ "keychain-missing": "The Muse Code Keychain entry was not found. Run `muse login` again, or paste an existing Muse Code key when prompted.",
11
+ "keychain-unreadable": "Could not read the Muse Code credential from the macOS Keychain. Run `muse login` again, or paste an existing Muse Code key when prompted.",
12
+ "keychain-invalid": "The Muse Code Keychain entry is not valid JSON or has an invalid shape. Run `muse login` to rewrite it.",
13
+ "key-missing": "The Muse Code Keychain entry carries no usable API key. Run `muse login` again, or paste an existing Muse Code key when prompted.",
14
+ "key-format": "The Muse Code credential is not in the expected Meta API key format. Run `muse login` again, or paste an existing Muse Code key when prompted.",
15
+ "manual-key-missing": "The pasted Muse Code credential carries no usable API key. Copy it again from https://dev.meta.ai.",
16
+ "manual-key-format": "The pasted Muse Code credential is not in the expected Meta API key format. Copy it again from https://dev.meta.ai.",
17
+ "validation-network": "Could not reach the Meta Model API to validate the Muse Code credential. Check the proxy host's network and retry.",
18
+ "validation-timeout": "The Meta Model API credential validation timed out after 10s. Check the proxy host's network and retry.",
19
+ "validation-http": "The Muse Code credential was rejected by the Meta Model API. Renew it with `muse login` or copy it again from https://dev.meta.ai, then retry.",
20
+ } as const;
21
+
22
+ export type MuseLoginErrorKind = keyof typeof messages;
23
+
24
+ export class MuseLoginError extends Error {
25
+ constructor(readonly kind: MuseLoginErrorKind, readonly status?: number) {
26
+ super(museLoginErrorMessage(kind, status));
27
+ this.name = "MuseLoginError";
28
+ }
29
+ }
30
+
31
+ export function museLoginErrorMessage(kind: MuseLoginErrorKind, status?: number): string {
32
+ const message = Object.hasOwn(messages, kind) ? messages[kind] : messages["keychain-unreadable"];
33
+ return kind === "validation-http" && Number.isInteger(status) && status! >= 100 && status! <= 599
34
+ ? `${message} (HTTP ${status})`
35
+ : message;
36
+ }
37
+
38
+ export function publicMuseLoginErrorMessage(error: unknown): string | undefined {
39
+ // Reconstruct from the closed kind; even a mutated Error.message cannot leak a key.
40
+ return error instanceof MuseLoginError ? museLoginErrorMessage(error.kind, error.status) : undefined;
41
+ }
@@ -0,0 +1,52 @@
1
+ import { MuseLoginError } from "./meta-muse-errors";
2
+
3
+ // Approval includes typing a password. Five seconds kills security while the user types.
4
+ export const MUSE_KEYCHAIN_TIMEOUT_MS = 120_000;
5
+ export interface MuseKeychainProcess {
6
+ stdout: ReadableStream<Uint8Array>;
7
+ exited: Promise<number>;
8
+ kill(): void;
9
+ }
10
+ interface KeychainDeps {
11
+ spawn?: (argv: string[]) => MuseKeychainProcess;
12
+ timeoutMs?: number;
13
+ }
14
+
15
+ export async function readMuseKeychain(signal?: AbortSignal, deps: KeychainDeps = {}): Promise<string> {
16
+ if (signal?.aborted) throw new MuseLoginError("cancelled");
17
+ const timeout = AbortSignal.timeout(deps.timeoutMs ?? MUSE_KEYCHAIN_TIMEOUT_MS);
18
+ const deadline = signal ? AbortSignal.any([signal, timeout]) : timeout;
19
+ let child: MuseKeychainProcess | undefined;
20
+ let exited = false;
21
+ let onAbort: (() => void) | undefined;
22
+ try {
23
+ child = (deps.spawn ?? (argv => Bun.spawn(argv, { stdin: "ignore", stdout: "pipe", stderr: "ignore" })))(
24
+ ["/usr/bin/security", "find-generic-password", "-s", "ai.meta.dev.credentials", "-a", "meta", "-w"],
25
+ );
26
+ const finished = Promise.all([new Response(child.stdout).text(), child.exited.then(code => { exited = true; return code; })]);
27
+ const aborted = new Promise<never>((_, reject) => {
28
+ onAbort = () => reject(new MuseLoginError(signal?.aborted ? "cancelled" : "keychain-timeout"));
29
+ if (deadline.aborted) onAbort();
30
+ else deadline.addEventListener("abort", onAbort, { once: true });
31
+ });
32
+ const [out, code] = await Promise.race([finished, aborted]);
33
+ if (signal?.aborted) throw new MuseLoginError("cancelled");
34
+ if (code !== 0) {
35
+ // security returns OSStatus truncated to an 8-bit process exit code (SecBase.h).
36
+ // Unknown failures stay unknown; no stderr or credential output is retained.
37
+ if (code === 44) throw new MuseLoginError("keychain-missing"); // errSecItemNotFound -25300
38
+ if (code === 36) throw new MuseLoginError("keychain-locked"); // errSecInteractionNotAllowed -25308
39
+ if (code === 51 || code === 128) throw new MuseLoginError("keychain-denied"); // -25293 / -128
40
+ throw new MuseLoginError("keychain-unreadable");
41
+ }
42
+ if (!out.trim()) throw new MuseLoginError("keychain-unreadable");
43
+ return out.trim();
44
+ } catch (error) {
45
+ if (signal?.aborted) throw new MuseLoginError("cancelled");
46
+ if (error instanceof MuseLoginError) throw error;
47
+ throw new MuseLoginError("keychain-unreadable");
48
+ } finally {
49
+ if (onAbort) deadline.removeEventListener("abort", onAbort);
50
+ if (child && !exited) { try { child.kill(); } catch { /* already exited */ } }
51
+ }
52
+ }
@@ -26,14 +26,13 @@ import { homedir } from "node:os";
26
26
  import { join } from "node:path";
27
27
  import { sanitizeApiKeyValue } from "../providers/api-keys";
28
28
  import { MuseDeviceLoginError, loginMetaMuseDevice } from "./meta-muse-device";
29
+ import { MuseLoginError, publicMuseLoginErrorMessage } from "./meta-muse-errors";
30
+ import { readMuseKeychain } from "./meta-muse-keychain";
29
31
  import type { OAuthController, OAuthCredentials } from "./types";
30
32
 
31
33
  const MUSE_POINTER_PATH = join(homedir(), ".config", "muse", "auth.json");
32
- const KEYCHAIN_SERVICE = "ai.meta.dev.credentials";
33
- const KEYCHAIN_ACCOUNT = "meta";
34
34
  const MODELS_URL = "https://api.meta.ai/v1/models";
35
35
  const VALIDATE_TIMEOUT_MS = 10_000;
36
- const KEYCHAIN_TIMEOUT_MS = 5_000;
37
36
 
38
37
  /**
39
38
  * Shown BEFORE any credential is read.
@@ -82,44 +81,9 @@ export interface MuseLoginOptions {
82
81
  async function defaultReadPointer(): Promise<string | null> {
83
82
  try {
84
83
  return await Bun.file(MUSE_POINTER_PATH).text();
85
- } catch {
86
- return null;
87
- }
88
- }
89
-
90
- /**
91
- * `security` can block indefinitely — the Keychain may raise an interactive approval
92
- * prompt, and on a headless or locked machine nobody answers it. Without a deadline the
93
- * login would hang before the validation timeout below is even created, so the bound
94
- * lives here rather than only around the fetch.
95
- */
96
- async function defaultReadKeychain(signal?: AbortSignal): Promise<string | null> {
97
- const deadline = signal
98
- ? AbortSignal.any([signal, AbortSignal.timeout(KEYCHAIN_TIMEOUT_MS)])
99
- : AbortSignal.timeout(KEYCHAIN_TIMEOUT_MS);
100
- let proc: Bun.Subprocess<"ignore", "pipe", "pipe"> | undefined;
101
- try {
102
- proc = Bun.spawn(
103
- ["security", "find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", KEYCHAIN_ACCOUNT, "-w"],
104
- { stdout: "pipe", stderr: "pipe" },
105
- );
106
- const child = proc;
107
- const finished = Promise.all([new Response(child.stdout).text(), child.exited]);
108
- const timedOut = new Promise<null>((resolve) => {
109
- if (deadline.aborted) { resolve(null); return; }
110
- deadline.addEventListener("abort", () => resolve(null), { once: true });
111
- });
112
- const settled = await Promise.race([finished, timedOut]);
113
- if (settled === null) return null;
114
- const [out, code] = settled;
115
- if (code !== 0) return null;
116
- const trimmed = out.trim();
117
- return trimmed.length > 0 ? trimmed : null;
118
- } catch {
119
- return null;
120
- } finally {
121
- // A prompt still on screen keeps the child alive after the race resolves.
122
- if (proc && proc.exitCode === null) { try { proc.kill(); } catch { /* already gone */ } }
84
+ } catch (error) {
85
+ if ((error as { code?: string })?.code === "ENOENT") return null;
86
+ throw new MuseLoginError("pointer-unreadable");
123
87
  }
124
88
  }
125
89
 
@@ -160,6 +124,10 @@ async function manualKeyCredential(
160
124
  return pasted.length > 0 ? pasted : null;
161
125
  }
162
126
 
127
+ function isRecord(value: unknown): value is Record<string, unknown> {
128
+ return typeof value === "object" && value !== null && !Array.isArray(value);
129
+ }
130
+
163
131
  function normalizedEmail(value: unknown): string | undefined {
164
132
  if (typeof value !== "string") return undefined;
165
133
  const trimmed = value.trim().toLowerCase();
@@ -179,6 +147,7 @@ export async function loginMetaMuse(
179
147
  // Before ANY read: the CLI has no other warning surface.
180
148
  ctrl.onProgress?.(CONSENT_WARNING);
181
149
 
150
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
182
151
  const platform = deps.platform ?? process.platform;
183
152
 
184
153
  // Import first on darwin, and only for a plain login. A user who already ran `muse
@@ -187,8 +156,19 @@ export async function loginMetaMuse(
187
156
  // a request to arrive at the same key. `forceLogin` maps to "off" upstream because
188
157
  // reimporting is how an add-account silently re-adds the account the user already has.
189
158
  if (platform === "darwin" && options.importLocal !== "off") {
190
- const imported = await importFromKeychain(ctrl, deps);
191
- if (imported) return imported;
159
+ try {
160
+ const imported = await importFromKeychain(ctrl, deps);
161
+ if (imported) return imported;
162
+ } catch (error) {
163
+ if (isCancellation(error, ctrl)) throw error;
164
+ const reason = publicMuseLoginErrorMessage(error);
165
+ // Recovery requires an explicit paste, never an automatic grant or key mint.
166
+ if (!reason || !ctrl.onManualCodeInput) throw error;
167
+ const pasted = await manualKeyCredential(ctrl, reason);
168
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
169
+ if (pasted === null) throw error;
170
+ return await validatedMetaMuseCredential(pasted, ctrl, deps, undefined, "manual");
171
+ }
192
172
  }
193
173
 
194
174
  // fetchImpl/sleep/now are FORWARDED. Without this the device module would carry its own
@@ -221,46 +201,46 @@ export async function loginMetaMuse(
221
201
  * caller can offer a device grant instead of a dead end. Everything else still throws: a
222
202
  * corrupt file, an unmeasured storage backend, a Keychain read that times out, and a
223
203
  * Keychain entry that is unreadable or carries no usable key are all cases where a
224
- * credential probably EXISTS, and silently starting a browser grant would create a second
225
- * login to work around a permissions dialog.
204
+ * credential probably EXISTS. The caller can offer explicit key paste recovery, but never
205
+ * silently starts a second login to work around a permissions dialog.
226
206
  */
227
207
  async function importFromKeychain(
228
208
  ctrl: OAuthController,
229
209
  deps: MuseImportDeps,
230
210
  ): Promise<OAuthCredentials | null> {
231
211
  const pointerRaw = await (deps.readPointer ?? defaultReadPointer)();
212
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
232
213
  if (pointerRaw === null) return null;
233
214
 
234
215
  let pointer: MusePointer;
235
216
  try {
236
217
  pointer = JSON.parse(pointerRaw) as MusePointer;
218
+ if (!isRecord(pointer) || (pointer.providers !== undefined && !isRecord(pointer.providers))
219
+ || (pointer.providers?.meta !== undefined && !isRecord(pointer.providers.meta))) throw new Error();
237
220
  } catch {
238
- throw new Error(`Muse Code credential file at ${MUSE_POINTER_PATH} is not valid JSON. Run \`muse login\` to rewrite it.`);
221
+ throw new MuseLoginError("pointer-invalid");
239
222
  }
240
223
 
241
224
  const meta = pointer.providers?.meta;
242
225
  if (!meta || meta.mechanism !== "oauth") return null;
243
226
  // A different storage backend is a shape we have not measured; refuse rather than guess.
244
227
  if (meta.storage !== "keychain") {
245
- throw new Error(
246
- `Muse Code stored its credential with an unsupported backend (${String(meta.storage)}); only the macOS Keychain is verified.`,
247
- );
228
+ throw new MuseLoginError("storage-unsupported");
248
229
  }
249
230
 
250
- // ctrl.signal is handed to the reader, not dropped: the caller can cancel a Keychain
251
- // prompt nobody is going to answer.
252
- const secretRaw = await (deps.readKeychain ?? defaultReadKeychain)(ctrl.signal);
253
- if (secretRaw === null) {
254
- throw new Error(
255
- "Could not read the Muse Code credential from the macOS Keychain within 5s. Approve the Keychain prompt, or run `muse login` again, or add the account again to sign in with a browser code instead.",
256
- );
257
- }
231
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
232
+ // Publish a hint before the OS dialog so the dashboard can poll and cancel it.
233
+ ctrl.onAuth?.({ url: "", instructions: "Approve the Muse Code Keychain prompt on the proxy host. Waiting up to 120 seconds." });
234
+ const secretRaw = await (deps.readKeychain ?? readMuseKeychain)(ctrl.signal);
235
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
236
+ if (secretRaw === null) throw new MuseLoginError("keychain-unreadable");
258
237
 
259
238
  let secret: MuseKeychainSecret;
260
239
  try {
261
240
  secret = JSON.parse(secretRaw) as MuseKeychainSecret;
241
+ if (!isRecord(secret)) throw new Error();
262
242
  } catch {
263
- throw new Error("The Muse Code Keychain entry is not valid JSON. Run `muse login` to rewrite it.");
243
+ throw new MuseLoginError("keychain-invalid");
264
244
  }
265
245
 
266
246
  // access_token is present but 401s against the Model API (003 §B) — never fall back to it.
@@ -287,7 +267,7 @@ function deviceFailureReason(error: unknown): string {
287
267
  case "subscription-inactive":
288
268
  return "This Meta account has no active Muse Code subscription.";
289
269
  case "entitlement-required":
290
- return error.message;
270
+ return "Complete the Muse Code subscription setup at https://dev.meta.ai, then retry.";
291
271
  case "mint-rate-limited":
292
272
  return "Meta rate-limited the Muse Code key request.";
293
273
  case "device-denied":
@@ -314,7 +294,7 @@ function noPasteSurfaceError(platform: string, reason: string): Error {
314
294
  const platformClause = platform === "darwin"
315
295
  ? ""
316
296
  : platform === "win32"
317
- ? " Meta ships no native Windows Muse Code CLI, so there is no credential to import."
297
+ ? " The Windows Muse Code credential store has not been verified, so there is no credential to import through this reader."
318
298
  : " The Muse Code CLI runs here, but where it stores its credential has not been measured, "
319
299
  + "so importing one is refused rather than guessed.";
320
300
  return new Error(
@@ -338,22 +318,10 @@ async function validatedMetaMuseCredential(
338
318
  email: string | undefined,
339
319
  source: "local-cli" | "manual",
340
320
  ): Promise<OAuthCredentials> {
341
- const retry = source === "manual"
342
- ? `Copy it again from ${MANUAL_KEY_URL}.`
343
- : "Run `muse login` again.";
321
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
344
322
  const apiKey = sanitizeApiKeyValue(candidate);
345
- if (!apiKey) {
346
- // The macOS wording is preserved verbatim. Extraction is a refactor, and a refactor
347
- // that quietly rewrites a user-facing error is a behavior change in disguise.
348
- throw new Error(source === "manual"
349
- ? `The pasted Muse Code credential carries no usable API key. ${retry}`
350
- : "The Muse Code Keychain entry carries no usable API key. Run `muse login` again.");
351
- }
352
- if (!/^LLM\|\d+\|[A-Za-z0-9_-]{10,}$/.test(apiKey)) {
353
- throw new Error(source === "manual"
354
- ? `The pasted Muse Code credential is not in the expected Meta API key format. ${retry}`
355
- : "The Muse Code credential is not in the expected Meta API key format. Run `muse login` again.");
356
- }
323
+ if (!apiKey) throw new MuseLoginError(source === "manual" ? "manual-key-missing" : "key-missing");
324
+ if (!/^LLM\|\d+\|[A-Za-z0-9_-]{10,}$/.test(apiKey)) throw new MuseLoginError(source === "manual" ? "manual-key-format" : "key-format");
357
325
  ctrl.onProgress?.(source === "manual"
358
326
  ? "Validating the pasted Meta credential..."
359
327
  : "Validating the imported Meta credential...");
@@ -366,18 +334,17 @@ async function validatedMetaMuseCredential(
366
334
  let response: Response;
367
335
  try {
368
336
  response = await fetchImpl(MODELS_URL, {
369
- headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
337
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json", "x-api-version": "1.0.0" },
338
+ redirect: "error",
370
339
  signal,
371
340
  });
372
- } catch (error) {
373
- if (ctrl.signal?.aborted) throw ctrl.signal.reason ?? new DOMException("Meta Muse login aborted", "AbortError");
374
- throw new Error(`Could not reach the Meta Model API to validate the credential: ${(error as Error).message}`);
375
- }
376
- if (!response.ok) {
377
- throw new Error(
378
- `The Muse Code credential was rejected by the Meta Model API (HTTP ${response.status}). ${retry}`,
379
- );
341
+ } catch {
342
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
343
+ throw new MuseLoginError(signal.aborted ? "validation-timeout" : "validation-network");
380
344
  }
345
+ if (ctrl.signal?.aborted) throw new MuseLoginError("cancelled");
346
+ void response.body?.cancel().catch(() => {});
347
+ if (!response.ok) throw new MuseLoginError("validation-http", response.status);
381
348
 
382
349
  return {
383
350
  access: apiKey,
@@ -5,6 +5,9 @@ import { resolveProviderTransport } from "../providers/xai-transport";
5
5
 
6
6
  /** Discovery owns its configured row (a frozen capture for catalog gathers), not live config. */
7
7
  export function mayResolveModelsOAuth(name: string, provider: OcxProviderConfig | undefined): boolean {
8
+ // The DSH account token is pinned to its own adapter and fixed DeepSeek hosts. Its catalog is
9
+ // static, so no discovery path may resolve it, whatever adapter or baseUrl the row was edited to.
10
+ if (name === "dsh-account") return false;
8
11
  return name !== "anthropic2" || (provider !== undefined && provider.disabled !== true
9
12
  && isBuiltinAnthropicInstanceRow(name, provider));
10
13
  }
@@ -22,7 +22,7 @@ import { readAnthropicModelRoutes } from "./anthropic-model-routes";
22
22
  */
23
23
  export type PoolSettingsKind = "codex" | "anthropic" | "generic";
24
24
 
25
- export const GENERIC_POOL_STRATEGIES = ["quota", "round-robin", "fill-first", "least-loaded"] as const;
25
+ export const GENERIC_POOL_STRATEGIES = ["quota", "round-robin", "fill-first", "reset-first", "least-loaded"] as const;
26
26
  export type GenericPoolStrategy = typeof GENERIC_POOL_STRATEGIES[number];
27
27
 
28
28
  export function poolSettingsCapability(name: string, provider: OcxProviderConfig | undefined): PoolSettingsKind | null {
@@ -33,7 +33,9 @@ export function poolSettingsCapability(name: string, provider: OcxProviderConfig
33
33
  }
34
34
 
35
35
  export function parseGenericPoolStrategy(value: unknown, providerName?: string): GenericPoolStrategy | null {
36
- // Keep the common three names in the shared parser; the fourth is Kiro-specific.
36
+ // reset-first is shared with the Codex pool, so it is named here rather than added to the
37
+ // three-name shared parser that the Anthropic kind also uses. least-loaded is Kiro-specific.
38
+ if (value === "reset-first") return value;
37
39
  if (value === "least-loaded") return providerName === "kiro" ? value : null;
38
40
  return parseAccountPoolStrategy(value);
39
41
  }
@@ -0,0 +1,48 @@
1
+ import { createAdmissionGate } from "../lib/admission";
2
+ import { isAnthropicOAuthInstance } from "../providers/anthropic-instance";
3
+ import { clearObservedAnthropicRefreshIntent } from "./anthropic-oauth-definitions";
4
+ import { logOAuthEvent } from "./log";
5
+ import { markOAuthRefreshIntentStaleOwner, readOAuthRefreshIntent } from "./store";
6
+
7
+ /** Independent of client lifetime; a cancelled caller can still be awaiting a shared refresh. */
8
+ export const MAX_OAUTH_TOKEN_REFRESH_WAITERS = 256;
9
+ export const tokenRefreshWaiters = createAdmissionGate("oauth_token_refresh_waiters", MAX_OAUTH_TOKEN_REFRESH_WAITERS);
10
+
11
+ export interface OAuthRefreshFlightEvidence { flightId: string; dispatched: boolean }
12
+
13
+ export interface RetirableOAuthRefreshFlight extends OAuthRefreshFlightEvidence {
14
+ settled?: boolean;
15
+ retirementDeferredLogged?: boolean;
16
+ }
17
+
18
+ // Retire only after the old attempt has finished its definitive-result cleanup. A provider
19
+ // may ignore abort; marking its intent sooner would poison a late, safely retryable rejection.
20
+ // Failed durable retirement retains the same bounded registry slot for a later retry.
21
+ export function retireSettledOAuthRefreshFlight<F extends RetirableOAuthRefreshFlight>(
22
+ tokenRefreshes: Map<string, F>, key: string, flight: F,
23
+ ): boolean {
24
+ if (!flight.settled) return false;
25
+ const [provider, accountId] = key.split("\0");
26
+ const defer = (reason: "uncertain_intent" | "intent_changed" | "storage_failure"): false => {
27
+ if (!flight.retirementDeferredLogged) {
28
+ flight.retirementDeferredLogged = true;
29
+ logOAuthEvent("OAuth refresh flight retirement deferred", { provider: provider ?? "unknown", reason });
30
+ }
31
+ return false;
32
+ };
33
+ try {
34
+ if (provider && accountId && isAnthropicOAuthInstance(provider)) {
35
+ const intent = readOAuthRefreshIntent(provider, accountId);
36
+ if (intent?.uncertain) return defer("uncertain_intent");
37
+ if (intent?.flightId === flight.flightId && !intent.staleOwner && !intent.cleanupPending) {
38
+ const retired = flight.dispatched
39
+ ? markOAuthRefreshIntentStaleOwner(provider, accountId, intent.generation, flight.flightId)
40
+ : clearObservedAnthropicRefreshIntent(provider, accountId, intent);
41
+ if (!retired) return defer("intent_changed");
42
+ }
43
+ }
44
+ } catch { return defer("storage_failure"); }
45
+ if (tokenRefreshes.get(key) === flight) tokenRefreshes.delete(key);
46
+ return true;
47
+ }
48
+
@@ -0,0 +1,23 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ /** Shared conversation identity precedence; Desktop cohort cache keys never bind sessions. */
4
+ export function oauthSessionKeyFromParts(input: {
5
+ sessionIdHeader?: string | null;
6
+ threadIdHeader?: string | null;
7
+ promptCacheKey?: string | null;
8
+ clientThreadId?: string | null;
9
+ /** When true, prompt_cache_key is a shared Desktop cohort — ignore it for affinity. */
10
+ promptCacheKeyIsSharedCohort?: boolean;
11
+ }): string | null {
12
+ const preferred = input.clientThreadId?.trim()
13
+ || input.sessionIdHeader?.trim()
14
+ || input.threadIdHeader?.trim()
15
+ || "";
16
+ if (preferred) {
17
+ return preferred.length <= 128 ? preferred : createHash("sha256").update(preferred).digest("hex");
18
+ }
19
+ if (input.promptCacheKeyIsSharedCohort) return null;
20
+ const cacheKey = input.promptCacheKey?.trim() ?? "";
21
+ if (!cacheKey) return null;
22
+ return cacheKey.length <= 128 ? cacheKey : createHash("sha256").update(cacheKey).digest("hex");
23
+ }
@@ -32,6 +32,13 @@ export class AnthropicInstanceCollisionError extends Error {
32
32
  }
33
33
  }
34
34
 
35
+ export class AnthropicInstanceConfigReadError extends Error {
36
+ constructor() {
37
+ super("Cannot verify Anthropic Pool 2 ownership in the existing config file.");
38
+ this.name = "AnthropicInstanceConfigReadError";
39
+ }
40
+ }
41
+
35
42
  export function assertAnthropicInstanceLoginConfig(config: Pick<OcxConfig, "providers">, provider: string, credentialWritten = false): void {
36
43
  if (provider !== "anthropic2") return;
37
44
  const row = config.providers.anthropic2;
@@ -44,11 +51,11 @@ export function assertAnthropicInstanceLoginConfig(config: Pick<OcxConfig, "prov
44
51
  try { raw = readFileSync(getConfigPath(), "utf8"); }
45
52
  catch (error) {
46
53
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return;
47
- throw new Error("Cannot verify Anthropic Pool 2 ownership in the existing config file.");
54
+ throw new AnthropicInstanceConfigReadError();
48
55
  }
49
56
  let parsed: unknown;
50
57
  try { parsed = JSON.parse(raw.replace(/^\uFEFF/, "")); }
51
- catch { throw new Error("Cannot verify Anthropic Pool 2 ownership in the existing config file."); }
58
+ catch { throw new AnthropicInstanceConfigReadError(); }
52
59
  const providers = parsed && typeof parsed === "object" && "providers" in parsed ? parsed.providers : undefined;
53
60
  if (providers && typeof providers === "object" && Object.hasOwn(providers, provider)) {
54
61
  const persisted = (providers as Record<string, unknown>)[provider];
@@ -1028,8 +1028,8 @@ export async function saveCredential(
1028
1028
  provider: string,
1029
1029
  cred: OAuthCredentials,
1030
1030
  opts: { preserveIdentityless?: boolean; assertBeforePersist?: () => void } = {},
1031
- ): Promise<void> {
1032
- await saveCredentialWithReceipt(provider, cred, opts);
1031
+ ): Promise<OAuthCredentialWriteReceipt | null> {
1032
+ return await saveCredentialWithReceipt(provider, cred, opts);
1033
1033
  }
1034
1034
 
1035
1035
  /**
@@ -1234,23 +1234,29 @@ export function getAccountCredentialWithStatus(
1234
1234
  };
1235
1235
  }
1236
1236
 
1237
- /** Persist an explicit login/reauth credential for one account; runtime rotations use mergeAccountCredential. */
1237
+ /**
1238
+ * Persist an explicit login/reauth credential for one account; runtime rotations use
1239
+ * mergeAccountCredential. Resolves false when nothing was written (the account is gone).
1240
+ */
1238
1241
  export async function saveAccountCredential(
1239
1242
  provider: string,
1240
1243
  accountId: string,
1241
1244
  cred: OAuthCredentials,
1242
1245
  opts: { assertBeforePersist?: () => void; rotateLoginId?: boolean } = {},
1243
- ): Promise<void> {
1246
+ ): Promise<boolean> {
1244
1247
  if (isAnthropicInstanceId(provider)) assertAnthropicCredentialSource(provider, cred);
1245
1248
  const safe = normalizeCredential(cred);
1246
- if (!safe) return;
1249
+ if (!safe) return false;
1250
+ let written = false;
1247
1251
  await mutateStore(store => {
1252
+ written = false;
1248
1253
  if (provider === "anthropic2") assertAnthropicInstanceLoginConfig(loadConfig(), provider);
1249
1254
  if (isAnthropicInstanceId(provider)) assertNoCrossAnthropicRegistration(store, provider, safe);
1250
1255
  const set = store[provider];
1251
1256
  const account = set?.accounts.find(a => a.id === accountId);
1252
1257
  if (!set || !account) return;
1253
1258
  account.credential = safe;
1259
+ written = true;
1254
1260
  if (opts.rotateLoginId) account.loginId = randomUUID();
1255
1261
  delete account.needsReauth;
1256
1262
  delete account.needsReauthReason;
@@ -1263,6 +1269,7 @@ export async function saveAccountCredential(
1263
1269
  set.selectionRevision = randomUUID();
1264
1270
  }
1265
1271
  }, [provider, accountId, safe], { assertBeforePersist: registrationPersistGuard(provider, opts.assertBeforePersist) });
1272
+ return written;
1266
1273
  }
1267
1274
 
1268
1275
  function accountSelection(set: ProviderAccountSet): OAuthAccountSelection {