@bitkyc08/opencodex 2.84.0 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -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]],
package/src/grok/sync.ts CHANGED
@@ -18,7 +18,7 @@ export interface GrokSyncDeps {
18
18
  }
19
19
 
20
20
  async function defaultFetchAllModels(config: OcxConfig): Promise<CatalogModel[]> {
21
- const { fetchAllModels } = await import("../server/management-api");
21
+ const { fetchAllModels } = await import("../server/management/shared");
22
22
  return fetchAllModels(config);
23
23
  }
24
24
 
@@ -20,6 +20,7 @@ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
20
20
  import { cloneProviderOpaqueToolCallMetadata } from "../responses/provider-opaque-metadata";
21
21
  import type { AttemptRecoveryKind } from "../usage/log";
22
22
  import { bridgeToResponsesSSE } from "../bridge";
23
+ import { isTerminalRefusalIncomplete } from "../bridge/terminal-refusal";
23
24
  import { clearableDeadline, idleDeadline } from "../lib/abort";
24
25
  import { readBoundedResponseBody } from "../lib/bounded-body";
25
26
  import { applyUpstreamRecoveryInit, fetchWithResetRetry, prepareSameTarget429Wait } from "../lib/upstream-retry";
@@ -39,6 +40,7 @@ import { submitVideoJob } from "./xai-video-client";
39
40
  import { downloadVideoToArtifact, createImageBudget, pruneArtifacts } from "./artifacts";
40
41
  import { IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME } from "./synthetic-tool";
41
42
  import type { ImageBridgePlan, VideoBridgePlan } from "./types";
43
+ import { guardEmptyToolCatalogStream, type ClientToolCatalogRestriction } from "../lib/empty-tool-catalog";
42
44
 
43
45
  const SSE_HEADERS = {
44
46
  "Content-Type": "text/event-stream",
@@ -241,7 +243,11 @@ function extractIterationThinking(events: AdapterEvent[]): OcxThinkingContent[]
241
243
  };
242
244
 
243
245
  for (const e of events) {
244
- if (e.type === "thinking_delta") {
246
+ if (e.type === "text_delta" && e.anthropicThinkingSignature) {
247
+ flushVisible();
248
+ flushRaw();
249
+ parts.push({ type: "thinking", thinking: e.text, signature: e.anthropicThinkingSignature });
250
+ } else if (e.type === "thinking_delta") {
245
251
  flushRaw();
246
252
  thinking += e.thinking;
247
253
  } else if (e.type === "reasoning_raw_delta") {
@@ -286,6 +292,7 @@ class LoopError extends Error {
286
292
  */
287
293
  export interface ImageBridgeDeps {
288
294
  parsed: OcxParsedRequest;
295
+ clientToolCatalogRestriction?: ClientToolCatalogRestriction;
289
296
  adapter: ProviderAdapter;
290
297
  incomingMeta: IncomingMeta;
291
298
  plan?: ImageBridgePlan;
@@ -319,6 +326,12 @@ export interface ImageBridgeDeps {
319
326
  waitForRequestSlot?: (signal?: AbortSignal) => Promise<ProviderRequestSlot | void>;
320
327
  /** Raw adapter usage at the terminal event, pre wire-normalization (see bridgeToResponsesSSE onUsage). */
321
328
  onUsage?: (usage: OcxUsage | undefined) => void;
329
+ /**
330
+ * After an `on429` rotation, offered the usage of the hidden iterations the left credential
331
+ * served. Returning true means it was billed to that credential's attempt, so `onUsage` will not
332
+ * report it again; the client terminal still includes it.
333
+ */
334
+ onRotatedUsage?: (usage: OcxUsage) => boolean;
322
335
  /**
323
336
  * Optional 429 failover for the routed (non-xAI) model. Return a rebuilt adapter for the
324
337
  * rotated credential, or null when the pool is exhausted. Async hooks support OAuth refresh;
@@ -353,6 +366,8 @@ export interface ImageBridgeDeps {
353
366
  onCompletedResponse?: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => void;
354
367
  /** WebSocket Responses path only — leave response id empty for protocol compatibility. */
355
368
  forceEmptyResponseId?: boolean;
369
+ /** Responses inbound only: deliver a non-retryable upstream refusal as `response.failed`. */
370
+ terminalRefusalAsFailed?: boolean;
356
371
  }
357
372
 
358
373
  /**
@@ -377,6 +392,10 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
377
392
  let paidImageCalls = 0;
378
393
  let paidVideoCalls = 0;
379
394
  let hiddenUsage: OcxUsage | undefined;
395
+ // Hidden usage already billed through onRotatedUsage: still on the client terminal, not in
396
+ // ledgerUsage, which is what onUsage reports once a rotation has moved some.
397
+ let rotatedUsage: OcxUsage | undefined;
398
+ let ledgerUsage: OcxUsage | undefined;
380
399
  let activeProducers = 0;
381
400
 
382
401
  const addUsage = (a: OcxUsage | undefined, b: OcxUsage | undefined): OcxUsage | undefined => {
@@ -569,6 +588,7 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
569
588
  // runTurn adapters signal errors via {type:"error"} events, not HTTP status codes.
570
589
  const errorEvent = events.find(e => e.type === "error");
571
590
  if (errorEvent && errorEvent.type === "error") {
591
+ hiddenUsage = addUsage(hiddenUsage, errorEvent.usage);
572
592
  if (errorEvent.code === "translation_buffer_limit") {
573
593
  throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
574
594
  }
@@ -709,6 +729,10 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
709
729
  const rotated = await deps.on429(prepared.response.headers.get("retry-after"), prepared.response.headers,
710
730
  iterParsed, prepared.response);
711
731
  if (!rotated) break;
732
+ if (hiddenUsage && deps.onRotatedUsage?.(hiddenUsage)) {
733
+ rotatedUsage = addUsage(rotatedUsage, hiddenUsage);
734
+ hiddenUsage = undefined;
735
+ }
712
736
  try { void prepared.response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
713
737
  adapter = rotated.adapter;
714
738
  yield { type: "heartbeat" };
@@ -772,6 +796,8 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
772
796
  signal,
773
797
  inactivityTimeoutMs: stallTimeoutMs,
774
798
  translatorBudget,
799
+ // A pass that fails was still billed; the error terminal below reports it with the hidden passes.
800
+ onErrorUsage: usage => { hiddenUsage = addUsage(hiddenUsage, usage); },
775
801
  })) {
776
802
  if (event.type === "heartbeat") yield event;
777
803
  else {
@@ -863,13 +889,17 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
863
889
  // Loop (fulfill + re-ask) ONLY when the model's actionable output is purely image_gen. A
864
890
  // real tool call means this turn is terminal for Codex — finalize so those calls reach
865
891
  // Codex. forceFinal also finalizes.
866
- const shouldLoop = split.calls.length > 0 && !split.hasRealToolCall && !forceFinal;
892
+ // A refused iteration also finalizes: fulfilling its media call would re-ask the model
893
+ // with the refused context (#7148).
894
+ const shouldLoop = split.calls.length > 0 && !split.hasRealToolCall && !forceFinal
895
+ && !split.passthrough.some(isTerminalRefusalIncomplete);
867
896
  if (!shouldLoop) {
868
- if (hiddenUsage) {
897
+ if (hiddenUsage || rotatedUsage) {
869
898
  for (let i = split.passthrough.length - 1; i >= 0; i--) {
870
899
  const e = split.passthrough[i];
871
900
  if (e?.type === "done" || e?.type === "incomplete") {
872
- split.passthrough[i] = { ...e, usage: addUsage(hiddenUsage, e.usage) };
901
+ ledgerUsage = addUsage(hiddenUsage, e.usage);
902
+ split.passthrough[i] = { ...e, usage: addUsage(rotatedUsage, ledgerUsage) };
873
903
  break;
874
904
  }
875
905
  }
@@ -881,6 +911,10 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
881
911
  // Discarded iteration still contributed tokens — accumulate for the final onUsage.
882
912
  takeUsageFrom(split.passthrough);
883
913
 
914
+ for (const event of split.passthrough) {
915
+ if (event.type === "text_delta" && event.anthropicThinkingSignature) yield event;
916
+ }
917
+
884
918
  // Fulfill each image/video call, then inject ONE assistant turn (thinking once + all tool
885
919
  // calls) so Anthropic extended-thinking continuations stay valid across parallel calls.
886
920
  const iterationThinking = extractIterationThinking(split.passthrough);
@@ -1052,6 +1086,8 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
1052
1086
  });
1053
1087
  }
1054
1088
  } catch (e) {
1089
+ ledgerUsage = hiddenUsage;
1090
+ const usage = addUsage(rotatedUsage, hiddenUsage);
1055
1091
  if (isTranslatorBudgetExceededError(e)) {
1056
1092
  yield {
1057
1093
  type: "error",
@@ -1059,14 +1095,14 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
1059
1095
  errorType: "upstream_error",
1060
1096
  code: e.code,
1061
1097
  message: "upstream translation buffer exceeded the safe limit",
1062
- ...(hiddenUsage ? { usage: hiddenUsage } : {}),
1098
+ ...(usage ? { usage } : {}),
1063
1099
  };
1064
1100
  } else {
1065
1101
  yield {
1066
1102
  type: "error",
1067
1103
  message: e instanceof LoopError ? e.message : (e instanceof Error ? e.message : String(e)),
1068
1104
  ...(e instanceof LoopError ? { status: e.status } : {}),
1069
- ...(hiddenUsage ? { usage: hiddenUsage } : {}),
1105
+ ...(usage ? { usage } : {}),
1070
1106
  };
1071
1107
  }
1072
1108
  return;
@@ -1079,13 +1115,15 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
1079
1115
  }
1080
1116
 
1081
1117
  const sse = bridgeToResponsesSSE(
1082
- produce(), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeform, toolSearch, () => {
1118
+ guardEmptyToolCatalogStream(produce(), deps.clientToolCatalogRestriction, translatorBudget), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeform, toolSearch, () => {
1083
1119
  internalAbort.abort("client closed responses stream");
1084
1120
  }, 2_000,
1085
1121
  {
1086
1122
  translatorBudget,
1087
1123
  replayCacheScope: parsed._reasoningReplayScope,
1124
+ ...(deps.clientToolCatalogRestriction ? { ...deps.clientToolCatalogRestriction, enforceDeclaredToolNames: false } : {}),
1088
1125
  ...(deps.forceEmptyResponseId ? { responseId: "" } : {}),
1126
+ ...(deps.terminalRefusalAsFailed ? { terminalRefusalAsFailed: true } : {}),
1089
1127
  hideThinkingSummary: parsed.options.hideThinkingSummary,
1090
1128
  hideRawReasoning: parsed.options.hideRawReasoning,
1091
1129
  stallTimeoutSec: deps.stallTimeoutSec,
@@ -1093,7 +1131,7 @@ export async function runWithImageBridge(deps: ImageBridgeDeps): Promise<Respons
1093
1131
  ...(deps.onUsage ? {
1094
1132
  // Terminal done/incomplete already includes hiddenUsage (merged above). Do not
1095
1133
  // add it again here or request logs double-count multi-iteration image turns.
1096
- onUsage: (usage: OcxUsage | undefined) => deps.onUsage?.(usage),
1134
+ onUsage: (usage: OcxUsage | undefined) => deps.onUsage?.(rotatedUsage ? ledgerUsage : usage),
1097
1135
  } : {}),
1098
1136
  ...(deps.onCompletedResponse ? { onCompletedResponse: deps.onCompletedResponse } : {}),
1099
1137
  },
@@ -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", "gentle-shell"],
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));
@@ -131,8 +131,10 @@ export const DYNAMIC_SEGMENT = "*";
131
131
  * paths silently have no declaration.
132
132
  */
133
133
  const CLIENT_MANAGED_PATHS = {
134
+ qoder: [["providers", OPENCODE_PROVIDER_ID], ["modelConfigs", "customModels", DYNAMIC_SEGMENT]],
134
135
  opencode: [["provider", OPENCODE_PROVIDER_ID], ["providers", OPENCODE_PROVIDER_ID]],
135
136
  pi: [["providers", OPENCODE_PROVIDER_ID]],
137
+ "gentle-shell": [["providers", OPENCODE_PROVIDER_ID]],
136
138
  omp: [["providers", OPENCODE_PROVIDER_ID]],
137
139
  hermes: [["providers", OPENCODE_PROVIDER_ID]],
138
140
  openclaw: [["models", "providers", OPENCODE_PROVIDER_ID]],
@@ -9,6 +9,7 @@
9
9
  * Design of record: devlog/_fin/260802_client_toggle_api/021 §1.
10
10
  */
11
11
  import { homedir } from "node:os";
12
+ import { gentleShellPaths } from "../clients/gentle-shell-path";
12
13
  import { assertDroidSettingsUnambiguous } from "./droid-settings";
13
14
  import { readPath } from "./merge";
14
15
  import type { OwnershipRecord } from "./ownership";
@@ -17,6 +18,8 @@ import { dirname, join } from "node:path";
17
18
  import {
18
19
  ClientPathError,
19
20
  buildDroidContribution,
21
+ qoderConfigPath,
22
+ qoderHomeDir,
20
23
  clineConfigPath,
21
24
  clineSettingsDir,
22
25
  droidConfigPath,
@@ -280,6 +283,11 @@ function xdgConfigHome(env: NodeJS.ProcessEnv, home: string): string {
280
283
  }
281
284
 
282
285
  export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientSpec> = {
286
+ qoder: {
287
+ id: "qoder",
288
+ configPath: qoderConfigPath,
289
+ detectDir: (env = process.env, home = homedir()) => qoderHomeDir(env, home),
290
+ },
283
291
  opencode: {
284
292
  id: "opencode",
285
293
  // These take `home` explicitly. The export registry's `destination` reads
@@ -498,6 +506,13 @@ export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientS
498
506
  return { configPath: droidConfigPath(env, home), detectDir };
499
507
  },
500
508
  },
509
+ "gentle-shell": {
510
+ id: "gentle-shell",
511
+ configPath: (env, home, io) => gentleShellPaths(env, home, io).configPath,
512
+ detectDir: (env, home, io) => gentleShellPaths(env, home, io).detectDir,
513
+ resolvePaths: (env, home, _context, io) => gentleShellPaths(env, home, io),
514
+ unresolvedPathHint: (_env, home = homedir()) => join(home, ".gentle-shell", "agent", "models.json"),
515
+ },
501
516
  };
502
517
 
503
518
  export const INTEGRATION_CLIENT_IDS: readonly IntegrationClientId[] =
@@ -16,6 +16,7 @@ import {
16
16
  captureSnapshot,
17
17
  clearPruneFailure,
18
18
  countSnapshots,
19
+ discardUncommittedSnapshot,
19
20
  findOperation,
20
21
  findCommittedOperation,
21
22
  listOperations,
@@ -53,6 +54,7 @@ export interface IntegrationStateStore {
53
54
  findOperation(opId: string): JournalEntry | null;
54
55
  findCommittedOperation(opId: string): JournalEntry | null;
55
56
  captureSnapshot(clientId: IntegrationClientId, opId: string, text: string | null): SnapshotRef;
57
+ discardUncommittedSnapshot(clientId: IntegrationClientId, opId: string): void;
56
58
  readSnapshot(entry: JournalEntry):
57
59
  | { kind: "none" }
58
60
  | { kind: "stored"; text: string; path: string }
@@ -92,6 +94,7 @@ export function createIntegrationStateStore(root: string = integrationsDir()): I
92
94
  findOperation: opId => findOperation(opId, dir),
93
95
  findCommittedOperation: opId => findCommittedOperation(opId, dir),
94
96
  captureSnapshot: (clientId, opId, text) => captureSnapshot(clientId, opId, text, dir),
97
+ discardUncommittedSnapshot: (clientId, opId) => discardUncommittedSnapshot(clientId, opId, dir),
95
98
  readSnapshot: entry => readSnapshot(entry, dir),
96
99
  countSnapshots: clientId => countSnapshots(clientId, dir),
97
100
  pruneSnapshots: clientId => pruneSnapshots(clientId, dir),
@@ -18,7 +18,7 @@ import { shouldInjectApiAuthHeader } from "../codex/inject";
18
18
  import { detachedConfigSnapshot } from "../config/admitted-identity";
19
19
  import { copyPlainData } from "../lib/plain-data";
20
20
  import type { OcxConfig } from "../types";
21
- import { PARSE_FAILED, defaultIntegrationIO, loadTarget, parseConfig, type IntegrationIO } from "./config-io";
21
+ import { IntegrationWriteSizeError, PARSE_FAILED, defaultIntegrationIO, loadTarget, parseConfig, type IntegrationIO } from "./config-io";
22
22
  import { inspectKiloCandidates } from "./kilo-candidates";
23
23
  import {
24
24
  fingerprint,
@@ -162,6 +162,15 @@ function commit(args: CommitArgs): WriteOutcome {
162
162
  }
163
163
  } catch (error) {
164
164
  if (transactionStarted) return compensate(args, error, "could not write both Cline files");
165
+ if (error instanceof IntegrationWriteSizeError && args.snapshotPath) {
166
+ try {
167
+ args.store.discardUncommittedSnapshot(clientId, args.entry.opId);
168
+ } catch (cleanupError) {
169
+ return refuse(clientId, "write_failed", args.state,
170
+ `${messageOf(error)}; could not remove the unused backup: ${messageOf(cleanupError)}`, args.snapshotPath);
171
+ }
172
+ return refuse(clientId, "write_failed", args.state, messageOf(error));
173
+ }
165
174
  return refuse(clientId, "write_failed", args.state, messageOf(error), args.snapshotPath);
166
175
  }
167
176
  try {
@@ -0,0 +1,16 @@
1
+ /** Host activation seam. Registration is synchronous; optional code loads only on use. */
2
+ import type { OcxConfig } from "../types";
3
+ import { setAdvisorPlanFactory } from "../server/responses/advisor-plan-slot";
4
+
5
+ export function activateAdvisor(config: OcxConfig): void {
6
+ if (config.advisor?.enabled !== true || typeof config.advisor.model !== "string" || !config.advisor.model.trim()) {
7
+ setAdvisorPlanFactory(config, null);
8
+ return;
9
+ }
10
+ setAdvisorPlanFactory(config, async input => {
11
+ // A later settings write may disable the same live config before this request starts.
12
+ if (config.advisor?.enabled !== true || !config.advisor.model?.trim()) return null;
13
+ const { createAdvisorRuntimePlan } = await import("../advisor/runtime");
14
+ return createAdvisorRuntimePlan(input);
15
+ });
16
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Fire-and-forget work the process started and nobody awaits.
3
+ *
4
+ * A lazy quota prime or a startup reconcile is deliberately left running behind a
5
+ * request so the request never waits on it. The work still owns files: the pool
6
+ * prime holds the native-main claim database open across ACL hardening and a WHAM
7
+ * read. Whoever later removes the directory those files live in has to wait for the
8
+ * owner. Without a handle on the work there was nothing to wait for: test teardown
9
+ * spun a synchronous retry loop, which blocks the very event loop the owner needs to
10
+ * reach its `finally` and close the file. On Windows every overlap became a 15 s EBUSY.
11
+ *
12
+ * Registering costs one Set entry per in-flight task and changes nothing about how
13
+ * the work runs. Settling is opt-in: test teardown calls it; requests never do.
14
+ */
15
+
16
+ interface BackgroundWork {
17
+ readonly label: string;
18
+ readonly settled: Promise<void>;
19
+ }
20
+
21
+ const inFlight = new Set<BackgroundWork>();
22
+
23
+ /** Record `work` until it settles and return it unchanged. Rejections stay the caller's. */
24
+ export function trackBackgroundWork<T>(label: string, work: Promise<T>): Promise<T> {
25
+ const entry: BackgroundWork = {
26
+ label,
27
+ settled: work.then(() => { inFlight.delete(entry); }, () => { inFlight.delete(entry); }),
28
+ };
29
+ inFlight.add(entry);
30
+ return work;
31
+ }
32
+
33
+ /** Labels of work still running, for diagnostics. */
34
+ export function pendingBackgroundWork(): string[] {
35
+ return [...inFlight].map(entry => entry.label);
36
+ }
37
+
38
+ export interface BackgroundWorkSettlement {
39
+ readonly settled: boolean;
40
+ readonly pending: string[];
41
+ }
42
+
43
+ /**
44
+ * Wait until no tracked work is in flight, including work started by work that was
45
+ * already running. A bounded wait reports what is still running instead of throwing,
46
+ * so the caller decides whether that is fatal.
47
+ */
48
+ export async function settleBackgroundWork(options: { timeoutMs?: number } = {}): Promise<BackgroundWorkSettlement> {
49
+ const deadline = options.timeoutMs === undefined ? Infinity : Date.now() + options.timeoutMs;
50
+ while (inFlight.size > 0) {
51
+ const remaining = deadline - Date.now();
52
+ if (remaining <= 0) break;
53
+ const batch = Promise.all([...inFlight].map(entry => entry.settled)).then(() => false);
54
+ if (remaining === Infinity) {
55
+ await batch;
56
+ continue;
57
+ }
58
+ let timer: ReturnType<typeof setTimeout> | undefined;
59
+ const expired = new Promise<boolean>(resolve => { timer = setTimeout(() => resolve(true), remaining); });
60
+ const timedOut = await Promise.race([batch, expired]);
61
+ clearTimeout(timer);
62
+ if (timedOut) break;
63
+ }
64
+ return { settled: inFlight.size === 0, pending: pendingBackgroundWork() };
65
+ }
@@ -0,0 +1,58 @@
1
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "node:fs";
2
+
3
+ const MAX_BOUNDED_READ_BYTES = 64 * 1024 * 1024;
4
+ const READ_CHUNK_BYTES = 64 * 1024;
5
+
6
+ function sameIdentity(
7
+ left: { dev: bigint; ino: bigint },
8
+ right: { dev: bigint; ino: bigint },
9
+ ): boolean {
10
+ return left.dev === right.dev && left.ino === right.ino;
11
+ }
12
+
13
+ export function readBoundedRegularFileSync(path: string, limit: number): Buffer {
14
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_BOUNDED_READ_BYTES) {
15
+ throw new RangeError("invalid bounded file read limit");
16
+ }
17
+
18
+ const windows = process.platform === "win32";
19
+ const flags = constants.O_RDONLY | (windows ? 0 : constants.O_NONBLOCK | constants.O_NOFOLLOW);
20
+ const entryBefore = windows ? lstatSync(path, { bigint: true }) : undefined;
21
+ if (entryBefore && (entryBefore.isSymbolicLink() || !entryBefore.isFile())) {
22
+ throw new Error("not a regular file");
23
+ }
24
+ const fd = openSync(path, flags);
25
+ try {
26
+ const opened = fstatSync(fd, { bigint: true });
27
+ if (!opened.isFile() || opened.size > BigInt(limit)) throw new Error("file is not bounded regular data");
28
+ if (entryBefore && (!sameIdentity(entryBefore, opened) || opened.ino === 0n)) {
29
+ throw new Error("file entry changed while opening");
30
+ }
31
+
32
+ // Start near the observed size, but keep probing up to limit + 1 if the file grows.
33
+ let bytes = Buffer.alloc(Number(opened.size) + 1);
34
+ let length = 0;
35
+ while (length < limit + 1) {
36
+ if (length === bytes.length) {
37
+ const capacity = Math.min(limit + 1, Math.max(bytes.length * 2, bytes.length + READ_CHUNK_BYTES));
38
+ const next = Buffer.alloc(capacity);
39
+ bytes.copy(next, 0, 0, length);
40
+ bytes = next;
41
+ }
42
+ const count = readSync(fd, bytes, length, Math.min(READ_CHUNK_BYTES, bytes.length - length), null);
43
+ if (count === 0) break;
44
+ length += count;
45
+ }
46
+ if (length > limit) throw new Error("file exceeds byte limit");
47
+
48
+ if (entryBefore) {
49
+ const entryAfter = lstatSync(path, { bigint: true });
50
+ if (entryAfter.isSymbolicLink() || !entryAfter.isFile() || !sameIdentity(opened, entryAfter)) {
51
+ throw new Error("file entry changed while reading");
52
+ }
53
+ }
54
+ return bytes.subarray(0, length);
55
+ } finally {
56
+ closeSync(fd);
57
+ }
58
+ }
@@ -5,7 +5,16 @@ export interface DesktopCliIo {
5
5
  export interface PathBunIo extends DesktopCliIo {
6
6
  now?: () => number;
7
7
  realpath?: (path: string) => string;
8
- stat?: (path: string) => { isFile(): boolean; mode: number };
8
+ stat?: (path: string) => { isFile(): boolean; mode: number; uid?: number };
9
+ lstat?: (path: string) => {
10
+ isFile(): boolean;
11
+ isDirectory(): boolean;
12
+ isSymbolicLink(): boolean;
13
+ mode: number;
14
+ uid?: number;
15
+ };
16
+ readlink?: (path: string) => string;
17
+ geteuid?: () => number | undefined;
9
18
  isRealBunBinary?: (path: string) => boolean;
10
19
  spawnSync?: (path: string, args: string[], options: {
11
20
  env: NodeJS.ProcessEnv;