@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,86 @@
1
+ /** Account-bound, time-limited compatibility activation; no persistent settings or timers. */
2
+ export type Observation = 'exhausted' | 'available' | 'protected';
3
+ export type ApplyScope = 'selected-external-model' | 'account-ui-compatibility';
4
+ type RefreshResult = {requested:number;closed:number;failed:number};
5
+ export class UsageActivation {
6
+ private mode: 'observe' | 'apply' = 'observe';
7
+ private phase = 'observing';
8
+ private generation = 0;
9
+ private latest: { kind:Observation; at:number } | null = null;
10
+ private startedAt: number | null = null;
11
+ private outputs = 0;
12
+ constructor(private binding:string, private refresh:(binding:string)=>Promise<RefreshResult>,
13
+ private verifyIdentity:()=>Promise<string|null>,
14
+ private clock:()=>number=Date.now, private timeoutMs=180000) {
15
+ if(!binding || !Number.isSafeInteger(timeoutMs) || timeoutMs<1 || timeoutMs>180000) throw new Error('Invalid activation limits');
16
+ }
17
+ snapshot() { return {mode:this.mode,phase:this.phase,generation:this.generation,outputs:this.outputs,
18
+ appCacheConfirmed:false,requestedUiScope:'account-wide',providerIsolationAvailable:false}; }
19
+ invalidateIdentity() {
20
+ this.mode='observe';this.phase='identity-invalidated';this.startedAt=null;this.latest=null;this.outputs=0;++this.generation;
21
+ }
22
+ observe(binding:string,kind:Observation) {
23
+ if(binding!==this.binding)return false;
24
+ this.latest={kind,at:this.clock()};
25
+ // A recovered or protected account must not silently re-enter a previous trial
26
+ // if a later snapshot becomes exhausted again. A new trial requires consent.
27
+ if(this.mode==='apply'&&kind!=='exhausted'){
28
+ this.mode='observe';this.phase=kind==='available'?'usage-recovered':'protected-observation';
29
+ this.startedAt=null;this.outputs=0;++this.generation;
30
+ }
31
+ return true;
32
+ }
33
+ private async reset(phase:string) {
34
+ this.mode='observe';this.phase=phase;this.startedAt=null;this.latest=null;this.outputs=0;++this.generation;
35
+ return this.refresh(this.binding);
36
+ }
37
+ async activate(options:{scope:ApplyScope;accountWideConsent:boolean}) {
38
+ if(options.scope!=='account-ui-compatibility')return{accepted:false,reason:'selected-model-scope-unavailable',...this.snapshot()};
39
+ if(!options.accountWideConsent)return{accepted:false,reason:'account-wide-scope-not-accepted',...this.snapshot()};
40
+ const beforeVerification=this.generation,binding=this.binding;
41
+ let verified:string|null;
42
+ try{verified=await this.verifyIdentity();}catch{verified=null;}
43
+ if(verified!==binding||this.binding!==binding||this.generation!==beforeVerification)return{accepted:false,reason:'identity-not-verified',...this.snapshot()};
44
+ if(this.mode==='apply')return{accepted:false,reason:'activation-already-pending',...this.snapshot()};
45
+ const age=this.latest?this.clock()-this.latest.at:Infinity;
46
+ if(!this.latest||this.latest.kind!=='exhausted'||age<0||age>300000)return{accepted:false,reason:'no-fresh-eligible-exhaustion',...this.snapshot()};
47
+ // An exhausted observation authorizes one trial, not every retry within its TTL.
48
+ this.latest=null;
49
+ this.mode='apply';this.phase='awaiting-fresh-usage';this.startedAt=this.clock();this.outputs=0;
50
+ const generation=++this.generation;
51
+ let refreshed:RefreshResult;
52
+ try { refreshed=await this.refresh(this.binding); }
53
+ catch {
54
+ if(this.generation===generation){this.mode='observe';this.phase='refresh-failed';this.startedAt=null;this.latest=null;this.outputs=0;++this.generation;}
55
+ return{accepted:false,reason:'refresh-failed',...this.snapshot()};
56
+ }
57
+ // Account/mode changes while a close callback awaited invalidate this activation.
58
+ if(this.generation!==generation)return{accepted:false,reason:'superseded',...this.snapshot()};
59
+ // The registry settles every close and reports failures; it does not reject the batch.
60
+ // Partial closure cannot leave rewriting enabled while an old app cache may survive.
61
+ if(!Number.isSafeInteger(refreshed.requested)||!Number.isSafeInteger(refreshed.closed)
62
+ ||!Number.isSafeInteger(refreshed.failed)||refreshed.requested<0||refreshed.closed<0
63
+ ||refreshed.failed!==0||refreshed.closed!==refreshed.requested){
64
+ this.mode='observe';this.phase='refresh-failed';this.startedAt=null;this.latest=null;this.outputs=0;++this.generation;
65
+ return{accepted:false,reason:'refresh-failed',refresh:refreshed,...this.snapshot()};
66
+ }
67
+ return{accepted:true,reason:'awaiting-new-response',refresh:refreshed,...this.snapshot()};
68
+ }
69
+ recordOutput(binding:string,generation:number) {
70
+ if(binding!==this.binding||generation!==this.generation||this.mode!=='apply')return false;
71
+ if(this.startedAt===null||this.clock()-this.startedAt<0||this.clock()-this.startedAt>=this.timeoutMs)return false;
72
+ ++this.outputs;this.phase='response-produced-app-confirmation-needed';return true;
73
+ }
74
+ async expireIfNeeded() {
75
+ if(this.startedAt===null||this.clock()-this.startedAt<this.timeoutMs&&this.clock()-this.startedAt>=0)return false;
76
+ await this.reset('expired-awaiting-original-response');return true;
77
+ }
78
+ async observeOnly() { return this.reset('observing-awaiting-original-response'); }
79
+ async changeBinding(binding:string) {
80
+ if(!binding)throw new Error('Verified account binding is required');
81
+ const previous=this.binding;
82
+ this.mode='observe';this.phase='account-changed';this.startedAt=null;this.latest=null;this.outputs=0;++this.generation;
83
+ this.binding=binding;
84
+ return this.refresh(previous);
85
+ }
86
+ }
@@ -0,0 +1,59 @@
1
+ import { UsageRelayController } from './usage-controller';
2
+ import { controlledUsageSse } from './usage-sse-controller';
3
+ import { boundedJsonBody } from './json-body';
4
+
5
+ /** Inject as the listener's fetchImpl. Existing auth forwarding/TLS/WS code stays unchanged. */
6
+ export function createUsageControlledFetch(controller: UsageRelayController, upstreamFetch: typeof fetch = fetch): typeof fetch {
7
+ return (async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {
8
+ const upstream = await upstreamFetch(input, init);
9
+ const pathname = new URL(input instanceof Request ? input.url : String(input)).pathname;
10
+ const method = init?.method ?? (input instanceof Request ? input.method : 'GET');
11
+ const exchange = { pathname, method, status: upstream.status };
12
+ if (method !== 'GET' || upstream.status !== 200 || !upstream.body
13
+ || !['/backend-api/wham/usage', '/backend-api/wham/usage/stream'].includes(pathname)) return upstream;
14
+ const contentType = upstream.headers.get('content-type') ?? '';
15
+ const headers = new Headers(upstream.headers);
16
+ const output = (body: BodyInit, transformed = false) => {
17
+ headers.delete('content-length'); headers.delete('content-encoding');
18
+ if (transformed) {
19
+ for (const name of ['etag', 'last-modified', 'digest', 'content-md5']) headers.delete(name);
20
+ headers.set('cache-control', 'no-store');
21
+ }
22
+ return new Response(body, { status: upstream.status, statusText: upstream.statusText, headers });
23
+ };
24
+ if (contentType.includes('application/json') || contentType.endsWith('+json')) {
25
+ const body = await boundedJsonBody(upstream.body);
26
+ if ('stream' in body) return output(body.stream);
27
+ let text: string;
28
+ try { text = new TextDecoder('utf-8', { fatal: true }).decode(body.bytes); }
29
+ catch { return output(new Uint8Array(body.bytes)); }
30
+ const changed = await controller.rewriteJson(text, exchange);
31
+ return output(changed ?? new Uint8Array(body.bytes), changed !== null);
32
+ }
33
+ if (!contentType.includes('text/event-stream') || pathname !== '/backend-api/wham/usage/stream') return upstream;
34
+ let registration: ReturnType<UsageRelayController['registerStream']> = null;
35
+ const reader = upstream.body.pipeThrough(controlledUsageSse(text => controller.rewriteJson(text, exchange, registration))).getReader();
36
+ let ended = false, sink: ReadableStreamDefaultController<Uint8Array>;
37
+ const close = async () => {
38
+ if (ended) return;
39
+ ended = true; registration?.release();
40
+ const cancelled = reader.cancel();
41
+ try { sink.close(); } catch { /* caller already cancelled */ }
42
+ await cancelled;
43
+ };
44
+ const body = new ReadableStream<Uint8Array>({
45
+ start(value) { sink = value; },
46
+ async pull(value) {
47
+ try {
48
+ const next = await reader.read();
49
+ if (ended) return;
50
+ if (next.done) { ended = true; registration?.release(); value.close(); }
51
+ else value.enqueue(next.value);
52
+ } catch (error) { if (!ended) { ended = true; registration?.release(); value.error(error); } }
53
+ },
54
+ cancel: close,
55
+ });
56
+ registration = controller.registerStream(exchange, close);
57
+ return output(body, true);
58
+ }) as typeof fetch;
59
+ }
@@ -0,0 +1,107 @@
1
+ import { UsageActivation, type ApplyScope } from './usage-activation';
2
+ import { UsageRefreshRegistry } from './usage-refresh';
3
+ import { evaluateUsageRewrite, type UsageRewriteContext } from './usage-policy';
4
+
5
+ export type UsageIdentity = NonNullable<UsageRewriteContext['account']> & { credentialGeneration?: string };
6
+ type Registration = NonNullable<ReturnType<UsageRefreshRegistry['register']>>;
7
+ type Exchange = { method: string; pathname: string; status: number };
8
+ const same = (a: UsageIdentity | null, b: UsageIdentity) => a !== null
9
+ && a.id === b.id && a.userId === b.userId && a.plan === b.plan && a.structure === b.structure
10
+ && a.credentialGeneration === b.credentialGeneration;
11
+ const object = (value: unknown): value is Record<string, unknown> => value !== null
12
+ && typeof value === 'object' && !Array.isArray(value);
13
+
14
+ /** Response-level controller. Construction never starts a listener, timer or trust operation. */
15
+ export class UsageRelayController {
16
+ private readonly account: UsageIdentity;
17
+ private readonly key: string;
18
+ private readonly refresh = new UsageRefreshRegistry();
19
+ private readonly activation: UsageActivation;
20
+ private jsonSnapshots = 0;
21
+ private streamSnapshots = 0;
22
+ private lastSnapshotAt: number | null = null;
23
+ constructor(account: UsageIdentity, private readonly readCurrentIdentity: () => Promise<UsageIdentity | null>,
24
+ verifyFreshIdentity: () => Promise<UsageIdentity | null>, private readonly clock: () => number,
25
+ private readonly expiresAt: number, timeoutMs = 180000, private readonly contextValid: () => boolean | Promise<boolean> = () => true) {
26
+ if (!account.id || !account.userId || !['plus', 'pro'].includes(account.plan)
27
+ || account.structure !== 'personal' || !Number.isFinite(expiresAt) || expiresAt <= clock()) {
28
+ throw new Error('A verified supported identity and finite safety deadline are required');
29
+ }
30
+ this.account = { ...account };
31
+ this.key = JSON.stringify([account.id, account.userId, account.plan, account.credentialGeneration ?? null]);
32
+ this.activation = new UsageActivation(this.key, key => this.refresh.refresh(key), async () => {
33
+ return same(await verifyFreshIdentity(), this.account) ? this.key : null;
34
+ }, clock, timeoutMs);
35
+ }
36
+ snapshot() { return { ...this.activation.snapshot(), trackedStreams: this.refresh.size, expired: this.clock() >= this.expiresAt,
37
+ observation: { jsonSnapshots: this.jsonSnapshots, streamSnapshots: this.streamSnapshots,
38
+ validatedActiveStreams: this.refresh.boundSize, lastSnapshotAt: this.lastSnapshotAt,
39
+ sourceProcessVerified: false as const, composerRecoveryVerified: false as const } }; }
40
+ private async checkContext() { try { return await this.contextValid(); } catch { return false; } }
41
+ async activate(options: { scope: ApplyScope; accountWideConsent: boolean }) {
42
+ const generation = this.activation.snapshot().generation, valid = await this.checkContext();
43
+ if (generation !== this.activation.snapshot().generation) return { accepted: false, reason: 'superseded', ...this.snapshot() };
44
+ if (!valid) { this.activation.invalidateIdentity(); return { accepted: false, reason: 'native-context-changed', ...this.snapshot() }; }
45
+ if (this.clock() >= this.expiresAt) { this.activation.invalidateIdentity(); return { accepted: false, reason: 'safety-deadline', ...this.snapshot() }; }
46
+ return this.activation.activate(options);
47
+ }
48
+ observeOnly() { return this.activation.observeOnly(); }
49
+ expireIfNeeded() { return this.activation.expireIfNeeded(); }
50
+ registerStream(exchange: Exchange, close: () => Promise<void>): Registration | null {
51
+ if (exchange.status !== 200) return null;
52
+ return this.refresh.register(exchange.method, exchange.pathname, close);
53
+ }
54
+ /** Only a complete original usage record may establish the stream's account binding. */
55
+ async rewriteJson(text: string, exchange: Exchange, stream?: Registration | null): Promise<string | null> {
56
+ if (exchange.method !== 'GET' || exchange.status !== 200
57
+ || !['/backend-api/wham/usage', '/backend-api/wham/usage/stream'].includes(exchange.pathname)) return null;
58
+ if (this.clock() >= this.expiresAt) { this.activation.invalidateIdentity(); stream?.exclude(); return null; }
59
+ const observedGeneration = this.activation.snapshot().generation;
60
+ let current: UsageIdentity | null;
61
+ try { current = await this.readCurrentIdentity(); } catch { current = null; }
62
+ if (!same(current, this.account)) { this.activation.invalidateIdentity(); stream?.exclude(); return null; }
63
+ // Do not apply an activation that raced this response's identity read.
64
+ if (observedGeneration !== this.activation.snapshot().generation) return null;
65
+ if (this.clock() >= this.expiresAt) { this.activation.invalidateIdentity(); stream?.exclude(); return null; }
66
+ if (Buffer.byteLength(text, 'utf8') > 262144) { stream?.exclude(); return null; }
67
+ let parsed: unknown;
68
+ try { parsed = JSON.parse(text); } catch { stream?.exclude(); return null; }
69
+ const envelope = object(parsed) && 'usage' in parsed ? parsed : null;
70
+ if ((exchange.pathname.endsWith('/stream') && !envelope)
71
+ || (envelope && (envelope.version !== 1 || typeof envelope.stream_id !== 'string' || !envelope.stream_id
72
+ || !Number.isSafeInteger(envelope.sequence) || Number(envelope.sequence) <= 0))) { stream?.exclude(); return null; }
73
+ const original = envelope ? envelope.usage : parsed;
74
+ if (!object(original) || original.account_id !== this.account.id || original.user_id !== this.account.userId
75
+ || original.plan_type !== this.account.plan) { stream?.exclude(); return null; }
76
+ const rate = original.rate_limit;
77
+ if (!object(rate) || typeof rate.allowed !== 'boolean' || typeof rate.limit_reached !== 'boolean') {
78
+ stream?.exclude(); return null;
79
+ }
80
+ if (stream && !stream.bind(this.key)) return null;
81
+ // Identity-checked responses received by this relay do not establish the
82
+ // sending process or prove that an authoritative UI cache consumed them.
83
+ if (exchange.pathname.endsWith('/stream')) this.streamSnapshots = Math.min(Number.MAX_SAFE_INTEGER, this.streamSnapshots + 1);
84
+ else this.jsonSnapshots = Math.min(Number.MAX_SAFE_INTEGER, this.jsonSnapshots + 1);
85
+ this.lastSnapshotAt = this.clock();
86
+ const context: UsageRewriteContext = { enabled: true, mode: 'observe', status: exchange.status,
87
+ pathname: exchange.pathname, account: this.account };
88
+ const observed = evaluateUsageRewrite(original, context);
89
+ this.activation.observe(this.key, observed.eligible ? 'exhausted'
90
+ : rate.allowed === true && rate.limit_reached === false ? 'available' : 'protected');
91
+ const state = this.activation.snapshot();
92
+ if (state.mode !== 'apply') return null;
93
+ const result = evaluateUsageRewrite(original, { ...context, mode: 'apply' });
94
+ if (!result.changed) return null;
95
+ // Observe and unchanged responses never spawn an installed-package probe.
96
+ const valid = await this.checkContext();
97
+ if (state.generation !== this.activation.snapshot().generation) return null;
98
+ if (!valid) { this.activation.invalidateIdentity(); stream?.exclude(); return null; }
99
+ // A native account or trial can change while the asynchronous build check runs.
100
+ try { current = await this.readCurrentIdentity(); } catch { current = null; }
101
+ if (state.generation !== this.activation.snapshot().generation) return null;
102
+ if (!same(current, this.account)) { this.activation.invalidateIdentity(); stream?.exclude(); return null; }
103
+ if (this.clock() >= this.expiresAt) { this.activation.invalidateIdentity(); stream?.exclude(); return null; }
104
+ if (!this.activation.recordOutput(this.key, state.generation)) return null;
105
+ return JSON.stringify(envelope ? { ...envelope, usage: result.value } : result.value);
106
+ }
107
+ }
@@ -0,0 +1,59 @@
1
+ /** Pure response policy. Real quota windows, credits and spending restrictions stay unchanged. */
2
+ export interface UsageRewriteContext {
3
+ enabled: boolean;
4
+ mode: 'observe' | 'apply';
5
+ status: number;
6
+ pathname: string;
7
+ account: {
8
+ id: string;
9
+ userId: string;
10
+ plan: 'plus' | 'pro';
11
+ structure: 'personal';
12
+ } | null;
13
+ }
14
+
15
+ type RecordValue = Record<string, unknown>;
16
+ function record(value: unknown): value is RecordValue {
17
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
18
+ }
19
+
20
+ /**
21
+ * Transform only a complete, matched personal-account usage snapshot.
22
+ * Account context must come from a trusted live integration; this module does not
23
+ * discover it or prove provider isolation. Never persist the adjusted snapshot as
24
+ * actual provider/account capacity. Unknown or protected states pass unchanged.
25
+ */
26
+ export function evaluateUsageRewrite(value: unknown, context: UsageRewriteContext) {
27
+ const unchanged = (reason: string) => ({ value, changed: false, eligible: false, reason });
28
+ if (!context.enabled) return unchanged('off');
29
+ if (context.status !== 200) return unchanged('non-success-response');
30
+ if (!['/backend-api/wham/usage', '/backend-api/wham/usage/stream'].includes(context.pathname)) return unchanged('unowned-path');
31
+ const account = context.account;
32
+ if (!account || !account.id || !account.userId || account.structure !== 'personal' || !['plus', 'pro'].includes(account.plan)) return unchanged('unsupported-account');
33
+ if (!record(value) || value.account_id !== account.id || value.user_id !== account.userId || value.plan_type !== account.plan) return unchanged('identity-mismatch');
34
+ const rate = value.rate_limit;
35
+ if (!record(rate) || rate.allowed !== false || rate.limit_reached !== true) return unchanged('not-explicitly-exhausted');
36
+ const spend = value.spend_control;
37
+ // Absence is unknown, not evidence that no protected spending restriction exists.
38
+ if (!record(spend) || spend.reached !== false) return unchanged('protected-or-unknown-spend');
39
+ const reason = value.rate_limit_reached_type;
40
+ if (reason != null && (!record(reason) || reason.type !== 'rate_limit_reached')) return unchanged('protected-or-unknown-reason');
41
+ const credits = value.credits;
42
+ if (!record(credits) || typeof credits.has_credits !== 'boolean' || typeof credits.unlimited !== 'boolean') return unchanged('unknown-credit-schema');
43
+ if (credits.overage_limit_reached != null && credits.overage_limit_reached !== false) return unchanged('overage-restriction');
44
+ // These entitlements already keep the native gate open. Rewriting the ordinary
45
+ // rate limit would unnecessarily change the app's Reserve activation predicate.
46
+ if (credits.has_credits || credits.unlimited) return unchanged('credits-available');
47
+ const upsell = value.rate_limit_upsell, additional = value.additional_rate_limits;
48
+ if (upsell != null && !record(upsell)) return unchanged('unknown-reserve-schema');
49
+ if (additional != null && (!Array.isArray(additional) || additional.some(limit => !record(limit)
50
+ || limit.limit_name === 'gpt-reserve' && (!record(limit.rate_limit) || typeof limit.rate_limit.allowed !== 'boolean')))) return unchanged('unknown-reserve-schema');
51
+ if (record(upsell) && upsell.banner_type === 'luna_reserve' && Array.isArray(additional)
52
+ && additional.some(limit => record(limit) && limit.limit_name === 'gpt-reserve'
53
+ && record(limit.rate_limit) && limit.rate_limit.allowed === true)) return unchanged('reserve-active');
54
+ if (context.mode !== 'apply') return { value, changed: false, eligible: true, reason: 'observe-only' };
55
+ return {
56
+ value: { ...value, rate_limit: { ...rate, allowed: true, limit_reached: false } },
57
+ changed: true, eligible: true, reason: 'personal-usage-only',
58
+ };
59
+ }
@@ -0,0 +1,33 @@
1
+ /** Exact usage-stream refresh registry. No timers, sockets or core-path imports. */
2
+ export class UsageRefreshRegistry {
3
+ private entries = new Set<{ binding: string | null; tainted: boolean; close: () => Promise<void> }>();
4
+ register(method: string, pathname: string, close: () => Promise<void>) {
5
+ if (method !== 'GET' || pathname !== '/backend-api/wham/usage/stream') return null;
6
+ const entry = { binding: null as string | null, tainted: false, close };
7
+ this.entries.add(entry);
8
+ return {
9
+ // Caller must invoke only after validating a complete upstream snapshot's identity.
10
+ // A connection that changes account identity never becomes a refresh target again.
11
+ bind: (verifiedBinding: string) => {
12
+ if (!verifiedBinding || entry.tainted || !this.entries.has(entry)) return false;
13
+ if (entry.binding !== null && entry.binding !== verifiedBinding) {
14
+ entry.tainted = true; entry.binding = null; return false;
15
+ }
16
+ entry.binding = verifiedBinding; return true;
17
+ },
18
+ exclude: () => { entry.tainted = true; entry.binding = null; },
19
+ release: () => { this.entries.delete(entry); },
20
+ };
21
+ }
22
+ async refresh(binding: string) {
23
+ if (!binding) throw new Error('Verified account binding is required');
24
+ const selected = [...this.entries].filter(e => !e.tainted && e.binding === binding);
25
+ // Detach before awaiting; concurrent refreshes cannot close the same stream twice.
26
+ for (const entry of selected) this.entries.delete(entry);
27
+ const results = await Promise.allSettled(selected.map(entry => Promise.resolve().then(entry.close)));
28
+ return { requested: selected.length, closed: results.filter(r => r.status === 'fulfilled').length,
29
+ failed: results.filter(r => r.status === 'rejected').length };
30
+ }
31
+ get size() { return this.entries.size; }
32
+ get boundSize() { return [...this.entries].filter(entry => !entry.tainted && entry.binding !== null).length; }
33
+ }
@@ -0,0 +1,86 @@
1
+ type Rewrite = (text: string) => Promise<string | null>;
2
+
3
+ async function rewriteRecord(bytes: Buffer, first: boolean, rewrite: Rewrite): Promise<Buffer> {
4
+ let text: string;
5
+ try { text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes); }
6
+ catch { return Buffer.from(bytes); }
7
+ const lines = [...text.matchAll(/([^\r\n]*)(\r\n|\r|\n|$)/g)].filter(m => m[0].length > 0);
8
+ const data: string[] = [], indexes = new Set<number>(); let event = '';
9
+ lines.forEach((line, i) => {
10
+ const content = first && i === 0 ? line[1]!.replace(/^\uFEFF/, '') : line[1]!;
11
+ if (content.startsWith(':')) return;
12
+ const colon = content.indexOf(':');
13
+ const key = colon < 0 ? content : content.slice(0, colon);
14
+ const value = colon < 0 ? '' : content.slice(colon + 1).replace(/^ /, '');
15
+ if (key === 'event') event = value;
16
+ if (key === 'data') { data.push(value); indexes.add(i); }
17
+ });
18
+ if (event !== 'usage.snapshot' || !data.length) return Buffer.from(bytes);
19
+ const changed = await rewrite(data.join('\n'));
20
+ if (changed === null) return Buffer.from(bytes);
21
+ let written = false;
22
+ return Buffer.from(lines.map((line, i) => {
23
+ if (!indexes.has(i)) return line[0];
24
+ if (written) return '';
25
+ written = true;
26
+ const bom = first && i === 0 && line[1]!.startsWith('\uFEFF') ? '\uFEFF' : '';
27
+ return `${bom}data: ${changed}${line[2]}`;
28
+ }).join(''));
29
+ }
30
+
31
+ /** Byte-bounded SSE framing; never fabricates events or changes stream sequence numbers. */
32
+ export function controlledUsageSse(rewrite: Rewrite, cap = 262144): TransformStream<Uint8Array, Uint8Array> {
33
+ if (!Number.isSafeInteger(cap) || cap < 1 || cap > 1048576) throw new Error('Invalid record limit');
34
+ const buffer = Buffer.alloc(cap);
35
+ let used = 0, lineHasBytes = false, pendingCR = false, first = true, passthrough = false;
36
+ return new TransformStream({
37
+ async transform(chunk, controller) {
38
+ let rawStart = passthrough ? 0 : -1;
39
+ const enterPassthrough = (start: number) => {
40
+ controller.enqueue(Buffer.from(buffer.subarray(0, used)));
41
+ used = 0; passthrough = true; rawStart = start;
42
+ };
43
+ const endRecord = async (rawEnd: number) => {
44
+ if (passthrough) {
45
+ if (rawEnd > rawStart) controller.enqueue(chunk.subarray(rawStart, rawEnd));
46
+ passthrough = false; rawStart = -1;
47
+ } else {
48
+ controller.enqueue(await rewriteRecord(buffer.subarray(0, used), first, rewrite));
49
+ }
50
+ first = false; used = 0; lineHasBytes = false;
51
+ };
52
+ for (let i = 0; i < chunk.length; i++) {
53
+ const byte = chunk[i]!;
54
+ if (pendingCR) {
55
+ pendingCR = false;
56
+ if (byte !== 10) {
57
+ if (!lineHasBytes) await endRecord(i);
58
+ lineHasBytes = false;
59
+ }
60
+ else {
61
+ if (!passthrough && used === cap) enterPassthrough(i);
62
+ if (!passthrough) buffer[used++] = byte;
63
+ const blankLine = !lineHasBytes;
64
+ lineHasBytes = false;
65
+ if (blankLine) await endRecord(i + 1);
66
+ continue;
67
+ }
68
+ }
69
+ if (!passthrough && used === cap) enterPassthrough(i);
70
+ if (!passthrough) buffer[used++] = byte;
71
+ if (byte === 13) pendingCR = true;
72
+ else if (byte === 10) {
73
+ const blankLine = !lineHasBytes;
74
+ lineHasBytes = false;
75
+ if (blankLine) await endRecord(i + 1);
76
+ } else lineHasBytes = true;
77
+ }
78
+ if (passthrough && rawStart < chunk.length) controller.enqueue(chunk.subarray(rawStart));
79
+ },
80
+ async flush(controller) {
81
+ if (passthrough) return;
82
+ if (pendingCR && !lineHasBytes) { controller.enqueue(await rewriteRecord(buffer.subarray(0, used), first, rewrite)); used = 0; }
83
+ if (used > 0) controller.enqueue(Buffer.from(buffer.subarray(0, used)));
84
+ },
85
+ });
86
+ }
@@ -0,0 +1,44 @@
1
+ // Kept inline so source and standalone Bun distributions carry the same COM adapter.
2
+ export const WINDOWS_ACTIVATION_SOURCE = String.raw`using System;
3
+ using System.Runtime.InteropServices;
4
+ using System.Text;
5
+
6
+ // Windows.Launch preserves package identity and forwards explicit application arguments.
7
+ public static class OpenCodexPackageActivation {
8
+ [ComImport, Guid("2E941141-7F97-4756-BA1D-9DECDE894A3D"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
9
+ private interface IApplicationActivationManager {
10
+ [PreserveSig] int ActivateApplication([MarshalAs(UnmanagedType.LPWStr)] string appId, [MarshalAs(UnmanagedType.LPWStr)] string arguments, uint options, out uint processId);
11
+ [PreserveSig] int ActivateForFile([MarshalAs(UnmanagedType.LPWStr)] string appId, IntPtr items, [MarshalAs(UnmanagedType.LPWStr)] string verb, out uint processId);
12
+ [PreserveSig] int ActivateForProtocol([MarshalAs(UnmanagedType.LPWStr)] string appId, IntPtr items, out uint processId);
13
+ }
14
+ [DllImport("ole32.dll", PreserveSig=true)]
15
+ private static extern int CoCreateInstance(ref Guid clsid, IntPtr outer, uint context, ref Guid iid, [MarshalAs(UnmanagedType.Interface)] out IApplicationActivationManager manager);
16
+ [DllImport("kernel32.dll", SetLastError=true)] private static extern IntPtr OpenProcess(uint access, bool inherit, uint pid);
17
+ [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr handle);
18
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] private static extern int GetPackageFullName(IntPtr process, ref uint length, StringBuilder name);
19
+ private static IApplicationActivationManager Manager() {
20
+ var clsid=new Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C");
21
+ var iid=new Guid("2E941141-7F97-4756-BA1D-9DECDE894A3D");
22
+ IApplicationActivationManager manager;
23
+ // LOCAL_SERVER preserves activation argument lifetime after the short launcher exits.
24
+ Marshal.ThrowExceptionForHR(CoCreateInstance(ref clsid,IntPtr.Zero,4,ref iid,out manager));
25
+ return manager;
26
+ }
27
+ public static void ValidateActivationService() {var manager=Manager();Marshal.FinalReleaseComObject(manager);}
28
+ public static uint Activate(string appId,string arguments) {
29
+ var manager=Manager();
30
+ try {uint pid;Marshal.ThrowExceptionForHR(manager.ActivateApplication(appId,arguments,0,out pid));return pid;}
31
+ finally {Marshal.FinalReleaseComObject(manager);}
32
+ }
33
+ public static string PackageOf(uint pid) {
34
+ var handle=OpenProcess(0x1000,false,pid);
35
+ if(handle==IntPtr.Zero)throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
36
+ try {uint length=0;int status=GetPackageFullName(handle,ref length,null);
37
+ if(status==15700)return null;
38
+ if(status!=122)throw new System.ComponentModel.Win32Exception(status);
39
+ var value=new StringBuilder((int)length);status=GetPackageFullName(handle,ref length,value);
40
+ if(status!=0)throw new System.ComponentModel.Win32Exception(status);
41
+ return value.ToString();
42
+ }finally{CloseHandle(handle);}
43
+ }
44
+ }`;
@@ -0,0 +1,81 @@
1
+ import { execFile } from "node:child_process";
2
+ import { X509Certificate } from "node:crypto";
3
+ import { resolveTrustedWindowsPowerShellExe } from "../../lib/windows-elevation";
4
+ import type { StoredDesktopAuthority } from "./certificate-store";
5
+ import { isTestHomeGuardArmed } from "../../lib/test-home-guard";
6
+ import { isServerAuthOnlyCertificate } from "../../claude/intercept/local-ca";
7
+
8
+ export type DesktopCertificateTrust = "trusted" | "not-trusted" | "unknown";
9
+ export type TrustOperation = "inspect" | "trust" | "remove";
10
+ export type CertificateTrustRunner = (operation: TrustOperation, certificateDer: string, fingerprint: string) => Promise<DesktopCertificateTrust>;
11
+
12
+ const SCRIPT = [
13
+ "$ErrorActionPreference='Stop'",
14
+ "$inputData=[Console]::In.ReadToEnd()|ConvertFrom-Json",
15
+ "$bytes=[Convert]::FromBase64String($inputData.certificate)",
16
+ "$cert=[Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes)",
17
+ "$sha=[Security.Cryptography.SHA256]::Create()",
18
+ "try {$fingerprint=([BitConverter]::ToString($sha.ComputeHash($bytes))).Replace('-','')}finally{$sha.Dispose()}",
19
+ "if($fingerprint -cne $inputData.fingerprint){throw 'Certificate changed'}",
20
+ "$store=[Security.Cryptography.X509Certificates.X509Store]::new('Root','CurrentUser')",
21
+ "$mode=if($inputData.operation -eq 'inspect'){[Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly}else{[Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite}",
22
+ "try {",
23
+ " $store.Open($mode)",
24
+ " $foundCertificates=@($store.Certificates.Find([Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint,$cert.Thumbprint,$false))",
25
+ " foreach($certificateMatch in $foundCertificates){if([Convert]::ToBase64String($certificateMatch.RawData) -cne [Convert]::ToBase64String($bytes)){throw 'Certificate mismatch'}}",
26
+ " if($inputData.operation -eq 'trust'){if($foundCertificates.Count -eq 0){$store.Add($cert)}}",
27
+ " elseif($inputData.operation -eq 'remove'){foreach($certificateMatch in $foundCertificates){$store.Remove($certificateMatch)}}",
28
+ " elseif($inputData.operation -ne 'inspect'){throw 'Unknown operation'}",
29
+ " $present=@($store.Certificates.Find([Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint,$cert.Thumbprint,$false)).Count -gt 0",
30
+ " if($present){[Console]::Out.Write('trusted')}else{[Console]::Out.Write('not-trusted')}",
31
+ "}finally{$store.Close();$store.Dispose();$cert.Dispose()}",
32
+ ].join("\n");
33
+
34
+ const run: CertificateTrustRunner = (operation, certificateDer, fingerprint) => {
35
+ if (process.platform !== "win32") return Promise.resolve("unknown");
36
+ if (operation !== "inspect" && isTestHomeGuardArmed()) return Promise.resolve("unknown");
37
+ return new Promise(resolve => {
38
+ const child = execFile(resolveTrustedWindowsPowerShellExe(), ["-NoProfile", "-NonInteractive", "-Command", SCRIPT], {
39
+ timeout: operation === "inspect" ? 10_000 : 120_000, maxBuffer: 4096, windowsHide: true, encoding: "utf8",
40
+ }, (error, stdout) => resolve(!error && (stdout.trim() === "trusted" || stdout.trim() === "not-trusted")
41
+ ? stdout.trim() as DesktopCertificateTrust : "unknown"));
42
+ child.stdin?.on("error", () => { /* completion and readback decide the result */ });
43
+ child.stdin?.end(JSON.stringify({ operation, certificate: certificateDer, fingerprint }));
44
+ });
45
+ };
46
+
47
+ /** Public status cannot create a certificate, decrypt a key or alter OS trust. */
48
+ export async function inspectWindowsCertificateTrust(certPem: string, expectedFingerprint: string, runner: CertificateTrustRunner = run): Promise<DesktopCertificateTrust> {
49
+ const certificate = new X509Certificate(certPem);
50
+ const fingerprint = certificate.fingerprint256.replaceAll(":", "");
51
+ if (!certificate.ca || fingerprint !== expectedFingerprint || !/^[A-F0-9]{64}$/.test(expectedFingerprint)) {
52
+ throw new Error("desktop_compatibility_certificate_mismatch");
53
+ }
54
+ return runner("inspect", certificate.raw.toString("base64"), fingerprint).catch(() => "unknown" as const);
55
+ }
56
+
57
+ /** Mutations require the store-validated private key, not unverified public status metadata. */
58
+ export function createWindowsCertificateTrust(authority: StoredDesktopAuthority, expectedFingerprint: string, runner: CertificateTrustRunner = run) {
59
+ const certificate = new X509Certificate(authority.authority.certPem);
60
+ const fingerprint = certificate.fingerprint256.replaceAll(":", "");
61
+ if (fingerprint !== expectedFingerprint || !certificate.ca || !certificate.checkPrivateKey(authority.authority.privateKey)
62
+ || !certificate.verify(authority.authority.publicKey) || !/^OpenCodex Codex Desktop [0-9a-f-]{36}$/.test(authority.commonName)
63
+ || !certificate.subject.split("\n").includes(`CN=${authority.commonName}`)) throw new Error("desktop_compatibility_certificate_mismatch");
64
+ const encoded = certificate.raw.toString("base64");
65
+ const inspect = () => runner("inspect", encoded, fingerprint).catch(() => "unknown" as const);
66
+ let pending: Promise<DesktopCertificateTrust> | null = null;
67
+ const change = (operation: "trust" | "remove") => {
68
+ if (pending) return Promise.reject(new Error("desktop_compatibility_trust_busy"));
69
+ if (operation === "trust" && Date.parse(certificate.validTo) <= Date.now()) return Promise.reject(new Error("desktop_compatibility_authority_expired"));
70
+ if (operation === "trust" && !isServerAuthOnlyCertificate(certificate)) return Promise.reject(new Error("desktop_compatibility_authority_renewal_required"));
71
+ pending = (async () => {
72
+ const before = await inspect();
73
+ if (before === "unknown") return before;
74
+ if (before === (operation === "trust" ? "trusted" : "not-trusted")) return before;
75
+ try { await runner(operation, encoded, fingerprint); } catch { /* read the exact store after an uncertain action */ }
76
+ return inspect();
77
+ })().finally(() => { pending = null; });
78
+ return pending;
79
+ };
80
+ return { inspect, trust: () => change("trust"), remove: () => change("remove") };
81
+ }