@bitkyc08/opencodex 2.83.0 → 2.86.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 (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -47,7 +47,7 @@ const DATA: Record<string, readonly Row[]> = {
47
47
  "azure-openai": [["gpt-4.1",1047576,32768,"text,image",0,null,2,8,0.5,0],["gpt-4o",128000,16384,"text,image",0,null,2.5,10,1.25,0],["gpt-4o-mini",128000,16384,"text,image",0,null,0.15,0.6,0.075,0],["o3",200000,100000,"text,image",1,null,2,8,0.5,0],["o3-mini",200000,100000,"text",1,null,1.1,4.4,0.55,0]],
48
48
  "cerebras": [["gemma-4-31b",131072,40960,"text,image",1,null,0.99,1.49,0,0],["gpt-oss-120b",131072,40960,"text",1,null,0.35,0.75,0,0],["llama3.1-8b",32000,8000,"text",0,null,0.1,0.1,0,0],["qwen-3-235b-a22b-instruct-2507",131000,32000,"text",0,null,0.6,1.2,0,0],["qwen-3-coder-480b",131072,32768,"text",0,null,0,0,0,0],["zai-glm-4.6",131072,32768,"text",0,null,0,0,0,0],["zai-glm-4.7",131072,40960,"text",1,null,2.25,2.75,2.25,0]],
49
49
  "deepseek": [["deepseek-v4-flash",1048576,384000,"text",1,null,0.14,0.28,0.0028,0],["deepseek-v4-pro",1048576,384000,"text",1,null,0.435,0.87,0.003625,0]],
50
- "google": [["deep-research-max-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["deep-research-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["gemini-1.5-flash",1000000,8192,"text,image",0,null,0.075,0.3,0.01875,0],["gemini-1.5-flash-8b",1000000,8192,"text,image",0,null,0.0375,0.15,0.01,0],["gemini-1.5-pro",1000000,8192,"text,image",0,null,1.25,5,0.3125,0],["gemini-2.0-flash",1048576,8192,"text,image",0,null,0.1,0.4,0.025,0],["gemini-2.0-flash-lite",1048576,8192,"text,image",0,null,0.075,0.3,0,0],["gemini-2.5-computer-use-preview-10-2025",131072,65536,"text,image",1,null,1.25,10,0,0],["gemini-2.5-flash",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-2.5-flash-lite",1048576,65536,"text,image",1,null,0.1,0.4,0.01,0],["gemini-2.5-flash-lite-preview-06-17",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-lite-preview-09-2025",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-preview-04-17",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-05-20",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-09-2025",1048576,65536,"text,image",1,null,0.3,2.5,0.075,0],["gemini-2.5-pro",1048576,65536,"text,image",1,null,1.25,10,0.125,0],["gemini-2.5-pro-preview-05-06",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-2.5-pro-preview-06-05",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-3-flash-preview",1048576,65536,"text,image",1,null,0.5,3,0.05,0],["gemini-3-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-flash-lite",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-lite-image",65536,65536,"text,image",1,null,0.25,30,0,0],["gemini-3.1-flash-lite-preview",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-live-preview",131072,65536,"text,image",1,null,0.75,4.5,0,0],["gemini-3.1-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-pro-preview-customtools",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.5-flash",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-3.5-flash-lite",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-3.6-flash",1048576,65536,"text,image",1,null,1.5,7.5,0.15,0],["gemini-3.7-flash",1048576,65536,"text,image",1],["gemini-3.8-flash",1048576,65536,"text,image",1],["gemini-flash-latest",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-flash-lite-latest",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-live-2.5-flash",128000,8000,"text,image",1,null,0.5,2,0,0],["gemini-live-2.5-flash-preview-native-audio",131072,65536,"text",1,null,0.5,2,0,0],["gemini-robotics-er-1.6-preview",131072,65536,"text,image",1,null,1,5,0,0],["gemma-3-27b-it",131072,8192,"text,image",0,null,0,0,0,0],["gemma-4-26b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-26b-a4b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-26b-it",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-E2B-it",131072,8192,"text,image",1,null,0,0,0,0],["gemma-4-E4B-it",131072,8192,"text,image",1,null,0,0,0,0]],
50
+ "google": [["deep-research-max-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["deep-research-preview-04-2026",131072,65536,"text,image",1,null,2,12,0.2,0],["gemini-1.5-flash",1000000,8192,"text,image",0,null,0.075,0.3,0.01875,0],["gemini-1.5-flash-8b",1000000,8192,"text,image",0,null,0.0375,0.15,0.01,0],["gemini-1.5-pro",1000000,8192,"text,image",0,null,1.25,5,0.3125,0],["gemini-2.0-flash",1048576,8192,"text,image",0,null,0.1,0.4,0.025,0],["gemini-2.0-flash-lite",1048576,8192,"text,image",0,null,0.075,0.3,0,0],["gemini-2.5-computer-use-preview-10-2025",131072,65536,"text,image",1,null,1.25,10,0,0],["gemini-2.5-flash",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-2.5-flash-lite",1048576,65536,"text,image",1,null,0.1,0.4,0.01,0],["gemini-2.5-flash-lite-preview-06-17",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-lite-preview-09-2025",1048576,65536,"text,image",1,null,0.1,0.4,0.025,0],["gemini-2.5-flash-preview-04-17",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-05-20",1048576,65536,"text,image",1,null,0.15,0.6,0.0375,0],["gemini-2.5-flash-preview-09-2025",1048576,65536,"text,image",1,null,0.3,2.5,0.075,0],["gemini-2.5-pro",1048576,65536,"text,image",1,null,1.25,10,0.125,0],["gemini-2.5-pro-preview-05-06",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-2.5-pro-preview-06-05",1048576,65536,"text,image",1,null,1.25,10,0.31,0],["gemini-3-flash-preview",1048576,65536,"text,image",1,null,0.5,3,0.05,0],["gemini-3-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-flash-lite",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-lite-image",65536,65536,"text,image",1,null,0.25,30,0,0],["gemini-3.1-flash-lite-preview",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-3.1-flash-live-preview",131072,65536,"text,image",1,null,0.75,4.5,0,0],["gemini-3.1-pro-preview",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.1-pro-preview-customtools",1048576,65536,"text,image",1,null,2,12,0.2,0],["gemini-3.5-flash",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-3.5-flash-lite",1048576,65536,"text,image",1,null,0.3,2.5,0.03,0],["gemini-3.6-flash",1048576,65536,"text,image",1,null,1.5,7.5,0.15,0],["gemini-3.7-flash",1048576,65536,"text,image",1],["gemini-3.8-flash",1048576,65536,"text,image",1],["gemini-4-argon",1048576,65536,"text,image",1],["gemini-flash-latest",1048576,65536,"text,image",1,null,1.5,9,0.15,0],["gemini-flash-lite-latest",1048576,65536,"text,image",1,null,0.25,1.5,0.025,0],["gemini-live-2.5-flash",128000,8000,"text,image",1,null,0.5,2,0,0],["gemini-live-2.5-flash-preview-native-audio",131072,65536,"text",1,null,0.5,2,0,0],["gemini-robotics-er-1.6-preview",131072,65536,"text,image",1,null,1,5,0,0],["gemma-3-27b-it",131072,8192,"text,image",0,null,0,0,0,0],["gemma-4-26b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-26b-a4b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-26b-it",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b",256000,8192,"text,image",1,null,0,0,0,0],["gemma-4-31b-it",262144,32768,"text,image",1,null,0,0,0,0],["gemma-4-E2B-it",131072,8192,"text,image",1,null,0,0,0,0],["gemma-4-E4B-it",131072,8192,"text,image",1,null,0,0,0,0]],
51
51
  "minimax": [["MiniMax-M2",196608,128000,"text",1,null,0.3,1.2,0,0],["MiniMax-M2.1",204800,131072,"text",1,null,0.3,1.2,0,0],["MiniMax-M2.5",204800,131072,"text",1,null,0.3,1.2,0.03,0.375],["MiniMax-M2.5-highspeed",204800,131072,"text",1,null,0.6,2.4,0.06,0.375],["MiniMax-M2.5-lightning",204800,32000,"text",1,null,0.3,2.4,0,0],["MiniMax-M2.7",204800,131072,"text",1,null,0.3,1.2,0.06,0.375],["MiniMax-M2.7-highspeed",204800,131072,"text",1,null,0.6,2.4,0.06,0.375],["minimax-m3",512000,128000,"text,image",1,null,0.6,2.4,0.12,0],["MiniMax-M3",1000000,128000,"text,image,video",1,null,0.3,1.2,0.06,0],["MiniMax-M3.1-Flash-Preview",1000000,null,"text,image,video",1]],
52
52
  "mistral": [["codestral-latest",256000,4096,"text",0,null,0.3,0.9,0,0],["devstral-2512",262144,262144,"text",0,null,0.4,2,0,0],["devstral-latest",262144,262144,"text",0,null,0.4,2,0,0],["devstral-medium-2507",128000,128000,"text",0,null,0.4,2,0,0],["devstral-medium-latest",262144,262144,"text",0,null,0.4,2,0,0],["devstral-small-2505",128000,128000,"text",0,null,0.1,0.3,0,0],["devstral-small-2507",128000,128000,"text",0,null,0.1,0.3,0,0],["labs-devstral-small-2512",256000,256000,"text,image",0,null,0,0,0,0],["magistral-medium-latest",128000,16384,"text",1,null,2,5,0,0],["magistral-small",128000,128000,"text",1,null,0.5,1.5,0,0],["ministral-3b-latest",128000,128000,"text",0,null,0.04,0.04,0,0],["ministral-8b-latest",128000,128000,"text",0,null,0.1,0.1,0,0],["mistral-large-2411",131072,16384,"text",0,null,2,6,0,0],["mistral-large-2512",262144,262144,"text,image",0,null,0.5,1.5,0,0],["mistral-large-latest",262144,262144,"text,image",0,null,0.5,1.5,0,0],["mistral-medium-2505",131072,131072,"text,image",0,null,0.4,2,0,0],["mistral-medium-2508",262144,262144,"text,image",0,null,0.4,2,0,0],["mistral-medium-2604",262144,262144,"text,image",1,null,1.5,7.5,0,0],["mistral-medium-latest",262144,262144,"text,image",1,null,1.5,7.5,0,0],["mistral-nemo",128000,128000,"text",0,null,0.15,0.15,0,0],["mistral-small-2506",128000,16384,"text,image",0,null,0.1,0.3,0,0],["mistral-small-2603",256000,256000,"text,image",1,null,0.15,0.6,0,0],["mistral-small-latest",256000,256000,"text,image",1,null,0.15,0.6,0,0],["open-mistral-7b",8000,8000,"text",0,null,0.25,0.25,0,0],["open-mistral-nemo",128000,128000,"text",0,null,0.15,0.15,0,0],["open-mixtral-8x22b",64000,64000,"text",0,null,2,6,0,0],["open-mixtral-8x7b",32000,32000,"text",0,null,0.7,0.7,0,0],["pixtral-12b",128000,128000,"text,image",0,null,0.15,0.15,0,0],["pixtral-large-latest",128000,128000,"text,image",0,null,2,6,0,0]],
53
53
  "moonshot": [["kimi-k2.5",262144,65536,"text,image",1,null,0,0,0,0]],
@@ -5,6 +5,7 @@
5
5
 
6
6
  export interface GrpcWebTrailer {
7
7
  status: number;
8
+ statusValues: string[];
8
9
  statusMessage?: string;
9
10
  metadata: Record<string, string>;
10
11
  }
@@ -12,6 +13,8 @@ export interface GrpcWebTrailer {
12
13
  export interface DecodedGrpcWebResponse {
13
14
  messages: Uint8Array[];
14
15
  status: number;
16
+ statusValues: string[];
17
+ framingComplete: boolean;
15
18
  statusMessage?: string;
16
19
  trailers?: GrpcWebTrailer;
17
20
  }
@@ -51,6 +54,7 @@ export function parseGrpcWebTrailers(bytes: Uint8Array): GrpcWebTrailer {
51
54
  const text = new TextDecoder("utf-8").decode(bytes);
52
55
  const lines = text.split(/\r?\n/);
53
56
  const metadata: Record<string, string> = {};
57
+ const statusValues: string[] = [];
54
58
  let status = 0;
55
59
  let statusMessage: string | undefined;
56
60
 
@@ -62,6 +66,7 @@ export function parseGrpcWebTrailers(bytes: Uint8Array): GrpcWebTrailer {
62
66
  if (!key) continue;
63
67
  metadata[key] = value;
64
68
  if (key === "grpc-status") {
69
+ statusValues.push(value);
65
70
  const parsed = parseInt(value, 10);
66
71
  if (!Number.isNaN(parsed)) {
67
72
  status = parsed;
@@ -75,7 +80,7 @@ export function parseGrpcWebTrailers(bytes: Uint8Array): GrpcWebTrailer {
75
80
  }
76
81
  }
77
82
 
78
- return { status, statusMessage, metadata };
83
+ return { status, statusValues, statusMessage, metadata };
79
84
  }
80
85
 
81
86
  /**
@@ -83,8 +88,11 @@ export function parseGrpcWebTrailers(bytes: Uint8Array): GrpcWebTrailer {
83
88
  */
84
89
  export function decodeGrpcWebResponse(bytes: Uint8Array): DecodedGrpcWebResponse {
85
90
  const messages: Uint8Array[] = [];
91
+ const statusValues: string[] = [];
86
92
  let offset = 0;
87
93
  let trailer: GrpcWebTrailer | undefined;
94
+ let firstTrailerPayload: Uint8Array | undefined;
95
+ let framingComplete = true;
88
96
 
89
97
  while (offset + HEADER_SIZE <= bytes.length) {
90
98
  const flag = bytes[offset];
@@ -99,10 +107,22 @@ export function decodeGrpcWebResponse(bytes: Uint8Array): DecodedGrpcWebResponse
99
107
 
100
108
  const payload = bytes.subarray(frameStart, frameEnd);
101
109
 
110
+ // Preserve legacy decoding; callers needing confirmation inspect framingComplete.
102
111
  if (flag === FRAME_DATA) {
112
+ if (firstTrailerPayload !== undefined) framingComplete = false;
103
113
  messages.push(payload);
104
114
  } else if (flag === FRAME_TRAILER) {
115
+ const firstTrailer = firstTrailerPayload;
116
+ if (firstTrailer === undefined) {
117
+ firstTrailerPayload = payload;
118
+ } else if (payload.length !== firstTrailer.length
119
+ || payload.some((byte, index) => byte !== firstTrailer[index])) {
120
+ framingComplete = false;
121
+ }
105
122
  trailer = parseGrpcWebTrailers(payload);
123
+ for (const value of trailer.statusValues) statusValues.push(value);
124
+ } else {
125
+ framingComplete = false;
106
126
  }
107
127
 
108
128
  offset = frameEnd;
@@ -114,6 +134,8 @@ export function decodeGrpcWebResponse(bytes: Uint8Array): DecodedGrpcWebResponse
114
134
  return {
115
135
  messages,
116
136
  status: finalStatus,
137
+ statusValues,
138
+ framingComplete: framingComplete && offset === bytes.length,
117
139
  statusMessage: finalMessage,
118
140
  trailers: trailer,
119
141
  };
@@ -25,6 +25,8 @@ export interface GrokResetCouponOperationRecord {
25
25
  attemptedAt?: number;
26
26
  code?: string;
27
27
  settledAt?: number;
28
+ /** Durable status of a replayed record, so callers apply the terminal-replay predicate. */
29
+ status?: "attempted" | "settled" | "failed";
28
30
  }
29
31
 
30
32
  interface GrokResetCouponOperationState {
@@ -77,8 +79,15 @@ function readGrokCouponLedger(filePath: string): GrokResetCouponLedger {
77
79
  op.status = "attempted";
78
80
  delete op.code;
79
81
  }
82
+ // A v1 attempted record never carried a confirmed outcome.
83
+ if (op.status === "attempted") delete op.code;
80
84
  }
81
85
  }
86
+ // Only a settled or failed record carries an outcome. An open or attempted
87
+ // record with a code would replay an unconfirmed result as definitive.
88
+ for (const op of Object.values(parsed.operations)) {
89
+ if ((op.status === "open" || op.status === "attempted") && op.code !== undefined) throw new Error();
90
+ }
82
91
  return { version: 2, operations: parsed.operations };
83
92
  } catch {
84
93
  throw new Error("Grok coupon ledger is unreadable; existing records were preserved.");
@@ -163,6 +172,7 @@ export function openGrokResetCouponOperation(
163
172
  attemptedAt: existing.status === "attempted" ? existing.updatedAt : undefined,
164
173
  code: existing.code,
165
174
  settledAt: existing.updatedAt,
175
+ status: existing.status,
166
176
  };
167
177
  }
168
178
  return {
@@ -232,17 +242,37 @@ type GrokResetCouponPreflightResult =
232
242
  | { kind: "recorded" | "changed" }
233
243
  | { kind: "replay"; code: string; tokenId?: string; settledAt: number };
234
244
 
235
- const UNRESOLVED_COUPON_CODES = new Set(["redeem_failed", "attempt_unresolved", "attempt_in_progress", "attempt_reconcile_failed", "operation_state_changed"]);
245
+ /**
246
+ * Codes that never prove an outcome. A record carrying one is not replayed as
247
+ * definitive, even if a restored or hand-edited ledger stores it as settled/failed.
248
+ */
249
+ const UNRESOLVED_COUPON_CODES = new Set([
250
+ "redeem_failed", "attempt_unresolved", "attempt_in_progress", "attempt_reconcile_failed", "operation_state_changed",
251
+ "attempt_mark_failed", "operation_token_mismatch", "ledger_unavailable", "capacity",
252
+ ]);
253
+
254
+ /**
255
+ * Whether a durable record is a definitive outcome this account and token may replay:
256
+ * settled or failed, same account, same token when both are known, and a code that
257
+ * does not itself mean "unconfirmed".
258
+ */
259
+ export function isTerminalGrokCouponReplay(
260
+ record: { status?: string; accountId?: string; tokenId?: string; code?: string } | undefined,
261
+ accountId: string, tokenId: string | undefined,
262
+ ): boolean {
263
+ if (!record || record.accountId !== accountId) return false;
264
+ if (tokenId !== undefined && record.tokenId !== undefined && record.tokenId !== tokenId) return false;
265
+ return (record.status === "settled" || record.status === "failed")
266
+ && !!record.code && !UNRESOLVED_COUPON_CODES.has(record.code);
267
+ }
236
268
 
237
269
  /** The durable definitive outcome for this account and token, if the operation has one. */
238
270
  function terminalReplay(
239
271
  ledger: GrokResetCouponLedger, operationId: string, accountId: string, tokenId: string | undefined,
240
272
  ): Extract<GrokResetCouponPreflightResult, { kind: "replay" }> | null {
241
273
  const existing = ledger.operations[operationId];
242
- if (!existing || existing.accountId !== accountId) return null;
243
- if (tokenId !== undefined && existing.tokenId !== undefined && existing.tokenId !== tokenId) return null;
244
- if ((existing.status !== "settled" && existing.status !== "failed") || !existing.code || UNRESOLVED_COUPON_CODES.has(existing.code)) return null;
245
- return { kind: "replay", code: existing.code, tokenId: existing.tokenId, settledAt: existing.updatedAt };
274
+ if (!existing || !isTerminalGrokCouponReplay(existing, accountId, tokenId)) return null;
275
+ return { kind: "replay", code: existing.code!, tokenId: existing.tokenId, settledAt: existing.updatedAt };
246
276
  }
247
277
 
248
278
  /** Settle a refusal or inspect its winner without releasing the mutation transaction. */
@@ -259,6 +289,46 @@ export function settleGrokResetCouponPreflightRefusal(
259
289
  });
260
290
  }
261
291
 
292
+ export type GrokResetCouponPreOpenRefusalResult =
293
+ | GrokResetCouponPreflightResult
294
+ | { kind: "identity-mismatch" | "capacity" };
295
+
296
+ /**
297
+ * Refuse a request that never opened its supplied operationId. Under the mutation lock
298
+ * an absent id is reserved as a failed record, so a later open of the same id replays
299
+ * the refusal and can never be claimed (within the 30-day retention window). An id
300
+ * that already exists is never overwritten: a definitive outcome replays, anything
301
+ * else reports "changed" so the caller keeps its hold.
302
+ */
303
+ export function refuseGrokResetCouponBeforeOpen(
304
+ refusal: { operationId: string; accountId: string; tokenId?: string; code: string },
305
+ now = Date.now(),
306
+ journalPath?: string,
307
+ ): GrokResetCouponPreOpenRefusalResult {
308
+ return withConfigMutationLockSync(() => {
309
+ const filePath = journalPath ?? grokCouponJournalPath();
310
+ const ledger = readGrokCouponLedger(filePath);
311
+ const retentionCutoff = now - 30 * 24 * 60 * 60_000;
312
+ ledger.operations = Object.fromEntries(
313
+ Object.entries(ledger.operations).filter(([, op]) => op.updatedAt > retentionCutoff),
314
+ );
315
+ const existing = ledger.operations[refusal.operationId];
316
+ if (existing) {
317
+ if (existing.accountId !== refusal.accountId) return { kind: "identity-mismatch" };
318
+ // Same token binding as a normal replay: a different requested coupon never
319
+ // replays another coupon's outcome.
320
+ return terminalReplay(ledger, refusal.operationId, refusal.accountId, refusal.tokenId) ?? { kind: "changed" };
321
+ }
322
+ if (Object.keys(ledger.operations).length >= MAX_GROK_RESET_COUPON_OPERATION_IDS) return { kind: "capacity" };
323
+ ledger.operations[refusal.operationId] = {
324
+ accountId: refusal.accountId, ...(refusal.tokenId !== undefined ? { tokenId: refusal.tokenId } : {}),
325
+ status: "failed", code: refusal.code, createdAt: now, updatedAt: now,
326
+ };
327
+ writeGrokCouponLedger(filePath, ledger, now);
328
+ return { kind: "recorded" };
329
+ });
330
+ }
331
+
262
332
  /**
263
333
  * After a lost claim: the definitive winner to replay, or null while the
264
334
  * operation is still unresolved. Terminal states never revert, so reading after
@@ -231,10 +231,12 @@ function buildGrokHeaders(accessToken: string): Record<string, string> {
231
231
  * Reads available Grok reset tokens for the authenticated xAI account.
232
232
  */
233
233
  function assertConfirmedCouponStatus(decoded: ReturnType<typeof decodeGrpcWebResponse>): void {
234
- const rawStatus = decoded.trailers?.metadata["grpc-status"];
234
+ const rawStatus = decoded.statusValues[0];
235
235
  // The shared decoder defaults a missing/malformed status to zero. Coupon
236
236
  // availability and an irreversible redemption need affirmative confirmation.
237
- if (rawStatus === undefined || !/^\d+$/.test(rawStatus) || !Number.isSafeInteger(Number(rawStatus))) {
237
+ if (decoded.framingComplete !== true || rawStatus === undefined
238
+ || decoded.statusValues.some((value) => !/^\d+$/.test(value)
239
+ || !Number.isSafeInteger(Number(value)) || value !== rawStatus)) {
238
240
  throw new Error("Grok coupon response has no confirmed gRPC status");
239
241
  }
240
242
  const status = Number(rawStatus);
@@ -12,7 +12,7 @@ import {
12
12
  /** Refresh only previously connected clients; a refused file never blocks its peers. */
13
13
  export async function refreshOwnedCatalogIntegrations(
14
14
  input: Omit<OwnedIntegrationRefreshInput, "clientId">,
15
- clientIds: readonly IntegrationClientId[] = ["pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo"],
15
+ clientIds: readonly IntegrationClientId[] = ["pi", "aside", "raycast", "omo", "commandcode", "droid", "opencode", "kilo", "qoder"],
16
16
  options: { refreshOnly?: boolean; admit?: () => boolean } = {},
17
17
  ): Promise<OwnedIntegrationRefreshOutcome[]> {
18
18
  // Client files are shared with the live proxy a sibling instance runs beside; their entries
@@ -7,8 +7,9 @@
7
7
  *
8
8
  * Design of record: devlog/_fin/260802_client_toggle_api/021 §5-6.
9
9
  */
10
- import { lstatSync, mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
10
+ import { lstatSync, mkdirSync, rmSync, statSync } from "node:fs";
11
11
  import type { ConfigFormat } from "../clients/config-export";
12
+ import { readBoundedRegularFileSync } from "../lib/bounded-file-read";
12
13
  import { canonicalizeJsonc } from "../lib/jsonc";
13
14
  import { MAX_JSON_NESTING } from "./serialize";
14
15
  import { atomicWriteFileNoFollow, isMissingPathError } from "../config/atomic-write";
@@ -24,6 +25,10 @@ import type { IntegrationClientId } from "./registry";
24
25
  * then overwritten.
25
26
  */
26
27
  export const PARSE_FAILED = Symbol("parse-failed");
28
+ const MAX_INTEGRATION_CONFIG_FILE_BYTES = 4 * 1024 * 1024;
29
+
30
+ /** Raised before any target mutation, including pending-marker creation. */
31
+ export class IntegrationWriteSizeError extends Error {}
27
32
 
28
33
  /**
29
34
  * What `JSON.parse` has already discarded by the time we hold the parsed value,
@@ -285,7 +290,7 @@ export function fileIO(): Omit<IntegrationIO, "appendJournal" | "putRecord" | "d
285
290
  lstatKind,
286
291
  readText: path => {
287
292
  try {
288
- return { kind: "text", text: readFileSync(path, "utf8") };
293
+ return { kind: "text", text: readBoundedRegularFileSync(path, MAX_INTEGRATION_CONFIG_FILE_BYTES).toString("utf8") };
289
294
  } catch (error) {
290
295
  const code = (error as NodeJS.ErrnoException).code;
291
296
  return code === "ENOENT" ? { kind: "missing" } : { kind: "failed", ...(code ? { code } : {}) };
@@ -300,6 +305,19 @@ export function fileIO(): Omit<IntegrationIO, "appendJournal" | "putRecord" | "d
300
305
  }
301
306
  },
302
307
  writeText: (path, text) => {
308
+ /*
309
+ * One bound on both halves of the seam: a file past this size is one
310
+ * readText refuses, so writing it would strand the integration against
311
+ * its own configuration — or, for Cline's pending marker, leave a
312
+ * transaction recovery could never read back.
313
+ */
314
+ const bytes = Buffer.byteLength(text, "utf8");
315
+ if (bytes > MAX_INTEGRATION_CONFIG_FILE_BYTES) {
316
+ throw new IntegrationWriteSizeError(
317
+ `refusing oversized integration write to ${path}: `
318
+ + `${bytes} bytes is past the ${MAX_INTEGRATION_CONFIG_FILE_BYTES}-byte read bound`,
319
+ );
320
+ }
303
321
  const kind = lstatKind(path);
304
322
  if (kind !== "file" && kind !== "missing") {
305
323
  throw new Error(`refusing unsafe integration write target: ${path}`);
@@ -16,7 +16,7 @@ import { randomUUID } from "node:crypto";
16
16
  import { appendFileSync, existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
17
17
  import { join, resolve, sep } from "node:path";
18
18
  import { atomicWriteFile } from "../config";
19
- import { ensureDir, fingerprint, integrationsDir, isOwnershipRecord, type OwnershipRecord } from "./ownership";
19
+ import { ensureDir, fingerprint, integrationsDir, isOwnershipRecord, readRecordsStrict, type OwnershipRecord } from "./ownership";
20
20
  import { isIntegrationClientId, type IntegrationClientId } from "./registry";
21
21
 
22
22
  /**
@@ -171,6 +171,20 @@ export function captureSnapshot(
171
171
  return { kind: "stored", relPath: join("snapshots", clientId, opId) };
172
172
  }
173
173
 
174
+ /** Remove only an unused pre-write snapshot; uncertain or committed evidence survives. */
175
+ export function discardUncommittedSnapshot(
176
+ clientId: IntegrationClientId,
177
+ opId: string,
178
+ dir: string = integrationsDir(),
179
+ ): void {
180
+ const target = containedPath(dir, "snapshots", assertSafeComponent(clientId, "clientId"), assertSafeComponent(opId, "opId"));
181
+ if (findCommittedOperation(opId, dir)
182
+ || Object.values(readRecordsStrict(dir)).some(record => record?.opId === opId)) {
183
+ throw new Error("integration snapshot still belongs to a recorded operation");
184
+ }
185
+ rmSync(target, { force: true });
186
+ }
187
+
174
188
  /** Commit the row. Pruning is post-commit and can never fail the append. */
175
189
  export function appendOperation(entry: JournalEntry, dir: string = integrationsDir()): void {
176
190
  ensureDir(journalPath(dir));
@@ -32,6 +32,7 @@ import {
32
32
  assertDroidRecordedSettingsUnambiguous,
33
33
  isLoopbackOnly,
34
34
  resolveIntegrationPaths,
35
+ type ResolvedIntegrationPaths,
35
36
  restoreOwnershipCollision,
36
37
  type IntegrationClientId,
37
38
  } from "./registry";
@@ -130,6 +131,7 @@ export const DYNAMIC_SEGMENT = "*";
130
131
  * paths silently have no declaration.
131
132
  */
132
133
  const CLIENT_MANAGED_PATHS = {
134
+ qoder: [["providers", OPENCODE_PROVIDER_ID], ["modelConfigs", "customModels", DYNAMIC_SEGMENT]],
133
135
  opencode: [["provider", OPENCODE_PROVIDER_ID], ["providers", OPENCODE_PROVIDER_ID]],
134
136
  pi: [["providers", OPENCODE_PROVIDER_ID]],
135
137
  omp: [["providers", OPENCODE_PROVIDER_ID]],
@@ -567,7 +569,7 @@ export function restoreStoreDirectoryRefusal(
567
569
  if (!declared?.lockFile) return null;
568
570
  let storePath: string;
569
571
  try {
570
- storePath = declared.path(input.env, input.home);
572
+ storePath = declared.path(input.env, input.home, io);
571
573
  } catch (error) {
572
574
  if (error instanceof ClientPathError) return null;
573
575
  throw error;
@@ -602,10 +604,11 @@ export function observeRestore(
602
604
  const store = selectedOperation?.store ?? input.store ?? createIntegrationStateStore();
603
605
  let io = input.io ?? defaultIntegrationIO(store);
604
606
  const clientId = input.clientId;
605
- let resolved: { configPath: string; detectDir: string };
607
+ let resolved: ResolvedIntegrationPaths;
606
608
  try {
607
609
  const context = exportContextOf(input);
608
- resolved = input.resolvedPaths ?? resolveIntegrationPaths(clientId, input.env, input.home, context);
610
+ resolved = clientId === "zcode" || !input.resolvedPaths
611
+ ? resolveIntegrationPaths(clientId, input.env, input.home, context, io) : input.resolvedPaths;
609
612
  if (clientId === "droid" && input.resolvedPaths) assertDroidPathsUnambiguous(resolved.detectDir, context);
610
613
  } catch (error) {
611
614
  if (!(error instanceof ClientPathError)) throw error;
@@ -630,7 +633,7 @@ export function observeRestore(
630
633
  * config file. The answer also carries the document shape those bytes are in.
631
634
  */
632
635
  const rowTarget = declaredIntegrationTarget({
633
- clientId, configPath, resolvedConfigPath: resolved.configPath, env: input.env, home: input.home,
636
+ clientId, configPath, resolvedConfigPath: resolved.configPath, env: input.env, home: input.home, io,
634
637
  });
635
638
  if (rowTarget === null) {
636
639
  return {
@@ -848,7 +851,7 @@ export interface IntegrationWriteInput {
848
851
  store?: IntegrationStateStore;
849
852
  io?: IntegrationIO;
850
853
  /** Frozen once by the async coordinator; synchronous callers may omit it. */
851
- resolvedPaths?: { configPath: string; detectDir: string };
854
+ resolvedPaths?: ResolvedIntegrationPaths;
852
855
  }
853
856
 
854
857
  /** A refusal in the planner's own vocabulary, so observation does not depend on the writer's result type. */
@@ -922,7 +925,8 @@ export function observeIntegration(
922
925
  * verify account 1 was installed and then write account 0's catalog.
923
926
  */
924
927
  const context = exportContextOf(input);
925
- const resolved = input.resolvedPaths ?? resolveIntegrationPaths(clientId, input.env, input.home, context);
928
+ const resolved = clientId === "zcode" || !input.resolvedPaths
929
+ ? resolveIntegrationPaths(clientId, input.env, input.home, context, io) : input.resolvedPaths;
926
930
  if (clientId === "droid" && input.resolvedPaths) assertDroidPathsUnambiguous(resolved.detectDir, context);
927
931
  detectDir = resolved.detectDir;
928
932
  if (clientId === "cline") io = createClineIO(io, resolved.configPath, store, effects.recover);
@@ -945,9 +949,12 @@ export function observeIntegration(
945
949
  * collection route.
946
950
  */
947
951
  effective = resolveIntegrationTarget({
948
- clientId, configPath: recordedPath, io, record: stored, env: input.env, home: input.home,
952
+ clientId, configPath: recordedPath, io, record: stored, env: input.env, home: input.home, resolvedPaths: resolved,
949
953
  });
950
954
  configPath = effective.configPath;
955
+ if (effective.pathError && operation !== "disable") {
956
+ return { failed: observationFailure("unsafe", "unsafe", effective.pathError) } as const;
957
+ }
951
958
  } catch (error) {
952
959
  if (error instanceof ClineTransactionError) {
953
960
  return { failed: { ...observationFailure("unsafe", "unsafe", error.message, error.snapshotPath), residual: true } } as const;
@@ -12,10 +12,13 @@ import { homedir } from "node:os";
12
12
  import { assertDroidSettingsUnambiguous } from "./droid-settings";
13
13
  import { readPath } from "./merge";
14
14
  import type { OwnershipRecord } from "./ownership";
15
+ import type { IntegrationIO } from "./config-io";
15
16
  import { dirname, join } from "node:path";
16
17
  import {
17
18
  ClientPathError,
18
19
  buildDroidContribution,
20
+ qoderConfigPath,
21
+ qoderHomeDir,
19
22
  clineConfigPath,
20
23
  clineSettingsDir,
21
24
  droidConfigPath,
@@ -49,6 +52,8 @@ import {
49
52
  raycastConfigPath,
50
53
  zcodeConfigPath,
51
54
  zcodeHomeDir,
55
+ zcodeLegacyHomeDir,
56
+ ZcodeSettingsPathError,
52
57
  zcodeProviderStorePath,
53
58
  buildZcodeStoreContribution,
54
59
  zcodeStoreSchemaEstablished,
@@ -75,12 +80,21 @@ import {
75
80
  */
76
81
  export type IntegrationClientId = ExportClientId;
77
82
 
83
+ export interface ResolvedIntegrationPaths {
84
+ configPath: string;
85
+ detectDir: string;
86
+ /** Snapshot of a mutable store resolution from the same bootstrap read. */
87
+ storePath?: string;
88
+ /** The default config path is only a recovery hint when the active root is unknown. */
89
+ pathError?: string;
90
+ }
91
+
78
92
  export interface IntegrationClientSpec {
79
93
  id: IntegrationClientId;
80
94
  /** The client's config file, honoring that client's own environment override. */
81
- configPath: (env?: NodeJS.ProcessEnv, home?: string) => string;
95
+ configPath: (env?: NodeJS.ProcessEnv, home?: string, io?: Pick<IntegrationIO, "readText">) => string;
82
96
  /** Directory whose existence is the cheap "is it installed?" signal. */
83
- detectDir: (env?: NodeJS.ProcessEnv, home?: string) => string;
97
+ detectDir: (env?: NodeJS.ProcessEnv, home?: string, io?: Pick<IntegrationIO, "readText">) => string;
84
98
  /**
85
99
  * The provider store this client reads INSTEAD of `configPath`.
86
100
  *
@@ -98,7 +112,7 @@ export interface IntegrationClientSpec {
98
112
  * client, never merged into on a guess.
99
113
  */
100
114
  currentStore?: {
101
- path: (env?: NodeJS.ProcessEnv, home?: string) => string;
115
+ path: (env?: NodeJS.ProcessEnv, home?: string, io?: Pick<IntegrationIO, "readText">) => string;
102
116
  format: ConfigFormat;
103
117
  establishes: (parsed: unknown) => boolean;
104
118
  buildContribution: BuildContribution;
@@ -139,7 +153,7 @@ export interface IntegrationClientSpec {
139
153
  * Droid uses the same seam to check competing settings against the export
140
154
  * context before status, preview, or mutation proceeds.
141
155
  */
142
- resolvePaths?: (env?: NodeJS.ProcessEnv, home?: string, exportContext?: ExportContext) => { configPath: string; detectDir: string };
156
+ resolvePaths?: (env?: NodeJS.ProcessEnv, home?: string, exportContext?: ExportContext, io?: Pick<IntegrationIO, "readText">) => ResolvedIntegrationPaths;
143
157
  /**
144
158
  * Where the client's config WOULD live, for a client whose real path cannot
145
159
  * be resolved yet.
@@ -183,10 +197,11 @@ export function resolveIntegrationPaths(
183
197
  env: NodeJS.ProcessEnv = process.env,
184
198
  home: string = homedir(),
185
199
  exportContext?: ExportContext,
186
- ): { configPath: string; detectDir: string } {
200
+ io?: Pick<IntegrationIO, "readText">,
201
+ ): ResolvedIntegrationPaths {
187
202
  const spec = INTEGRATION_CLIENTS[clientId];
188
- if (spec.resolvePaths) return spec.resolvePaths(env, home, exportContext);
189
- return { configPath: spec.configPath(env, home), detectDir: spec.detectDir(env, home) };
203
+ if (spec.resolvePaths) return spec.resolvePaths(env, home, exportContext, io);
204
+ return { configPath: spec.configPath(env, home, io), detectDir: spec.detectDir(env, home, io) };
190
205
  }
191
206
 
192
207
  export function assertDroidPathsUnambiguous(root: string, exportContext?: ExportContext): void {
@@ -267,6 +282,11 @@ function xdgConfigHome(env: NodeJS.ProcessEnv, home: string): string {
267
282
  }
268
283
 
269
284
  export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientSpec> = {
285
+ qoder: {
286
+ id: "qoder",
287
+ configPath: qoderConfigPath,
288
+ detectDir: (env = process.env, home = homedir()) => qoderHomeDir(env, home),
289
+ },
270
290
  opencode: {
271
291
  id: "opencode",
272
292
  // These take `home` explicitly. The export registry's `destination` reads
@@ -357,15 +377,31 @@ export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientS
357
377
  },
358
378
  zcode: {
359
379
  id: "zcode",
360
- configPath: (env = process.env, home = homedir()) => zcodeConfigPath(env, home),
361
- detectDir: (env = process.env, home = homedir()) => zcodeHomeDir(env, home),
380
+ configPath: (env = process.env, home = homedir(), io) => zcodeConfigPath(env, home, io),
381
+ detectDir: (env = process.env, home = homedir(), io) => zcodeHomeDir(env, home, io),
382
+ resolvePaths: (env = process.env, home = homedir(), _context, io) => {
383
+ let root: string;
384
+ let pathError: string | undefined;
385
+ try { root = zcodeHomeDir(env, home, io); }
386
+ catch (error) {
387
+ if (!(error instanceof ZcodeSettingsPathError)) throw error;
388
+ root = zcodeLegacyHomeDir(env, home);
389
+ pathError = error.message;
390
+ }
391
+ // Passing the resolved root prevents a second bootstrap read. An explicit
392
+ // provider-file override outranks both the setting and ZCODE_DATA_DIR.
393
+ const storePath = zcodeProviderStorePath({ ...env, ZCODE_DATA_DIR: root }, home, io);
394
+ return { configPath: join(root, "v2", "config.json"), detectDir: root, storePath, ...(pathError ? { pathError } : {}) };
395
+ },
396
+ bindsDriftedRecord: (path, env = process.env, home = homedir()) =>
397
+ path === join(zcodeLegacyHomeDir(env, home), "v2", "config.json"),
362
398
  /*
363
399
  * ZCode 3.14 reads its providers from `v2/provider_config.json` and reaches
364
400
  * `v2/config.json` only through the import that seeded it. Where the new
365
401
  * file exists the import is spent, so our write is read by nobody (#5348).
366
402
  */
367
403
  currentStore: {
368
- path: (env = process.env, home = homedir()) => zcodeProviderStorePath(env, home),
404
+ path: (env = process.env, home = homedir(), io) => zcodeProviderStorePath(env, home, io),
369
405
  format: "json",
370
406
  establishes: zcodeStoreSchemaEstablished,
371
407
  buildContribution: buildZcodeStoreContribution,
@@ -496,7 +532,7 @@ export function boundIntegrationConfigPath(input: {
496
532
  if (
497
533
  record && record.clientId === input.clientId &&
498
534
  record.configPath !== input.resolvedPath &&
499
- input.statKind(record.configPath) === "file" &&
535
+ (input.clientId === "zcode" ? input.statKind(record.configPath) !== "missing" : input.statKind(record.configPath) === "file") &&
500
536
  INTEGRATION_CLIENTS[input.clientId].bindsDriftedRecord?.(record.configPath, input.env, input.home) === true
501
537
  ) {
502
538
  return record.configPath;
@@ -529,6 +565,11 @@ export function restoreOwnershipCollision(input: {
529
565
  }): string | null {
530
566
  const currentPath = input.currentPath;
531
567
  if (currentPath === null || currentPath === input.journaledPath) return null;
568
+ // A relocated ZCode store must not lose its single record to an older
569
+ // legacy undo, even when both journaled locations are legal for this home.
570
+ if (input.clientId === "zcode") {
571
+ return `that operation was recorded for ${input.journaledPath}, but ${currentPath} currently owns this integration`;
572
+ }
532
573
  const binds = INTEGRATION_CLIENTS[input.clientId].bindsDriftedRecord;
533
574
  if (!binds) return null;
534
575
  if (binds(input.journaledPath, input.env, input.home) !== true) return null;
@@ -39,6 +39,7 @@ import {
39
39
  assertDroidPathsUnambiguous,
40
40
  assertDroidRecordedSettingsUnambiguous,
41
41
  resolveIntegrationPaths,
42
+ type ResolvedIntegrationPaths,
42
43
  unresolvedPathHintFor,
43
44
  type IntegrationClientId,
44
45
  } from "./registry";
@@ -482,7 +483,7 @@ export interface IntegrationStateInput {
482
483
  store?: IntegrationStateStore;
483
484
  io?: IntegrationIO;
484
485
  /** Internal explicit profile target; never accepted as a caller-provided path. */
485
- resolvedPaths?: { configPath: string; detectDir: string };
486
+ resolvedPaths?: ResolvedIntegrationPaths;
486
487
  }
487
488
 
488
489
  export function exportContextOf(input: {
@@ -592,11 +593,11 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
592
593
  */
593
594
  let installed: boolean;
594
595
  let effective: IntegrationTarget;
595
- let record: OwnershipRecord | null;
596
+ let record: OwnershipRecord | null = null;
596
597
  try {
597
598
  // One resolution for both, so a client whose paths come from mutable state
598
599
  // cannot report one account's install beside another account's config path.
599
- const paths = resolveStatePaths(input);
600
+ const paths = resolveStatePaths(input, io);
600
601
  installed = io.statKind(paths.detectDir) === "dir";
601
602
  if (input.clientId === "cline") io = createClineIO(io, paths.configPath, store);
602
603
  /*
@@ -611,7 +612,7 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
611
612
  statKind: io.statKind, env: input.env, home: input.home,
612
613
  });
613
614
  effective = resolveIntegrationTarget({
614
- clientId: input.clientId, configPath: recordedPath, io, record, env: input.env, home: input.home,
615
+ clientId: input.clientId, configPath: recordedPath, io, record, env: input.env, home: input.home, resolvedPaths: paths,
615
616
  });
616
617
  } catch (error) {
617
618
  if (!(error instanceof ClientPathError)) throw error;
@@ -626,6 +627,10 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
626
627
  * yet. A client that can name where its config would go gets `installed:
627
628
  * false` and that location, which reads as "not installed" in the UI.
628
629
  */
630
+ if (input.clientId === "zcode" && record) return {
631
+ clientId: input.clientId, state: "unsafe", installed: false, configPath: record.configPath,
632
+ reason: "unresolvable-path", appliedAt: record.appliedAt, lastOpId: record.opId, ...retention,
633
+ };
629
634
  const hint = unresolvedPathHintFor(input.clientId, input.env, input.home);
630
635
  return {
631
636
  clientId: input.clientId,
@@ -638,6 +643,12 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
638
643
  }
639
644
 
640
645
  const configPath = effective.configPath;
646
+ if (effective.pathError) return {
647
+ clientId: input.clientId, state: "unsafe", installed, configPath,
648
+ reason: "unresolvable-path",
649
+ ...(record?.configPath === configPath ? { appliedAt: record.appliedAt, lastOpId: record.opId } : {}),
650
+ ...retention,
651
+ };
641
652
  if (input.clientId === "kilo") {
642
653
  const candidates = inspectKiloCandidates({ io, selectedPath: configPath, env: input.env, home: input.home });
643
654
  if (candidates.kind !== "ok") return {
@@ -715,9 +726,10 @@ export function readIntegrationState(input: IntegrationStateInput): IntegrationS
715
726
  };
716
727
  }
717
728
 
718
- function resolveStatePaths(input: IntegrationStateInput): { configPath: string; detectDir: string } {
729
+ function resolveStatePaths(input: IntegrationStateInput, io: IntegrationIO): ResolvedIntegrationPaths {
719
730
  const context = exportContextOf(input);
720
- const paths = input.resolvedPaths ?? resolveIntegrationPaths(input.clientId, input.env, input.home, context);
731
+ const paths = input.clientId === "zcode" || !input.resolvedPaths
732
+ ? resolveIntegrationPaths(input.clientId, input.env, input.home, context, io) : input.resolvedPaths;
721
733
  if (input.clientId === "droid" && input.resolvedPaths) assertDroidPathsUnambiguous(paths.detectDir, context);
722
734
  return paths;
723
735
  }
@@ -727,7 +739,7 @@ export function readOwnedDroidReasoningDefaults(input: IntegrationStateInput): R
727
739
  const store = input.store ?? createIntegrationStateStore();
728
740
  const io = input.io ?? store.io();
729
741
  try {
730
- const paths = resolveStatePaths(input);
742
+ const paths = resolveStatePaths(input, io);
731
743
  const record = store.readRecords().droid;
732
744
  if (!record || record.clientId !== "droid" || record.configPath !== paths.configPath) return {};
733
745
  const effective = resolveIntegrationTarget({