@iislee/opencodex 2.11.0 → 2.35.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 (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -18,10 +18,11 @@ import { probePi } from "./probes/pi";
18
18
  import { probeGrok } from "./probes/grok";
19
19
  import { probeOpencode } from "./probes/opencode";
20
20
  import { probeAgy } from "./probes/agy";
21
+ import { probeCline } from "./probes/cline";
21
22
  import { readCcSwitchCurrentProfiles, type CcSwitchProfile } from "./probes/cc-switch";
22
23
  import { readPaseoProviderCommands, type PaseoProviderCommand } from "./probes/paseo";
23
24
 
24
- export type ClientId = "claude" | "codex" | "pi" | "grok" | "opencode" | "agy";
25
+ export type ClientId = "claude" | "codex" | "pi" | "grok" | "opencode" | "agy" | "cline";
25
26
 
26
27
  export type ClientVerdict = "ocx" | "direct" | "mixed" | "missing" | "unknown";
27
28
 
@@ -70,6 +71,7 @@ export interface EffectiveStatusOpts {
70
71
  grok?: typeof probeGrok;
71
72
  opencode?: typeof probeOpencode;
72
73
  agy?: typeof probeAgy;
74
+ cline?: typeof probeCline;
73
75
  ccSwitch?: typeof readCcSwitchCurrentProfiles;
74
76
  paseo?: typeof readPaseoProviderCommands;
75
77
  };
@@ -82,6 +84,7 @@ const CLIENT_LABELS: Record<ClientId, string> = {
82
84
  grok: "Grok Build",
83
85
  opencode: "OpenCode",
84
86
  agy: "Antigravity (agy)",
87
+ cline: "Cline",
85
88
  };
86
89
 
87
90
  /** Map client id → CC Switch `app_type` column (when present). */
@@ -90,6 +93,7 @@ const CC_SWITCH_APP: Partial<Record<ClientId, string>> = {
90
93
  codex: "codex",
91
94
  grok: "grokbuild",
92
95
  opencode: "opencode",
96
+ cline: "cline",
93
97
  };
94
98
 
95
99
  /** Map client id → Paseo `agents.providers` key. */
@@ -99,6 +103,7 @@ const PASEO_PROVIDER: Partial<Record<ClientId, string>> = {
99
103
  agy: "antigravity-acp",
100
104
  claude: "claude",
101
105
  codex: "codex",
106
+ cline: "cline",
102
107
  };
103
108
 
104
109
  export interface ProxyTarget {
@@ -220,13 +225,14 @@ export async function readClientsEffectiveStatus(
220
225
  const grokFn = opts.probes?.grok ?? probeGrok;
221
226
  const opencodeFn = opts.probes?.opencode ?? probeOpencode;
222
227
  const agyFn = opts.probes?.agy ?? probeAgy;
228
+ const clineFn = opts.probes?.cline ?? probeCline;
223
229
  const ccSwitchFn = opts.probes?.ccSwitch ?? readCcSwitchCurrentProfiles;
224
230
  const paseoFn = opts.probes?.paseo ?? readPaseoProviderCommands;
225
231
 
226
232
  const emptyProfiles: CcSwitchProfile[] = [];
227
233
  const emptyPaseo: PaseoProviderCommand[] = [];
228
234
 
229
- const [ccProfiles, paseoCommands, claude, codex, pi, grok, opencode, agy] = await Promise.all([
235
+ const [ccProfiles, paseoCommands, claude, codex, pi, grok, opencode, agy, cline] = await Promise.all([
230
236
  safeProbeAsync("cc-switch", () => Promise.resolve(ccSwitchFn({ home })), emptyProfiles),
231
237
  safeProbeAsync("paseo", () => Promise.resolve(paseoFn({ home })), emptyPaseo),
232
238
  safeProbeAsync("claude", () => Promise.resolve(claudeFn({ home })), {
@@ -247,6 +253,9 @@ export async function readClientsEffectiveStatus(
247
253
  safeProbeAsync("agy", () => Promise.resolve(agyFn({ home })), {
248
254
  present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"], binary: null,
249
255
  }),
256
+ safeProbeAsync("cline", () => Promise.resolve(clineFn({ home })), {
257
+ present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"], binary: null,
258
+ }),
250
259
  ]);
251
260
 
252
261
  const clients: ClientEffectiveStatus[] = [];
@@ -372,6 +381,25 @@ export async function readClientsEffectiveStatus(
372
381
  });
373
382
  }
374
383
 
384
+ // Cline
385
+ {
386
+ const raw = cline;
387
+ const { verdict, viaOcx } = verdictFromBaseUrl(raw.baseUrl, proxy, { present: raw.present });
388
+ clients.push({
389
+ id: "cline",
390
+ label: CLIENT_LABELS.cline,
391
+ present: raw.present,
392
+ viaOcx,
393
+ verdict: raw.present ? (raw.baseUrl ? verdict : "unknown") : "missing",
394
+ baseUrl: raw.baseUrl,
395
+ model: raw.model,
396
+ configPaths: raw.configPaths,
397
+ launcher: paseoLauncher("cline", paseoCommands) ?? (raw.binary ? "cline" : null),
398
+ switcher: switcherFor("cline", ccProfiles),
399
+ notes: raw.notes,
400
+ });
401
+ }
402
+
375
403
  return {
376
404
  generatedAt: Date.now(),
377
405
  proxy: {
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Cline effective-status probe.
3
+ * Reads providers.json and checks for opencodex / openai-compatible configuration.
4
+ */
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { clineConfigPath, clineHomeDir } from "../config-export";
7
+
8
+ export interface ClineProbeResult {
9
+ present: boolean;
10
+ baseUrl: string | null;
11
+ model: string | null;
12
+ configPaths: string[];
13
+ notes: string[];
14
+ binary: string | null;
15
+ }
16
+
17
+ function resolveBinary(name: string): string | null {
18
+ try {
19
+ const which = (globalThis as { Bun?: { which?: (cmd: string) => string | null } }).Bun?.which;
20
+ if (typeof which === "function") {
21
+ return which(name) ?? null;
22
+ }
23
+ } catch { /* ignore */ }
24
+ return null;
25
+ }
26
+
27
+ export function probeCline(opts: { home: string; env?: NodeJS.ProcessEnv } = { home: "" }): ClineProbeResult {
28
+ const notes: string[] = [];
29
+ const binary = resolveBinary("cline");
30
+ const configPath = clineConfigPath(opts.env, opts.home);
31
+ const homeDir = clineHomeDir(opts.env, opts.home);
32
+ const exists = existsSync(configPath);
33
+ const dirExists = existsSync(homeDir);
34
+ const present = exists || dirExists || Boolean(binary);
35
+
36
+ if (!exists) {
37
+ if (present) {
38
+ notes.push("Cline installation detected, but providers.json is not configured yet.");
39
+ }
40
+ return {
41
+ present,
42
+ baseUrl: null,
43
+ model: null,
44
+ configPaths: existsSync(homeDir) ? [homeDir] : [configPath],
45
+ notes,
46
+ binary,
47
+ };
48
+ }
49
+
50
+ let baseUrl: string | null = null;
51
+ let model: string | null = null;
52
+
53
+ try {
54
+ const raw = readFileSync(configPath, "utf8");
55
+ const parsed = JSON.parse(raw) as {
56
+ lastUsedProvider?: string;
57
+ providers?: Record<string, { settings?: { baseUrl?: string; model?: string; provider?: string } }>;
58
+ };
59
+ const providers = parsed?.providers ?? {};
60
+ const ocxProvider = providers.opencodex ?? providers["openai-compatible"];
61
+ if (ocxProvider?.settings?.baseUrl) {
62
+ baseUrl = ocxProvider.settings.baseUrl;
63
+ model = ocxProvider.settings.model ?? null;
64
+ } else if (parsed?.lastUsedProvider && providers[parsed.lastUsedProvider]?.settings?.baseUrl) {
65
+ const active = providers[parsed.lastUsedProvider]!;
66
+ baseUrl = active.settings?.baseUrl ?? null;
67
+ model = active.settings?.model ?? null;
68
+ }
69
+ } catch (error) {
70
+ notes.push(`Could not read providers.json: ${error instanceof Error ? error.message : String(error)}`);
71
+ }
72
+
73
+ return {
74
+ present: true,
75
+ baseUrl,
76
+ model,
77
+ configPaths: [configPath],
78
+ notes,
79
+ binary,
80
+ };
81
+ }
@@ -1,8 +1,31 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
- import type { CodexAccount } from "../types";
2
+ import type { CodexAccount, OcxConfig } from "../types";
3
+ import type { CodexAuthContext } from "./auth-context";
4
+ import { MAIN_CODEX_ACCOUNT_ID } from "./main-account";
3
5
 
4
6
  export const CODEX_ACCOUNT_LOG_LABEL_RE = /^p[a-f0-9]{6}$/;
5
7
 
8
+ /**
9
+ * Account log labels come in two families (#2699):
10
+ *
11
+ * - `p<hex6>` (plus the literal `main`) — a Codex pool account.
12
+ * - `o<hex6>` — a non-Codex OAuth provider account (xai, cursor, and siblings).
13
+ *
14
+ * Both are sha256-derived digests, never an email and never a raw provider account id. That is
15
+ * a privacy requirement, not a formatting preference: these labels are written to the usage log
16
+ * and served over the management API.
17
+ *
18
+ * hex6 is 16.7M values, so two accounts CAN collide and merge into one reported row. That is a
19
+ * reporting inaccuracy at operator scale, not a correctness or privacy failure, and it is the
20
+ * accepted cost of keeping the existing `p` format byte-compatible.
21
+ */
22
+ export const OAUTH_ACCOUNT_LOG_LABEL_RE = /^o[a-f0-9]{6}$/;
23
+ export const ACCOUNT_LOG_LABEL_RE = /^(?:main|[po][a-f0-9]{6})$/;
24
+
25
+ export function oauthAccountLogLabel(accountId: string, provider = ""): string {
26
+ return `o${createHash("sha256").update(`${provider}\0${accountId}`).digest("hex").slice(0, 6)}`;
27
+ }
28
+
6
29
  export function createCodexAccountLogLabel(existingLabels: Iterable<string | undefined | null> = []): string {
7
30
  const used = new Set([...existingLabels].filter((value): value is string => !!value));
8
31
  for (let i = 0; i < 16; i++) {
@@ -22,6 +45,17 @@ export function codexAccountLogLabel(account: CodexAccount): string {
22
45
  : fallbackCodexAccountLogLabel(account.id);
23
46
  }
24
47
 
48
+ /** Effective durable label for a resolved Codex Pool account. */
49
+ export function codexAuthContextLogLabel(
50
+ authCtx: CodexAuthContext,
51
+ config: Pick<OcxConfig, "codexAccounts">,
52
+ ): "main" | `p${string}` | undefined {
53
+ if (authCtx.kind !== "pool" && authCtx.kind !== "main-pool") return undefined;
54
+ if (authCtx.accountId === MAIN_CODEX_ACCOUNT_ID) return "main";
55
+ const account = (config.codexAccounts ?? []).find(candidate => candidate.id === authCtx.accountId);
56
+ return account ? codexAccountLogLabel(account) as `p${string}` : undefined;
57
+ }
58
+
25
59
  export function withCodexAccountLogLabel(
26
60
  account: Omit<CodexAccount, "logLabel"> & Partial<Pick<CodexAccount, "logLabel">>,
27
61
  existingAccounts: readonly CodexAccount[],
@@ -1,3 +1,10 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import {
3
+ atomicWriteFile,
4
+ getConfigPath,
5
+ saveConfigPreservingClaudeCode,
6
+ withConfigMutationLockSync,
7
+ } from "../config";
1
8
  import { removeCodexAccountCredential } from "./account-store";
2
9
  import { clearAccountNeedsReauth } from "./account-runtime-state";
3
10
  import { getMainChatgptAccountId } from "./auth-collision";
@@ -5,18 +12,43 @@ import { MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account";
5
12
  import { clearAccountQuota } from "./quota";
6
13
  import { clearCodexUpstreamHealthForAccount, clearThreadAccountMapForAccount } from "./routing";
7
14
  import { invalidateCodexWebSocketsForAccount } from "./websocket-registry";
8
- import { clearMainAccountInfoCache } from "./main-account-cache";
15
+ import { clearMainAccountCredentialPresence, clearMainAccountInfoCache } from "./main-account-cache";
9
16
  import { forgetCodexAccountPause } from "./account-pause";
17
+ import { clearCodexAccountPin, forgetCodexAccountPriority } from "./account-priority";
18
+ import { codexAccountNamespaceEntries, codexAccountPickerEnabled } from "./account-namespaces";
10
19
  import type { OcxConfig } from "../types";
11
20
 
12
21
  let observedMainChatgptAccountId: string | undefined;
13
22
 
23
+ export class CodexAccountDeleteCleanupError extends Error {
24
+ constructor() {
25
+ super("Account deletion was saved, but local credential cleanup did not complete. Retry removal.");
26
+ this.name = "CodexAccountDeleteCleanupError";
27
+ }
28
+ }
29
+
30
+ export class CodexAccountDeleteRollbackError extends Error {
31
+ constructor() {
32
+ super("Account deletion failed and the previous config could not be restored. Restart before retrying.");
33
+ this.name = "CodexAccountDeleteRollbackError";
34
+ }
35
+ }
36
+
14
37
  export function purgeCodexAccountRuntimeState(accountId: string): void {
15
38
  clearAccountNeedsReauth(accountId);
16
39
  clearAccountQuota(accountId);
17
40
  clearThreadAccountMapForAccount(accountId);
18
41
  clearCodexUpstreamHealthForAccount(accountId);
19
- if (accountId === MAIN_CODEX_ACCOUNT_ID) clearMainAccountInfoCache();
42
+ if (accountId === MAIN_CODEX_ACCOUNT_ID) {
43
+ clearMainAccountInfoCache();
44
+ clearMainAccountCredentialPresence();
45
+ }
46
+ }
47
+
48
+ function purgeMainCodexAccountRuntimeState(): void {
49
+ purgeCodexAccountRuntimeState(MAIN_CODEX_ACCOUNT_ID);
50
+ setMainAccountPlan(null);
51
+ invalidateCodexWebSocketsForAccount(MAIN_CODEX_ACCOUNT_ID);
20
52
  }
21
53
 
22
54
  /**
@@ -34,22 +66,107 @@ export function reconcileMainCodexAccountRuntimeState(): boolean {
34
66
  observedMainChatgptAccountId = currentAccountId;
35
67
  if (previousAccountId === undefined || previousAccountId === currentAccountId) return false;
36
68
 
37
- purgeCodexAccountRuntimeState(MAIN_CODEX_ACCOUNT_ID);
38
- setMainAccountPlan(null);
39
- invalidateCodexWebSocketsForAccount(MAIN_CODEX_ACCOUNT_ID);
69
+ purgeMainCodexAccountRuntimeState();
70
+ return true;
71
+ }
72
+
73
+ /**
74
+ * Apply a transaction-confirmed physical native-login change without waiting for
75
+ * a later auth.json observation. The caller owns credential commit/rollback.
76
+ */
77
+ export function applyConfirmedMainCodexAccountTransition(
78
+ fromAccountId: string,
79
+ toAccountId: string,
80
+ ): boolean {
81
+ if (!fromAccountId || !toAccountId || fromAccountId === toAccountId) {
82
+ if (toAccountId) observedMainChatgptAccountId = toAccountId;
83
+ return false;
84
+ }
85
+ observedMainChatgptAccountId = toAccountId;
86
+ purgeMainCodexAccountRuntimeState();
40
87
  return true;
41
88
  }
42
89
 
43
90
  export function resetMainCodexAccountIdentityTrackingForTests(): void {
44
91
  observedMainChatgptAccountId = undefined;
92
+ clearMainAccountCredentialPresence();
93
+ }
94
+
95
+ function restoreRuntimeConfig(target: OcxConfig, snapshot: OcxConfig): void {
96
+ for (const key of Object.keys(target) as Array<keyof OcxConfig>) delete target[key];
97
+ Object.assign(target, snapshot);
98
+ }
99
+
100
+ function restorePersistedConfig(configPath: string, previousBytes: string): void {
101
+ try {
102
+ if (readFileSync(configPath, "utf8") === previousBytes) return;
103
+ } catch (error) {
104
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
105
+ }
106
+ atomicWriteFile(configPath, previousBytes);
45
107
  }
46
108
 
47
- export function deleteCodexAccount(runtimeConfig: OcxConfig, accountId: string): void {
48
- removeCodexAccountCredential(accountId);
49
- runtimeConfig.codexAccounts = (runtimeConfig.codexAccounts ?? [])
50
- .filter(account => account.isMain || account.id !== accountId);
51
- forgetCodexAccountPause(runtimeConfig, accountId);
52
- if (runtimeConfig.activeCodexAccountId === accountId) runtimeConfig.activeCodexAccountId = undefined;
53
- purgeCodexAccountRuntimeState(accountId);
54
- invalidateCodexWebSocketsForAccount(accountId);
109
+ /**
110
+ * Delete a stored account while retaining its selector binding.
111
+ *
112
+ * When the runtime config is backed by an existing config.json, commit the config deletion before
113
+ * credentials or runtime state are destroyed. Transient callers intentionally skip durable config
114
+ * persistence because they have no durable account row to protect. The whole sequence shares the
115
+ * config mutation coordinator so a cooperating writer cannot re-add a persisted account between
116
+ * the durable config commit and credential cleanup.
117
+ *
118
+ * Returns true when a picker-visible row disappeared and the catalog must converge.
119
+ */
120
+ export function deleteCodexAccount(runtimeConfig: OcxConfig, accountId: string): boolean {
121
+ let cleanupFailed = false;
122
+ const pickerVisibilityChanged = withConfigMutationLockSync(() => {
123
+ const previousConfig = structuredClone(runtimeConfig);
124
+ const configPath = getConfigPath();
125
+ const hasPersistedConfig = existsSync(configPath);
126
+ const previousPersistedConfig = hasPersistedConfig ? readFileSync(configPath, "utf8") : undefined;
127
+ const hadStoredAccount = (runtimeConfig.codexAccounts ?? [])
128
+ .some(account => !account.isMain && account.id === accountId);
129
+ const hadVisiblePickerBinding = hadStoredAccount
130
+ && codexAccountPickerEnabled(runtimeConfig)
131
+ && codexAccountNamespaceEntries(runtimeConfig)
132
+ .some(([, boundAccountId]) => boundAccountId === accountId);
133
+
134
+ runtimeConfig.codexAccounts = (runtimeConfig.codexAccounts ?? [])
135
+ .filter(account => account.isMain || account.id !== accountId);
136
+ forgetCodexAccountPause(runtimeConfig, accountId);
137
+ forgetCodexAccountPriority(runtimeConfig, accountId);
138
+ clearCodexAccountPin(runtimeConfig, accountId);
139
+ if (runtimeConfig.activeCodexAccountId === accountId) runtimeConfig.activeCodexAccountId = undefined;
140
+
141
+ if (previousPersistedConfig !== undefined) {
142
+ try {
143
+ // Persist first for durable configs. Destructive cleanup below must never run for a
144
+ // deletion that failed to commit. Transient configs intentionally skip this write.
145
+ saveConfigPreservingClaudeCode(runtimeConfig);
146
+ } catch (error) {
147
+ restoreRuntimeConfig(runtimeConfig, previousConfig);
148
+ try {
149
+ restorePersistedConfig(configPath, previousPersistedConfig);
150
+ } catch {
151
+ throw new CodexAccountDeleteRollbackError();
152
+ }
153
+ throw error;
154
+ }
155
+ }
156
+
157
+ try {
158
+ removeCodexAccountCredential(accountId);
159
+ purgeCodexAccountRuntimeState(accountId);
160
+ invalidateCodexWebSocketsForAccount(accountId);
161
+ } catch {
162
+ // Do not throw through the mutation coordinator after config.json committed: that would roll
163
+ // back only the SQLite generation transaction, not the already-atomic file replacement.
164
+ cleanupFailed = true;
165
+ }
166
+
167
+ return hadVisiblePickerBinding;
168
+ });
169
+
170
+ if (cleanupFailed) throw new CodexAccountDeleteCleanupError();
171
+ return pickerVisibilityChanged;
55
172
  }
@@ -1,6 +1,10 @@
1
1
  import type { CodexAccount, OcxConfig } from "../types";
2
2
  import { COMBO_NAMESPACE } from "../combos/types";
3
3
  import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
4
+ import {
5
+ POLICY_NAMESPACE,
6
+ routingProfileAliasNamespacePrefixes,
7
+ } from "../routing/profile-namespace";
4
8
  import {
5
9
  CODEX_ACCOUNT_LOG_LABEL_RE,
6
10
  createCodexAccountLogLabel,
@@ -21,6 +25,7 @@ const RESERVED_NAMESPACE_KEYS = new Set([
21
25
  "constructor",
22
26
  COMBO_NAMESPACE,
23
27
  OPENAI_CODEX_PROVIDER_ID,
28
+ POLICY_NAMESPACE,
24
29
  ].map(codexProviderNamespaceKey));
25
30
  const PUBLIC_ACCOUNT_SELECTOR_MAX_ATTEMPTS = 16;
26
31
 
@@ -68,17 +73,21 @@ function claimNamespace(requested: string, used: Set<string>): string {
68
73
  return namespace;
69
74
  }
70
75
 
71
- function occupiedNamespaces(config: Pick<OcxConfig, "combos" | "providers">): Set<string> {
76
+ /** Collect every public namespace that a generated account selector must not claim. */
77
+ function occupiedNamespaces(
78
+ config: Pick<OcxConfig, "combos" | "providers" | "routingProfiles">,
79
+ ): Set<string> {
72
80
  return new Set([
73
81
  ...Object.keys(config.providers).map(codexProviderNamespaceKey),
74
82
  ...comboAliasNamespaces(config),
83
+ ...routingProfileAliasNamespacePrefixes(config),
75
84
  ...RESERVED_NAMESPACE_KEYS,
76
85
  ]);
77
86
  }
78
87
 
79
88
  /** Build an initial account-selector map without deriving public selectors from aliases or ids. */
80
89
  export function defaultCodexAccountNamespaces(
81
- config: Pick<OcxConfig, "codexAccounts" | "combos" | "providers">,
90
+ config: Pick<OcxConfig, "codexAccounts" | "combos" | "providers" | "routingProfiles">,
82
91
  ): Record<string, string> {
83
92
  const namespaces: Record<string, string> = {};
84
93
  const used = occupiedNamespaces(config);
@@ -99,13 +108,39 @@ export function defaultCodexAccountNamespaces(
99
108
  return namespaces;
100
109
  }
101
110
 
111
+ /**
112
+ * Initialize generated selectors only for an explicit opt-in with no existing bindings.
113
+ * A true result means the map was replaced and the caller must persist the updated config.
114
+ */
115
+ export function initializeDefaultCodexAccountNamespaces(
116
+ config: Pick<
117
+ OcxConfig,
118
+ | "codexAccountPickerEnabled"
119
+ | "codexAccountNamespaces"
120
+ | "codexAccounts"
121
+ | "combos"
122
+ | "providers"
123
+ | "routingProfiles"
124
+ >,
125
+ ): boolean {
126
+ if (config.codexAccountPickerEnabled !== true
127
+ || Object.keys(config.codexAccountNamespaces ?? {}).length > 0) return false;
128
+
129
+ const namespaces = defaultCodexAccountNamespaces(config);
130
+ config.codexAccountNamespaces = namespaces;
131
+ return true;
132
+ }
133
+
102
134
  /**
103
135
  * Add one account to a generated map without renaming or replacing explicit existing entries.
104
136
  * The account-creation layer must reject a new id that already equals an existing selector key.
105
137
  * A true result means the map was mutated in place; callers must persist the updated config.
106
138
  */
107
139
  export function appendDefaultCodexAccountNamespace(
108
- config: Pick<OcxConfig, "codexAccountNamespaces" | "codexAccounts" | "combos" | "providers">,
140
+ config: Pick<
141
+ OcxConfig,
142
+ "codexAccountNamespaces" | "codexAccounts" | "combos" | "providers" | "routingProfiles"
143
+ >,
109
144
  account: Pick<CodexAccount, "id" | "isMain" | "logLabel">,
110
145
  ): boolean {
111
146
  const namespaces = config.codexAccountNamespaces;
@@ -147,3 +182,14 @@ export function codexAccountNamespaceEntries(
147
182
  return Object.entries(config.codexAccountNamespaces ?? {})
148
183
  .map(([namespace, accountId]) => [namespace, normalizeCodexAccountNamespaceTarget(accountId)]);
149
184
  }
185
+
186
+ /**
187
+ * Whether generated account-qualified rows are enabled for catalog discovery.
188
+ * A non-empty hand-written map predating the explicit override remains enabled.
189
+ */
190
+ export function codexAccountPickerEnabled(
191
+ config: Pick<OcxConfig, "codexAccountNamespaces" | "codexAccountPickerEnabled">,
192
+ ): boolean {
193
+ return (config.codexAccountPickerEnabled === undefined || config.codexAccountPickerEnabled === true)
194
+ && Object.keys(config.codexAccountNamespaces ?? {}).length > 0;
195
+ }
@@ -1,4 +1,5 @@
1
1
  import type { OcxConfig } from "../types";
2
+ import { deleteConfigTopLevelKey } from "../config/rebase-provenance";
2
3
 
3
4
  /** Whether an account is administratively excluded from future pool selection. */
4
5
  export function isCodexAccountPaused(config: OcxConfig, accountId: string): boolean {
@@ -12,7 +13,7 @@ export function setCodexAccountPaused(config: OcxConfig, accountId: string, paus
12
13
  else pausedIds.delete(accountId);
13
14
 
14
15
  if (pausedIds.size > 0) config.pausedCodexAccountIds = [...pausedIds];
15
- else delete config.pausedCodexAccountIds;
16
+ else deleteConfigTopLevelKey(config, "pausedCodexAccountIds");
16
17
  }
17
18
 
18
19
  export function forgetCodexAccountPause(config: OcxConfig, accountId: string): void {
@@ -0,0 +1,84 @@
1
+ import { isValidCodexAccountId, MAIN_CODEX_ACCOUNT_ID } from "./account-id";
2
+ import { DEFAULT_ACCOUNT_PRIORITY, normalizeAccountPriority } from "./pool-rotation";
3
+ import type { OcxConfig } from "../types";
4
+ import { deleteConfigTopLevelKey } from "../config/rebase-provenance";
5
+
6
+ /**
7
+ * Which ids may carry a selection order: any pool account, plus the synthetic
8
+ * `__main__` Desktop login. Owned here rather than by the config schema because
9
+ * every writer needs it — the schema rejects a bad key on load, but the management
10
+ * API has to reject one before `setCodexAccountPriority` persists it as an own
11
+ * property that the schema would then reject, degrading the whole map.
12
+ */
13
+ export function isCodexAccountPriorityKey(key: unknown): key is string {
14
+ return key === MAIN_CODEX_ACCOUNT_ID || isValidCodexAccountId(key);
15
+ }
16
+
17
+ /**
18
+ * Persisted selection order for one account: higher numbers are used earlier.
19
+ * Stored as a config sidecar rather than a `codexAccounts` row field because the
20
+ * Codex Desktop login (`__main__`) has no row and must be orderable too.
21
+ */
22
+ export function getCodexAccountPriority(config: OcxConfig, accountId: string): number {
23
+ return codexAccountPriorityLookup(config)(accountId);
24
+ }
25
+
26
+ /**
27
+ * Write one account's selection order. The default is stored as absence, so a
28
+ * pool that was never ordered keeps no map at all and routing takes its fast path.
29
+ * Entries are rebuilt through `Object.fromEntries` so a reserved key such as
30
+ * `__proto__` becomes an own data property instead of invoking a prototype setter.
31
+ */
32
+ export function setCodexAccountPriority(config: OcxConfig, accountId: string, priority: number): void {
33
+ const entries = new Map(Object.entries(config.codexAccountPriorities ?? {}));
34
+ if (priority === DEFAULT_ACCOUNT_PRIORITY) entries.delete(accountId);
35
+ else entries.set(accountId, priority);
36
+
37
+ if (entries.size > 0) config.codexAccountPriorities = Object.fromEntries(entries);
38
+ else deleteConfigTopLevelKey(config, "codexAccountPriorities");
39
+ }
40
+
41
+ export function forgetCodexAccountPriority(config: OcxConfig, accountId: string): void {
42
+ setCodexAccountPriority(config, accountId, DEFAULT_ACCOUNT_PRIORITY);
43
+ }
44
+
45
+ /**
46
+ * Stable lookup for one selection pass. Routing calls this once per pick and
47
+ * hands the closure to `selectPriorityTier`, so a pool without stored order pays
48
+ * a single map read rather than one per candidate.
49
+ */
50
+ export function codexAccountPriorityLookup(config: OcxConfig): (accountId: string) => number {
51
+ const priorities = config.codexAccountPriorities;
52
+ if (!priorities) return () => DEFAULT_ACCOUNT_PRIORITY;
53
+ return accountId => (
54
+ Object.hasOwn(priorities, accountId)
55
+ ? normalizeAccountPriority(priorities[accountId])
56
+ : DEFAULT_ACCOUNT_PRIORITY
57
+ );
58
+ }
59
+
60
+ /**
61
+ * The account an operator most recently selected by hand. While pinned, priority
62
+ * never preempts upward: the pin is a tier *ceiling* for round-robin/fill-first --
63
+ * `selectPriorityTier` skips every tier above the pinned account's, so higher-ordered
64
+ * accounts are suppressed rather than the pinned one being guaranteed -- and a veto in
65
+ * the quota path, until the account crosses the auto-switch threshold.
66
+ */
67
+ export function pinnedCodexAccountId(config: OcxConfig): string | undefined {
68
+ return config.activeCodexAccountPinned;
69
+ }
70
+
71
+ export function isCodexAccountPinned(config: OcxConfig, accountId: string): boolean {
72
+ return config.activeCodexAccountPinned === accountId;
73
+ }
74
+
75
+ export function setCodexAccountPin(config: OcxConfig, accountId: string): void {
76
+ config.activeCodexAccountPinned = accountId;
77
+ }
78
+
79
+ /** Release the pin. With `accountId` given, only when it is the pinned account. */
80
+ export function clearCodexAccountPin(config: OcxConfig, accountId?: string): void {
81
+ if (accountId === undefined || config.activeCodexAccountPinned === accountId) {
82
+ deleteConfigTopLevelKey(config, "activeCodexAccountPinned");
83
+ }
84
+ }
@@ -386,6 +386,19 @@ function findFreshCredentialForGrant(
386
386
  return null;
387
387
  }
388
388
 
389
+ async function notePlanFromRefreshedAccessToken(
390
+ id: string,
391
+ accessToken: string,
392
+ generation: number,
393
+ ): Promise<void> {
394
+ try {
395
+ const { noteCodexAccountAccessToken } = await import("./plan-from-token");
396
+ noteCodexAccountAccessToken(id, accessToken, generation);
397
+ } catch {
398
+ // Derived plan metadata must not fail credential refresh.
399
+ }
400
+ }
401
+
389
402
  export async function getValidCodexToken(id: string): Promise<CodexTokenResult> {
390
403
  const record = readCodexAccountRecord(id);
391
404
  const cred = record?.deletedAt == null ? record?.credential : undefined;
@@ -415,10 +428,12 @@ export async function getValidCodexToken(id: string): Promise<CodexTokenResult>
415
428
  if (!saveCodexAccountCredentialIfGeneration(id, current.generation, refreshed.credential)) {
416
429
  throw new CodexCredentialGenerationConflictError();
417
430
  }
431
+ const generation = current.generation + 1;
432
+ await notePlanFromRefreshedAccessToken(id, refreshed.credential.accessToken, generation);
418
433
  return {
419
434
  accessToken: refreshed.credential.accessToken,
420
435
  chatgptAccountId: refreshed.credential.chatgptAccountId,
421
- generation: current.generation + 1,
436
+ generation,
422
437
  };
423
438
  }
424
439
  return getValidCodexToken(id);
@@ -491,11 +506,22 @@ export async function getValidCodexToken(id: string): Promise<CodexTokenResult>
491
506
  throw new TokenRefreshError(reason, `Codex token refresh failed (${reason}); reauthenticate the account.`);
492
507
  }
493
508
  const data = (await res.json()) as { access_token: string; refresh_token?: string; expires_in: number };
509
+ // Guard against a missing/non-finite/negative expires_in (malformed upstream
510
+ // response): a NaN expiry would never compare as expired, and a negative
511
+ // duration would stamp an already-past expiry — both block refresh semantics.
512
+ const expiresIn =
513
+ typeof data.expires_in === "number" && Number.isFinite(data.expires_in) && data.expires_in >= 0
514
+ ? data.expires_in
515
+ : 3600;
516
+ // The computed timestamp itself must stay finite: Number.MAX_VALUE passes
517
+ // Number.isFinite but overflows to Infinity once multiplied by 1000.
518
+ const expiresAt = Date.now() + expiresIn * 1000;
519
+ const safeExpiresAt = Number.isFinite(expiresAt) ? expiresAt : Date.now() + 3600 * 1000;
494
520
 
495
521
  const updated: CodexAccountCredentials = {
496
522
  accessToken: data.access_token,
497
523
  refreshToken: data.refresh_token ?? lockedCred.refreshToken,
498
- expiresAt: Date.now() + data.expires_in * 1000,
524
+ expiresAt: safeExpiresAt,
499
525
  chatgptAccountId: lockedCred.chatgptAccountId,
500
526
  };
501
527
  if (!saveCodexAccountCredentialIfGeneration(id, startGeneration, updated)) {
@@ -509,6 +535,7 @@ export async function getValidCodexToken(id: string): Promise<CodexTokenResult>
509
535
  flight = { promise: refreshPromise, startedAt: Date.now(), abort };
510
536
  refreshLocks.set(refreshGrantFingerprint, flight);
511
537
  const result = await refreshPromise;
538
+ await notePlanFromRefreshedAccessToken(id, result.accessToken, result.generation);
512
539
  return {
513
540
  accessToken: result.accessToken,
514
541
  chatgptAccountId: result.chatgptAccountId,