@bitkyc08/opencodex 2.86.0 → 2.88.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 (400) hide show
  1. package/gui/dist/assets/App-CHC89EXQ.js +58 -0
  2. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  3. package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
  4. package/gui/dist/assets/index-B5bsvxor.js +94 -0
  5. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  6. package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic/beta-allowlist.ts +9 -8
  10. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  11. package/src/adapters/anthropic/passthrough.ts +7 -23
  12. package/src/adapters/anthropic-progress.ts +84 -0
  13. package/src/adapters/anthropic-rejection-memo.ts +71 -0
  14. package/src/adapters/anthropic-replay.ts +135 -0
  15. package/src/adapters/anthropic-tool-catalog.ts +11 -0
  16. package/src/adapters/anthropic.ts +151 -33
  17. package/src/adapters/base.ts +2 -0
  18. package/src/adapters/claude-cli/adapter.ts +111 -23
  19. package/src/adapters/claude-cli/profiles.ts +2 -1
  20. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  21. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  22. package/src/adapters/coding-agent/protocol.ts +1 -1
  23. package/src/adapters/coding-agent/turn.ts +18 -2
  24. package/src/adapters/dsh-account.ts +97 -0
  25. package/src/adapters/google-progress-history.ts +53 -0
  26. package/src/adapters/google.ts +52 -9
  27. package/src/adapters/openai-chat/messages.ts +4 -3
  28. package/src/adapters/openai-chat/passthrough.ts +8 -2
  29. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  30. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  31. package/src/adapters/openai-responses/passthrough.ts +57 -125
  32. package/src/adapters/openai-responses/request-strips.ts +25 -3
  33. package/src/adapters/openai-responses/transport.ts +81 -0
  34. package/src/adapters/qoder/adapter.ts +4 -2
  35. package/src/adapters/registry.ts +5 -0
  36. package/src/adapters/zed.ts +26 -1
  37. package/src/advisor/activity.ts +55 -0
  38. package/src/advisor/budget.ts +62 -0
  39. package/src/advisor/consult.ts +4 -1
  40. package/src/advisor/context.ts +16 -4
  41. package/src/advisor/evidence-tool.ts +21 -0
  42. package/src/advisor/evidence.ts +117 -0
  43. package/src/advisor/runtime.ts +79 -31
  44. package/src/advisor/settings.ts +11 -2
  45. package/src/bridge/errors.ts +19 -1
  46. package/src/bridge/internal.ts +7 -1
  47. package/src/bridge/response-json.ts +18 -3
  48. package/src/bridge/sse.ts +59 -8
  49. package/src/bridge/terminal-refusal.ts +59 -0
  50. package/src/claude/desktop-3p-library.ts +32 -7
  51. package/src/claude/desktop-3p.ts +55 -13
  52. package/src/claude/desktop-first-party.ts +1 -1
  53. package/src/claude/desktop-picker-platform.ts +4 -0
  54. package/src/claude/desktop-picker.ts +15 -8
  55. package/src/claude/desktop-system-proxy.ts +50 -4
  56. package/src/claude/inbound-content-options.ts +77 -0
  57. package/src/claude/inbound.ts +33 -0
  58. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  59. package/src/claude/intercept/picker-runtime.ts +6 -5
  60. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  61. package/src/claude/intercept/picker-trust.ts +19 -0
  62. package/src/claude/intercept/port-policy.ts +30 -0
  63. package/src/claude/intercept/runtime.ts +97 -94
  64. package/src/claude/outbound.ts +46 -13
  65. package/src/cli/account-api.ts +1 -0
  66. package/src/cli/account-policy-dto.ts +6 -0
  67. package/src/cli/account-policy.ts +34 -5
  68. package/src/cli/account-preservation.ts +44 -0
  69. package/src/cli/account.ts +8 -2
  70. package/src/cli/advisor.ts +17 -3
  71. package/src/cli/capabilities-accounts.ts +20 -1
  72. package/src/cli/capabilities-agents-routing.ts +5 -2
  73. package/src/cli/capabilities-base.ts +38 -10
  74. package/src/cli/capabilities-observe-system.ts +63 -3
  75. package/src/cli/capabilities-provider-models.ts +11 -0
  76. package/src/cli/codex-cli-update.ts +151 -41
  77. package/src/cli/codex-provider.ts +44 -0
  78. package/src/cli/combo-input.ts +10 -1
  79. package/src/cli/combo-result.ts +1 -1
  80. package/src/cli/combo.ts +2 -1
  81. package/src/cli/cross-home-owner.ts +33 -16
  82. package/src/cli/dispatch.ts +25 -4
  83. package/src/cli/doctor.ts +6 -1
  84. package/src/cli/ensure-desktop-cold-start.ts +60 -7
  85. package/src/cli/help.ts +3 -2
  86. package/src/cli/index.ts +1 -1
  87. package/src/cli/integration-plan-dto.ts +1 -0
  88. package/src/cli/log-history.ts +172 -0
  89. package/src/cli/loopback-family.ts +31 -0
  90. package/src/cli/models-runtime-subcommands.ts +1 -0
  91. package/src/cli/models-runtime.ts +72 -8
  92. package/src/cli/observe-stream.ts +2 -0
  93. package/src/cli/observe.ts +16 -4
  94. package/src/cli/provider.ts +27 -5
  95. package/src/cli/registry.ts +28 -8
  96. package/src/cli/runtime-api.ts +5 -3
  97. package/src/cli/system-command.ts +40 -8
  98. package/src/cli/uninstall-client-state.ts +32 -0
  99. package/src/cli/usage-compare.ts +309 -0
  100. package/src/cli/usage-model-search.ts +4 -4
  101. package/src/cli/usage-report.ts +2 -2
  102. package/src/cli/v2.ts +1 -1
  103. package/src/client/machine-listener.ts +1 -3
  104. package/src/client/runtime.ts +1 -0
  105. package/src/clients/client-path.ts +51 -0
  106. package/src/clients/config-export/contracts.ts +2 -1
  107. package/src/clients/config-export.ts +18 -44
  108. package/src/clients/gentle-shell-path.ts +87 -0
  109. package/src/codex/account-auto-switch.ts +2 -0
  110. package/src/codex/account-identity.ts +29 -0
  111. package/src/codex/account-label.ts +1 -1
  112. package/src/codex/account-lifecycle.ts +21 -5
  113. package/src/codex/account-preservation.ts +28 -0
  114. package/src/codex/account-use-remaining.ts +23 -0
  115. package/src/codex/app-server-processes.ts +97 -7
  116. package/src/codex/app-server-restart-service.ts +55 -4
  117. package/src/codex/auth-api/account-list.ts +4 -0
  118. package/src/codex/auth-api/account-pause-group.ts +6 -50
  119. package/src/codex/auth-api/account-preservation.ts +57 -0
  120. package/src/codex/auth-api/routes.ts +8 -0
  121. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  122. package/src/codex/auth-api.ts +0 -1
  123. package/src/codex/auth-context.ts +2 -2
  124. package/src/codex/catalog/auto-review.ts +20 -3
  125. package/src/codex/catalog/build-entries.ts +9 -1
  126. package/src/codex/catalog/combo-member.ts +14 -5
  127. package/src/codex/catalog/configured-tiers.ts +1 -1
  128. package/src/codex/catalog/gather-capture.ts +2 -0
  129. package/src/codex/catalog/metadata.ts +11 -9
  130. package/src/codex/catalog/model-hints.ts +21 -16
  131. package/src/codex/catalog/model-visibility.ts +3 -1
  132. package/src/codex/catalog/parsing.ts +30 -86
  133. package/src/codex/catalog/provider-models.ts +36 -34
  134. package/src/codex/catalog/routed-gather.ts +14 -13
  135. package/src/codex/cli-install-provenance.ts +105 -17
  136. package/src/codex/cli-update-plan.ts +545 -0
  137. package/src/codex/config-write-lock.ts +23 -2
  138. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  139. package/src/codex/desktop-app-restart.ts +21 -0
  140. package/src/codex/desktop-compatibility/installed-build.ts +24 -6
  141. package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
  142. package/src/codex/desktop-compatibility/runtime.ts +12 -3
  143. package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
  144. package/src/codex/features.ts +85 -45
  145. package/src/codex/inject/history-failure.ts +27 -0
  146. package/src/codex/inject/restore.ts +24 -2
  147. package/src/codex/inject/routing-classify.ts +4 -3
  148. package/src/codex/inject.ts +18 -27
  149. package/src/codex/main-account-hard-lock.ts +30 -1
  150. package/src/codex/native-profile-startup.ts +1 -1
  151. package/src/codex/ocx-compaction-history.ts +12 -10
  152. package/src/codex/paths.ts +86 -1
  153. package/src/codex/provider-routing.ts +174 -0
  154. package/src/codex/refresh.ts +2 -2
  155. package/src/codex/routing/health-store.ts +1 -1
  156. package/src/codex/routing/selection.ts +4 -3
  157. package/src/codex/routing/thread-affinity.ts +1 -1
  158. package/src/codex/signed-in-identity.ts +43 -0
  159. package/src/codex/subagent-model-fallback.ts +1 -1
  160. package/src/codex/sync.ts +6 -1
  161. package/src/codex/user-identity.ts +16 -5
  162. package/src/codex/websocket-activity.ts +161 -0
  163. package/src/codex/websocket-registry.ts +25 -1
  164. package/src/codex/windows-local-appdata.ts +84 -0
  165. package/src/combos/failover.ts +6 -1
  166. package/src/combos/index.ts +1 -0
  167. package/src/combos/resolve.ts +46 -10
  168. package/src/combos/types.ts +15 -0
  169. package/src/config/diagnostics.ts +33 -6
  170. package/src/config/live-reconcile.ts +38 -2
  171. package/src/config/load-degrade.ts +17 -0
  172. package/src/config/opencode-wire-upgrade.ts +54 -0
  173. package/src/config/opencode-zen-upgrade.ts +53 -0
  174. package/src/config/owner-registry.ts +22 -17
  175. package/src/config/provider-validation.ts +45 -2
  176. package/src/config/schema/config-schema.ts +22 -0
  177. package/src/config/schema/leaf-validators.ts +38 -9
  178. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  179. package/src/config/schema/native-reasoning-retention.ts +26 -0
  180. package/src/config.ts +2 -0
  181. package/src/generated/compatibility-version.json +657 -297
  182. package/src/grok/sync.ts +1 -1
  183. package/src/images/loop.ts +116 -27
  184. package/src/integrations/catalog-refresh.ts +1 -1
  185. package/src/integrations/mutation-plan.ts +1 -0
  186. package/src/integrations/registry.ts +8 -0
  187. package/src/lib/codex-restart-contract.ts +15 -0
  188. package/src/lib/desktop-upstream-tunnel.ts +18 -53
  189. package/src/lib/empty-tool-catalog.ts +62 -0
  190. package/src/lib/errors.ts +19 -3
  191. package/src/lib/gcp-adc.ts +96 -9
  192. package/src/lib/gcp-credential-input.ts +12 -0
  193. package/src/lib/lab-live-route-production.ts +5 -0
  194. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  195. package/src/lib/redact.ts +2 -2
  196. package/src/lib/request-failure-model.ts +2 -0
  197. package/src/oauth/account-quota-rank.ts +69 -2
  198. package/src/oauth/anthropic-account-refusal.ts +20 -2
  199. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  200. package/src/oauth/anthropic-routing.ts +1 -21
  201. package/src/oauth/anthropic.ts +38 -7
  202. package/src/oauth/callback-server.ts +27 -1
  203. package/src/oauth/chatgpt-device.ts +1 -1
  204. package/src/oauth/chatgpt-shared.ts +83 -0
  205. package/src/oauth/chatgpt.ts +8 -72
  206. package/src/oauth/dsh.ts +203 -0
  207. package/src/oauth/generic-account-failover.ts +40 -11
  208. package/src/oauth/generic-session-affinity.ts +125 -0
  209. package/src/oauth/index.ts +184 -109
  210. package/src/oauth/local-token-detect.ts +21 -1
  211. package/src/oauth/login-cli-errors.ts +44 -0
  212. package/src/oauth/login-cli.ts +69 -7
  213. package/src/oauth/login-flow-state.ts +371 -23
  214. package/src/oauth/meta-muse-errors.ts +41 -0
  215. package/src/oauth/meta-muse-keychain.ts +52 -0
  216. package/src/oauth/meta-muse.ts +51 -84
  217. package/src/oauth/model-discovery-auth.ts +3 -0
  218. package/src/oauth/pool-settings-capability.ts +4 -2
  219. package/src/oauth/refresh-flight-retirement.ts +48 -0
  220. package/src/oauth/session-affinity-key.ts +23 -0
  221. package/src/oauth/store-anthropic-instance.ts +9 -2
  222. package/src/oauth/store.ts +12 -5
  223. package/src/protocols/encoders/adapter-events.ts +6 -5
  224. package/src/protocols/encoders/messages.ts +9 -6
  225. package/src/protocols/provider-summary.ts +2 -1
  226. package/src/providers/api-key-cooldowns.ts +49 -0
  227. package/src/providers/api-key-resolve.ts +11 -6
  228. package/src/providers/api-key-response-ownership.ts +57 -0
  229. package/src/providers/api-key-selection.ts +31 -6
  230. package/src/providers/api-keys.ts +214 -3
  231. package/src/providers/claude-cli-usage.ts +96 -0
  232. package/src/providers/dsh-account-destination.ts +31 -0
  233. package/src/providers/dsh-account-models.ts +41 -0
  234. package/src/providers/gcp-credential-cleanup.ts +59 -0
  235. package/src/providers/key-failover.ts +54 -50
  236. package/src/providers/key-store.ts +7 -2
  237. package/src/providers/model-rename-fields.ts +3 -0
  238. package/src/providers/model-rename-migration.ts +2 -2
  239. package/src/providers/openai-tiers-destination.ts +11 -2
  240. package/src/providers/openai-tiers.ts +5 -12
  241. package/src/providers/opencode-go-transport.ts +3 -1
  242. package/src/providers/opencode-zen-rate-limit.ts +7 -2
  243. package/src/providers/quota/account-cache.ts +6 -5
  244. package/src/providers/quota/account-row.ts +34 -0
  245. package/src/providers/quota/antigravity.ts +28 -7
  246. package/src/providers/quota/dsh-account.ts +70 -0
  247. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  248. package/src/providers/quota-types.ts +2 -0
  249. package/src/providers/quota.ts +14 -20
  250. package/src/providers/registry/entries-core.ts +37 -8
  251. package/src/providers/registry/types.ts +1 -1
  252. package/src/providers/request-pacing.ts +77 -19
  253. package/src/providers/routed-config.ts +339 -0
  254. package/src/providers/service-tier.ts +2 -1
  255. package/src/quota/window-mapping.ts +5 -2
  256. package/src/responses/anthropic-progress.ts +34 -0
  257. package/src/responses/assistant-replay.ts +35 -0
  258. package/src/responses/compaction-history-framing.ts +65 -0
  259. package/src/responses/compaction.ts +6 -1
  260. package/src/responses/incomplete-reason.ts +31 -0
  261. package/src/responses/parser.ts +50 -5
  262. package/src/responses/reasoning-envelope.ts +4 -0
  263. package/src/responses/reasoning-replay-cache.ts +74 -6
  264. package/src/responses/reasoning-retention.ts +173 -0
  265. package/src/responses/retained-compaction.ts +46 -0
  266. package/src/responses/schema.ts +23 -0
  267. package/src/router.ts +8 -321
  268. package/src/routing/analytics.ts +7 -15
  269. package/src/routing/health.ts +37 -22
  270. package/src/routing/history/indexer.ts +6 -5
  271. package/src/routing/history/ingest.ts +6 -2
  272. package/src/routing/profile.ts +1 -1
  273. package/src/server/admission-model-scope.ts +17 -0
  274. package/src/server/auth-cors.ts +11 -0
  275. package/src/server/chat-completions.ts +7 -1
  276. package/src/server/chat-native-reasoning.ts +123 -0
  277. package/src/server/chat-native-sse.ts +110 -7
  278. package/src/server/chat-native.ts +76 -17
  279. package/src/server/claude-messages.ts +56 -414
  280. package/src/server/effort-policy.ts +1 -1
  281. package/src/server/images.ts +15 -2
  282. package/src/server/index/live-sideband.ts +3 -2
  283. package/src/server/index/model-alias-id.ts +36 -0
  284. package/src/server/index/optional-listeners.ts +2 -0
  285. package/src/server/index/serve-options.ts +20 -6
  286. package/src/server/index/startup-warnings.ts +2 -0
  287. package/src/server/index/websocket-handler.ts +18 -4
  288. package/src/server/index.ts +8 -8
  289. package/src/server/inference/advisor-diagnostics.ts +31 -0
  290. package/src/server/lifecycle.ts +13 -1
  291. package/src/server/management/advisor-routes.ts +15 -4
  292. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  293. package/src/server/management/combo-routes.ts +176 -50
  294. package/src/server/management/companion-routes.ts +1 -1
  295. package/src/server/management/config-routes.ts +75 -4
  296. package/src/server/management/cursor-integration-routes.ts +1 -1
  297. package/src/server/management/model-routes.ts +11 -6
  298. package/src/server/management/model-rows.ts +14 -0
  299. package/src/server/management/native-integration-routes.ts +2 -7
  300. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  301. package/src/server/management/oauth-account-routes.ts +87 -14
  302. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  303. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  304. package/src/server/management/provider-overwrite-carry.ts +7 -0
  305. package/src/server/management/provider-routes.ts +25 -30
  306. package/src/server/management/route-registry.ts +10 -1
  307. package/src/server/management-api.ts +3 -1
  308. package/src/server/messages-native-tool-guard.ts +185 -0
  309. package/src/server/messages-native.ts +117 -24
  310. package/src/server/messages-shared.ts +431 -0
  311. package/src/server/relay.ts +1 -1
  312. package/src/server/request-log-terminal-status.ts +1 -1
  313. package/src/server/request-log.ts +6 -1
  314. package/src/server/responses/account-change-state.ts +8 -2
  315. package/src/server/responses/adapter-continuation.ts +47 -19
  316. package/src/server/responses/adapter-delivery.ts +72 -12
  317. package/src/server/responses/adapter-dispatch.ts +46 -3
  318. package/src/server/responses/advisor-retention.ts +117 -0
  319. package/src/server/responses/advisor-slot.ts +73 -41
  320. package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
  321. package/src/server/responses/codex-ws-wire.ts +15 -8
  322. package/src/server/responses/combo-stream-preflight.ts +3 -2
  323. package/src/server/responses/compact.ts +303 -125
  324. package/src/server/responses/compaction-recovery.ts +20 -10
  325. package/src/server/responses/compaction-retention.ts +61 -0
  326. package/src/server/responses/context-overflow.ts +11 -6
  327. package/src/server/responses/core-auth.ts +6 -0
  328. package/src/server/responses/core-codex-account.ts +11 -1
  329. package/src/server/responses/core-combo-failure.ts +2 -0
  330. package/src/server/responses/core-combo.ts +121 -36
  331. package/src/server/responses/core-errors.ts +3 -3
  332. package/src/server/responses/core-lifetime.ts +8 -4
  333. package/src/server/responses/core-opaque-recovery.ts +35 -0
  334. package/src/server/responses/core-options.ts +7 -0
  335. package/src/server/responses/core-pipeline.ts +141 -0
  336. package/src/server/responses/core-replay.ts +23 -3
  337. package/src/server/responses/core.ts +2 -115
  338. package/src/server/responses/fetch-helpers.ts +73 -19
  339. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  340. package/src/server/responses/native-response-control.ts +4 -2
  341. package/src/server/responses/passthrough-delivery.ts +3 -1
  342. package/src/server/responses/passthrough-dispatch.ts +15 -13
  343. package/src/server/responses/passthrough-execution.ts +3 -2
  344. package/src/server/responses/request-prepare.ts +77 -12
  345. package/src/server/responses/request-sidecar-auth.ts +2 -2
  346. package/src/server/responses/request-transport.ts +104 -27
  347. package/src/server/responses/response-effects.ts +2 -0
  348. package/src/server/responses/response-tool-ownership.ts +21 -0
  349. package/src/server/responses/run-turn-execution.ts +96 -30
  350. package/src/server/responses/sidecar-execution.ts +63 -9
  351. package/src/server/responses/sidecar-send-budget.ts +2 -2
  352. package/src/server/responses/terminal-guard.ts +2 -16
  353. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  354. package/src/server/ws-bridge.ts +2 -0
  355. package/src/service/auth-preflight.ts +71 -0
  356. package/src/service/cli.ts +4 -2
  357. package/src/service/definition.ts +97 -0
  358. package/src/service/guards.ts +1 -61
  359. package/src/service/health-sentinel-linux.ts +112 -0
  360. package/src/service/health-sentinel-main.ts +76 -57
  361. package/src/service/health.ts +5 -89
  362. package/src/service/launchd.ts +27 -7
  363. package/src/service/logging.ts +72 -9
  364. package/src/service/repair.ts +2 -1
  365. package/src/service/state.ts +7 -8
  366. package/src/service/systemd.ts +1 -1
  367. package/src/service/windows-guarded-manager.ts +2 -1
  368. package/src/service/windows-ops.ts +1 -1
  369. package/src/service/windows-scheduler.ts +2 -7
  370. package/src/service/windows-taskxml.ts +2 -2
  371. package/src/service/windows-wscript.ts +14 -0
  372. package/src/service.ts +4 -2
  373. package/src/sidecar/auth.ts +2 -0
  374. package/src/types/config.ts +64 -3
  375. package/src/types/opencode-go.ts +61 -0
  376. package/src/types/opencode-zen.ts +35 -0
  377. package/src/types/provider.ts +32 -6
  378. package/src/types/request.ts +31 -2
  379. package/src/types/wire.ts +31 -37
  380. package/src/types.ts +2 -0
  381. package/src/update/npm-invocation.mjs +12 -2
  382. package/src/usage/cost.ts +48 -12
  383. package/src/usage/expected-prices.ts +3 -0
  384. package/src/usage/ledger-retention.ts +2 -1
  385. package/src/usage/ledger-scanner.ts +6 -5
  386. package/src/usage/log.ts +17 -8
  387. package/src/usage/summary.ts +30 -11
  388. package/src/usage/telemetry-contract.ts +1 -0
  389. package/src/usage/user-cost-overlays.ts +14 -19
  390. package/src/usage/user-cost-validation.ts +38 -0
  391. package/src/vision/eligibility.ts +13 -0
  392. package/src/web-search/anthropic-history.ts +59 -0
  393. package/src/web-search/loop.ts +236 -73
  394. package/src/web-search/progress-stream.ts +15 -2
  395. package/src/web-search/run-turn-loop.ts +9 -1
  396. package/gui/dist/assets/App-BjlttVLy.js +0 -58
  397. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  398. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  399. package/gui/dist/assets/index-BMssP4k3.js +0 -94
  400. package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
@@ -5,7 +5,7 @@ export interface UsageModelView {
5
5
  query: string;
6
6
  matchedModelCount: number;
7
7
  returnedModelCount: number;
8
- limit: 100;
8
+ limit: number;
9
9
  truncated: boolean;
10
10
  }
11
11
 
@@ -36,7 +36,7 @@ export function takeUsageSearchOption(args: string[]): string | undefined {
36
36
  return value;
37
37
  }
38
38
 
39
- export function selectUsageModelView<T extends { models?: unknown }>(report: T, rawQuery: string): Omit<T, "models"> & { models: SearchableModel[]; modelView: UsageModelView } {
39
+ export function selectUsageModelView<T extends { models?: unknown }>(report: T, rawQuery: string, limit = 100): Omit<T, "models"> & { models: SearchableModel[]; modelView: UsageModelView } {
40
40
  if (!report || !Array.isArray(report.models)) throw new Error("Invalid usage model rows; retry after checking the runtime.");
41
41
  const models = report.models.map((raw: unknown): SearchableModel => {
42
42
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Invalid usage model row.");
@@ -53,6 +53,6 @@ export function selectUsageModelView<T extends { models?: unknown }>(report: T,
53
53
  const matching = models.toSorted((a, b) => b.totalTokens - a.totalTokens).filter(row => !query
54
54
  || row.model.toLowerCase().includes(query) || row.provider.toLowerCase().includes(query)
55
55
  || (row.resolvedModel ?? "").toLowerCase().includes(query));
56
- const selected = matching.slice(0, 100);
57
- return { ...report, models: selected, modelView: { query, matchedModelCount: matching.length, returnedModelCount: selected.length, limit: 100, truncated: matching.length > selected.length } };
56
+ const selected = matching.slice(0, limit);
57
+ return { ...report, models: selected, modelView: { query, matchedModelCount: matching.length, returnedModelCount: selected.length, limit, truncated: matching.length > selected.length } };
58
58
  }
@@ -113,7 +113,7 @@ function describeScope(data: UsageReportInput): string {
113
113
  return terminalText(parts.join(", "));
114
114
  }
115
115
 
116
- export function formatUsageReport(data: UsageReportInput, options?: { modelView?: UsageModelView }): string[] {
116
+ export function formatUsageReport(data: UsageReportInput, options?: { modelView?: UsageModelView; modelRowLimit?: number }): string[] {
117
117
  const summary = data.summary ?? {};
118
118
  const lines: string[] = [describeScope(data), ""];
119
119
  if (data.source === "hub" && data.scope === "client") {
@@ -192,7 +192,7 @@ export function formatUsageReport(data: UsageReportInput, options?: { modelView?
192
192
  const models = view ? (data.models ?? []) : (data.models ?? []).filter(row => row.requests > 0);
193
193
  if (models.length > 0) {
194
194
  lines.push("");
195
- const shown = models.slice(0, view?.limit ?? MAX_MODEL_ROWS);
195
+ const shown = models.slice(0, view?.limit ?? options?.modelRowLimit ?? MAX_MODEL_ROWS);
196
196
  lines.push(...table(
197
197
  ["MODEL", "PROVIDER", "REQUESTS", "TOKENS", "EST. COST"],
198
198
  shown.map(row => [row.model ?? "-", row.provider, count(row.requests), count(row.totalTokens), usd(row.estimatedCostUsd)]),
package/src/cli/v2.ts CHANGED
@@ -148,7 +148,7 @@ function printLocalStatus(deps: V2CliDeps): number {
148
148
  const modeHint = getMultiAgentModeHintText();
149
149
  log.log(`multi_agent_mode_hint_text: ${modeHint === null ? "(unset — effort-derived policy: ultra=proactive, else explicit)" : JSON.stringify(modeHint)}`);
150
150
  if (isEnabled() && hasMaxThreads()) {
151
- log.log("WARNING: [agents] max_threads is set — codex refuses to start while multi_agent_v2 is enabled. Remove it from config.toml (concurrency lives in features.multi_agent_v2.max_concurrent_threads_per_session).");
151
+ log.log("WARNING: [agents] max_concurrent_threads_per_session / max_threads is set — codex refuses to start while multi_agent_v2 is enabled. Remove the agents limit from config.toml (concurrency lives in features.multi_agent_v2.max_concurrent_threads_per_session).");
152
152
  }
153
153
  return 0;
154
154
  }
@@ -107,9 +107,7 @@ export function startMachineListener(
107
107
  const machineApiDeps: MachineApiDeps = {
108
108
  sync: deps.machineApi?.sync ?? syncConnectedClient,
109
109
  disconnect: deps.machineApi?.disconnect ?? disconnectClient,
110
- scheduleStandaloneRecycle: deps.machineApi?.scheduleStandaloneRecycle ?? (tokenFingerprint => {
111
- void import("./runtime").then(module => module.scheduleStandaloneRecycle(tokenFingerprint));
112
- }),
110
+ scheduleStandaloneRecycle: deps.machineApi?.scheduleStandaloneRecycle ?? (() => {}),
113
111
  hubReachability: deps.machineApi?.hubReachability ?? (() => hubReachability),
114
112
  setHubReachability: deps.machineApi?.setHubReachability ?? (value => { hubReachability = value; }),
115
113
  ...(linkMode ? { linkHomeName: () => linkHomeName(connection, sidecarOrNull) } : {}),
@@ -250,6 +250,7 @@ export async function bindClientListener(
250
250
  try {
251
251
  const server = startListener(port, {
252
252
  state: request.state,
253
+ machineApi: { scheduleStandaloneRecycle },
253
254
  ...(request.linkStatus ? { linkStatus: request.linkStatus } : {}),
254
255
  ...(request.linkKeySource ? { linkKeySource: request.linkKeySource } : {}),
255
256
  ...(request.linkTunnel ? { linkTunnel: request.linkTunnel } : {}),
@@ -0,0 +1,51 @@
1
+ import { homedir } from "node:os";
2
+ import { isAbsolute, join } from "node:path";
3
+ import type { OpencodeLaunchEnv } from "./config-export/contracts";
4
+
5
+ // Leaf owner of client path validation, shared by config-export and gentle-shell-path
6
+ // without an import cycle between them.
7
+
8
+ /**
9
+ * Expand `~` against the effective home; REFUSE anything still relative.
10
+ *
11
+ * A first attempt at this called `resolve()` and claimed the path would "mean
12
+ * the same thing next time". It does not: `resolve()` only anchors the current
13
+ * invocation, so applying from one directory and disabling from another still
14
+ * resolved two different files — the second reported "not applied" and left
15
+ * the managed block behind with nothing claiming it.
16
+ *
17
+ * There is no honest way to recover the other process's cwd, so a relative
18
+ * selector is refused at the boundary instead of being silently anchored to
19
+ * whichever directory we happened to start in. A `~` path IS stable, because
20
+ * it anchors to the effective home rather than the cwd.
21
+ */
22
+ export class ClientPathError extends Error {}
23
+
24
+ export function absoluteClientPath(raw: string, home: string, variable: string): string {
25
+ const trimmed = raw.trim();
26
+ if (trimmed === "~") return home;
27
+ if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) return join(home, trimmed.slice(2));
28
+ if (!isAbsolute(trimmed)) {
29
+ throw new ClientPathError(
30
+ `${variable} must be an absolute path or start with ~; "${trimmed}" depends on the working directory, `
31
+ + "so opencodex and the client would disagree about which file it names.",
32
+ );
33
+ }
34
+ return trimmed;
35
+ }
36
+
37
+ /**
38
+ * Pi resolves its agent directory from `PI_CODING_AGENT_DIR`, falling back to
39
+ * `~/.pi/agent`. `ompAgentDir` below already reads that variable — OMP is a Pi
40
+ * derivative — so Pi's own resolver honoring it is what makes the two agree
41
+ * rather than a new claim about Pi's contract.
42
+ *
43
+ * A relative override is refused for the same reason MCode's and ZCode's are: a
44
+ * background proxy and a foreground client can have different working
45
+ * directories, and would otherwise disagree about which file is named.
46
+ */
47
+ export function piAgentDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
48
+ const override = env.PI_CODING_AGENT_DIR?.trim();
49
+ if (override) return absoluteClientPath(override, home, "PI_CODING_AGENT_DIR");
50
+ return join(home, ".pi", "agent");
51
+ }
@@ -220,7 +220,8 @@ export type ExportClientId =
220
220
  | "cline"
221
221
  | "commandcode"
222
222
  | "kilo"
223
- | "droid";
223
+ | "droid"
224
+ | "gentle-shell";
224
225
 
225
226
  export interface ExportClientSpec {
226
227
  id: ExportClientId;
@@ -20,6 +20,9 @@
20
20
  * targeting it is the caller's explicit act.
21
21
  */
22
22
  import { homedir } from "node:os";
23
+ import { gentleShellPaths } from "./gentle-shell-path";
24
+ import { absoluteClientPath, ClientPathError, piAgentDir } from "./client-path";
25
+ export { ClientPathError, piAgentDir } from "./client-path";
23
26
  import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
24
27
  import { basename, dirname, isAbsolute, join, resolve, win32 } from "node:path";
25
28
  import { shouldInjectApiAuthHeader, standaloneCodexRoutingTarget } from "../codex/inject";
@@ -213,21 +216,6 @@ function ompProfileName(env: OpencodeLaunchEnv): string | undefined {
213
216
  return profile;
214
217
  }
215
218
 
216
- /**
217
- * Pi resolves its agent directory from `PI_CODING_AGENT_DIR`, falling back to
218
- * `~/.pi/agent`. `ompAgentDir` below already reads that variable — OMP is a Pi
219
- * derivative — so Pi's own resolver honoring it is what makes the two agree
220
- * rather than a new claim about Pi's contract.
221
- *
222
- * A relative override is refused for the same reason MCode's and ZCode's are: a
223
- * background proxy and a foreground client can have different working
224
- * directories, and would otherwise disagree about which file is named.
225
- */
226
- export function piAgentDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
227
- const override = env.PI_CODING_AGENT_DIR?.trim();
228
- if (override) return absoluteClientPath(override, home, "PI_CODING_AGENT_DIR");
229
- return join(home, ".pi", "agent");
230
- }
231
219
 
232
220
  /** Pi's canonical custom-provider catalog. */
233
221
  export function piConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
@@ -289,35 +277,6 @@ export function hermesConfigPath(env: OpencodeLaunchEnv = process.env, home: str
289
277
  return join(hermesHomeDir(env, home), "config.yaml");
290
278
  }
291
279
 
292
- /**
293
- * Expand `~` against the effective home; REFUSE anything still relative.
294
- *
295
- * A first attempt at this called `resolve()` and claimed the path would "mean
296
- * the same thing next time". It does not: `resolve()` only anchors the current
297
- * invocation, so applying from one directory and disabling from another still
298
- * resolved two different files — the second reported "not applied" and left
299
- * the managed block behind with nothing claiming it.
300
- *
301
- * There is no honest way to recover the other process's cwd, so a relative
302
- * selector is refused at the boundary instead of being silently anchored to
303
- * whichever directory we happened to start in. A `~` path IS stable, because
304
- * it anchors to the effective home rather than the cwd.
305
- */
306
- export class ClientPathError extends Error {}
307
-
308
- function absoluteClientPath(raw: string, home: string, variable: string): string {
309
- const trimmed = raw.trim();
310
- if (trimmed === "~") return home;
311
- if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) return join(home, trimmed.slice(2));
312
- if (!isAbsolute(trimmed)) {
313
- throw new ClientPathError(
314
- `${variable} must be an absolute path or start with ~; "${trimmed}" depends on the working directory, `
315
- + "so opencodex and the client would disagree about which file it names.",
316
- );
317
- }
318
- return trimmed;
319
- }
320
-
321
280
  /**
322
281
  * OpenClaw's EFFECTIVE home: `OPENCLAW_HOME` outranks the OS home.
323
282
  *
@@ -1711,6 +1670,21 @@ export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
1711
1670
  buildContribution: buildDroidContribution,
1712
1671
  loopbackOnly: true,
1713
1672
  },
1673
+ "gentle-shell": {
1674
+ id: "gentle-shell",
1675
+ filename: "gentle-shell-models.json",
1676
+ destination: env => gentleShellPaths(env).configPath,
1677
+ apiKeyEnv: "",
1678
+ exportHint: "Gentle Shell uses its persistent selected home; restart the shell to load the catalog. Link/shared homes use the Pi integration.",
1679
+ build: ctx => buildPiClientConfig(ctx, PI_EXPORT_OPTIONS),
1680
+ format: "json",
1681
+ summarize: summarizePi,
1682
+ buildContribution: ctx => {
1683
+ const doc = buildPiClientConfig(ctx, PI_EXPORT_OPTIONS);
1684
+ return singleFragment("gentle-shell", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
1685
+ },
1686
+ loopbackOnly: true,
1687
+ },
1714
1688
  };
1715
1689
 
1716
1690
  export const EXPORT_CLIENT_IDS: readonly ExportClientId[] = Object.keys(EXPORT_CLIENTS) as ExportClientId[];
@@ -0,0 +1,87 @@
1
+ import { realpathSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
4
+ import { readBoundedRegularFileSync } from "../lib/bounded-file-read";
5
+ import { ClientPathError, piAgentDir } from "./client-path";
6
+
7
+ interface BootstrapIO {
8
+ readText(path: string): { kind: "text"; text: string } | { kind: "missing" } | { kind: "failed" };
9
+ }
10
+
11
+ function absolute(raw: string, label: string): string {
12
+ // Unlike Pi's resolver, the Shell launcher does not expand a literal '~'.
13
+ if (!isAbsolute(raw)) throw new ClientPathError(`${label} must be an absolute path; the proxy cannot infer the launcher's working directory.`);
14
+ return resolve(raw);
15
+ }
16
+
17
+ function readBootstrap(path: string, io?: BootstrapIO): string | undefined {
18
+ try {
19
+ if (!io) return readBoundedRegularFileSync(path, 64 * 1024).toString("utf8");
20
+ const read = io.readText(path);
21
+ if (read.kind === "missing") return undefined;
22
+ if (read.kind === "text" && Buffer.byteLength(read.text, "utf8") <= 64 * 1024) return read.text;
23
+ } catch (error) {
24
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
25
+ }
26
+ throw new ClientPathError(`Cannot read Gentle Shell launcher config at ${path}; no catalog was selected.`);
27
+ }
28
+
29
+ /** Resolve existing ancestors too, so a not-yet-created catalog cannot hide a directory alias. */
30
+ function canonical(path: string): string {
31
+ const missing: string[] = [];
32
+ let current = resolve(path);
33
+ for (;;) {
34
+ try {
35
+ const value = join(realpathSync.native(current), ...missing);
36
+ return process.platform === "win32" ? value.toLowerCase() : value;
37
+ } catch (error) {
38
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT" || dirname(current) === current) {
39
+ throw new ClientPathError(`Cannot verify Gentle Shell destination ${path}.`);
40
+ }
41
+ missing.unshift(basename(current));
42
+ current = dirname(current);
43
+ }
44
+ }
45
+ }
46
+
47
+ function sameFile(left: string, right: string): boolean {
48
+ if (canonical(left) === canonical(right)) return true;
49
+ // Hard-linked catalogs can have different canonical names but the same identity.
50
+ try {
51
+ const a = statSync(left, { bigint: true });
52
+ const b = statSync(right, { bigint: true });
53
+ return a.ino !== 0n && a.dev === b.dev && a.ino === b.ino;
54
+ } catch (error) {
55
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
56
+ throw new ClientPathError("Cannot verify Gentle Shell and Pi catalog identities.");
57
+ }
58
+ }
59
+
60
+ /** Persistent selectors only: a proxy cannot observe one-off `gentle-shell --home` flags. */
61
+ export function gentleShellPaths(
62
+ env: NodeJS.ProcessEnv = process.env,
63
+ home: string = homedir(),
64
+ io?: BootstrapIO,
65
+ ): { configPath: string; detectDir: string } {
66
+ const config = env.GENTLE_SHELL_CONFIG
67
+ ? absolute(env.GENTLE_SHELL_CONFIG, "GENTLE_SHELL_CONFIG")
68
+ : join(home, ".gentle-shell", "config.json");
69
+ const text = readBootstrap(config, io);
70
+ let selector: unknown;
71
+ try {
72
+ const parsed: unknown = text === undefined ? undefined : JSON.parse(text);
73
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) selector = (parsed as Record<string, unknown>).home;
74
+ } catch { /* The launcher also falls back to isolated for malformed JSON. */ }
75
+ const shared = "Gentle Shell uses a Pi destination; use the Pi integration for link/shared homes.";
76
+ if (selector === "link") throw new ClientPathError(shared);
77
+ const dir = typeof selector === "string" && selector.length > 0 && selector !== "isolated"
78
+ ? absolute(selector, "Gentle Shell saved home")
79
+ : env.GENTLE_SHELL_HOME
80
+ ? absolute(env.GENTLE_SHELL_HOME, "GENTLE_SHELL_HOME")
81
+ : join(home, ".gentle-shell", "agent");
82
+ const configPath = join(dir, "models.json");
83
+ const piHomes = [join(home, ".pi", "agent"), piAgentDir(env, home)];
84
+ if (env.GENTLE_SHELL_USER_PI_HOME) piHomes.push(absolute(env.GENTLE_SHELL_USER_PI_HOME, "GENTLE_SHELL_USER_PI_HOME"));
85
+ if (piHomes.some(pi => sameFile(configPath, join(pi, "models.json")))) throw new ClientPathError(shared);
86
+ return { configPath, detectDir: dir };
87
+ }
@@ -1,6 +1,7 @@
1
1
  import type { OcxConfig } from "../types";
2
2
  import { deleteConfigObjectChildKey } from "../config/rebase-provenance";
3
3
  import { isValidCodexAccountId, MAIN_CODEX_ACCOUNT_ID } from "./account-id";
4
+ import { codexAccountUsesRemainingQuota } from "./account-use-remaining";
4
5
 
5
6
  export const DEFAULT_CODEX_AUTO_SWITCH_THRESHOLD = 80;
6
7
  export const MIN_CODEX_AUTO_SWITCH_THRESHOLD = 0;
@@ -34,6 +35,7 @@ export function getEffectiveCodexAutoSwitchThreshold(
34
35
  config: OcxConfig,
35
36
  accountId: string,
36
37
  ): number {
38
+ if (codexAccountUsesRemainingQuota(config, accountId)) return 0;
37
39
  const override = getCodexAccountAutoSwitchThresholdOverride(config, accountId);
38
40
  if (override !== null) return override;
39
41
  return config.autoSwitchThreshold ?? DEFAULT_CODEX_AUTO_SWITCH_THRESHOLD;
@@ -0,0 +1,29 @@
1
+ import type { OcxConfig } from "../types";
2
+ import { codexLoginIdentity as identity, codexTokenIdentity, type CodexLoginIdentity } from "./signed-in-identity";
3
+ import { MAIN_CODEX_ACCOUNT_ID, isSelectableCodexPoolAccount } from "./account-id";
4
+ import type { CodexTokenReadResult } from "./auth-collision";
5
+ import { getCodexAccountCredential } from "./account-store";
6
+
7
+ /** Match existing entries from an already-owned main observation, without exposing identity fields. */
8
+ export function linkedCodexAccountIds(config: OcxConfig, selectedId: string, main: CodexTokenReadResult | { status: "api-key-only" }): string[] | undefined {
9
+ // Unknown main identity must not produce a successful but incomplete pause.
10
+ if (main.status === "unreadable" || main.status === "invalid") return undefined;
11
+ const identities = new Map<string, CodexLoginIdentity | undefined>();
12
+ if (main.status === "ok") {
13
+ const selected = codexTokenIdentity(main.tokens);
14
+ if (selected === null) return undefined;
15
+ identities.set(MAIN_CODEX_ACCOUNT_ID, selected);
16
+ }
17
+ for (const account of config.codexAccounts ?? []) {
18
+ if (!isSelectableCodexPoolAccount(account)) continue;
19
+ const credential = getCodexAccountCredential(account.id);
20
+ identities.set(account.id, identity(credential?.chatgptAccountId, account.email));
21
+ }
22
+ const selected = identities.get(selectedId);
23
+ // Missing identity evidence never links unrelated logins, including members of one workspace.
24
+ if (!selected) return [selectedId];
25
+ return [selectedId, ...[...identities].flatMap(([id, candidate]) => (
26
+ id !== selectedId && candidate?.accountId === selected.accountId && candidate.email === selected.email
27
+ ? [id] : []
28
+ ))];
29
+ }
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
2
2
  import type { CodexAccount, OcxConfig } from "../types";
3
3
  import type { CodexAuthContext } from "./auth-context";
4
4
  import type { ProviderApiKeySelection } from "../types/provider";
5
- import { MAIN_CODEX_ACCOUNT_ID } from "./main-account";
5
+ import { MAIN_CODEX_ACCOUNT_ID } from "./account-id";
6
6
 
7
7
  export const CODEX_ACCOUNT_LOG_LABEL_RE = /^p[a-f0-9]{6}$/;
8
8
 
@@ -1,3 +1,4 @@
1
+ import { observeSignedInCodexIdentity } from "./signed-in-identity";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
3
  import {
3
4
  deleteConfigTopLevelKey,
@@ -8,17 +9,20 @@ import {
8
9
  import { captureConfigTopLevelRollback } from "../config/rebase-provenance";
9
10
  import { removeCodexAccountCredential } from "./account-store";
10
11
  import { clearAccountNeedsReauth } from "./account-runtime-state";
11
- import { getMainChatgptAccountId, readCodexTokensResult } from "./auth-collision";
12
+ import { readCodexTokensResult } from "./auth-collision";
12
13
  import { MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account";
13
14
  import { clearAccountQuota } from "./quota";
14
- import { clearCodexUpstreamHealthForAccount, clearThreadAccountMapForAccount } from "./routing";
15
+ import { forgetManualPreference } from "./routing/active-account";
16
+ import { deleteAllHealthForAccount } from "./routing/health-store";
17
+ import { clearThreadAccountMapForAccount } from "./routing/thread-affinity";
15
18
 
16
19
  import { clearCodexPoolRefreshFailure } from "./pool-refresh-backoff";
17
20
  import { invalidateCodexWebSocketsForAccount } from "./websocket-registry";
18
21
  import { clearMainAccountCredentialPresence, clearMainAccountInfoCache, observeMainQuotaCredential, observeMainQuotaIdentity } from "./main-account-cache";
19
- import { extractAccountIdClaims } from "../oauth/chatgpt";
22
+ import { extractAccountId, extractAccountIdClaims } from "../oauth/chatgpt";
20
23
  import { forgetCodexAccountPause } from "./account-pause";
21
24
  import { forgetCodexAccountCreditUse } from "./account-credit-use";
25
+ import { setCodexAccountUseRemainingQuota } from "./account-use-remaining";
22
26
  import { clearCodexAccountPin, forgetCodexAccountPriority } from "./account-priority";
23
27
  import { forgetCodexQuotaAutoRefreshAccount } from "./quota-auto-refresh-state";
24
28
  import { forgetCodexAccountAutoSwitchThreshold } from "./account-auto-switch";
@@ -45,7 +49,11 @@ export function purgeCodexAccountRuntimeState(accountId: string): void {
45
49
  clearAccountNeedsReauth(accountId);
46
50
  clearAccountQuota(accountId);
47
51
  clearThreadAccountMapForAccount(accountId);
48
- clearCodexUpstreamHealthForAccount(accountId);
52
+ // Deliberately not via `./routing`: the account-delete path only needs the health rows and
53
+ // the manual preference gone, and importing the router here closed a cycle through
54
+ // routing/health-store and routing/active-account.
55
+ deleteAllHealthForAccount(accountId);
56
+ forgetManualPreference(accountId);
49
57
  clearCodexPoolRefreshFailure(accountId);
50
58
  if (accountId === MAIN_CODEX_ACCOUNT_ID) {
51
59
  clearMainAccountInfoCache();
@@ -66,7 +74,11 @@ function purgeMainCodexAccountRuntimeState(): void {
66
74
  * cooldown, quota, reauth, and thread affinity do not leak across accounts.
67
75
  */
68
76
  export function reconcileMainCodexAccountRuntimeState(): boolean {
69
- const currentAccountId = getMainChatgptAccountId();
77
+ const read = readCodexTokensResult();
78
+ const currentAccountId = read.status === "ok"
79
+ ? extractAccountId(read.tokens.id_token, read.tokens.access_token) ?? (read.tokens.account_id || null)
80
+ : null;
81
+ if (read.status === "ok") observeSignedInCodexIdentity(read.tokens);
70
82
  // A missing/malformed auth.json is an unknown identity, not a confirmed account switch. Keep the
71
83
  // prior observation and its safety state until a real account id can be read again.
72
84
  if (currentAccountId === null) return false;
@@ -110,6 +122,7 @@ export function initializeMainAccountPolicyBinding(authPath: string): boolean {
110
122
  const previousAccountId = observedMainChatgptAccountId;
111
123
  observedMainChatgptAccountId = accountId;
112
124
  if (previousAccountId !== undefined && previousAccountId !== accountId) purgeMainCodexAccountRuntimeState();
125
+ observeSignedInCodexIdentity(tokens);
113
126
  observeMainQuotaIdentity(accountId);
114
127
  return observeMainQuotaCredential(tokens.access_token, accountId) !== undefined;
115
128
  }
@@ -122,6 +135,7 @@ export function applyConfirmedMainCodexAccountTransition(
122
135
  fromAccountId: string,
123
136
  toAccountId: string,
124
137
  ): boolean {
138
+ observeSignedInCodexIdentity();
125
139
  if (!fromAccountId || !toAccountId || fromAccountId === toAccountId) {
126
140
  if (toAccountId) {
127
141
  observedMainChatgptAccountId = toAccountId;
@@ -137,6 +151,7 @@ export function applyConfirmedMainCodexAccountTransition(
137
151
 
138
152
  export function resetMainCodexAccountIdentityTrackingForTests(): void {
139
153
  observedMainChatgptAccountId = undefined;
154
+ observeSignedInCodexIdentity();
140
155
  clearMainAccountCredentialPresence();
141
156
  }
142
157
 
@@ -183,6 +198,7 @@ export function deleteCodexAccount(runtimeConfig: OcxConfig, accountId: string):
183
198
  forgetCodexAccountCreditUse(runtimeConfig, accountId);
184
199
  forgetCodexAccountPriority(runtimeConfig, accountId);
185
200
  forgetCodexAccountAutoSwitchThreshold(runtimeConfig, accountId);
201
+ setCodexAccountUseRemainingQuota(runtimeConfig, accountId, false);
186
202
  if (runtimeConfig.codexQuotaAutoRefresh?.[accountId]) {
187
203
  const retained = { ...runtimeConfig.codexQuotaAutoRefresh };
188
204
  delete retained[accountId];
@@ -0,0 +1,28 @@
1
+ import type { OcxConfig } from "../types";
2
+ import { MAIN_CODEX_ACCOUNT_ID, isSelectableCodexPoolAccount } from "./account-id";
3
+ import { getCodexAccountCredential } from "./account-store";
4
+ import { codexAccountPriorityLookup } from "./account-priority";
5
+ import { MIN_ACCOUNT_PRIORITY, MAX_ACCOUNT_PRIORITY } from "./pool-rotation";
6
+ import { codexLoginIdentity, getSignedInCodexIdentity, sameCodexLogin } from "./signed-in-identity";
7
+
8
+ /** Resolve current rows afresh: a pool replacement must not inherit the prior login's role. */
9
+ export function signedInCodexAccountIds(config: OcxConfig): ReadonlySet<string> {
10
+ const ids = new Set([MAIN_CODEX_ACCOUNT_ID]);
11
+ const main = getSignedInCodexIdentity();
12
+ if (!main) return ids;
13
+ for (const account of config.codexAccounts ?? []) {
14
+ if (!isSelectableCodexPoolAccount(account)) continue;
15
+ const credential = getCodexAccountCredential(account.id);
16
+ if (sameCodexLogin(main, codexLoginIdentity(credential?.chatgptAccountId, account.email))) ids.add(account.id);
17
+ }
18
+ return ids;
19
+ }
20
+
21
+ /** Two disjoint bands, preserving custom order inside each; never writes the stored priorities. */
22
+ export function codexSelectionPriorityLookup(config: OcxConfig): (id: string) => number {
23
+ const priority = codexAccountPriorityLookup(config);
24
+ if (config.codexPreserveSignedInAccount !== true) return priority;
25
+ const reserved = signedInCodexAccountIds(config);
26
+ const band = MAX_ACCOUNT_PRIORITY - MIN_ACCOUNT_PRIORITY + 1;
27
+ return id => priority(id) - (reserved.has(id) ? band : 0);
28
+ }
@@ -0,0 +1,23 @@
1
+ import type { OcxConfig } from "../types";
2
+ import { deleteConfigTopLevelKey } from "../config/rebase-provenance";
3
+ import { isValidCodexAccountId, MAIN_CODEX_ACCOUNT_ID } from "./account-id";
4
+
5
+ type Policy = Pick<OcxConfig, "codexUseRemainingQuotaAccountIds">;
6
+
7
+ /** Included-quota preference only; never grants permission to spend credits. */
8
+ export function codexAccountUsesRemainingQuota(config: Policy | undefined, accountId: string): boolean {
9
+ return config?.codexUseRemainingQuotaAccountIds?.includes(accountId) ?? false;
10
+ }
11
+
12
+ export function validRemainingQuotaAccountIds(value: unknown): value is string[] {
13
+ return Array.isArray(value) && value.every(id => id === MAIN_CODEX_ACCOUNT_ID || isValidCodexAccountId(id));
14
+ }
15
+
16
+ /** Preserve the underlying thresholds so turning this off restores their current values. */
17
+ export function setCodexAccountUseRemainingQuota(config: OcxConfig, accountId: string, enabled: boolean): void {
18
+ const ids = new Set(config.codexUseRemainingQuotaAccountIds ?? []);
19
+ if (enabled) ids.add(accountId);
20
+ else ids.delete(accountId);
21
+ if (ids.size) config.codexUseRemainingQuotaAccountIds = [...ids];
22
+ else deleteConfigTopLevelKey(config, "codexUseRemainingQuotaAccountIds");
23
+ }